Apache HTTP Server HTTP/2 DoS 취약점
(CVE-2026-49975)
■ 서론
2026년 6월, Apache HTTP Server에서 서비스 거부(DoS) 취약점(CVE-2026-49975)이 공개되었다. 해당 취약점은 Apache HTTP Server가 HTTP/2 요청의 Cookie 헤더를 처리하는 과정에서 발생하며, 공격자는 조작된 HTTP/2 요청을 통해 서버가 비정상적으로 많은 메모리를 사용하도록 만들 수 있다.
Apache HTTP Server는 웹사이트와 웹 서비스를 운영하는 데 널리 사용되는 오픈소스 웹 서버이다. 주로 사용자의 HTTP/HTTPS 요청을 받아 정적 파일을 제공하거나, 필요한 요청을 백엔드 서버로 전달하는 역할을 한다.
그림 1. W3Techs 웹서버 점유율 (2026.06.08)
높은 사용률을 보이는 만큼 취약한 버전을 사용하는 환경도 상당수 존재할 수 있으며, 특히 외부에서 HTTP/2 요청을 직접 받는 Apache HTTP Server는 이 취약점에 직접 노출될 수 있다. 공격이 성공할 경우 서버 응답 지연, 정상 요청 처리 실패, 최악의 경우 서비스 중단으로 이어질 수 있으므로, HTTP/2를 사용하는 환경에서는 취약 버전 여부를 확인하고 보안 패치를 적용해야 한다.
■ 영향받는 소프트웨어 버전
CVE-2026-49975에 취약한 소프트웨어는 다음과 같다.
| S/W 구분 | 취약 버전 |
| Apache HTTP Server | v2.4.17 <= version <= 2.4.67 |
■ 공격 시나리오
그림 2. 공격 시나리오
|
① 공격자가 피해자의 Apache HTTP Server에 메모리 사용량을 증폭시키는 HTTP/2 요청을 전송한다. ② 서버는 이 요청을 처리하는 과정에서 메모리를 비정상적으로 많이 사용하게 된다. ③ 이 상태에서 일반 사용자가 서버에 정상적인 HTTP 요청을 보낸다. ④ 서버는 이미 가용 메모리가 부족한 상태이므로 정상 요청을 처리하지 못하고, 일반 사용자는 서비스를 이용할 수 없는 서비스 거부(DoS) 상태가 발생한다. |
■ 테스트 환경 구성 정보
피해자 서버는 VMware 가상 머신(Kali Linux)으로 구성하고, 그 위에서 취약한 Apache HTTP Server를 Docker 컨테이너로 구동한다. 공격자는 호스트 PC에서 Docker로 구동 중인 Kali Linux 컨테이너를 사용하며, 가상 머신의 피해자 서버를 대상으로 공격을 수행한다.
| 이름 | 정보 |
| 피해자 |
Apache HTTP Server 2.4.67 (VMware Kali VM, RAM 2GB) (192.168.94.128:10081) |
| 공격자 |
Kali Linux (호스트 PC, Docker) (172.17.0.3) |
■ 취약점 테스트
Step 1. 취약 환경 구성
먼저 피해자 환경을 구성한다. 피해자는 HTTP/2 통신이 가능한 Apache HTTP Server이며, 취약 버전인 2.4.67을 사용한다. 가상 머신에서 다음 명령어로 Docker 이미지를 빌드한 뒤 컨테이너를 실행한다.
|
> git clone https://github.com/EQSTLab/cve-2026-49975.git > cd cve-2026-49975 > docker build -t cve-2026-49975 . > docker run -d --name cve-2026-49975 -p 10081:80 cve-2026-49975 |
Step 2. 공격 수행 및 결과 확인
공격 대상 서버 상태를 확인하기 위해 지속적으로 요청을 보낸 후 체크한다. 공격자 Kali에서 다음 명령어를 통해 서버 상태를 확인한다.
|
> git clone https://github.com/EQSTLab/cve-2026-49975.git > cd cve-2026-49975 > python3 check_server.py --host 192.168.94.128 --port 10081 --path / --interval 0.5 --duration 600 --timeout 10 |
그림 3. 서버 상태 확인
다른 터미널에서 다음 명령어를 통해 피해자 Apache HTTP Server에 DoS 공격을 시도한다.
|
> cd cve-2026-49975 > python3 poc.py --host 192.168.94.128 –-port 10081 |
그림 4. DoS 공격 요청 전송
앞서 실행한 check_server.py를 확인해 보면, 정상적으로 처리되던 요청이 DoS 공격 시도 이후로 처리 시간이 증가하거나 timed out이 발생하는 것을 확인할 수 있다. 이는 피해자의 서버가 공격자의 요청을 처리하는데 모든 메모리를 사용 중이기 때문이다.
그림 5. 정상적인 서버 이용이 불가능한 모습
■ 취약점 상세 분석
Step 1. HTTP/2
이번 스텝에서는 CVE-2026-49975를 이해하기 위해 HTTP/2의 요청을 처리 방식을 알아본다.
1. 스트림 기반 요청 처리 방식
HTTP/2는 하나의 연결 안에서 스트림 단위로 요청과 응답을 처리한다. 스트림은 하나의 요청과 그에 대한 응답을 묶어 관리하는 단위이며, 클라이언트는 연결을 새로 만들지 않고도 여러 스트림을 생성해 동시에 여러 요청을 전송할 수 있다.
그림 6. HTTP/2 요청 처리 방식
2. 요청 헤더 처리 방식
HTTP/1.1에서는 요청 헤더가 문자열 형태로 전송되는 반면 HTTP/2는 헤더를 그대로 문자열로 반복 전송하지 않고, HPACK이라는 헤더 압축 방식을 사용해 더 짧은 형태로 표현한다. HPACK은 자주 사용되는 헤더 이름과 값을 테이블에 등록하고, 이후 동일하거나 유사한 헤더가 등장하면 전체 문자열 대신 테이블의 인덱스를 참조한다.
|
HTTP/1.1 GET / HTTP/1.1 Host: example.com User-Agent: test-agent Cookie: session=abc123 HTTP/2 예시 82 → :method: GET (정적 테이블 index 2 참조) 84 → :path: / (정적 테이블 index 4 참조) 86 → :scheme: http (정적 테이블 index 6 참조) BE → :authority: example.com (동적 테이블 index 62 참조) BF → user-agent: test-agent (동적 테이블 index 63 참조) C0 → cookie: session=abc123 (동적 테이블 index 64 참조) |
HPACK에서 사용하는 테이블은 정적 테이블과 동적 테이블로 나뉜다. 정적 테이블은 HTTP/2 규격에 미리 정의된 고정 테이블로, 클라이언트와 서버는 이 테이블의 인덱스와 값을 동일하게 알고 있다. 다음은 정적 테이블의 일부 항목이다.
| Index | Header Name | Header Value |
1 (0x81) |
:authority |
- |
2 (0x82) |
:method |
GET |
4 (0x84) |
:path |
/ |
6 (0x86) |
:scheme |
http |
|
~ |
||
32 (0xA0) |
cookie |
- |
61 (0xBD) |
www-authenticate |
- |
동적 테이블은 통신 중 새로 등장한 헤더를 등록해 재사용하는 테이블이다. 아래는 Cookie와 user-agent 값이 동적 테이블에 등록된 예시이다.
| Index | Header Name | Header Value |
62 (0xBE) |
:authority |
example.com |
63 (0xBF) |
user-agent |
test-agent |
64 (0xC0) |
cookie |
session=abc123 |
정적 테이블에 있는 헤더는 고정된 인덱스로 참조할 수 있고, 동적 테이블에 등록된 헤더는 이후 요청에서 해당 인덱스를 참조해 다시 사용할 수 있다. 서버는 수신한 인덱스를 두 테이블에서 찾아 원래의 헤더 이름과 값으로 복원해서 사용한다.
특히 HPACK 압축 효율을 높이기 위해 Cookie 값을 여러 Cookie 헤더 필드로 분리해 표현할 수 있다. 예를 들어 세션 값, CSRF 토큰처럼 서로 다른 쿠키 항목을 별도의 Cookie 헤더 필드로 표현하면, 요청마다 바뀌지 않는 값은 HPACK 동적 테이블에서 참조해 재사용할 수 있다.
그림. 7 Cookie 분리 전송을 통한 HPACK 압축
Apache HTTP Server는 이렇게 나뉘어 들어온 Cookie 헤더 필드를 하나의 Cookie 값으로 병합하여 사용한다.
Cookie: session=abc123; csrftoken=def456; locale=ko-KR |
결과적으로 HTTP/2에서는 HPACK을 통해 반복되는 Cookie 헤더 필드를 짧은 인덱스 참조로 표현할 수 있다. 이 특징은 요청 전송을 효율적으로 만들지만, 서버 내부에서는 압축 해제와 Cookie 병합 처리가 추가로 발생한다.
Step 2. 취약 코드 분석
CVE-2026-49975는 Cookie 헤더 병합 시 헤더 필드 개수 반영 누락과 HTTP/2 흐름 제어 특성이 결합되어 발생한다. 취약 버전의 Apache HTTP Server는 기존 Cookie 값에 새로운 Cookie 헤더 필드 값을 병합하는 경우에는 헤더가 추가되었다고 판단하지 않아, Cookie 헤더 필드가 LimitRequestFields 기반 헤더 개수 제한에 영향을 받지 않았다.
이를 통해, 공격자는 대량의 Cookie 헤더 필드를 처리하게 만들어 Cookie 병합 과정에서 메모리 사용량이 증폭된다. 그리고 요청 처리를 지연시키면 해당 메모리의 회수도 늦어진다. 그 결과 서버 메모리 사용량이 높은 상태가 유지되며 서비스 거부(DoS) 상태로 이어질 수 있다.
그림 8. CVE-2026-49975 취약점 발생 흐름
1. Cookie 헤더 필드 개수 검증 누락
Apache HTTP Server는 LimitRequestFields 설정값을 기준으로, 요청 하나에서 처리할 수 있는 헤더 필드 수를 제한(기본값=100)한다. HTTP/2 요청 처리 과정에서는 스트림별로 처리된 헤더 필드 수가 request_headers_added 값에 기록되며, 이 값이 LimitRequestFields를 초과하면 요청을 차단한다.
서버는 헤더 처리 결과로 전달된 was_added 값이 참(True)일 때만 request_headers_added를 증가시킨다.
그림 9. was_added 값에 따른 헤더 필드 수 증가
이후 누적된 request_headers_added가 LimitRequestFields 값을 초과하면 에러를 반환하고 요청 처리를 중단한다.
그림 10. LimitRequestFields 초과 시 요청 차단
HTTP/2에서는 Cookie 헤더 필드를 여러 조각으로 나누어 전송할 수 있으며, 서버는 나뉘어 들어온 Cookie 헤더 필드를 "; " 구분자로 이어 붙여 하나의 Cookie 값으로 병합한다. 문제는 병합 로직에서 *pwas_added를 1로 변경하지 않는다는 점이다.
그림 11. Cookie 조각 병합 후 헤더 카운트가 증가하지 않는 부분
그 결과 두 번째 이후의 Cookie 헤더 필드는 서버 내부에서 기존 Cookie 값에 병합되지만, request_headers_added 값에는 반영되지 않는다. 즉 실제로는 다수의 Cookie 헤더 필드가 처리되고 있음에도 LimitRequestFields 제한에 걸리지 않고 Cookie 병합을 반복하게 된다.
2. Cookie 병합 과정의 메모리 증폭
실제 메모리 증가가 발생하는 지점은 대량의 Cookie 헤더 필드를 하나의 Cookie 값으로 병합하는 부분이다. 기존 Cookie 값(existing)과 새로 들어온 Cookie 값을 "; " 구분자로 합친 뒤, 그 결과를 새 문자열로 할당해 헤더 테이블에 다시 저장한다.
그림 12. Cookie 병합 시 새 문자열 할당
코드에서 existing은 헤더 테이블에 이미 저장되어 있던 Cookie 문자열 버퍼를 가리킨다. 첫 번째 Cookie 값이 저장된 이후 두 번째 Cookie 헤더 필드부터는 existing과 새 Cookie 값이 "; " 구분자로 결합된다. 이때 기존 버퍼를 확장하지 않고, 병합 결과를 담는 새 문자열 버퍼를 생성해 헤더 테이블에 다시 저장한다.
예를 들어 공격자가 다음과 같이 빈 Cookie를 동적 테이블에 할당하고, 반복 참조를 통해 수많은 쿠키 헤더 필드를 보냈다고 가정한다.
그림 13. HPACK 인덱스 참조가 복원된 Cookie 헤더 반복
새 문자열 버퍼에는 문자열 끝을 표시하는 NUL 1 byte가 함께 저장되므로, 실제 할당 기준 크기는 화면에 보이는 문자열 길이보다 1 byte 더 크다. 예를 들어 빈 문자열은 1 byte가 필요하고, "; "은 NUL을 포함해 3 bytes가 필요하다.
그림 14. 정렬 단위에 따른 할당 크기 증가
또한 이렇게 할당되는 문자열 버퍼는 CPU와 메모리 관리 방식에 맞춰 일정한 정렬 단위로 배치된다. 따라서 1, 3, 5, 7 bytes처럼 작은 문자열은 모두 8 bytes 영역에 저장될 수 있다. 반면 "; ; ; ; "처럼 화면상 길이가 8 bytes인 문자열은 NUL까지 포함하면 9 bytes가 필요하므로, 다음 정렬 단위인 16 bytes 영역에 저장될 수 있다.
따라서 Cookie 병합이 반복될수록 새 문자열 버퍼가 계속 생성되고, 각 버퍼는 실제 문자열 길이보다 큰 단위로 메모리를 차지한다. 또한 병합 과정에서 만들어진 중간 문자열은 요청 처리가 끝나기 전까지 정리되지 않아, 반복 횟수가 증가할수록 메모리 사용량이 크게 늘어난다.
그림 15. Cookie 병합으로 인한 문자열의 주소 증가
이 과정은 하나의 스트림에서 최대 약 16MB의 메모리 사용량을 발생시키며, 여러 스트림과 연결에서 같은 처리가 반복되면 서버 전체 메모리 사용량은 GB 단위로 확대될 수 있다.
3. 응답 전송 지연에 따른 메모리 유지
서버 내부 메모리 사용량이 증가하더라도, 요청 처리가 종료되면 해당 스트림과 관련된 메모리는 정리된다. 하지만 요청 처리를 지연시키면 증폭된 메모리 사용량이 장시간 유지될 수 있으며, HTTP/2의 WINDOW_UPDATE가 이에 악용될 수 있다.
HTTP/2는 응답 DATA 프레임의 전송량을 조절하기 위해 흐름 제어를 사용한다. 클라이언트는 연결을 시작할 때 수신할 응답 데이터의 크기를 window_size로 지정하여 서버에 전송할 수 있고, 서버는 이 크기만큼만 응답 데이터를 전송할 수 있다. 이후 클라이언트는 데이터를 더 받을 수 있을 때 WINDOW_UPDATE를 보내 수신할 수 있는 크기를 증가시킨다.
이 구조에서 클라이언트가 WINDOW_UPDATE 값을 1과 같은 작은 값으로 설정하고 일정 주기로 전송하는 경우, 서버는 응답 데이터를 매우 작은 단위로만 전송할 수 있으므로 응답 완료까지 오랜 시간이 소요된다. Apache HTTP Server는 요청 처리 중 생성된 헤더, 요청 객체, 응답 상태 등의 데이터를 요청 처리가 끝날 때까지 보관하므로, 이번 취약점에 악용될 경우 Cookie 헤더 병합에 사용된 큰 메모리가 장시간 유지된다. 그 결과 서버의 메모리 사용량이 지속적으로 증가하여 가용 메모리가 고갈되고, 서비스 거부 상태를 유도할 수 있다.
그림 16. HTTP/2 WINDOW_UPDATE 악용 흐름
그 결과, 작은 크기의 요청만으로도 서버가 대량의 Cookie 헤더 필드를 병합하게 만들 수 있으며, 응답 처리를 지연시켜 증가한 메모리 사용량을 장시간 유지함으로써 서비스 거부 상태를 유도할 수 있다.
그림 17. 서버 메모리를 모두 점유한 모습
■ 대응 방안
2026년 6월 Apache HTTP Server는 CVE-2026-49975 취약점에 대한 보안 패치를 공개하였다. 해당 패치에서는 HTTP/2 요청에서 여러 Cookie 헤더 필드가 하나의 Cookie 값으로 병합되는 경우에도 이를 헤더 추가로 계산하도록 수정하였다. 이를 통해 서버가 과도한 양의 Cookie 병합을 수행하지 않게 되어, DoS 공격에 대한 위험이 감소하였다.
| S/W 구분 | 패치 버전 |
| Apache HTTP Server | v2.4.68 |
Step 1. 보안 패치 적용
1. Cookie 병합 로직 보완
패치 이전에는 기존 Cookie를 병합하는 과정은 헤더가 추가된 것으로 판단하지 않았고, 그로 인해 대량의 Cookie 헤더 필드가 요청으로 들어오면, 많은 메모리를 할당하여 처리했다.
패치 이후에는 Cookie 헤더 필드 처리 방식이 변경되었다. 빈 Cookie 값은 무시하고, 값이 있는 Cookie 헤더 필드는 기존 Cookie 값과 병합된 후 request_headers_added에 더해져, 다른 헤더와 마찬가지로 헤더가 추가된 것으로 처리한다.
그림 18. Cookie 병합 시 헤더 추가 상태 반영
따라서 과도한 양의 Cookie 헤더 필드가 포함된 HTTP/2 요청은 LimitRequestFields 제한에 의해 차단되며, 패치 이전과 같은 대량의 반복 병합으로 인한 메모리 증폭이 불가능해졌다.
2. 패치 후 메모리 점유율 변화
보안 패치 적용 후, Cookie 헤더 필드 수를 제외하고 동일 조건에서 공격을 테스트해 본 결과, 약 30MB의 메모리 점유가 가능하였다. LimitRequestFields 기본 제한 값인 100개의 헤더 필드를 전송하는 경우, 패치 이전처럼 많은 양의 메모리를 점유하려면 훨씬 많은 연결이나 스트림이 필요하므로 실질적으로 DoS 공격이 어려워졌다.
그림 19. Cookie 헤더 검증 추가에 의한 메모리 점유율 감소
Step 2. 패치 적용이 어려운 경우
보안 패치를 즉시 적용하기 어려운 경우에는 HTTP/2 사용을 비활성화하여 공격 경로를 차단할 수 있다. 이 취약점은 HTTP/2 연결에서 여러 스트림을 유지하면서 서버 내부 처리 과정에 영향을 주는 방식으로 악용될 수 있다. 따라서, 패치 적용 전까지는 해당 가상 호스트에서 HTTP/1.1만 사용하도록 제한하는 것이 임시 완화책이 될 수 있다.
Apache HTTP Server는 Protocols 지시어를 사용해 가상 호스트에서 허용할 프로토콜을 지정할 수 있다. 다음과 같이 대상 Virtual Host 설정에 Protocols http/1.1을 추가한다.
|
# /usr/local/apache2/conf/httpd.conf <VirtualHost *:443> ServerName example.com Protocols http/1.1 </VirtualHost> |
보다 확실하게 HTTP/2 기능을 비활성화하려면 설정 파일에서 http2_module 또는 proxy_http2_module을 로드하는 LoadModule 라인을 주석 처리할 수 있다.
|
#LoadModule http2_module modules/mod_http2.so #LoadModule proxy_http2_module modules/mod_proxy_http2.so |
또한 기존 설정에 Protocols 지시어가 이미 존재하는 경우에는, h2, h2c 키워드를 제거하고 http/1.1만 남기도록 수정한다.
Protocols http/1.1 |
설정 변경 후에는 문법 검사를 수행한 뒤 서비스를 재시작한다.
|
> apachectl configtest > systemctl restart httpd |
다만 이 설정은 근본적인 해결책이 아니며, HTTP/2를 사용하는 서비스에서는 성능이나 클라이언트 호환성에 영향을 줄 수 있다. 따라서 패치 적용 전까지의 임시 완화책으로만 사용하고, 최종적으로는 Apache HTTP Server 2.4.68 이상으로 업데이트해야 한다.
■ 참고 사이트
• NVD
◦ https://nvd.nist.gov/vuln/detail/CVE-2026-49975
• Apache
◦ https://github.com/josdejong/mathjs/pull/3656/commits
◦ https://httpd.apache.org/docs/current/mod/core.html
• httpd GitHub
◦ https://github.com/apache/httpd/commit/47d3100b252dc6668a9e46ae885242be9eeca9cd
• mod_h2 GitHub
◦ https://github.com/icing/mod_h2/commit/35c6e405390ed361189a82acd96675401ea5947c
• Calif Researcher
◦ https://blog.calif.io/p/codex-discovered-a-hidden-http2-bomb