懂你网络系列9之网络安全中的XSS攻击
一.简介
通过系列8中同源的策略我们知道,由于其非绝对性,能通过在页面嵌入跨域资源,通过cors来来允许跨源访问和通过js中的API来实现跨文档消息机制,这些机制同时带了很多安全问题。
当页面被注入了恶意 JavaScript 脚本时,浏览器无法区分这些脚本是被恶意注入的还是正 常的页面内容,所以恶意注入 JavaScript 脚本也拥有所有的脚本权限。
这其中最典型的就是XSS(Cross-site scripting,XSS)攻击了,为了与“CSS”区分开来,故简称 XSS,翻译过来就 是“跨站脚本。是一种安全漏洞,攻击者可以利用这种漏洞在网站上注入恶意的客户端代码。
当被攻击者登陆网站时就会自动运行这些恶意代码,从而,攻击者可以突破网站的访问权限,冒充受害者。根据开放式 Web 应用安全项目(OWASP),XSS Z在2017 年被认为 7 种最常见的 Web 应用程序漏洞之一。
如果 Web 应用程序没有部署足够的安全验证,那么,这些攻击很容易成功。浏览器无法探测到这些恶意脚本是不可信的,所以,这些脚本可以任意读取 cookie,session tokens,或者其它敏感的网站信息,或者让恶意脚本重写HTML内容。
恶意内容一般包括 JavaScript,但是,有时候也会包括 HTML,FLASH 或是其他浏览器可执行的代码。XSS 攻击的形式千差万别,但他们通常都会:将 cookies 或其他隐私信息发送给攻击者,将受害者重定向到由攻击者控制的网页,或是经由恶意网站在受害者的机器上进行其他恶意操作。
XSS攻击分类
1.存储型XSS(持久型)
注入型脚本永久存储在目标服务器上。当浏览器请求数据时,脚本从服务器上传回并执行。
步骤 :
1.攻击者将恶意代码提交到目标网站的数据库中。
2.用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
3.用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
4.恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作.
这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。
2.反射型 XSS(非持久性)
当用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。Web服务器将注入脚本,比如一个错误信息,搜索结果等 返回到用户的浏览器上(反射)。由于浏览器认为这个响应来自"可信任"的服务器,所以会执行这段脚本。
步骤:
1.攻击者构造出特殊的 URL,其中包含恶意代码。
2.用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
3.用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
4.恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
值得注意的是Web 服务器不会存储反射型 XSS 攻击的恶意脚本,这是和存储型 XSS 攻击不同的地方。因此它是非持久性的
反射型 XSS 漏洞通过 URL 传递参数的功能,如网站搜索、跳转等。
由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。
POST 的内容也可以触发反射型 XSS,只不过其触发条件比较苛刻(需要构造表单提交页面,并引导用户点击),所以非常少见。
3.基于 DOM 的 XSS
通过修改原始的客户端代码,受害者浏览器的 DOM 环境改变,导致有效载荷的执行。也就是说,页面本身并没有变化,但由于DOM环境被恶意修改,有客户端代码被包含进了页面,并且意外执行。
步骤:
1.攻击者构造出特殊的 URL,其中包含恶意代码。
用户打开带有恶意代码的 URL。
2.用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
3.恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
三.XSS攻击防范
由前面的三种攻击方式可知存储型 XSS 攻击和反射型 XSS 攻击都是需要经过 Web 服务器来处理的,因此可 以认为这两种类型的漏洞是服务端的安全漏洞。而基于 DOM 的 XSS 攻击全部都是在浏览器端完成的,因此基于 DOM 的 XSS 攻击是属于前端的安全漏洞。
通过前面的描述可以知道一个XSS的攻击可以抽象为以下三个步骤,根据这三点我们可以定制更加立体的防御方式:
1.输入:攻击者提交恶意代码
2.发送:恶意消息的发送到服务器。
3.执行:浏览器执行恶意代码
1.在前端和服务端对输入的脚本进行过滤或者转码
用户在通过一些前端控件提交数据时,无论是提交到本地还是服务端,想由前端的过滤进行后再进行提交,过滤掉一些非法的字符,如用户通过输入恶意的代码,例如:
html:<script>alert("你被xss攻击了")</script>
过滤后整个script标签内容都没了,那么这段脚本是不可能执行了。
再者还可以通过转码:如上述在html中的代码被转成:
<script>alert('你被XSS攻击了';)</script>
经过转码之后的内容,如script标签被转换为<script>,因此即使这段脚本 返回给页面,页面也不会执行这段脚本。
但是道高一尺,魔高一丈,上述这些在前端过滤和转码的普通的手段是不够了,有一些代码会绕过 过滤和转码:如:
<scr <script> ipt> alealertrt(你被xss攻击了)</ scr </ script> ipt>
绕过后执行:
<script> alert ("你被xss攻击了") </ script>
这就需要采用业内比较成熟的,通用的过滤转码库了,如果使用的是vue ,node,react等框架,可以安装其提供的相应的插件。
有的甚至直接绕过了前端部分的防御,直接发送到服务器上,这就需要在发送和执行的层面对其进行防御了,服务端也有比较成熟的解决方案了
2.利用CSP
服务器端执行过滤或者转码可以阻止 XSS 攻击的发生,但完全依靠服务器端依然是不够的,这种单一的开发手段任然是不不够的。
Content Security Policy (CSP),内容安全策略是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。
实施严格的 CSP 可以有效地防范 XSS 攻击,可以起到以下作用:
-
CSP通过指定有效域——即浏览器认可的可执行脚本的有效来源——使服务器管理者有能力减少或消除XSS攻击所依赖的载体。
-
一个CSP兼容的浏览器将会仅执行从白名单域获取到的脚本文件,忽略所有的其他脚本 (包括内联脚本和HTML的事件处理属性)。
-
禁止向第三方域提交数据,这样用户数据也不会外泄。
-
还提供了上报机制,这样可以帮助我们尽快发现有哪些 XSS 攻击,以便尽快修复问题。
使用
为使CSP可用, 你需要配置你的网络服务器返回 Content-Security-Policy HTTP头部。
例如:
一个网站管理者允许内容来自信任的域名及其子域名 (域名不必须与CSP设置所在的域名相同)
ontent-Security-Policy: default-src 'self' *.trusted.com
除此之外, 元素也可以被用来配置该策略, 例如:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">
更多具体使用方法见:Content-Security-Policy
3.利用httpOnly属性
为避免跨域脚本 (XSS) 攻击,通过JavaScript的 Document.cookie API无法访问带有 HttpOnly 标记的Cookie,它们只应该发送给服务端。如果包含服务端 Session 信息的 Cookie 不想被客户端 JavaScript 脚本调用,那么就应该为其设置 HttpOnly 标记 例如:
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
标记为 Secure 的Cookie只应通过被HTTPS协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过Cookie传输,因为Cookie有其固有的不安全性,Secure 标记也无法提供确实的安全保障。
4.合理编码防御DOM型XSS攻击
DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。
在使用 .innerHTML、.outerHTML、document.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent、.setAttribute() 等。
如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTML、outerHTML 的 XSS 隐患。
DOM 中的内联事件监听器,如 location、onclick、onerror、onload、onmouseover 等, 标签的 href 属性,JavaScript 的 eval()、setTimeout()、setInterval() 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。
<!-- 内联事件监听器中包含恶意代码 -->
![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2018b/3e724ce0.data:image/png,)
<!-- 链接内包含恶意代码 -->
<a href="UNTRUSTED">1</a>
<script>
// setTimeout()/setInterval() 中调用恶意代码
setTimeout("UNTRUSTED")
setInterval("UNTRUSTED")
// location 调用恶意代码
location.href = 'UNTRUSTED'
// eval() 中调用恶意代码
eval("UNTRUSTED")
</script>
如果项目中有用到这些的话,一定要避免在字符串中拼接不可信数据。
5.其他防御措施
- 输入内容长度控制:受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度
- 验证码:防止脚本冒充用户提交危险操作。
- 等等。。。
参考资料
Cross-site scripting on Wikipedia