添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
首发于 Airing的编程小屋
前端 | XSS 的攻击手段及其防御

前端 | XSS 的攻击手段及其防御

1. XSS 的攻击手段

XSS (Cross-Site Scripting,跨域脚本攻击)攻击是最常见的 Web 攻击,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。其重点是『 跨域 』和『 客户端执行 』。

XSS 的本质:

  • 恶意代码未经过滤,与网站正常的代码混在一起;
  • 浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。

XSS 攻击一般存在以下几类:

  • Reflected XSS(反射型 XSS 攻击)
  • Stored XSS(存储型 XSS 攻击)
  • DOM XSS
  • JSONP XSS

1.1 Reflected XSS

反射型的 XSS 攻击,主要是由于 服务端接收到客户端的不安全输入 在客户端触发执行 从而发起 Web 攻击。

具体而言,反射型 XSS 只是简单地把用户输入的数据 “反射” 给浏览器,这种攻击方式往往需要攻击者诱使用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。这是一种 非持久型 的攻击。

比如:在某购物网站搜索物品,搜索结果会显示搜索的关键词。搜索关键词填入 <script>alert('handsome boy')</script> ,点击搜索。页面没有对关键词进行过滤,这段代码就会直接在页面上执行,弹出 alert。

1.2 Stored XSS

基于存储的 XSS 攻击,是通过 提交带有恶意脚本的内容存储在服务器上 当其他人看到这些内容时发起 Web 攻击 。一般提交的内容都是通过一些 富文本编辑器 编辑的,很容易插入危险代码。

比较常见的一个场景是攻击者在社区或论坛上写下一篇包含恶意 JavaScript 代码的文章或评论,文章或评论发表后,所有访问该文章或评论的用户,都会在他们的浏览器中执行这段恶意的 JavaScript 代码。这是一种 持久型 的攻击。

1.3 DOM XSS

基于 DOM 的 XSS 攻击是指通过恶意脚本修改页面的 DOM 结构,是 纯粹发生在客户端的攻击

DOM 型 XSS 跟前两种 XSS 的区别: DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞 。举个栗子 :

<input type="text" id="input">
<button id="btn">Submit</button>
<div id="div"></div>
<script>
    const input = document.getElementById('input');
    const btn = document.getElementById('btn');
    const div = document.getElementById('div');
    let val;
    input.addEventListener('change', (e) => {
        val = e.target.value;
    }, false);
    btn.addEventListener('click', () => {
        div.innerHTML = `<a href=${val}>testLink</a>`
    }, false);
</script>

点击 Submit 按钮后,会在当前页面插入一个链接,其地址为用户的输入内容。如果用户在输入时构造了如下内容:

" onclick=alert(/xss/)

用户提交之后,页面代码就变成了:

<a href onlick="alert(/xss/)">testLink</a>

此时,用户点击生成的链接,就会执行对应的脚本。 DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。 在使用 .innerHTML .outerHTML document.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent .setAttribute() 等。
DOM 中的内联事件监听器,如 location onclick onerror onload onmouseover 等, <a> 标签的 href 属性,JavaScript 的 eval() setTimeout() setInterval() 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。

1.4 JSONP XSS

JSONP 的 callback 参数非常危险,他有两种风险可能导致 XSS:

  1. callback 参数 意外截断 js 代码 ,特殊字符单引号双引号,换行符均存在风险。
  2. callback 参数 恶意添加标签 ,造成 XSS 漏洞。

浏览器为了保证跨域访问的安全性,会默认发一个 callback 参数到后台,接口拿到这个参数之后,需要将返回的 JSON 数据外面包上 callback 参数。

具体的返回格式:

CALLBACK(JSON)

如果 ajax 请求是 JSONP 请求,返回的内容浏览器还会自动检测,如果不是按这个格式返回或者 callback 的内容不对,这次请求就算失败了。

这里有一个机制,那就是 请求的 callback 会被放入返回的内容当中 ,这也是可能出问题的地方。举个栗子,如果返回的页面,那么 Content-Type: text/html ,那么 callback 注入的 html 元素都可以直接放到页面上了。那么,html 页面必然不能支持 callback。支持 JSONP 的链接如果直接放到浏览器里面访问,浏览器就不会做 callback 校验了。

2. XSS 的防御方式

2.1 防御 XSS 的根本之道

通过前面的介绍可以得知,XSS 攻击有两大要素:

  1. 攻击者提交恶意代码。
  2. 浏览器执行恶意代码。

根本的解决方法: 从输入到输出都需要过滤、转义。

2.2.1 输入

输入指客户端请求参数,具体包括:

  • 用户输入
  • URL 参数
  • POST 参数

针对 HTML 代码的编码方式是 HTMLEncode,它的作用是将字符串转换成 HTMLEntities。目前来说,为了对抗 XSS,需要对以下六个字符进行实体化转义。

当然,上面的只是最基本而且是最必要的,HTMLEncode 还有很多很多,具体可以参考: Web安全系列(四):XSS 的防御 | 掘金 一文中提及的特殊字符。

除此之外,富文本的输入需要额外注意:

  1. 首先例行进行输入检查,保证用户输入的是完整的 HTML 代码,而不是有拼接的代码
  2. 通过 htmlParser 解析出 HTML 代码的标签、属性、事件
  3. 富文本的事件肯定要被禁止 ,因为富文本并不需要事件这种东西,另外一些危险的标签也需要禁止,例如: <iframe> <script> <base> <form>
  4. 利用白名单机制,只允许安全的标签嵌入,例如: <a> <img> div 等,白名单不仅仅适用于标签,也适用于属性
  5. 过滤用户 CSS,检查是否有危险代码

2.2.2 输出

不要以为在输入的时候进行过滤就万事大吉了,恶意攻击者们可能会层层绕过防御机制进行 XSS 攻击,一般来说, 所有需要输出到 HTML 页面的变量,全部需要使用编码或者转义来防御 。输出需要转义的部分,具体包括:

  • 在 HTML 中输出
  • 在 JavaScript 中输出
  • 在 CSS 中输出
  • 在 URL 中输出

2.2.2.1 在 HTML 中的输出

HTML 的部分和输入的转义方式相同,使用 HTMLEncode,此处不再复述。

2.2.2.2 在 JavaScript 中的输出

JavaScript 的部分同样需要编码转义,比如在 JSONP 中可以通过意外截断 JSON 数据或者在页面中玩转引号来造成 XSS 攻击。

let a = "我是变量"
// 我是变量 = ";alert(1);//