- HTTP/2 CONTINUATION Flood는
END_HEADERS없이 헤더 프레임을 계속 보내 서버 가용성을 무너뜨릴 수 있는 HTTP/2 구현 취약점 계열임 - 공격 요청이 완성되지 않아 HTTP 접근 로그에 남지 않으며, 원인 파악에는 원시 트래픽 바이트 분석이 필요할 수 있음
- 구현에 따라 CPU 고갈, 다중 연결 OOM, 단일 연결 OOM, 연결 해제 타이밍 버그로 인한 크래시까지 영향 범위가 달라짐
- Go, Firefox, Node.js 사례에서는 각각 HPACK 디코딩 지속, 응답 헤더 크기 제한 누락,
CONTINUATION처리 중 연결 해제와 메모리 카운터 갱신 충돌이 드러남 - Rapid Reset과 달리 많은 구현에서 단일 TCP 연결만으로도 서버 크래시가 가능해, HTTP/2를 쓰는 광범위한 인터넷 서비스가 영향을 받을 수 있었음
HTTP/2에서 CONTINUATION 프레임이 쓰이는 방식
- HTTP/2는 HTTP/1.1처럼 텍스트 라인을 주고받지 않고 바이너리 프레임을 교환하는 프로토콜임
HEADERS프레임은 요청과 응답의 HTTP 헤더를 보내며, 헤더 데이터는HPACK으로 인코딩된 field block fragment에 저장됨HEADERS프레임에는 헤더와 스트림의 끝을 알리는 플래그가 있음END_HEADERS: 해당 프레임이 보내려는 모든 헤더를 포함한다는 신호END_STREAM: 요청 또는 응답 본문이 더 없다는 신호
- 프레임에는 통신 시작 시 정해지는 최대 크기가 있고, 수신 프레임이 허용 크기를 넘으면 프로토콜 오류로 연결이 끊김
- 단일
HEADERS프레임에 헤더를 모두 담을 수 없으면END_HEADERS없는HEADERS뒤에CONTINUATION프레임들이 이어짐END_HEADERS없는HEADERS프레임END_HEADERS없는 추가CONTINUATION프레임들- 마지막
CONTINUATION프레임에END_HEADERS설정
- 마지막 헤더 프레임 뒤에는 요청 데이터를 담는
DATA프레임이 오거나 HTTP/2 스트림이 끝남
취약점의 핵심: 끝나지 않는 헤더 스트림
- 클라이언트가 새 HTTP/2 스트림을 시작한 뒤
HEADERS와CONTINUATION프레임을 보내면서END_HEADERS를 절대 설정하지 않으면, 서버는 무한 헤더 스트림을 계속 파싱하고 저장하려 함 - HTTP/1.1 서버는 일반적으로 무한 헤더를 막는 두 가지 장치를 갖고 있음
- 헤더 목록이 허용 크기를 넘으면 연결을 끊는 헤더 크기 제한
- 요청이나 헤더가 제때 전송되지 않으면 연결을 끊는 요청/헤더 타임아웃
- 여러 HTTP/2 구현에서는 이런 보호가 없거나 잘못 구현되어 있었고, Apache httpd, Envoy, 여러 HTTP/2 패키지와 코덱도 포함됨
- 구현별 결과는 네 갈래로 나뉨
- CPU 고갈: 추가 헤더를 읽고 디코딩하는 동안 CPU 사용량이 늘어 다른 요청 응답이 느려지거나 막힘
- 다중 연결 기반 OOM:
CONTINUATION헤더가 메모리에 저장되고, 헤더 목록 크기 제한은 있지만 헤더 타임아웃이 없어 각 연결이 메모리를 계속 점유함 - 단일 연결 기반 OOM: 일부 구현은 메모리가 찰 때까지 헤더를 계속 읽어 OS가 프로세스를 종료하게 됨
- 몇 개 프레임만으로 크래시:
CONTINUATION스트림 중간에 연결이 끊길 때 구현 버그로 서버가 크래시됨
END_HEADERS가 없으면 요청이 제대로 닫히지 않으므로 악성 클라이언트의 요청은 접근 로그에 저장되지 않음
Go 사례: HPACK 디코딩이 멈추지 않는 CPU 고갈
- Go는
CONTINUATIONFlood에서 CPU 고갈이 두드러진 사례임 - Go 구현은
http2MetaHeadersFrame추상화로 하나의HEADERS프레임, 0개 이상의CONTINUATION프레임, HPACK 디코더를 묶음 readMetaFrame은 헤더 크기 제한에 도달하거나 오류가 발생하면SetEmitEnabled(false)를 호출해 디코딩된 헤더 방출을 멈춤- 하지만 헤더 방출을 멈춘 뒤에도 HPACK 디코더는 입력 바이트를 계속 디코딩함
- 프레임 공급 루프는
HeadersEnded()가true가 될 때만 멈추며, 이는END_HEADERS플래그가 설정될 때 발생함 - 공격자가
END_HEADERS를 보내지 않으면readMetaFrame은 반환되지 않고, 공격자가 보내는 동안 HPACK 디코더가 계속 새 바이트를 처리함
OOM 사례와 Firefox 클라이언트 영향
- OOM은
CONTINUATION프레임으로 만들어지는 헤더 목록 크기를 제한하지 않은 구현에서 발생함 - 헤더 타임아웃이 없는 구현에서는 단일 HTTP/2 연결만으로 서버를 크래시시킬 수 있었음
- idle timeout이 있는 구현에서도 여러 HTTP/2 연결이 연결별 한도에 가까운 RAM을 점유하게 만든 뒤, 마지막
CONTINUATION프레임을 몇 초마다 바이트 단위로 보내 연결을 유지하는 방식이 가능했음 CONTINUATIONFlood는 서버뿐 아니라 브라우저 같은 클라이언트 측에서도 발생할 수 있음- Mozilla Firefox 수정 커밋에는 집계된 헤더 크기와 새 프레임 크기의 합이
network_http_max_response_header_size()를 넘으면PROTOCOL_ERROR로 세션 오류를 반환하는 검사가 추가됨
Node.js 사례: 연결 해제 중 assertion 크래시
- Node.js는 무한
CONTINUATION프레임 스트림 자체는 적절히 처리했지만, 헤더 스트림 중 연결이 끊길 때 data race가 발생함 - 공격 코드 실행 중 Node.js는
Http2Session::~Http2Session()에서CHECK_EQ(current_nghttp2_memory_, 0)assertion 실패로 크래시함 - 크래시는 HTTP/2 클라이언트가 Node.js 서버에서 연결을 끊는 정확한 시점과 연결되어 있었고, assertion은
Http2Session소멸자 안에 있었음 - Node.js는 HTTP/2 연결 처리를 위해 nghttp2 라이브러리를 내장함
current_nghttp2_memory_는 nghttp2 내부에서 할당된 메모리를 추적하며, 소멸자의session_.reset()이후 모든 nghttp2 아티팩트가 메모리에서 제거됐는지 확인함- 조사 결과
CONTINUATION프레임 파싱 중 nghttp2 콜백과reset()이 함께 실행되는 경우가 있었음NGHTTP2_IB_EXPECT_CONTINUATION상태에서CONTINUATION프레임이 도착함- 상태가
NGHTTP2_IB_READ_HEADER_BLOCK으로 바뀜 session_after_header_block_received,session_call_on_frame_received,on_frame_recv_callback흐름이 이어짐- Node.js의
OnFrameReceive와HandleHeadersFrame에서 메모리 카운터를 갱신함
HandleHeadersFrame과Http2Session::~Http2Session()이 동시에 실행되면current_session_memory_가 동시에 업데이트되고,current_nghttp2_memory_값이 음수가 되어CHECK_EQ가 실패함
2019년 HTTP/2 취약점들과의 차이
- 2019년 Netflix와 Google이 보고한 HTTP/2 취약점 묶음은 CERT/CC Vulnerability Note VU#605641에 정리되어 있음
- CVE-2019-9516, “0-Length Headers Leak”은 길이 0의 헤더 이름과 값으로 일부 구현이 메모리를 할당하고 세션이 끝날 때까지 유지하게 만드는 문제임
CONTINUATIONFlood는 빈 헤더가 아니라 서버가 설정한 프레임 크기 제한까지 많은 무작위 헤더를 보내는 방식임- CVE-2019-9518, “Empty Frame Flooding”은 빈 payload의
DATA,HEADERS,CONTINUATION,PUSH_PROMISE프레임 등을 end-of-stream 없이 보내 상대가 공격 대역폭에 비해 과도한 처리 시간을 쓰게 만드는 문제임 CONTINUATIONFlood는 빈 프레임이 아니라 가능한 한 큰 프레임을 사용해 메모리를 점유하고 디코딩 과정에서 CPU 사이클을 소비함
Rapid Reset보다 더 심각할 수 있었던 이유
- 2023년 10월 HTTP/2 프로토콜의 zero-day인 “Rapid Reset” 세부 내용이 공개됐고, “현재까지 가장 큰 DDoS 공격”으로 불렸음
- Rapid Reset은
END_STREAM과END_HEADERS가 설정된HEADERS프레임과RST_STREAM프레임 조합을 사용함 - 이 방식에서는 rate limiting 같은 표준 완화책이 피해를 줄일 수 있고, 서버 관리자는 많은 인바운드 요청을 로그에서 보고 경고를 받을 수 있음
CONTINUATIONFlood에서는END_HEADERS가 없으므로 단 하나의 요청도 완성되지 않으며, 관리자는 로그에서 요청을 볼 수 없음- 많은 구현에서
CONTINUATIONFlood는 단일 TCP 연결만으로 서버 크래시가 가능했고, 일부 사례는 아주 적은 데이터만으로도 가능했음 - Rapid Reset은 DDoS 공격에 사용됐고, 대부분의 경우 성공적인 공격에 botnet이 필요했음
인터넷 서비스에 미칠 수 있었던 영향
- Cloudflare Radar에 따르면 HTTP/2 트래픽은 봇을 제외한 인간 HTTP 트래픽의 약 60% 를 차지함
- Cloudflare Radar는 HTTP 트래픽이 전체 인터넷 전송의 70% 이상이라고 추정함
- 영향받는 프로젝트의 중요성과 쉬운 악용 가능성을 고려하면 인터넷의 큰 부분이 이 취약점에 노출되어 있었음
- HTTP는 웹사이트뿐 아니라 많은 RESTful API에도 사용됨
- 중요한 기업·정부 API와 웹사이트의 가용성 문제는 수백만 달러 손실이나 혼란을 만들 수 있음
- 악용됐다면 서버 관리자가 HTTP/2 지식 없이 디버깅하기 매우 어려웠을 수 있음
- 악성 HTTP 요청이 제대로 닫히지 않음
- 서버 접근 로그에 요청이 보이지 않음
- 대부분의 HTTP/2 서버에는 고급 프레임 분석 기능이 부족함
- 수동으로 원시 연결 데이터를 분석해야 함
조정 공개와 CERT/CC 대응
- 이 취약점 계열은 인터넷 안전에 상당한 위험을 가졌음
- 2024년 1월 보고 이후 CERT/CC는 이 문제를 추적하기 위해 Vulnerability Coordination case를 열었음
- 여러 기술 대기업과 오픈소스 프로젝트가 관련된 책임 있는 공개 과정에 참여함
- 단일 연구자가 많은 구현을 모두 점검하기 어렵기 때문에, 여러 벤더에 영향을 주는 문제에는 Vulnerability Coordination이 필요했음
- CERT/CC는 이 문제에 대한 Vulnerability Note를 공개했으며, 이런 노트는 매년 몇 개만 공개됨