URL Hash(#) 을 이용한 XSS 우회기법

최근에 Facebook의 Stored XSS 취약점 관련 버그바운티 리포트가 올라왔습니다. og tag를 이용해서 xss까지 진행된 케이스인데, 생각보다 조금 의외의 내용이라 글로 공유합니다.

Open Graph(og)는 웹 페이지에서 메타정보를 제공하는 프로토콜로 meta 태그에 페이지에 대한 정보를 담아 다른 어플리케이션(브라우저, 웹을 읽어오는 앱 등)에서 이 정보를 활용할 수 있도록 제공할 수 있습니다. 대표적으로 메신저나 SNS에서 링크를 공유할 때 사이트의 주요 이미지, 타이틀 정보 등을 요약하여 작성하는데 사용됩니다.

다만 og 태그 자체에 대한 문제는 아닙니다. 웹 브라우저에서 URL Hash(#)을 처리하는 방식에서 발생한 문제이지요.

XSS in MediaElements.js

우선 해당 리포트에서 문제점인 라이브러리는 Facebook에서 사용되고 있던 MediaElements.js 입니다. 이 Javascript는 ogVideoType이 video/flv인 경우 FlashMediaElement.swf를 로드하고 ogVideoUrl을 FlashME.swf로 전달하여 영상을 재생합니다.

FlashME로 ogVideoUrl을 전달하는 과정의 코드를 보면 이런 형태입니다.

function absolutizeUrl(ogVideoUrl) {
    var tempDiv = document.createElement('div');
    tempDiv.innerHTML = '<a href="' + ogVideoUrl.toString().split('"').join('&quot;') + '">x</a>';
    return tempDiv.firstChild.href;
}

flashDiv.innerHTML ='<embed src="FlashME.swf?videoFile=' + encodeURI(absolutizeUrl(ogVideoUrl )) +'" type="application/x-shockwave-flash">';

absolutizeUrl() 함수를 통해서 url 데이터를 가공한 후 플래시 파일의 인자값으로 데이터를 전달해주는데, 크롬에선 #과 “(quot)가 순차로 들어간 경우엔 필터링되지 않고 구문 밖으로 나가게 됩니다.

이걸 이용해서 XSS를 하였고 리포트 이후 이제 세상에 드러나게 되었습니다.

URL Hash and Bypass

URL Hash(#) 문자는 URL에서 # 뒤에 구간, 즉 Hash 영역을 표기하기 위한 문자입니다. 그래서 Javascript에서 location.hash를 통해 hash를 읽으면 #과 그 뒤에 fragment가 같이 포함되어 리턴됩니다.

재미있는건 URL Hash가 웹 브라우저별로 처리 방식이 다르다는 점인데요. 보통 웹 브라우저에서 URL Query 이후에 속한 부분들 중 "' 는 %22, %27로 URL 인코딩되어 사용됩니다. 다만 '의 경우는 iPhone safari에선 예외적으로 인코딩되지 않긴 하죠.

이와 유사하게 Firefox를 제외한 나머지 브라우저에선 Hash 영역의 " 는 URL 인코딩하지 않아 마치 Curl에서만 재현되는 형태의 XSS가 실제로 브라우저에서 동작하게된 케이스입니다.

그럼 간단하게 실험을 해봅시다.

Without Hash

#이 없는 경우 "가 %22로 URL encoding되어 페이지에 삽입됩니다. onerror 구문을 처리되지 않습니다.

document.write("<img/src="+absolutizeUrl('http://www.hahwul.com" onerror=alert(45)')+">");

With Hash

#이 있는 경우에는 "가 인코딩되지 않기 때문에 구문을 탈출하고 onerror 핸들러가 처리됩니다.

document.write("<img/src="+absolutizeUrl('http://www.hahwul.com#" onerror=alert(45)')+">");

원래 코드대로라면 싱글쿼터(‘) 안에 있기 때문에 모두 URL 인코딩을 거쳐 문자열로 처리되어야 하지만 위의 코드가 해시 태그로 인해 특수문자가 그대로 들어가게 됩니다. 그래서 구문 밖으로 탈출 할 수 있고 XSS가 가능해집니다.

Conclusion

단순히 Facebook에서 쓰고있던 FlashMediaElement.swf의 문제가 아니란 겁니다. 결국은 Hash를 포함한 URL 형태의 문자를 브라우저에서 처리하는 방식의 차이로 트리거까지 가능한 케이스입니다. URL을 처리하는 기능이나 페이지에선 당연히 영향력이 존재하고 일반적인 XSS 우회 기법으로도 충분히 사용할 수 있다고 생각되네요.

https://www.hahwul.com#” => XSS

일부 XSS와 DOM XSS는 FE단에서 직접 필터링 하는 경우가 있습니다. 잘 찾다보면 은근히 보이는 타입입니다. 이런 경우에 사용할 수 있는 좋은 방법으로 보입니다 :D