(3/6) 프로토콜·상태 고갈 공격 — 연결 테이블을 터뜨리기
- DDoS 완전정복 — 개념과 공격 지형도
- 볼류메트릭·반사증폭 공격 — 대역폭을 말려버리는 방법
- 프로토콜·상태 고갈 공격 — 연결 테이블을 터뜨리기 ← 지금 글
- 애플리케이션 계층(L7) 공격 — 정상 요청처럼 굴며 서버를 태우기
- DDoS 방어와 완화 — 탐지부터 스크러빙까지
- 내 사이트 DDoS 내성 테스트 — 합법적으로, 제대로
Summary
지난 글에서는 회선 자체를 트래픽으로 틀어막는 볼류메트릭 공격을 다뤘습니다. 그런데 대역폭이 넉넉해도 서버가 쓰러지는 경우가 있어요. 이번에 볼 프로토콜·상태 고갈 공격은 회선이 아니라 서버와 중간 장비가 붙잡고 있는 “연결의 흔적”을 노립니다. TCP 핸드셰이크의 빈틈을 파고들어 반쯤 열린 연결을 잔뜩 쌓아두거나, 방화벽·로드밸런서의 상태 테이블 슬롯을 하나씩 갉아먹는 식이에요. 초당 패킷 수는 볼류메트릭에 한참 못 미치는데도 훨씬 적은 힘으로 서비스를 멈추게 만들 수 있어서, 이 비대칭성이 이 계열 공격의 핵심입니다.
💡 이 글에서 다루는 것
- 대역폭이 아니라 연결 슬롯·상태 테이블을 소진하는 자원 고갈의 원리
- TCP 3-way handshake와 SYN 플러드가 backlog 큐를 채우는 방식
- ACK·RST·SYN-ACK 플러드가 방화벽·LB를 소모시키는 이유
- 조각화 공격과 재조합 버퍼, teardrop의 개념
- Ping of Death·Smurf·LAND 같은 고전 기법과 지금 배울 이유
1. 상태 고갈이란 — 대역폭이 아니라 자원을 노린다
볼류메트릭 공격이 “수도관을 물로 꽉 채우는” 방식이었다면, 상태 고갈 공격은 “식당 예약석을 전부 가짜 이름으로 잡아두는” 쪽에 가깝습니다. 회선은 멀쩡한데 앉을 자리가 없는 거예요.
서버와 네트워크 장비는 연결 하나하나를 그냥 흘려보내지 않습니다. 각 연결마다 상태를 기억해요. 이 연결이 어느 단계까지 진행됐는지, 출발지와 목적지 포트는 무엇인지, 타이머가 언제 만료되는지 같은 정보를 테이블에 적어둡니다. 이 테이블은 유한합니다. 슬롯 개수에 한계가 있어요.
공격자는 바로 그 유한한 슬롯을 노립니다. 진짜 통신을 완성할 생각 없이, 연결을 여는 시늉만 반복해서 상태 테이블을 가짜 엔트리로 채워버리는 거죠. 슬롯이 다 차면 정상 사용자가 아무리 정상적으로 접속을 시도해도 “자리가 없다”며 거절당합니다.
이런 공격이 무서운 이유는 트래픽 양이 적어도 통한다는 점입니다. 큰 파이프를 뚫을 필요가 없어요. 상태 테이블의 슬롯이 예를 들어 수만 개 수준이라면, 그만큼의 반쯤 열린 연결만 유지해도 목표를 이룰 수 있습니다.
2. TCP 3-way handshake 복습
상태 고갈 공격을 이해하려면 TCP가 연결을 어떻게 여는지부터 짚어야 합니다. TCP는 데이터를 주고받기 전에 세 번의 인사로 연결을 확립해요. 이걸 3-way handshake라고 부릅니다.
아래는 정상적인 연결 수립 과정입니다.
클라이언트 서버
| |
| (1) SYN ───────────────────> | 연결 요청
| | 서버: 반쯤 열린 연결을
| | backlog 큐에 기록
| (2) SYN-ACK <─────────────── | 요청 수락 + 확인
| |
| (3) ACK ───────────────────> | 확인의 확인
| | → 연결 ESTABLISHED
| <========= 데이터 전송 =======> |
여기서 눈여겨볼 대목은 두 번째 단계 직후입니다. 서버는 SYN을 받으면 곧바로 자원을 할당해요. 아직 연결이 완성되지 않았는데도, 이 요청을 기억하기 위해 backlog 큐라는 대기 공간에 “반쯤 열린 연결”을 저장합니다. 그리고 마지막 ACK가 도착하기를 기다려요. 이 기다림의 시간과 유한한 큐 크기가 다음 절의 약점이 됩니다.
3. SYN 플러드
SYN 플러드는 상태 고갈 공격의 교과서 같은 사례입니다. 원리는 3-way handshake의 마지막 단추를 일부러 끼우지 않는 것이에요.
공격자는 서버에 SYN 패킷을 보냅니다. 서버는 규칙대로 SYN-ACK를 돌려주고, backlog 큐에 반쯤 열린 연결을 기록하며 마지막 ACK를 기다려요. 그런데 공격자는 그 ACK를 영영 보내지 않습니다. 게다가 출발지 IP를 스푸핑해두면, 서버가 보낸 SYN-ACK는 엉뚱한 주소로 날아가고 진짜 응답은 아무도 하지 않아요.
이 과정을 쉬지 않고 반복하면 어떻게 될까요. backlog 큐에는 완성되지 못한 연결이 SYN_RECV 상태로 차곡차곡 쌓입니다. 각 엔트리는 타이머가 만료될 때까지 슬롯을 붙들고 있어요. 큐가 가득 차는 순간, 서버는 새 SYN을 받아줄 자리가 없어집니다. 그 결과 진짜 사용자의 정상적인 연결 요청까지 함께 거절당해요.
무서운 지점은 공격자의 비용이 거의 없다는 데 있습니다. SYN 패킷 한 개를 던지고 끝이에요. 응답을 기다리지도, 연결을 완성하지도 않습니다. 반면 서버는 그 패킷 하나마다 메모리를 할당하고 타이머를 걸고 큐 슬롯을 내줘야 합니다. 이 노력의 불균형이 SYN 플러드를 오래도록 살아남게 한 이유입니다.
⚠️ SYN 플러드에 대응하는 고전적 방법이 SYN 쿠키입니다. 서버가 backlog에 상태를 저장하는 대신, 연결 정보를 시퀀스 번호 안에 암호학적으로 인코딩해 SYN-ACK에 실어 보냅니다. 진짜 ACK가 돌아올 때 그 번호를 검증해 연결을 복원하므로, 반쯤 열린 연결을 위한 슬롯을 미리 잡아둘 필요가 사라져요.
4. ACK·RST·SYN-ACK 플러드
SYN만 문제가 되는 건 아닙니다. 핸드셰이크의 다른 조각들도 무기가 될 수 있어요. 특히 방화벽이나 로드밸런서처럼 연결 상태를 추적하는 장비가 표적입니다.
이 장비들은 지나가는 패킷이 “이미 알고 있는 연결”에 속하는지 매번 확인합니다. 상태 테이블을 뒤져서 매칭되는 세션이 있는지 찾아야 해요. 공격자는 이 조회 비용을 노립니다.
- ACK 플러드: 아무 연결에도 속하지 않은 ACK 패킷을 대량으로 던집니다. 장비는 이 ACK가 어느 세션의 것인지 테이블을 뒤지느라 CPU를 소모하고, 매칭 실패 처리에도 자원을 씁니다.
- RST 플러드: 연결 종료를 뜻하는 RST를 무작위로 뿌립니다. 역시 상태 테이블 조회를 유발해요.
- SYN-ACK 플러드: 응답에 해당하는 SYN-ACK를 보내 요청 없는 응답을 처리하게 만듭니다.
SYN-ACK에는 한 가지 결이 더 있습니다. 앞 글에서 본 반사 공격과 엮이는 지점이에요. 공격자가 출발지 IP를 피해자 주소로 스푸핑해 여러 서버에 SYN을 뿌리면, 그 서버들이 보내는 SYN-ACK가 전부 피해자에게 쏟아집니다. 정상 서버들이 자기도 모르게 반사판 역할을 하게 되는 구조죠.
5. 연결 고갈과 상태 테이블 공격
반쯤 열린 연결만 문제가 아닙니다. 완전히 수립된 연결도 무기가 될 수 있어요. 이걸 established connection flood라고 부릅니다.
공격자는 실제로 3-way handshake를 끝까지 완성해 진짜 연결을 여럿 맺습니다. 그런 다음 데이터를 거의 보내지 않은 채로 그 연결을 계속 붙들고 있어요. 연결 하나하나는 정상처럼 보이지만, 이런 연결이 수만 개 쌓이면 서버의 동시 연결 한도를 잠식합니다. 반쯤 열린 연결보다 탐지가 까다로운 이유는, 하나만 떼어 보면 멀쩡한 트래픽이기 때문입니다.
여기서 진짜 병목은 종종 서버가 아니라 그 앞단의 장비입니다. 방화벽, 로드밸런서, NAT 게이트웨이는 지나가는 모든 연결의 상태를 추적하는 conntrack 테이블을 유지해요. 리눅스 커널의 연결 추적 테이블을 떠올리면 됩니다.
이 장비들이 병목이 되는 이유를 정리하면 이렇습니다.
- 상태 테이블 크기가 유한해서, 슬롯이 다 차면 새 연결을 통과시키지 못합니다.
- 뒤에 있는 서버가 아무리 튼튼해도, 앞단 장비의 테이블이 먼저 터지면 그 뒤는 다 죽습니다.
- NAT는 특히 취약합니다. 내부 여러 클라이언트의 연결을 하나의 공인 IP 아래 매핑해 추적하므로, 매핑 엔트리가 고갈되면 전체 내부 통신이 마비돼요.
정리하면, 상태를 기억하는 장비는 모두 잠재적 표적입니다. 상태를 기억하는 능력 자체가 이 계열 공격의 공격면이 되는 셈이에요.
6. 조각화(fragmentation) 공격
IP 패킷은 지나가는 경로의 최대 전송 단위(MTU)보다 크면 여러 조각으로 나뉩니다. 이걸 조각화라고 해요. 나뉜 조각들은 목적지에서 원래 패킷으로 다시 맞춰집니다. 조각화 공격은 이 재조합 과정을 악용합니다.
수신 측은 조각들이 모두 도착할 때까지 이미 받은 조각을 버퍼에 담아둡니다. 오프셋 정보를 보고 퍼즐을 맞추려는 거예요. 공격자는 이 성질을 이용해 절대 완성되지 않는 조각을 뿌립니다. 마지막 조각을 일부러 빠뜨리거나, 앞뒤가 안 맞는 조각만 계속 보내는 식이죠. 수신 측은 오지 않을 나머지 조각을 기다리며 재조합 버퍼를 계속 붙들고, 이 버퍼가 쌓이면 메모리와 처리 자원이 고갈됩니다.
teardrop은 조각화 공격의 고전입니다. 재조합할 조각들의 오프셋을 일부러 겹치도록 조작해서 보내는 방식이에요. 정상이라면 조각들은 이어 붙게 배치되는데, 겹치는 오프셋을 받으면 취약한 옛 운영체제의 재조합 로직이 계산을 잘못해 비정상 동작이나 크래시로 이어졌습니다. 지금의 커널은 이런 겹침을 정상 처리하도록 오래전에 고쳐졌지만, 오프셋을 조작해 재조합기를 혼란시킨다는 발상 자체는 여전히 참고할 값어치가 있습니다.
7. 고전이 된 기법들
프로토콜 공격의 역사에는 지금은 대부분 막힌 유명한 기법들이 있습니다. 실전에서 통하지 않게 된 지 오래지만, 원리를 뜯어보면 프로토콜의 어느 가정을 깨뜨렸는지가 선명하게 드러나요.
- Ping of Death: 규격상 허용된 최대 크기를 넘는 IP 패킷을 조각으로 나눠 보냅니다. 수신 측이 재조합하면 버퍼 한도를 초과해, 경계 검사가 허술하던 옛 시스템에서 오버플로와 크래시를 일으켰어요. 크기 검증이 강화되면서 막혔습니다.
- Smurf: 출발지를 피해자로 스푸핑한 ICMP 요청을 네트워크의 브로드캐스트 주소로 보냅니다. 그 대역의 모든 호스트가 일제히 피해자에게 응답을 쏟아내는 반사·증폭 공격이에요. 라우터가 브로드캐스트로 향하는 요청을 기본적으로 차단하게 되면서 힘을 잃었습니다.
- LAND: 출발지 IP·포트와 목적지 IP·포트를 똑같이 맞춘 패킷을 보냅니다. 취약한 시스템은 자기 자신에게 응답하려다 무한 루프에 빠졌어요. 지금은 이런 패킷을 대부분 걸러냅니다.
세 기법 모두 공통점이 있습니다. “설마 이런 값이 오겠어?”라는 프로토콜 구현의 암묵적 가정을 정면으로 어겼다는 점이에요. 최대 크기를 넘겨보고, 브로드캐스트를 반사판으로 쓰고, 출발지와 목적지를 같게 만드는 식입니다. 방어의 역사가 곧 이런 가정에 명시적 검증을 하나씩 붙여온 과정이라고 봐도 좋습니다.
8. 왜 적은 트래픽으로도 아픈가
이 계열 공격을 관통하는 한 단어는 비대칭성입니다. 공격자가 패킷 하나를 만드는 비용과, 그 패킷을 받은 쪽이 상태를 유지하는 비용이 전혀 같지 않아요.
SYN 패킷 하나는 60바이트 안팎에 불과합니다. 공격자는 이걸 던지고 잊으면 끝이에요. 하지만 받는 서버는 그 하나 때문에 메모리를 할당하고, 타이머를 걸고, backlog 슬롯을 내주고, 수십 초 동안 그 상태를 유지해야 합니다. 공격자의 노력은 일회성이고 수신자의 노력은 지속형이라, 이 시간 차이가 그대로 증폭 효과가 됩니다.
주요 프로토콜 공격이 각각 무엇을 고갈시키는지 한눈에 정리했습니다.
| 공격 | 고갈 대상 |
|---|---|
| SYN 플러드 | backlog 큐 슬롯 |
| ACK·RST 플러드 | 상태 테이블 조회 CPU |
| 연결 고갈 | 동시 연결 한도 |
| 조각화 공격 | 재조합 버퍼 |
| conntrack 공격 | NAT·방화벽 테이블 |
핵심은 하나입니다. 대역폭이 아무리 넉넉해도, 상태를 기억하는 자원이 유한하다면 그 자원이 먼저 바닥납니다. 그래서 이 공격들은 큰 회선 없이도, 적은 트래픽만으로도 서비스를 멈추게 만들 수 있어요.
마치며
프로토콜·상태 고갈 공격은 “많이 보내서 이기는” 싸움이 아니라 “상대가 붙들고 있는 것을 갉아먹는” 싸움입니다. TCP 핸드셰이크의 대기 구간, 방화벽의 상태 테이블, 재조합 버퍼처럼 서버가 무언가를 기억하려고 할당해둔 모든 공간이 표적이 될 수 있어요. 방어의 출발점은 이 자원들이 어디에 얼마나 있는지, 그리고 어느 지점이 가장 먼저 터질지를 아는 데 있습니다.
일단 오늘은 여기까지…..
다음 글에서는 정상 요청처럼 굴면서 서버를 태우는 애플리케이션 계층(L7) 공격을 파볼게요.
← 이전 글: (2/6) 볼류메트릭·반사증폭 공격 — 대역폭을 말려버리는 방법 | 다음 글 →: (4/6) 애플리케이션 계층(L7) 공격 — 정상 요청처럼 굴며 서버를 태우기
참고: Cloudflare Learning — SYN flood attack · RFC 4987 — TCP SYN Flooding Attacks and Common Mitigations · Cloudflare — Smurf attack