XSS 简介
XSS,全称Cross Site Scripting,即跨站脚本攻击,是最普遍的Web应用安全漏洞。这类漏洞能够使得攻击者嵌入恶意脚本代码到正常用户会访问到的页面中,当正常用户访问该页面时,则可导致嵌入的恶意脚本代码的执行,从而达到恶意攻击用户的目的。需要强调的是,XSS不仅仅限于JavaScript,还包括flash等其它脚本语言。根据攻击代码的工作方式,XSS可以分为反射型的XSS、存储型的XSS和DOM型的XSS。
反射型
反射型的XSS是非持久化的,攻击者事先制作好攻击链接,需要欺骗用户自己去点击链接才能触发XSS代码,但是服务器中没有这样的页面和内容,一般容易出现在搜索页面。
存储型
存储型的XSS是持久化的,代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,每当有用户访问该页面的时候都会触发代码执行。这种XSS非常危险,容易造成蠕虫,大量盗窃cookie。
DOM型
DOM型的XSS是基于文档对象模型Document Objeet Model,DOM)的一种漏洞。DOM是一个与平台、编程语言无关的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果能够成为显示页面的一部分。DOM中有很多对象,其中一些是用户可以操纵的,如uRI ,location,refelTer等。客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而从客户端获得DOM中的数据在本地执行,如果DOM中的数据没有经过严格确认,就会产生DOM XSS漏洞。例如服务器端经常使用document.boby.innerHtml等函数动态生成html页面,如果这些函数在引用某些变量时没有进行过滤或检查,就会产生DOM型的XSS。DOM型XSS可能是存储型,也有可能是反射型。
一些常用的标签与属性
下面我列举的标签大部分是可以自动触发js代码的,无需用户去交互,大部分情况下我们也是希望是自动触发而不是等用户去触发。
scirpt 标签
img 标签
标签定义 HTML 页面中的图像。
input 标签
标签规定了用户可以在其中输入数据的输入字段。
onfocus 事件在对象获得焦点时发生:
竞争焦点,从而触发onblur事件:
input 标签的 autofocus 属性规定当页面加载时 元素应该自动获得焦点。可以通过autofocus属性自动执行本身的focus事件,这个向量是使焦点自动跳到输入元素上,触发焦点事件,无需用户去触发:
details 标签
使用details 标签的 open 属性触发ontoggle事件,无需用户去点击即可触发:
svg 标签
select 标签
通过autofocus属性规定当页面加载时元素应该自动获得焦点,这个向量是使焦点自动跳到输入元素上,触发焦点事件,无需用户去触发:
iframe 标签
video 标签
audio 标签
body 标签
标签定义文档的主体。
onscroll 事件在元素滚动条在滚动时触发。我们可以利用换行符以及autofocus,当用户滑动滚动条的时候自动触发,无需用户去点击触发:
textarea 标签
keygen 标签
//仅限火狐
marquee 标签
//Chrome不行,火狐和IE都可以
isindex 标签
//仅限于IE +WX:machinegunjoe666免费领取资料
利用 link 远程包含 JavaScript 文件
标签定义文档与外部资源的关系。在无CSP的情况下才可以使用:
利用 JavaScript 伪协议
javascript: 这个特殊的协议类型声明了URL的主体是任意的javascript代码,它由javascript的解释器运行。当浏览器装载了这样的URL时,并不会转向某个URL,而是执行这个URL中包含的javascript代码,并把最后一条javascript语句的字符串值作为新文档的内容显示出来。
a 标签
xss
iframe 标签
img 标签
//IE7以下
form 标签
XSS 常见绕过姿势
绕过空格过滤
当空格被过滤了时,我们可以用 / 来代替空格:
也可以:
绕过引号过滤
如果是html标签中,我们可以不用引号。如果是在js中,我们可以用反引号代替单双引号:
绕过括号过滤
当括号被过滤的时候可以使用throw来绕过。throw 语句用于当错误发生时抛出一个错误。
绕过关键字过滤大小写绕过
双写绕过
有些waf可能会只替换一次且是替换为空,这种情况下我们可以考虑双写关键字绕过
alert(1);
字符串拼接绕过
利用eval()函数
与PHP的eval()函数相同,JavaScript的eval()函数也可以计算 JavaScript 字符串,并把它作为脚本代码来执行。
// 在js中,我们可以用反引号代替单双引号
利用top
XSS 输出点总结
WAF最大的问题,在于不知道输出的位置,导致攻击者根据具体环境以及具体输出的标签类型便可以绕过。
输出在属性里
例如输出的位置位于value属性中:
我们可以选择直接闭合标签:
">
// 输出后如下:
//
" type=text>
如果 < > 被过滤的话可以换成选择使用事件来闭合属性,并将后面的引号注释掉或闭合:
" autofocus onfocus=alert(1)//
" autofocus onfocus=alert(1) "
// 输出后如下:
//
同样还有很多其他的payload:
" onmouseover=prompt(0) x="
" onfocusin=alert(1) autofocus x="
" onfocusout=alert(1) autofocus x="
" onblur=alert(1) autofocus a="
还有一些特殊的场景,如:
这里只能把input标签闭合,然后直接执行脚本,否则会因为type为hidden导致无法执行脚本。
输出在HTML标签之间
例如输出的位置如下:
[输出]
直接提交 即可触发XSS,但是当标签是不能执行脚本的标签时,如下面这几个:
那么就得先把那个标签闭合(后文会讲到原理),然后在注入XSS语句,例如:
输出在script标签之间
例如:
可控位置在input,可以闭合script标签插入代码,但是同样我们仅仅闭合双引号就可以执行js代码了:
";alert(1)//
// 输出后如下:
//
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节点来做接下来的解析工作,比如在处理诸如
但是如果直接放进去标签的内容呢,不带转义字符呢,如下:
同样也是不会触发XSS的:
这涉及到了RCDATA的一个特殊的情况。即在浏览器解析RCDATA元素的过程中,解析器会进入“RCDATA状态”。在这个状态中,如果遇到“<”字符,它会转换到“RCDATA小于号状态”。如果“<”字符后没有紧跟着“/”和对应的标签名,解析器会转换回“RCDATA状态”,并不会进入“标签开始状态”的。这意味着在RCDATA元素标签的内容中,唯一能够被解析器认做是标签的就只有 或者 ,因此,在
另外还有一点要注意:我们从上面HTML的五类元素中还发现有一个原始文本元素
那么如何才能让里面的内容进行转义并执行弹窗呢,这里需要利用到XSS的一个黑魔法——“svg”,我们下文中会提及。
URL编码
我们可以并将src或href属性中的内容进行URL编码,当HTML解析器对src或href中的字符完成HTML解码后,接下来URL解析器会对src或href中的值进行URL解码。
xx
下面给出几个实例。
test
注意,伪协议头 javascript: 是不能进行编码的。这里就有一个URL解析过程中的一个细节了,即不能对协议类型进行任何的编码操作,否则URL解析器会认为它无类型,就会导致DOM节点中被编码的“javascript”没有被解码,当然不会被URL解析器识别了。就比如说 http://www.baidu.com 可以被URL编码为
http://%77%77%77%2e%62%61%69%64%75%2e%63%6f%6d,但是不能把协议也进URL编码:
%68%74%74%70%3a%2f%2f%77%77%77%2e%62%61%69%64%75%2e%63%6f%6d 。
但是伪协议头 javascript: 可以进行HTML编码。
Javascript 编码
我们可以将DOM节点中的内容转化为 Javascript 编码。当HTML解析产生DOM节点后,会根据DOM节点来做接下来的解析工作,比如在处理诸如
test
test
但要注意,我们同样也不能对伪协议头 javascript: 进行 Javascript 编码。并且像圆括号、双引号、单引号这样的符号我们也不能进 Javascript 编码,但是能进行HTML编码。
在DOM环境中的JavaScript编码
对于八进制编码和十六进制编码,与 Unicode 编码还是有区别,像下面的XSS向量是不能直接执行的:
test
如下图,插入之后没有任何反应:
要想让他们能够执行我们要将他们放在DOM环境中,即DOM型的XSS。
测试代码:
test
以上情况很多都是出现在你搜索后,显示你所查询的关键字,变量 search 是一个可控点,当我们查询一个XSS攻击向量后,变量 search 就会被赋值为这个XSS向量,从而插入到div标签中触发XSS,如下所示:
test
+WX:machinegunjoe666免费领取资料
此时如果过滤了 <、>、'、"、&、% 等等这些字符的话,我们便可以用JavaScript编码的方法将XSS向量全部编码,即 的以下编码都可以弹窗:
// Unicode编码
\u003C\u0069\u0066\u0072\u0061\u006D\u0065\u0020\u0073\u0072\u0063\u003D\u006A\u0061\u0076\u0061\u0073\u0063\u0072\u0069\u0070\u0074\u003A\u0061\u006C\u0065\u0072\u0074\u0028\u0027\u0078\u0073\u0073\u0027\u0029\u003E\u003C\u002F\u0069\u0066\u0072\u0061\u006D\u0065\u003E
// 八进制编码
\74\151\146\162\141\155\145\40\163\162\143\75\152\141\166\141\163\143\162\151\160\164\72\141\154\145\162\164\50\47\170\163\163\47\51\76\74\57\151\146\162\141\155\145\76
// 十六进制编码
\x3c\x69\x66\x72\x61\x6d\x65\x20\x73\x72\x63\x3d\x6a\x61\x76\x61\x73\x63\x72\x69\x70\x74\x3a\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29\x3e\x3c\x2f\x69\x66\x72\x61\x6d\x65\x3e
还有一种让八进制和十六进制编码的XSS攻击向量执行的方式便是将XSS向量放在某个能把字符串当做JavaScript代码来执行的函数里,比如eval()、setTimeout()、setInterval()等函数。如下示例:
test
或者也可以直接将一整段js代码编码后放入eval()函数中执行。
混合编码
混合编码就是对一个XSS向量同时进行多种编码,如下示例:
// 对javascript:进行HTML编码, 对alert("xss")进行URL编码
test
// 对javascript:进行HTML编码, 对alert进行Unicode编码
test
也可以利用解码顺序进行混合编码,如下示例:
首先对“alert”进行JavaScript Unicode编码:
test
然后再对 \u0061\u006c\u0065\u0072\u0074 进行URL编码:
test
最后对标签中的
javascript:%5c%75...%37%34("xss") 整体进行HTML编码即可:
test
SVG:XSS的一个黑魔法
我们在上文HTML编码那里最后留了一个坑,即HTML的五类元素中,像
那如何绕过HTML原始文本元素进而执行HTML实体解码呢,这涉及到了
但是当在前面加上
这是为什么呢?
是因为
[CISCN2019 华东北赛区]Web2 这道题运用的就是这个知识点。
XSS 测试流程思路
下面让我们来看一下XSS绕过的测试流程。
现实中,大多数的场所是用的黑名单来做XSS过滤器的,有三种方式绕过黑名单的测试:
- 暴力测试(输入大量的payload,看返回结果)
- 根据正则推算
- 利用浏览器bug
初步测试
(1)尝试插入比较正常的HTML标签,例如:、、、 等,来看一下返回页面的情况是怎样的,是否被HTML编码了,或者标签被过滤了。