Node2Shell EVOLVES to HTTP2Shell.
Node2Shell이 HTTP2Shell로 확산되다
Brotli 디코더 취약점 CVE-2026-111497은 Node.js뿐 아니라 Python 패키지와 여러 HTTP 구성요소에도 영향을 줍니다. 취약 버전과 패치 방법, Brotli 처리를 잠시 끄는 우회책을 정리합니다.
- 주제
에디터 노트
실제 위험은 Brotli를 쓰는 HTTP 경로에만 한정되지 않고, 공격자가 조작한 데이터가 취약한 디코더에 도달하는 모든 경로에 있습니다. 원문도 HTTP 요청과 응답뿐 아니라 라이브러리와 런타임에 포함된 디코더를 점검하라고 설명합니다. 따라서 HTTP2Shell이라는 이름은 주된 노출 경로를 드러내지만 취약점의 범위를 HTTP 자체로 오해하게 만들 수 있다는 댓글의 지적도 설득력이 있습니다. 패치 전 제거와 헤더 차단은 각 경로를 줄이는 임시 조치이며, Accept-Encoding에서 br을 빼는 것만으로는 악성 서버의 Brotli 응답을 막지 못합니다. distroless 컨테이너는 공격 도구와 취약점 연쇄를 제한하지만, 댓글에서 지적했듯 앱이 가진 비밀 정보나 네트워크 접근 권한까지 없애지는 않습니다. 원문이 제시한 버전 목록과 함께 번들된 라이브러리, 간접 의존성, 실행 중인 이미지까지 확인하는 것이 실질적인 대응입니다.
AI 요약
Brotli 디코더의 메모리 손상 취약점 CVE-2026-111497이 Node.js에서 확인된 뒤 Python 패키지와 여러 언어 및 HTTP 서비스 구성요소로 확산됐습니다. 공격자는 조작한 Brotli 데이터를 보내 메모리 누출이나 프로세스 충돌을 일으키고, 경우에 따라 임의 코드를 실행할 수 있습니다. 취약점은 인증 없이 원격에서 악용될 수 있으며 CVSS 4.0 점수는 9.2입니다.
취약점의 원인
문제는 Brotli 디코더 함수 DecodeBlockTypeAndLength()가 입력 오류와 추가 입력 필요 상태를 구분하지 못하는 데 있습니다. 특정 형태의 압축 스트림에서 블록 길이가 끝난 뒤 유효하지 않은 블록 전환을 요청하면 디코더가 처리를 멈추지 않고 기존 상태로 계속 진행할 수 있습니다. 이 경로에서 작업 버퍼 경계를 벗어난 쓰기가 발생합니다.
상류 수정 커밋은 함수 반환값을 오류 코드로 바꾸고 BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH 오류를 추가했습니다. 잘못된 블록 전환이 발생하면 안전 경로와 빠른 경로 모두 디코딩을 중단합니다. 원문은 Brotli 1.2.0과 1.2.1의 소스 배포본을 비교한 결과 수정본에 버전 변경과 해당 디코더 수정만 포함됐다고 설명합니다.
영향을 받는 구성요소와 패치
Brotli 1.0.3부터 1.2.0까지의 디코더가 영향을 받습니다. Python에서는 Brotli 1.2.0 이하와 brotlicffi 1.2.0.x를 각각 1.2.1 이상, 1.2.1.0 이상으로 올려야 합니다. 애플리케이션 코드가 두 패키지를 직접 불러오지 않아도 requests, urllib3, httpx, aiohttp 같은 HTTP 클라이언트가 설치된 패키지를 이용해 Brotli 응답을 자동으로 해제할 수 있습니다. Scrapy 등 간접 의존성으로 설치하는 경우도 점검 대상입니다.
원문이 제시한 업데이트 대상에는 Node.js 22.23.4, 24.22.1, 26.12.1, Bun 1.4.4, Envoy의 지원 브랜치별 수정 버전, Istio 1.29.9, 1.30.6, 1.31.2가 포함됩니다. 네이티브 Brotli는 1.2.1 이상으로 올리고, 운영체제 패키지는 공급업체 보안 업데이트를 적용해야 합니다. 정적으로 포함되거나 제품에 번들된 라이브러리는 해당 애플리케이션도 다시 빌드해야 합니다.
당장 업그레이드하기 어려운 경우
Python 환경에서 Brotli 기능이 선택 사항이면 Brotli와 brotlicffi를 제거하고 의존성 잠금 파일과 컨테이너 이미지를 다시 만들어야 합니다. gzip과 deflate는 계속 쓸 수 있지만, 서버가 Brotli 응답을 보내면 응답을 읽지 못하거나 애플리케이션 오류가 날 수 있습니다. 이 조치는 다른 런타임이나 제품에 별도로 포함된 디코더까지 제거하지는 않습니다.
요청을 받는 서비스는 취약한 디코더에 도달하기 전에 프록시나 미들웨어에서 Content-Encoding에 br이 포함된 요청을 거부할 수 있습니다. 외부로 보내는 요청은 Accept-Encoding에서 br을 빼고, 응답을 자동 해제하기 전에 Brotli 응답을 거부하거나 디코딩을 꺼야 합니다. Accept-Encoding만 바꾸면 악성 서버가 Brotli 응답을 보내는 경로는 막지 못합니다. 패치 후에는 잠금 파일과 컨테이너를 갱신하고 실제 실행 중인 서비스가 수정된 구성요소를 쓰는지 확인해야 합니다.
Reddit 반응
- @you_up_in — HTTP라는 표현은 마케팅을 앞세운 보도자료 같고, 이 문제는 Brotli 압축 라이브러리에 관한 것이지 HTTP와는 별 관련이 없다고 봅니다.
- @Not-the-best-name — 손상된 데이터의 Brotli 압축을 푸는 경로가 문제라면, 보통 스택의 HTTP 끝단에서 그 처리가 이뤄지므로 표현이 어느 정도 맞습니다.
- @jorgecardleitao — Rust의 구현인 rust-brotli는 영향을 받지 않습니다. 다만 Cargo를 포함한 Rust 생태계의 상당 부분은 brotli-sys를 통해 취약한 구현을 사용합니다.
- @Jolly-Warthog-1427 — 컨테이너를 가능한 한 작게 만들고 distroless로 실행하는 것은 좋은 관행입니다. 셸과 리눅스 도구가 없다면 공격자가 활용할 수 있는 수단도 줄어듭니다.
- @Not-the-best-name — 셸이 없는 컨테이너라도 웹 앱이 비밀 정보에 접근하고 데이터베이스와 Redis에 연결할 수 있습니다. 셸이 없다는 점이 앱에 대한 루트 접근을 막아 주나요?
- @Phezh — 실행에 필요한 것만 담은 최소 컨테이너라면 취약점이 있어도 공격자가 쓸 도구가 줄어들어 공격 표면과 취약점 연쇄를 제한합니다. 그렇다고 앱 자체의 취약점이 사라지는 것은 아닙니다.
- @bapfelbaum — 코드를 실행할 수 있으면 필요한 도구를 직접 추가할 수 있으니 셸이 없다고 쓸모가 없어지는 것은 아닙니다. 그래도 최소 구성 권고는 여전히 타당합니다.
원문: Aikido / 번역·요약: Trawling