XSS filtering 취약점 공격 방어 시나리오:
client → WAF → 어플리케이션 환경에서 WAF가 요청 검증해서 XSS 공격 코드 차단
※ WAF: Web Application Firewall, 웹 방화벽
1. 시나리오
APP은 WAF를 통과한 데이터를 받아 작업 수행한다.
⇒ WAF는 전달 받은 데이터를 다시 디코딩해서는 안 됨, 그러면 오히려 공격자가 더블 인코딩으로 웹 방화벽의 검증을 쉽게 우회함
<script>(내용)</script>
// %253Csript%253E (내용을 더블 인코딩 한 값)
// 웹 방화벽이 해당 데이터를 디코딩 후 검증 -> %3Csript%3E(내용을 한 번 인코딩)-> 음 안전하네
// app에서 해당 데이터를 다시 디코딩해서 <script> %3Csript%3E을 게시판 DB에 저장, 검증 후 디코딩 발생
// 희생자가 해당 게시글 읽으면 XSS가 발생하여 악성 자바스크립트 코드 실행
검사가 미흡한 php 예시
<?php
$query = $_GET["query"]; // PHP는 GET, POST하고 자동으로 디코딩함
if (stripos($query, "<script>") !== FALSE) {
header("HTTP/1.1 403 Forbidden");
die("XSS attempt detected: " . htmlspecialchars($query, ENT_QUOTES|ENT_HTML5, "UTF-8"));
}
...
$searchQuery = urldecode($_GET["query"]); // 한 번 더 디코딩(urldecodes)
?>
<h1>Search results for: <?php echo $searchQuery; ?></h1>
아래의 php 코드를 보면 디코딩이 두 번 이루어짐을 알 수 있다.
POST /search?query=%3Cscript%3Ealert(document.cookie)%3C/script%3E HTTP/1.1
if (stripos($query, "<script>") !== FALSE)
...
-----
HTTP/1.1 403 Forbidden
XSS attempt detected: <script>alert(document.cookie)</script>]
// 공격 실패
POST /search?query=%253Cscript%253Ealert(document.cookie)%253C/script%253E HTTP/1.1
...
-----
HTTP/1.1 200 OK
<h1>Search results for: <script>alert(document.cookie)</script></h1>
// 공격 성공
위의 경우엔 (내용)을 한번만 인코딩하였으므로 두 번째 디코딩에서 필터링된다. 그러나 아래의 경우에서는 (내용)을 두번 디코딩하게 되어 결과적으로 페이로드를 실행하게 되어 공격이 성공한다.
2. 방어 방법
HTML entity encoding(출력 시 인코딩)은 HTML 정규화의 반대 과정으로 브라우저가 정규화를 통해 코드를 실행하지 못하도록, 의도적으로 문자를 뭉개서 단순 텍스트로 보이게 만드는 방어 과정이다. 서버에 어떤 값이 저장되어 있든 브라우저에 뿌려줄 때 <를 <로 바꿔서 출력하면 브라우저는 이를 코드로 실행하지 않고 단순한 문자로 인식한다.
브라우저는 HTML 문서를 읽을 때 두 가지 모드로 작동합니다.
- 실행 모드 (Parsing Tags): <script>를 만나면 자바스크립트 코드로서 실행
- 출력 모드 (Rendering Text): <script>를 만나면 화면에 <script>라고 출력하라는 의미구나 생각
해결법: $searchQuery를 출력할 때 htmlspecialchars()를 사용한다.
만약 공격자가 <script>alert(1)</script>라는 값을 입력햇을 때, 서버가 htmlspecialchars()를 써서 보내준다면 서버(php)는 공격자가 보낸 데이터를 받자마자 바로 화면에 뿌리지 않고, htmlspecialchars()에 집어 넣는다.
| 구분 | 서버가 브라우저에 보내는 코드 (Source) | 브라우저가 사용자에게 보여주는 화면 (View) | 스크립트 실행 여부 |
| 인코딩 안 함 | <h1><script>alert(1)</script></h1> | (알림창이 뜸) | 실행됨 (위험) |
| 인코딩 함 | <h1><script>alert(1)</script></h1> | <script>alert(1)</script> | 실행 안 됨 (안전) |