Node.js Brotli CVE-2026-111497 패치
배포를 끝내고 가방을 메는 저녁에 알림이 온다. CISA가 방금 KEV에 올린 번호 하나가 Node.js 안에 있다. 조치 기한은 2주도 남지 않았다. 가방을 다시 내려놓는 날, 이런 날이 온다.
KEV에 오른 그날
그 번호는 CVE-2026-111497이다. 2026-10-27에 CISA 실제 악용 목록(KEV)에 올랐고, 미국 연방기관에 준 조치 기한은 2026-11-10이다. 제품명은 Google Brotli로 적혀 있다. 범위는 Brotli 1.0.3부터 1.2.0까지다. Node.js뿐 아니라 그 Brotli를 번들한 소프트웨어가 전부 걸린다.
Node.js 공지에 적힌 내용은 이렇다. Brotli 디코더가 잘못된 블록 전환을 형식 오류로 끊지 않고 「입력이 더 필요하다」로 처리한다. 그래서 디코더가 8바이트짜리 내부 버퍼 뒤로 입력 바이트를 계속 이어 쓴다. 공격자가 준 Brotli 데이터를 푸는 앱이면 프로세스가 죽거나, 메모리가 새거나, 원격 코드 실행까지 간다. Node.js 공지에 적힌 제보자는 OpenAI Codex Security다. AI가 찾았다고 밝힌 취약점이 실제 악용 목록에 오른 사례다.
Brotli가 왜 Node 안에 있나
당번이 Brotli를 쓴 기억이 없어도 범위 안일 수 있다. Node.js는 Brotli 디코더를 번들해서 node:zlib로
노출한다. 외부에서 받은 br 압축 데이터를 푸는 코드가 이 디코더에 닿는다.
직접 require('node:zlib')를 부르지 않아도, HTTP 클라이언트 라이브러리가 Accept-Encoding: br로
받은 응답을 자동으로 푸는 경우가 있다. 서버라면 Content-Encoding: br로 들어온 요청 본문을 푸는
경로다. 외부 입력을 Brotli로 푸는 지점이 있는지부터 센다.
# 소스에서 br 해제 지점을 찾는다
grep -rn "brotliDecompress\|createBrotliDecompress\|BrotliDecompress" src/
올릴 버전
Node.js는 2026-10-21 보안 공지에서 26.12.1, 24.22.1, 22.23.4로 고쳤다. 이 버전 이상이면 Brotli cherry-pick이 들어가 있다.
| 계열 | 이 버전 이상 | 상태 |
|---|---|---|
| 26.x | 26.12.1 | 현재 최신 26.13.0 |
| 24.x | 24.22.1 | 현재 최신 24.22.2, Active LTS |
| 22.x | 22.23.4 | Maintenance LTS |
| 20.x | 없음 | 2026-04-30 지원 종료, 20.20.2가 마지막 |
Node.js 20은 이번 수정을 받지 못한다. 20을 아직 돌리고 있으면 22 이상으로 옮기는 일이 패치 목록에 같이 올라온다.
node -v
버전이 KEV 기한을 넘기지 않게 올리는 것이 1순위다. 나머지 9건은 같은 공지에 묶여 있어 올리면 함께 닫힌다.
같은 공지가 고친 인증서 검증
Brotli 말고도 TLS 호스트명 검증이 세 건 들어갔다. 세 건 다 표준이 아닌 형태로 쓴 IPv4 주소를
tls.checkServerIdentity()가 잘못 읽어서, 그 주소용이 아닌 인증서가 통과하는 문제다.
- CVE-2026-105483(26.x, 24.x): 전각 숫자(
127.0.0.1)로 쓴 주소를 IP로 인식하지 않아, DNS 이름에127.0.0.1을 적은 인증서가 IP 연결을 인증했다. CVE-2026-48618 수정 뒤 생긴 회귀다. - CVE-2026-106069(26.x, 24.x, 22.x):
0127.0.0.1처럼 앞자리가 0인 주소를 WHATWG 규칙은 8진수(87.0.0.1)로, OS 리졸버는 10진수(127.0.0.1)로 읽는다. 검증과 실제 연결이 다른 주소를 보게 된다. - CVE-2026-106072(medium):
0x7f.0.0.1,2130706433,127.1같은 표기에서 DNS SAN만 있는 인증서가 IP 연결을 인증했다. 신뢰하는 CA가 발급한 숫자형 DNS SAN 인증서와 glibc류 리졸버가 조건이다.
IP 주소로 직접 TLS 연결을 맺는 내부 호출이 있으면 해당한다.
같이 고친 CVE-2026-105482는 dns.resolve*()와 dns.reverse()가 NUL 문자가 든 이름을 조용히 잘라서
조회하는 문제다. 도메인 허용 목록으로 이름을 검사한 뒤 dns.resolve*()로 조회하는 코드라면, 검사한
이름과 다른 이름의 레코드를 받을 수 있었다.
번들 CA 목록도 NSS 3.131.1로 갱신해 eMudhra 사고에 대응했다. 이 사고는 Python 쪽 글에서 더 다룬다.
컨테이너는 이미지 안 node를 본다
호스트에 깐 node를 올려도, 돌고 있는 컨테이너는 이미지 안에 구운 node를 그대로 쓴다. 두 번호가 다를 수 있으니 컨테이너 안쪽을 따로 본다.
# 돌고 있는 컨테이너 안의 node
docker exec <컨테이너> node -v
# nvm으로 깐 경우 쉘마다 버전이 다를 수 있다
nvm current
베이스 이미지를 node:24 같은 태그로 고정해 뒀으면, 이미지를 다시 받아(docker pull) 다시 굽지
않는 한 옛 패치가 그대로다. 같은 태그여도 이미지에 구운 node는 굽던 날의 버전이다. 다시 굽고 교체한
뒤 위 명령으로 번호를 확인한다.
패치가 적용됐는지는 KEV 기한 안에 번호로 확인되면 된다. 가방은 그다음에 다시 멘다.