구글이 개발한 무손실 압축 알고리즘 브로틀리(Brotli)는 HTML, CSS, 자바스크립트 같은 웹 콘텐츠를 더 작게 만들어 전송하는 데 널리 쓰인다. 바로 이 브로틀리의 디코더에서 발견된 치명적 결함이 특정 런타임을 넘어 HTTP를 쓰는 수많은 라이브러리와 애플리케이션으로 파급되고 있다. CVE-2026-111497로 등록된 이 취약점은 처음 Node.js에서 'Node2Shell'이라는 이름으로 공개됐지만, 영향 범위가 HTTP 생태계 전반으로 확대되면서 'HTTP2Shell'로 불리기 시작했다. CVSS 4.0 기준 9.2점(심각)으로, 버전 1.0.3부터 1.2.0까지 모든 플랫폼이 대상이다.
양방향으로 작동하는 메모리 손상
문제의 본질은 out-of-bounds write, 즉 경계를 벗어난 메모리 쓰기다. 결함은 블록 전환(스트림이 사용하는 허프만 트리를 바꾸는 지점)을 처리하는 DecodeBlockTypeAndLength() 함수에 있다. 취약한 버전에서 이 함수는 단순 불리언을 반환하는데, BROTLI_FALSE가 '스트림이 손상됐다'는 의미인지 '입력이 더 필요하다'는 의미인지 호출부가 구분할 수 없다. 더구나 빠른 처리 경로에서는 반환값을 아예 버린다. 공격자가 블록 유형을 하나만 선언한 뒤 그 블록의 길이를 고갈시키면 성공할 수 없는 전환이 강제되고, 디코더는 이미 가지고 있던 블록 상태로 계속 복호화를 진행하다 메모리를 침범한다.
이 결함이 특히 위험한 이유는 양방향으로 작동하기 때문이다. 2월에 이 문제를 처음 발견한 OpenAI는 인증 없는 원격 코드 실행으로 보고했는데, 악의적인 클라이언트가 패치되지 않은 서버를 장악할 수도, 압축 응답을 받아들이는 패치되지 않은 클라이언트를 악의적인 서버가 장악할 수도 있다. 원격의 비인증 공격자가 조작된 브로틀리 스트림만 보내면 메모리 손상, 민감 정보 유출, 프로세스 중단, 나아가 임의 코드 실행까지 가능하다.
코드에 임포트가 없어도 노출된다
실무자 입장에서 가장 까다로운 지점은 노출 경로다. PyPI의 Brotli와 brotlicffi 두 패키지는 Node.js가 번들하는 것과 동일한 취약 디코더를 컴파일하며, requests·httpx·aiohttp·urllib3 같은 인기 HTTP 클라이언트는 이 패키지가 설치돼 있기만 하면 자동으로 로드해 브로틀리 응답을 복호화한다. 즉 애플리케이션 코드 어디에도 브로틀리를 직접 임포트하지 않았더라도, Scrapy처럼 자동으로 끌어오는 의존성을 통해 환경에 들어와 있으면 노출된다. 공격 데이터는 앱이 보낸 요청에 대한 악성 응답으로 들어오거나, 브로틀리로 압축된 요청 본문으로 들어온다. Brotli 패키지의 월 다운로드가 8,280만 건에 이르는 만큼 전이 의존성으로 퍼진 범위는 상당하다.
조치는 간단하다. Brotli 1.2.1 또는 brotlicffi 1.2.1.0 이상으로 올리면 된다. 상류 수정 커밋(google/brotli 0ed4268)은 함수가 오류 코드를 반환하도록 바꾸고 BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH(-17)를 추가해, 안전 경로와 빠른 경로 모두에서 형식 오류 시 복호화를 멈추게 한다. OSM이 두 버전을 AddressSanitizer로 빌드해 실험한 결과, 1.2.0은 약 23MB 스트림을 끝까지 소비하면서 계속 입력이 더 필요하다고 보고한 반면 1.2.1은 매번 오류 -17로 거부했다. 의존성 잠금 파일을 갱신하고 컨테이너 이미지를 재빌드한 뒤 재배포·재시작하는 것까지가 한 세트다.
자동 스캐너를 믿지 말아야 하는 이유
이번 사안에서 반드시 짚어야 할 한계가 있다. 10월 23일 기준으로 어떤 자문 데이터베이스도 이 CVE를 두 PyPI 패키지에 매핑하지 않았다. 그 결과 pip-audit, Dependabot, Safety가 취약 버전을 아직 잡아내지 못한다. 수정 버전들의 릴리스 노트에도 CVE 언급이 없어, 자동 도구만 믿으면 패치가 필요한 상태를 놓치기 쉽다. 근본 원인은 2월에 소스에서 이미 고쳐졌지만 당시 보안 문제로 인식되지 않아 수정이 포함된 릴리스가 지금껏 나오지 않았고, 브로틀리를 번들하는 모든 프로젝트가 각자 업데이트를 내야 하는 구조다. 리눅스 배포판 패키지로 설치된 경우라면 수정도 배포판 보안 자문을 통해 와야 하니 그쪽도 확인해야 한다.
임시 완화책도 한계가 분명하다. Accept-Encoding 헤더만 바꾸는 것으로는 부족한데, 악성 서버는 클라이언트가 무엇을 광고하든 브로틀리 응답을 보낼 수 있고 urllib3는 응답의 Content-Encoding 헤더를 보고 디코더를 고르기 때문이다. Brotli와 brotlicffi를 제거하면 HTTP 클라이언트가 gzip과 deflate로 되돌아가 취약 디코더가 사라지고, 엣지에서 브로틀리 인코딩 요청을 거부하면 인바운드 노출은 줄지만 앱이 스스로 가져오는 브로틀리 응답까지 막아주지는 못한다. Apache의 표준 mod_brotli는 브로틀리를 복호화하지 않으므로 영향을 받지 않는다.
Node.js 쪽에서는 10월 22일 기준 실제 악용 사례가 보고되지 않았지만, 안이하게 볼 사안은 아니다. 신뢰할 수 없는 네트워크 입력을 다루는 구성 요소의 메모리 안전 결함이고, 동일한 디코더 코드가 수정되지 않은 Node.js 24.22.0 빌드를 상대로 실제 익스플로잇에 쓰였다는 보고(Imperva 등)가 있다. 수정은 평범한 버전 업그레이드에 불과하니, 외부 서버를 호출하거나 압축된 요청 본문을 받아들이는 서비스부터 먼저 올리는 것이 합리적인 우선순위다.