从0到1:CTFer成长之路
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.3 XSS的魔力

跨站脚本(Cross-Site Scripting,XSS)是一种网站应用程序的安全漏洞攻击,是代码注入的一种,允许恶意用户将代码注入网页,其他用户在观看网页时会受到影响。这类攻击通常包含HTML和用户端脚本语言。

XSS攻击通常是指通过利用网页开发时留下的漏洞,巧妙注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但实际上可以包括Java、VBScript、ActiveX、Flash或者普通的HTML。攻击成功后,攻击者可能得到更高的权限(如执行一些操作)、私密网页内容、会话和Cookie等内容。(摘自维基百科)

如上所述,XSS攻击是代码注入的一种。时至今日,浏览器上的攻与防片刻未歇,很多网站给关键Cookie增加了HTTP Only属性,这意味着执行JavaScript已无法获得用户的登录凭证(即无法通过XSS攻击窃取Cookie登录对方账号),虽然同源策略限制了JavaScript跨域执行的能力,但是XSS攻击依然可以理解为在用户浏览器上的代码执行漏洞,可以在悄无声息的情况下实现模拟用户的操作(包括文件上传等请求)。CTF比赛中曾数次出现这种类型的XSS题目。

2.3.1 XSS漏洞类型

1.反射/存储型XSS

根据XSS漏洞点的触发特征,XSS可以粗略分为反射型XSS存储型XSS。反射型XSS通常是指恶意代码未被服务器存储,每次触发漏洞的时候都将恶意代码通过GET/POST方式提交,然后触发漏洞。存储型XSS则相反,恶意代码被服务器存储,在访问页面时会直接被触发(如留言板留言等场景)。

这里模拟一个简单的反射型XSS(见图2-3-1),变量输入点没有任何过滤直接在HTML内容中输出,就像攻击者对HTML内容进行了“注入”,这也是XSS也称为HTML注入的原因,这样我们可以向网页中注入恶意的标签和代码,实现我们的功能,见图2-3-2。

然而这样的payload会被Google Chrome等浏览器直接拦截,无法触发,因为这样的请求(即GET参数中的JavaScript标签代码直接打印在HTML中)符合Google Chrome浏览器XSS过滤器(XSS Auditor)的规则,所以被直接拦截(这也是近年来Google Chrome加强防护策略导致的。在很长一段时间内,攻击者可以肆意地在页面中注入XSS恶意代码)。换用FireFox浏览器,结果见图2-3-3。

图2-3-1

图2-3-2

图2-3-3

输入的数据被拼接到HTML内容中时,有时被输出到一些特殊的位置,如标签属性、JavaScript变量的值,此时通过闭合标签或者语句可以实现payload的逃逸。

又如,下面的输入被输出到了标签属性的值中(见图2-3-4),通过在标签属性中注入on事件,我们可以执行恶意代码,见图2-3-5。在这两种情况下,由于特征比较明显,因此使用Google Chrome浏览器的时候会被Google Chrome XSS Auditor拦截。

第三种情况是我们的输入被输出到JavaScript变量中(见图2-3-6),这时可以构造输入,闭合前面的双引号,同时引入恶意代码(见图2-3-7)。

图2-3-4

图2-3-5

图2-3-6

图2-3-7

可以看到,这次页面源码并没有变红,意味着Google Chrome并未拦截这个输入,访问成功弹框,见图2-3-8。

图2-3-8

前三种是XSS中最简单的场景,即输入原封不动地被输出在页面中,通过精心构造的输入,使得输入中的恶意数据混入JavaScript代码中得以执行,这也是很多漏洞的根源所在,即:没有很好地区分开代码和数据,导致攻击者可以利用系统的缺陷,构造输入,进而在系统上执行任意代码。

2.DOM XSS

简单来讲,DOM XSS是页面中原有的JavaScript代码执行后,需要进行DOM树节点的增加或者元素的修改,引入了被污染的变量,从而导致XSS,见图2-3-9。其功能是获取imgurl参数中的图片链接,然后拼接出一个图片标签并显示到网页中,见图2-3-10。

图2-3-9

图2-3-10

输入并不会直接被打印到页面中被解析,而是等页面中原先的JavaScript执行后取出我们可控的变量,拼接恶意代码并写入页面中才会被触发,见图2-3-11。

可以看到,恶意代码最终被拼接到了img标签中并被执行。

3.其他场景

决定上传的文件能否被浏览器解析成HTML代码的关键是HTTP响应头中的元素Content-Type,所以无论上传的文件是以什么样的后缀被保存在服务器上,只要访问上传的文件时返回的Content-type是text/html,就可以成功地被浏览器解析并执行。类似地,Flash文件的application/x-shockwave-flash也可以被执行XSS。

事实上,浏览器会默认把请求响应当作HTML内容解析,如空的和畸形的Content-type,由于浏览器之间存在差异,因此在实际环境中要多测试。比如,Google Chrome中的空Content-type会被认为是text/html,见图2-3-12,也是可以弹框的,见图2-3-13。

图2-3-11

图2-3-12

图2-3-13

2.3.2 XSS的tricks

1.可以用来执行XSS的标签

基本上所有的标签都可以使用on事件来触发恶意代码,比如:

效果见图2-3-14。

图2-3-14

另一个比较常用的是img标签,效果见图2-3-15。

由于页面不存在路径为/x的图片,因此直接会加载出错,触发onerror事件并执行代码。

图2-3-15

其他常见的标签如下:

2.HTML5特性的XSS

HTML5的某些特性可以参考网站http://html5sec.org/。很多标签的on时间触发是需要交互的,如鼠标滑过点击,代码如下:

input标签的autofocus属性会自动使光标聚焦于此,不需交互就可以触发onfocus事件。两个input元素竞争焦点,当焦点到另一个input元素时,前面的会触发blur事件。例如:

3.伪协议与XSS

通常,我们在浏览器中使用HTTP/HTTPS协议来访问网站,但是在一个页面中,鼠标悬停在一个超链接上时,我们总会看到这样的链接:javascript:void(0)。这其实是用JavaScript伪协议实现的。如果手动单击,或者页面中的JavaScript执行跳转到JavaScript伪协议时,浏览器并不会带领我们去访问这个地址,而是把“javascript:”后的那一段内容当作JavaScript代码,直接在当前页面执行。所以,对于这样的标签:

单击这个标签时并不会跳转到其他网页,而是直接在当前页面执行alert(1),除了直接用a标签单击触发,JavaScript协议触发的方式还有很多。

比如,利用JavaScript进行页面跳转时,跳转的协议使用JavaScript伪协议也能进行触发,代码如下:

所以如果在一些登录/退出业务中存在这样的代码:

即跳转的地址是我们可控的,我们就能控制跳转的地址到JavaScript伪协议,从而实现XSS攻击,见图2-3-16。

图2-3-16

另外,iframe标签和form标签也支持JavaScript伪协议,感兴趣的读者可以自行尝试如下。不同的是,iframe标签不需交互即可触发,而form标签需要在提交表单时才会触发。

除了JavaScript伪协议,还有其他伪协议可以在iframe标签中实现类似的效果。比如,data伪协议:

4.二次渲染导致的XSS

后端语言如flask的jinja2使用不当时,可能存在模板注入,在前端也可能因为这样的原因形成XSS。例如,在AngularJS中:

上面的代码会将参数t直接输出到AngularJS的模板中,在我们访问页面时,JavaScript会解析模板中的代码,可以得到一个前端的模板注入。AngularJS引擎解析了表达式“3*3”并打印了结果,见图2-3-17。

图2-3-17

借助沙箱逃逸,我们便能达到执行任意JavaScript代码的目的。这样的XSS是因为前端对某部分输出进行了二次渲染导致的,所以没有script标签这样的特征,也就不会被浏览器随意的拦截,见图2-3-18。

图2-3-18

参考链接:https://portswigger.net/blog/XSS-without-html-client-side-template-injection-with-angularjs

2.3.3 XSS过滤和绕过

过滤的两个层为WAF层代码层。WAF(Web Application Firewall,Web应用防火墙)层通常在代码外,主机层对HTTP应用请求一个过滤拦截器。代码层则在代码中直接实现对用户输入的过滤或者引用第三方代码对用户输入进行过滤。

JavaScript非常灵活,所以对于普通的正则匹配,字符串对比很难拦截XSS漏洞。过滤的时候一般会面临多种场景。

1.富文本过滤

对于发送邮件和写博客的场景,标签是必不可少的,如嵌入超链接、图片需要HTML标签,如果对标签进行黑名单过滤,必然出现遗漏的情况,那么我们可以通过寻找没有被过滤的标签进行绕过。

我们也可以尝试fuzz过滤有没有缺陷,如在直接把script替换为空的过滤方式中,可以采用双写形式<scrscriptipt>;或者在没有考虑大小写时,可以通过大小写的变换绕过script标签,见图2-3-19。

图2-3-19

错误的过滤方式甚至可以帮助我们绕过浏览器的XSS过滤器。

2.输出在标签属性中

如果没有过滤“<”或“>”,我们可以直接引入新的标签,否则可以引入标签的事件,如onloadonmousemove等。当语句被输出到标签事件的位置时,我们可以通过对payload进行HTML编码来绕过检测,见图2-3-20。

图2-3-20

利用burpsuite对payload进行实体编码:

打开浏览器即可触发,见图2-3-21。

图2-3-21

这里能触发与浏览器渲染页面的顺序有关。我们的payload在标签属性中,触发事件前,浏览器已经对payload进行了一次解码,即从实体编码转换成了常规数据。

如果对JavaScript的函数进行过滤,如过滤了“eval(”这样的字符组合,那么可以通过下面的方式进行绕过:

正因为JavaScript非常灵活,所以通过黑名单的方式对XSS攻击进行过滤是很困难的。

3.输出在JavaScript变量中

通过闭合JavaScript语句,会使得我们的攻击语句逃逸,这时有经验的开发可能会对引号进行编码或者转义,进而防御XSS,但是配合一些特殊的场景依然可能形成XSS。例如,对于如下双输入的注入:

如果只过滤单引号而没考虑“\”,那么我们可以转义语句中的第二个单引号,使得第一个单引号和第三个单引号闭合,从而让攻击语句逃逸:

在XSS中也有类似的场景。例如,如下代码:

输入点和输出点都有两个,如果输入引号,会被编码成HTML实体字符,但是htmlentities函数并不会过滤“\”,所以我们可以通过“\”使得攻击语句逃逸,见图2-3-22。

图2-3-22

name处末尾输入“\”,在addr参数处闭合前面的JavaScript语句,同时插入恶意代码。进一步可以用eval(window.name)引入恶意代码或者使用JavaScript中的String.fromCharCode来避免使用引号等被过滤的字符。

再介绍几个小技巧,见图2-3-23,将payload藏在location.hash中,则URL中“”后的字符不会被发到服务器,所以不存在被服务器过滤的情况,见图2-3-24。

图2-3-23

图2-3-24

在JavaScript中,反引号可以直接当作字符串的边界符。

4.CSP过滤及其绕过

我们引用https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP的内容来介绍CSP。

CSP(Content Security Policy,内容安全策略)是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本(XSS)和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。

CSP被设计成完全向后兼容。不支持CSP的浏览器也能与实现了CSP的服务器正常合作,反之亦然:不支持CSP的浏览器只会忽略它,正常运行,默认网页内容使用标准的同源策略。如果网站不提供CSP头部,那么浏览器也使用标准的同源策略。

为了使CSP可用,我们需要配置网络服务器返回Content-Security-Policy HTTP头部(有时有X-Content-Security-Policy头部的提法,那是旧版本,不需如此指定它)。除此之外,<meta>元素也可以被用来配置该策略。

从前面的一些过滤绕过也可以看出,XSS的防御绝非易事,CSP应运而生。CSP策略可以看作为了防御XSS,额外添加的一些浏览器渲染页面、执行JavaScript的规则。这个规则是在浏览器层执行的,只需配置服务器返回Content-Security-Policy头。例如:

这段代码会规定,这个页面引用的JavaScript文件只允许来自百度的子域,其他任何方式的JavaScript执行都会被拦截,包括页面中本身的script标签内的代码。如果引用了不可信域的JavaScript文件,则在浏览器的控制台界面(按F12,打开console)会报错,见图2-3-25。

图2-3-25

CSP规则见表2-3-1。

表2-3-1

表中的每个规则都对应了浏览器中的某部分请求,如default-src指令定义了那些没有被更精确指令指定的安全策略,可以理解为页面中所有请求的一个默认策略;script-src可以指定允许加载的JavaScript资源文件的源。其余规则的含义读者可以自行学习,不再赘述。

在CSP规则的设置中,“*”可以作为通配符。例如,“*.baidu.com”指的是允许加载百度所有子域名的JavaScript资源文件;还支持指定具体协议和路径,如“Content-Security-Policy:script-src http://*.baidu.com/js/”指定了具体的协议以及路径。

除此之外,script-src还支持指定关键词,常见的关键词如下。

none:禁止加载所有资源。

self:允许加载同源的资源文件。

unsafe-inline:允许在页面内直接执行嵌入的JavaScript代码。

unsafe-eval:允许使用eval()等通过字符串创建代码的方法。

所有关键词都需要用单引号包裹。如果在某条CSP规则中有多个值,则用空格隔开;如果有多条指令,则用“;”隔开。比如:

5.常见的场景及其绕过

CSP规则众多,所以这里只简单举例,其他相关规则及绕过方式读者可以自行查阅相关资料。例如,对于“script-src'self'”,self对应的CSP规则允许加载本地的文件,我们可以通过这个站点上可控的链接写入恶意内容,如文件上传、JSONP接口。例如:

注意,如果是图片上传接口,即访问上传资源时返回的Content-Type是image/png之类的,则会被浏览器拒绝执行。

假设上传了一个a.xxxxx文件,通过URL的GET参数,把这个文件引入script标签的src属性,此时返回的Content-type为text/plain,解析结果见图2-3-26。

图2-3-26

除此之外,我们可以利用JSONP命令进行绕过。假设存在JSONP接口(见图2-3-27),我们可以通过JSONP接口引入符合JavaScript语法的代码,见图2-3-28。

图2-3-27

图2-3-28

若该JSONP接口处于白名单域下,可以通过更改callback参数向页面中注入恶意代码,在触发点页面引入构造好的链接,见图2-3-29。

图2-3-29

另一些常见的绕过方法如下:

当传出数据受限时,则可以利用JavaScript动态生成link标签,将数据传输到我们的服务器,如通过GET参数带出cookie:

还有就是利用页面跳转,包括a标签的跳转、location变量赋值的跳转,meta标签的跳转等手法。比如,通过跳转实现带出数据:

2.3.4 XSS绕过案例

CTF中的XSS题目通常利用XSS bot从后台模拟用户访问链接,进而触发答题者构造的XSS,读到出题者隐藏在bot浏览器中的flag。flag通常在bot浏览器的Cookie中,或者存在于只有bot的身份才可以访问到的路径。除了CTF题目,现实中也有相关XSS漏洞的存在,在第二个例子中,笔者将阐述一个自己曾经挖到的XSS漏洞案例。

1.0CTF 2017 Complicated XSS

题目中存在两个域名government.vip和admin.government.vip,见图2-3-30。

图2-3-30

题目提示:http://admin.government.vip:8000。测试后发现,我们可以在government.vip中输入任意HTML让BOT触发,也就是可以让bot在government.vip域执行任意JavaScript代码。经过进一步探测发现

<1>需要以管理员的身份向http://admin.government.vip:8000/upload接口上传文件后,才能得到flag

<2>http://admin.government.vip:8000中存在一个XSS,用户Cookie中的用户名直接会被显示在HTML内容中,见图2-3-31。

图2-3-31

<3>http://admin.government.vip:8000/页面存在过滤,删除了很多函数,需要想办法绕过才能把数据传输出去。过滤部分如下:

根据得到的信息可以梳理出思路,利用government.vip根域的XSS,将对admin子域攻击的代码写入Cookie,设置Cookie有效的域为所有子域(所有子域均可访问此Cookie)。设置完Cookie后,引导用户访问打印Cookie的页面,使bot在admin子域触发XSS,触发后利用XSS在admin子域中新建一个iframe页面,从而绕过页面中函数的限制,并读取管理员上传页面的HTML源码,最后构造上传包利用XSS触发上传,获得flag后发送给攻击者。

首先,在根域触发XSS的内容:

将payload设置到Cookie中,然后引导bot访问admin子域。恶意代码的利用分两次,第一次是读取管理员上传文件的HTML,读到的上传页面见图2-3-32。

图2-3-32

读到源码后,修改payload构造,利用JavaScript上传文件的代码,并且在上传成功后,将页面发送到自己的服务器。最后服务器收到带着flag的请求,见图2-3-33。flag就在上传文件的响应中。

图2-3-33

2.某互联网企业XSS

passport.example.com和wappass.example.com是该公司的通行证相关域,负责用户的通行证相关任务。例如,携带令牌跳转到其他子域进行授权登录,wappass子域负责二维码登录相关功能,可以在这个域进行密码更改等。

以前也挖掘到一些URL校验不严导致携带XXUSS跳转到第三方域的安全问题。XXUSS曾是他们公司的唯一通行证(HTTP Only Cookie)。自从某次修复后,携带通行证跳转的漏洞似乎彻底修复了,对于域名的校验极其严格,但存在利用的可能,如找到白名单子域的XSS或者可以带出referer的页面:

该公司跨域授权的URL是上面的URL,其中有多个参数:return type是指的授权类型可以是302跳转,也可以是form表单;tpl参数是指本次跳转到具体的什么服务,这个是服务名的缩写;u参数则是这个服务对应的授权URL。

经过测试发现,302跳转直接是带着通行证302重定向到子域;form表单则返回一个自动提交的表单且action为子域,参数为认证参数。

这次的问题就出在表单跳转处。上面提到对于u参数中的域名校验很严格,但是对于协议名校验并不严格。例如:

这样的协议名是可以正确返回响应头的,却是302跳转过来的链接。如果不是合法的HTTP(S)协议,链接是不会被浏览器所接受的,所以类似:

这样的URL是不可能弹框的,以上是所有的已知事情。

但是,在JavaScript中如果有这样的URL,那么是可以攻击的:

浏览器中,如果JavaScript调用了“javascript:”伪协议,那么后面的语句可以直接在当前页面当作脚本执行类似如下代码也是可以的。

只要单击它,就可以触发对应的脚本,然后似乎曾经看到过一种攻击payload:

这样的payload依然可以执行,因为“//”在JavaScript中代表的意思是注释,通过后面的“%0a”换行符,使得攻击语句跑到第2行,就避开了这个注释符。似乎只要是JavaScript型的跳转,就都可以触发JavaScript伪协议?form表单是否也可以看作一种携带着数据进行JavaScript跳转的方式?

测试代码如下,结果见图2-3-34。

图2-3-34

结果如预期般弹窗了。也就是说,只要是自动提交的表单,如果action中的协议和URL后半段可控,就能得到一个XSS。这时,结合前的修复不算完全的漏洞:“JavaScript型跳转,域名不可控,但是协议和URL可控”,那么就得到了一个该公司登录域的XSS,见图2-3-35。

这样便通过了URL校验,见图2-3-36,成功执行了我们的XSS代码。

图2-3-35

图2-3-36

此时,我们得到了一个该企业登录域的XSS并可以无视浏览器的过滤、通杀各种浏览器,前面提到该企业的二维码登录功能在此域实现。那么我们得到了这个XSS,就可以对用户进行CSRF攻击,让用户在访问我们的恶意页面的时候相当于完成了对登录二维码进行扫描和确认的动作。

诱导用户访问的页面内容,代码如下:

attack.php内容如下:

上述代码是最终利用的payload,当用户访问此网页时会触发XSS,并且通过CSRF的攻击手法,自动化对攻击者打开的一个二维码登录页面进行授权。

授权完毕,攻击者就可以在浏览器登录受害者的账号,进而以对方身份浏览各种业务。