
PoC Docker lab: 파일 업로드 우회 + 저장형 XSS를 연쇄하여 관리자 계정 생성. 침투 테스터를 위한 교육 자료.
의도적으로 취약하게 만든 웹 애플리케이션으로, CSP, CORS, CSRF 보호가 적용된 상태에서도 파일 업로드 우회가 저장형 XSS와 연계되어 백도어 관리자 계정을 생성하는 방법을 보여줍니다.
전체 블로그 글: KurtiseBear Blog
이 실습 환경은 방어적 보안 훈련을 위한 교육용입니다. 공개적으로 접근 가능한 곳에 배포하지 마십시오.
애플리케이션에는 실제 보안 제어가 적용되어 있습니다:
'self'로 제한합니다 (단 'unsafe-inline' 및 'unsafe-eval' 허용).낮은 권한의 사용자 계정을 가진 공격자는 두 가지 취약점을 연계하여 이 모든 것을 우회합니다:
파일 업로드 우회 -- 업로드 양식은 클라이언트 측 accept 속성을 통해 .pdf만 허용하지만, 서버는 파일 유형 검증을 전혀 수행하지 않습니다. 공격자는 JavaScript가 포함된 .js 파일을 업로드합니다. 다운로드 엔드포인트가 동일 출처에서 제공하므로 CSP와 CORS가 차단하지 않습니다.
메시지 제목을 통한 저장형 XSS -- 메시징 기능은 사용자 입력을 정제 없이 저장합니다. 관리자 받은 편지함은 메시지 제목을 원시 HTML로 렌더링합니다. XSS 페이로드는 `` 핸들러를 사용하여 업로드된 스크립트를 가져와서 eval()로 실행합니다. CSP가 이를 허용하는 이유는 'unsafe-inline'과 'unsafe-eval'이 허용되기 때문입니다.
API 엔드포인트에 CSRF 누락 -- 사용자 관리 API(/api/manage-user.php)는 양식 엔드포인트와 달리 CSRF 토큰을 검증하지 않습니다. XSS 페이로드는 관리자의 동일 출처 세션을 사용하여 이 API를 호출합니다. CSRF가 있더라도 동일 출처 JavaScript는 DOM에서 토큰을 읽을 수 있습니다.
결과: 관리자가 받은 편지함을 열면 XSS가 발동되고, JavaScript가 관리자의 세션을 사용하여 백도어 관리자 계정을 생성합니다. 모든 방어가 적용되어 정상 작동합니다. 이 체인이 작동하는 이유는 결코 출처를 벗어나지 않기 때문입니다.
docker-compose up -d
MySQL이 초기화될 때까지 10-15초 기다린 후 http://localhost:8080에 접속하세요.
| 역할 | 이메일 | 비밀번호 |
|---|---|---|
| Admin | [email protected] | admin |
| User | [email protected] | user |
http://localhost:8080으로 이동하여 [email protected] / user로 로그인하세요.
Upload Files로 이동하세요. 양식에는 "PDF only"라고 되어 있지만 클라이언트 측에서만 적용됩니다. 다음 중 하나를 수행하세요:
accept=".pdf" 속성을 제거하거나,제공된 payload.js(또는 자신의 파일)를 업로드하세요. 반환된 파일 ID(예: 1)를 기록하세요.
업로드된 파일은 이제 동일 출처의 /api/download.php?file_id=1에서 제공됩니다. CSP는 이 엔드포인트로의 가져오기를 차단하지 않습니다. 'self'이기 때문입니다.
Send Message로 이동하세요. 제목 필드에 다음을 입력하세요:
r.blob()).then(b=>b.text()).then(eval)">
(2단계의 실제 파일 ID로 1을 바꾸세요.)
본문에는 아무 내용이나 입력하세요. 우선순위를 체크하면 받은 편지함 맨 위에 위치합니다. 전송하세요.
onerror 핸들러가 작동하는 이유는 CSP가 'unsafe-inline'을 허용하기 때문입니다. eval()이 작동하는 이유는 CSP가 'unsafe-eval'을 허용하기 때문입니다. 다운로드 엔드포인트로의 가져오기가 작동하는 이유는 동일 출처이기 때문입니다.
로그아웃하세요. [email protected] / admin으로 로그인하세요. Inbox로 이동하세요.
메시지 제목이 원시 HTML로 렌더링됩니다. `` 태그 로드에 실패하여 onerror 핸들러가 발동하고, 업로드된 페이로드를 가져와서 eval()이 실행합니다. 페이로드는 관리자의 세션 쿠키(동일 출처 요청에 자동으로 첨부됨)를 사용하여 /api/manage-user.php로 POST 요청을 보냅니다. API 엔드포인트가 CSRF 토큰을 확인하지 않으므로 필요하지 않습니다.
Users로 이동하세요. BackdoorAdmin이라는 새 사용자가 역할 admin 및 이메일 [email protected]으로 표시되어야 합니다.
로그아웃 후 [email protected] / Compromised1!로 로그인하여 확인하세요.
CSP blocks external scripts
--> But the payload is hosted on the same origin via file upload
--> And unsafe-inline/unsafe-eval allow the onerror handler and eval()
CORS blocks cross-origin requests
--> But every request in the chain is same-origin
CSRF tokens protect form submissions
--> But the API endpoint doesn't validate them
--> And even if it did, same-origin JS can read tokens from the DOM
Session cookies have standard protections
--> But same-origin requests carry them automatically
방어는 모두 정상 작동합니다. 교차 출처 공격을 막도록 설계되었습니다. 이 체인은 결코 출처를 벗어나지 않습니다.
이 체인을 실제로 차단할 수 있는 방법:
htmlspecialchars()를 사용하세요. 받은 편지함이 $row['subject']를 원시로 렌더링합니다. 이는 XSS를 완전히 차단합니다.'unsafe-inline' 및 'unsafe-eval'을 제거하세요. 합법적인 인라인 스크립트에는 nonce 또는 해시를 사용하세요. 이는 onerror 핸들러와 eval()을 차단합니다.docker-compose down -v
이 애플리케이션은 의도적으로 취약하게 만들어졌습니다. 교육 및 방어적 보안 훈련 목적으로만 설계되었습니다. 신뢰할 수 없는 사용자가 접근 가능한 네트워크에 배포하지 마십시오. 명시적인 서면 승인 없이 시스템에 대해 이러한 기술을 사용하지 마십시오.