Node.js Brotli 디코더 out-of-bounds write 취약점


요약
CVE-2026-111497은 Brotli 디코더가 잘못된 block switch 상태를 처리하는 과정에서 발생하는 out-of-bounds write 취약점이다. 공격자가 조작한 Brotli 스트림을 Node.js 서비스가 해제하면 프로세스 메모리 손상이나 서비스 중단으로 이어질 수 있다.
취약한 경로는 애플리케이션이 직접 zlib API를 호출하는 경우뿐 아니라, Brotli 요청 본문을 자동 해제하는 middleware를 사용하는 경우에도 포함된다.
Warning
외부에서 전달된 Brotli 스트림을 처리하는 Node.js 서비스는 런타임 patch 적용 전까지 해당 입력 경로를 제한해야 한다. 요청 본문과 HTTP 응답의 자동 압축 해제 경로를 함께 점검한다.
영향 범위
| 항목 | 내용 |
|---|---|
| CVE | CVE-2026-111497 |
| 유형 | Brotli 디코더의 out-of-bounds write |
| 공격 조건 | 공격자가 제공한 조작된 Brotli 스트림을 애플리케이션이 해제 |
| 영향 | 프로세스 메모리 손상, 서비스 중단 |
| 관련 patch | Google Brotli의 invalid stream 조기 검출 patch |
| patch 게시일 | 2026-02-27 |
공격 경로
body-parser는 2.0.0부터 Brotli 요청 본문을 지원한다. 따라서 Express 애플리케이션에서 압축된 요청 본문을 자동 처리하면 Node.js 내부의 Brotli 디코더까지 비신뢰 입력이 전달될 수 있다.
취약점은 middleware의 파싱 로직보다 하위 디코더의 block switch 처리에 있다. 애플리케이션 코드에 brotliDecompress() 호출이 직접 없어도 자동 압축 해제 경로가 있으면 점검 대상이다.
네트워크
다음 요청 특성을 확인한다.
Content-Encoding: br- Brotli로 압축된 요청 본문
- 외부 서버에서 수신한 Brotli 응답을 애플리케이션이 자동 해제하는 경로
엔드포인트
Brotli 요청 본문을 허용하는 엔드포인트를 식별한다.
- Express의 JSON, raw, text, URL-encoded parser
body-parser2.x 기반 middleware- 요청 본문을 직접 읽은 뒤 Brotli 해제를 수행하는 API
서버
실행 중인 Node.js와 컨테이너 이미지를 확인한다.
node --version
rg "brotliDecompress|createBrotliDecompress|Content-Encoding" .
docker images --format "{{.Repository}}:{{.Tag}} {{.ID}}"
Node.js 실행 파일이 컨테이너 이미지나 애플리케이션 번들에 포함될 수 있으므로 OS 패키지 목록만으로 런타임 버전을 판단하지 않는다.
코드 분석
-static BROTLI_INLINE BROTLI_BOOL DecodeBlockTypeAndLength(
+static BROTLI_INLINE BrotliDecoderErrorCode DecodeBlockTypeAndLength(
int safe, BrotliDecoderState* s, int tree_type) {
brotli_reg_t max_block_type = s->num_block_types[tree_type];
- if (max_block_type <= 1) {
- return BROTLI_FALSE;
+ if (max_block_type <= 1) {
+ return BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH;
}
- if (!SafeReadSymbol(type_tree, br, &block_type)) return BROTLI_FALSE;
+ if (!SafeReadSymbol(type_tree, br, &block_type)) {
+ return BROTLI_DECODER_NEEDS_MORE_INPUT;
+ }
if (!SafeReadBlockLength(s, &s->block_length[tree_type], len_tree, br)) {
s->substate_read_block_length = BROTLI_STATE_READ_BLOCK_LENGTH_NONE;
BrotliBitReaderRestoreState(br, &memento);
- return BROTLI_FALSE;
+ return BROTLI_DECODER_NEEDS_MORE_INPUT;
}
DecodeBlockTypeAndLength()는 block type과 block length를 해석하는 함수다. patch는 반환형을 BROTLI_BOOL에서 BrotliDecoderErrorCode로 바꾸고, 불가능한 block switch 상태를 BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH로 구분한다.
반대로 입력이 부족한 경우에는 BROTLI_DECODER_NEEDS_MORE_INPUT을 반환한다. 이 구분으로 잘못된 스트림을 입력 부족 상태로 계속 처리하지 않고 조기에 거부할 수 있다.
대응 포인트
- Node.js 런타임을 보안 patch가 포함된 버전으로 업데이트한다.
- 컨테이너 이미지, 서버리스 런타임, reverse proxy 이미지에 포함된 Node.js 실행 파일을 별도로 확인한다.
Content-Encoding: br요청을 허용하는 엔드포인트와 Brotli 응답을 자동 해제하는 클라이언트를 식별한다.- patch 적용 전까지 비신뢰 트래픽의 Brotli 요청을 제한하고, 필요한 경우 WAF에서
Content-Encoding: br요청을 차단한다. - 압축 해제 직후 발생하는 프로세스 종료, decoder 오류, worker 재시작을 Node.js 버전과 함께 분석한다.