뒤로
2026/10/26
HTTP2Shell(CVE-2026-111497): 바이너리를 업데이트해도 실행 중인 프로세스는 패치되지 않는다
HTTP2Shell의 원인인 Brotli 취약점은 Node.js, Bun, Python에 영향을 줍니다. 실행 중인 프로세스에서 실제 노출을 찾고 AI 에이전트를 안전하게 재시작하는 방법을 소개합니다.
2026년 10월 21일 Node.js는 Google의 Brotli 디코더에서 발견된 경계 밖 쓰기(out-of-bounds write) 취약점 CVE-2026-111497의 수정 버전을 배포했습니다. 며칠 사이에 Bun, Python brotli 및 brotlicffi 패키지, Envoy, Istio의 보안 권고가 잇따랐고, 보안 언론은 이 취약점을 Node2Shell, 이후 HTTP2Shell이라고 불렀습니다. 이름과 달리 이것은 HTTP/2 프로토콜의 버그가 아닙니다. 압축 해제 과정의 버그이며, 공격자가 영향을 줄 수 있는 Brotli 데이터를 소프트웨어가 디코딩하는 모든 곳에서 문제가 됩니다.
이 글에서는 확인된 사실과 가능성에 그치는 내용을 구분하고, 자사 시스템을 점검하는 방법을 설명합니다. 또한 저희가 직접 대응하면서 얻은 교훈도 다룹니다. 바로 수정된 바이너리를 설치해도 이전 바이너리를 계속 실행 중인 프로세스는 수정되지 않는다는 점입니다. 저희 환경에서는 설치된 Claude Code를 업데이트한 후에도 장시간 실행 중이던 Claude Code 세션들이 이전 내장 런타임을 그대로 유지하고 있었습니다.
핵심 요약
- 영향 범위: 업스트림 Brotli 1.0.3~1.2.0. 수정 버전: 1.0.10, 1.1.1, 1.2.1. Brotli를 번들로 포함하는 제품은 각각 자체 수정 릴리스가 필요합니다.
- 실제로 노출되려면 공격자가 영향을 줄 수 있는 Brotli 데이터가 취약한 디코더에 도달하는 실제 경로가 있어야 합니다. 제품명이나 네트워크 접근만으로는 증거가 되지 않습니다.
- 설치된 패키지만이 아니라 실행 중인 프로세스를 확인해야 합니다. 프로세스는 재시작될 때까지 시작 시점의 런타임을 계속 사용합니다.
- 상태를 가진 AI 에이전트는 이벤트, 승인, 작업 상태를 먼저 인계한 뒤 한 번에 하나씩 재시작해야 합니다.
1. 취약점의 내용
디코더는 잘못된 블록 전환을 만나면 영구적인 형식 오류 대신 “입력이 더 필요함”을 보고했습니다. 그러면 스트리밍 방식의 호출자가 계속 바이트를 공급했고, 디코더는 이를 8바이트 내부 버퍼에 덧붙이면서 버퍼 끝을 넘어 기록했습니다. Node.js 보안 권고에 따르면, 애플리케이션이 node:zlib로 압축 해제하는 Brotli 데이터를 공급할 수 있는 공격자는 “프로세스 메모리를 손상시킬 수 있으며, 이는 크래시, 프로세스 메모리 노출 또는 원격 코드 실행으로 이어질 수 있습니다”.
심각도 평가는 출처에 따라 다릅니다. Node.js는 High로 평가합니다. Python 패키지에 대한 GitHub 보안 권고와 Envoy 보안 권고는 Critical, CVSS 4.0 점수 9.2로 평가합니다. Ubuntu는 현재 우선순위를 Medium으로 표시하고 있습니다. 10월 26일 기준으로 CVE 레코드에 있는 CISA의 평가는 악용 여부를 “none”으로 표시하고 있으며, 이 CVE는 CISA의 Known Exploited Vulnerabilities(KEV) 목록에 포함되어 있지 않습니다. 그래도 Node.js 보안 권고는 원격 코드 실행을 가능한 결과로 명시하고 있으므로, 보고된 공격이 없다는 사실을 대응을 미룰 이유로 삼아서는 안 됩니다.
2. 전제 조건: 실제로 노출되는 경우
다음 세 가지 조건이 모두 충족되어야 합니다.
- 취약한 디코더가 로드되어 있음: 프로세스에 수정이나 백포트가 적용되지 않은, 영향 범위 내의 번들 또는 시스템 Brotli 라이브러리가 있습니다.
- 이를 호출하는 코드가 있음: 명시적인 압축 해제, 또는
Content-Encoding: br의 자동 디코딩. - 입력이 공격자의 영향을 받을 수 있음: 임의 URL의 응답, 업로드된 파일, 수신 요청 본문, 또는 적대적인 네트워크상의 평문 HTTP 트래픽.
팀들이 가장 자주 놓치는 경로는 자동 디코딩입니다. Bun 보안 권고에 따르면 fetch는 기본적으로 Accept-Encoding: br를 전송하고 응답을 디코딩하므로, 신뢰할 수 없는 URL을 가져오는 모든 Bun 프로그램에는 경로가 존재합니다. Python에서는 brotli 또는 brotlicffi가 설치되면 urllib3가 Content-Encoding: br 응답을 자동으로 디코딩하고, requests도 요청에서 br을 지원한다고 알립니다. 이는 자체 코드에서 Brotli를 전혀 언급하지 않더라도 발생합니다. Envoy와 Istio는 Brotli 압축 해제기가 구성된 경우에만 노출됩니다.
3. 영향이 확인된 것과 가능성에 그치는 것
2026년 10월 26일 기준 현황입니다. 상황이 빠르게 변하고 있으므로 링크된 보안 권고를 확인하십시오.
| 구성 요소 | 상태 | 수정 버전 |
|---|---|---|
| 업스트림 Brotli (C) | 확인됨: 1.0.3~1.2.0 | 1.0.10, 1.1.1, 1.2.1 |
| Node.js 22, 24, 26 | 확인됨 | 22.23.4, 24.22.1, 26.12.1 |
| Node.js 20 및 이전 버전(지원 종료) | 평가되지 않음. Node.js는 지원 종료 라인을 항상 영향받는 것으로 간주 | 지원되는 라인으로 이전 |
| Bun(컴파일된 실행 파일 포함) | 확인됨: 1.0.21~1.4.3 | 1.4.4, 그리고 컴파일된 앱 재빌드 |
| Python brotli / brotlicffi | 확인됨: 1.0.3부터 | 1.2.1 / 1.2.1.0 |
| Envoy(Brotli 압축 해제기 구성 시) | 확인됨 | 1.40.0, 1.39.3, 1.38.6, 1.37.8(지원되지 않는 1.36 브랜치는 1.36.12) |
| Istio(Brotli 압축 해제 활성화 시) | 확인됨 | 1.31.2, 1.30.6, 1.29.9(1.29의 마지막 릴리스) |
| Ubuntu brotli 패키지 | 평가 필요(모든 릴리스) | 아직 수정 패키지 없음 |
| Ubuntu nodejs 패키지 | 20.04에서 취약(Brotli 번들). 22.04 이상은 시스템 Brotli를 사용하므로 위 행을 따름 | Ubuntu 트래커 참조 |
| Debian brotli 패키지 | bookworm과 trixie에서 수정됨. forky는 아직 미수정 | 1.0.9-2+deb12u1, 1.1.0-2+deb13u1 |
| Chrome, Electron 및 Electron 앱 | 알 수 없음: 벤더 발표를 찾지 못함 | 벤더 릴리스 노트 주시 |
마지막 행에 대해 덧붙입니다. Chrome과 Electron은 자체 Brotli 사본을 포함하며, Electron 앱은 Node.js 런타임도 내장합니다. 이 글을 쓰는 시점에 최신 Electron 안정 릴리스(45.0.0, 44.9.3, 43.8.3, 모두 10월 21일)는 Node.js 24.22.1이 아닌 24.22.0을 명시하고 있으며, 릴리스 노트에도 이 CVE에 대한 언급이 없습니다. 10월 23일자 Electron 프리릴리스에는 Node.js 24.22.1이 포함되어 있어 안정 버전의 수정도 예상되지만, 아직 배포되지는 않았습니다. 따라서 Electron 기반 데스크톱 앱은 취약성이 확인된 것이 아니라 노출 가능성이 있는 상태입니다. 채팅 클라이언트와 AI 데스크톱 도구도 마찬가지입니다. 영향을 받는다거나 안전하다고 판단하기 전에 각 벤더의 자체 발표를 확인하십시오.
함정이 하나 더 있습니다. 보고되는 라이브러리 버전은 어느 쪽으로도 증거가 되지 않습니다. 수정된 Node.js 22.23.4도 수정 사항이 백포트되었기 때문에 process.versions.brotli를 1.1.0으로 표시합니다. 배포판도 마찬가지입니다. Brotli 버전 문자열이 아니라 제품의 수정 버전이나 배포판의 보안 상태를 기준으로 판단하십시오.
4. 저희 환경에서 발견한 내용
저희는 아무것도 변경하기 전에 읽기 전용 인벤토리 조사를 수행했습니다. 아래는 관찰 결과를 익명화한 것으로, 확인된 사실과 검증하지 못한 위험을 구분해 정리했습니다.
확인된 관찰 결과
- 설치된 Claude Code는 이미 업데이트되어 있었지만, 장시간 실행 중인 세션 십여 개가 몇 주 전에 시작된 이전 빌드로 계속 실행되고 있었습니다. 이 세션들은 한 번도 재시작되지 않았습니다.
- 처음 집계에서는 그중 세 개를 놓쳤습니다. 다른 헬퍼 프로세스 아래에서 실행되고 있어 명령줄 검색으로는 일치하지 않았기 때문입니다. 실행 파일로 검색하자 찾을 수 있었습니다.
- 이전 빌드의 파일은 업데이터가 이미 디스크에서 삭제한 상태였기 때문에, 해당 프로세스에 내장된 Bun 버전을 읽을 수 없었습니다.
- 패키지 매니저로 설치된 이전 Claude Code 빌드가 남아 있었고, 여기에는 Bun 1.4.0이 내장되어 있었습니다. 정상 운영에서는 사용되지 않았지만 폴백 경로에서 사용될 가능성이 있었습니다. 이에 의존하는 것이 없음을 입증한 뒤 제거했습니다.
- 사용되지 않는 지원 종료 Node.js 런타임이 한 서버에 여전히 설치되어 있었습니다. 같은 방식으로 제거했습니다.
- 업그레이드한 현재 Claude Code 빌드(2.1.310)에는 Bun 1.4.4가 포함되어 있습니다. 바이너리를 검사하여 이를 확인했습니다. Anthropic의 변경 내역(changelog)에는 이 CVE가 언급되지 않으므로, 여러분의 빌드도 같은 방법으로 확인하십시오.
검증되지 않아 위험으로 간주한 사항
- 이전 세션이 실제로 취약했는지 여부. Bun 버전을 읽을 수 없었습니다. 수정된 Bun 1.4.4는 해당 빌드보다 뒤인 10월 22일경에야 나왔으므로, 영향을 받는 것으로 간주했습니다.
- 공격자가 예를 들어 악성 URL을 가져오게 하는 방식으로 이 세션들을 통해 디코더에 도달할 수 있었는지 여부. Bun의 문서화된
fetch동작을 보면 이 경로는 충분히 가능하지만, 저희가 입증하지는 않았습니다. - 자체 Brotli 사본을 번들로 포함하는 다른 소프트웨어가 영향을 받는지 여부. 벤더 발표가 없는 경우에는 안전하다거나 취약하다고 가정하지 않고, 벤더의 릴리스를 기준으로 해당 제품을 추적합니다.
악용 증거는 발견되지 않았습니다. 그렇다고 이를 안전의 증거로 받아들이지도 않았습니다. 해결책은 노출 가능성을 논리로 부정하는 것이 아니라 노출 자체를 제거하는 것이었습니다.
5. 탐지 및 조치 체크리스트
의존성 찾기
패키지로 설치된 곳뿐 아니라 번들로 포함된 곳에서도 Brotli를 찾으십시오. SBOM과 컨테이너 이미지에서 Node.js, Bun, Python 패키지, Envoy, 정적으로 링크된 Brotli를 확인합니다. Brotli는 선택적 extra로 함께 설치되는 경우가 많으므로 Python의 전이적 의존성도 확인하십시오.
# Node.js: compare with 22.23.4 / 24.22.1 / 26.12.1
node -p process.version
# Python packages: run once per interpreter and virtual environment
python -m pip list 2>/dev/null | grep -iE '^(brotli|brotlicffi) '
pipdeptree -r -p brotli # what pulled it in
# Debian / Ubuntu system library (then check the distro tracker)
dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${Version}\n' 'libbrotli*'
# Container images, with an SBOM scanner such as Syft
syft <image> | grep -iE 'brotli|node|bun'
내장 런타임 찾기
bun build --compile로 빌드된 프로그램은 자체 Bun 사본을 가지고 있습니다. Bun 보안 권고에 따르면 이러한 프로그램은 1.4.4로 재빌드될 때까지 계속 영향을 받습니다. 빌드 머신의 Bun을 업그레이드해도 이미 배포된 바이너리는 바뀌지 않습니다.
# The Bun version inside a compiled executable
grep -aoE 'Bun v1\.[0-9]+\.[0-9]+' /path/to/binary | sort -u
실행 중인 프로세스 찾기
설치된 것이 아니라 실행 중인 것을 확인하십시오. Linux에서는 실행 파일이 교체되거나 삭제된 프로세스에 (deleted)가 표시됩니다. 이는 아직 이전 빌드를 실행하고 있다는 강력한 신호입니다.
# Processes still running a deleted (replaced) executable (run as root to see all users)
for p in /proc/[0-9]*; do
e=$(readlink "$p/exe" 2>/dev/null) || continue
case "$e" in *"(deleted)") echo "${p#/proc/} $e";; esac
done
# Processes older than the fix
pgrep -af 'node|bun|claude' # then: ps -o pid,lstart,args -p <pid>
macOS에서는 lsof -p <pid>로 프로세스가 로드한 실행 파일을 확인할 수 있습니다. 프로세스 이름이 아니라 실행 파일로 대조하십시오. 그렇지 않으면 헬퍼와 백그라운드 워커를 놓치게 됩니다.
수정, 재빌드, 재배포
- 표에 있는 수정 버전으로 업그레이드하십시오. 백포트가 있는 경우 배포판의 패치된 패키지를 사용하십시오.
- 컨테이너 이미지와 컴파일된 실행 파일을 재빌드한 뒤 재배포하십시오. 베이스 이미지를 패치해도 재빌드하지 않은 이미지에는 아무 효과가 없습니다.
- 사용하지 않는 런타임과 남아 있는 설치본은 어떤 서비스, 작업, 스크립트도 의존하지 않음을 입증한 뒤 제거하십시오. 롤백 수단은 유지하십시오.
- 아직 수정 버전이 없는 경우, 안전하게 할 수 있는 범위에서 신뢰할 수 없는 입력에 대한 Brotli 디코딩을 비활성화하십시오. Bun에서는
fetch(url, { decompress: false })를 요청 단위로 사용할 수 있습니다. 각 완화 조치에는 담당자와 종료 일자를 지정하십시오.
재시작 및 검증
재시작 후에는 새 프로세스의 실제 실행 파일과 런타임 버전을 확인하십시오. 그다음 비즈니스 결과를 확인합니다. 큐가 모두 처리되었는지, 예약된 작업이 성공했는지, 모니터링 알림이 들어오고 있는지 확인합니다. 업그레이드 명령이 성공했다는 것은 명령이 실행되었다는 사실만 증명할 뿐입니다.
6. 상주형 AI 에이전트를 안전하게 재시작하기
장시간 실행되는 AI 에이전트는 대기 중인 도구 호출, 승인, 큐에 쌓인 메시지, 작업 점유 같은 상태를 보유합니다. 이를 한꺼번에 재시작하면 그 상태를 잃을 수 있습니다. 저희에게는 다음 순서가 효과적이었습니다.
- 각 세션을 인벤토리로 정리합니다: 실행 파일 버전, 상태(유휴, 대기, 작업 중), 마지막 활동, 대기 중인 작업, 기다리고 있거나 보유하고 있는 승인, 확인되지 않은 이벤트와 그 복구 방법.
- 승인을 메모리 밖으로 옮깁니다. 세션의 대화 안에만 존재하는 승인은 재시작하면 사라집니다. 재시작 전에 대기 중인 각 승인(무엇이, 누구에 의해, 정확히 어떤 변경에 대해 승인되었는지)을 티켓이나 풀 리퀘스트처럼 영구적인 곳에 기록하십시오. 재시작 후에는 세션이 여전히 그 승인을 인식하는지 확인합니다. 확신이 없으면 승인되었다고 가정하지 말고 다시 승인을 요청하십시오.
- 대체 수단이 작동하는지 먼저 입증합니다. 수정된 빌드의 세션이 이미 정상적으로 작동하는지, 그리고 이전 세션과 독립된 이벤트 감시자(watcher)가 실행 중인지 확인하여, 어떤 이벤트도 재시작되는 세션에 의존하지 않도록 합니다.
- 유휴 세션부터 한 번에 하나씩 재시작합니다. 하나를 재시작할 때마다 이전 프로세스가 사라졌는지, 새 프로세스가 수정된 실행 파일로 실행되는지, 대화 기록이 그대로인지, 원격 연결이 복구되었는지 확인합니다. 그런 다음 다음 세션으로 넘어갑니다.
- 가장 바쁜 담당 세션은 작업과 작업 사이에 마지막으로 재시작합니다. 인계 메모를 작성하게 하고, 대기 중인 이벤트와 확인되지 않은 이벤트가 0일 때만 감시자를 중지한 뒤 재시작합니다. 이후 감시자가 저장된 위치에서 누락 없이 재개되었는지 확인합니다.
- 재연결 시 큐에 쌓인 입력이 들어올 것을 예상합니다. 저희가 재시작한 세션 중 하나는 10일 동안 대기하던 메시지를 받고 그에 따라 동작했습니다. 재시작 전에 각 세션의 수신함을 확인하고 담당자에게 알리십시오.
- 어느 단계에서든 작업이 복구되었음을 보여줄 수 없다면 중단하고 에스컬레이션합니다. 모든 것을 강제 종료하는 방식으로 되돌아가지 마십시오.
시작하기 전에 ‘완료’를 정의하십시오. 완료란 영향받는 빌드로 실행 중인 프로세스가 하나도 없고, 모든 예외가 담당자 및 재확인 날짜와 함께 문서화되어 있으며, 비즈니스 점검이 통과하는 상태를 의미합니다. “패키지를 업그레이드했다”는 완료가 아닙니다.
Global
Japan
China
Taiwan
Vietnam
Thailand
Indonesia
Portugal
Spain
France
Germany
Egypt
Russia