前言
随着互联网的发达,各种WEB应用也变得越来越复杂,满足了用户的各种需求,但是随之而来的就是各种网络安全的问题。作为前端工程师的我们也逃不开这个问题。所以今天,就和大家一起聊一聊WEB前端的安全那些事儿,聊一聊前端工程师们需要注意的那些安全知识。
为什么要攻击?
其实真正为了玩的心态去进行黑网站的人,还是少数。多数攻击还是有利益的成分在里面的。以前听腾讯的工程师说过一句话,大概是这样的:开发者不可能确保自己的应用绝对无法被攻击,但是只要攻击我们的时候,黑客花费的成本远比他可以获取的利益大得多,黑客就不会去攻击。防范强如支付宝、QQ等产品,也都曾被报过漏洞,看来防御不是绝对的,我们只能想办法让我们的应用更加安全。
前端攻击都有哪些形式,我该如何防范?
1 XSS攻击
XSS是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。 其实在web前端方面,可以简单的理解为一种javascript代码注入。为了更形象的介绍,我们用发生在小明同学身边的事例来进行说明。 #####一个案例 某天,公司需要一个搜索页面,根据 URL 参数决定关键词的内容。小明很快把页面写好并且上线。代码如下:
<input type="text" value="<%= getParameter("keyword") %>">
<button>搜索</button>
<div>
您搜索的关键词是:<%= getParameter("keyword") %>
</div>
然而,在上线后不久,小明就接到了安全组发来的一个神秘链接:
小明带着一种不祥的预感点开了这个链接[请勿模仿,确认安全的链接才能点开]。果然,页面中弹出了写着"XSS"的对话框。 可恶,中招了!小明眉头一皱,发现了其中的奥秘:
当浏览器请求 http://xxx/search?keyword=">\,拼接到 HTML 中返回给浏览器。形成了如下的 HTML:
<input type="text" value=""><script>alert('XSS');</script>">
<button>搜索</button>
<div>
您搜索的关键词是:"><script>alert('XSS');</script>
</div>
浏览器无法分辨出 <script>alert('XSS');</script> 是恶意代码,因而将其执行。 这里不仅仅 div 的内容被注入了,而且 input 的 value 属性也被注入, alert 会弹出两次。 面对这种情况,我们应该如何进行防范呢? 其实,这只是浏览器把用户的输入当成了脚本进行了执行。那么只要告诉浏览器这段内容是文本就可以了。 聪明的小明很快找到解决方法,把这个漏洞修复:
<input type="text" value="<%= escapeHTML(getParameter("keyword")) %>">
<button>搜索</button>
<div>
您搜索的关键词是:<%= escapeHTML(getParameter("keyword")) %>
</div>
escapeHTML() 按照如下规则进行转义:
| 字符 | 转义后的字符 |
|---|---|
| < | < |
|> "|" '|' /|/ 经过了转义函数的处理后,最终浏览器接收到的响应为:
<input type="text" value=""><script>alert('XSS');</script>">
<button>搜索</button>
<div>
您搜索的关键词是:"><script>alert('XSS');</script>
</div>
恶意代码都被转义,不再被浏览器执行,而且搜索词能够完美的在页面显示出来。
通过这个事件,小明学习到了如下知识:
- 通常页面中包含的用户输入内容都在固定的容器或者属性内,以文本的形式展示。
- 攻击者利用这些页面的用户输入片段,拼接特殊格式的字符串,突破原有位置的限制,形成了代码片段。
- 攻击者通过在目标网站上注入脚本,使之在用户的浏览器上运行,从而引发潜在风险。
- 通过 HTML 转义,可以防止 XSS 攻击。[事情当然没有这么简单啦!请继续往下看]。
注意特殊的 HTML 属性、JavaScript API
自从上次事件之后,小明会小心的把插入到页面中的数据进行转义。而且他还发现了大部分模板都带有的转义配置,让所有插入到页面中的数据都默认进行转义。这样就不怕不小心漏掉未转义的变量啦,于是小明的工作又渐渐变得轻松起来。
但是,作为导演的我,不可能让小明这么简单、开心地改 Bug 。
不久,小明又收到安全组的神秘链接:http://xxx/?redirect_to=javascript:alert('XSS')。小明不敢大意,赶忙点开页面。然而,页面并没有自动弹出万恶的“XSS”。
小明打开对应页面的源码,发现有以下内容:
<a href="<%= escapeHTML(getParameter("redirect_to")) %>">跳转...</a>
这段代码,当攻击 URL 为 http://xxx/?redirect_to=javascript:alert('XSS'),服务端响应就成了:
<a href="javascript:alert('XSS')">跳转...</a>
虽然代码不会立即执行,但一旦用户点击 a 标签时,浏览器会就会弹出alert('xss')。
可恶,又失策了…
在这里,用户的数据并没有在位置上突破我们的限制,仍然是正确的 href 属性。但其内容并不是我们所预期的类型。
原来不仅仅是特殊字符,连 javascript: 这样的字符串如果出现在特定的位置也会引发 XSS 攻击。
小明眉头一皱,想到了解决办法:
// 禁止 URL 以 "javascript:" 开头
xss = getParameter("redirect_to").startsWith('javascript:');
if (!xss) {
<a href="<%= escapeHTML(getParameter("redirect_to"))%>">
跳转...
</a>
} else {
<a href="/404">
跳转...
</a>
}
只要 URL 的开头不是 javascript:,就安全了吧?
安全组随手又扔了一个连接:http://xxx/?redirect_to=jAvascRipt:alert('XSS')
这也能执行?…..好吧,浏览器就是这么强大。
小明欲哭无泪,在判断 URL 开头是否为 javascript: 时,先把用户输入转成了小写,然后再进行比对。
不过,所谓“道高一尺,魔高一丈”。面对小明的防护策略,安全组就构造了这样一个连接:
http://xxx/?redirect_to=%20javascript:alert('XSS')
%20javascript:alert('XSS') 经过 URL 解析后变成 javascript:alert('XSS'),这个字符串以空格开头。这样攻击者可以绕过后端的关键词规则,又成功的完成了注入。
最终,小明选择了白名单的方法,彻底解决了这个漏洞:
// 根据项目情况进行过滤,禁止掉 "javascript:" 链接、非法 scheme 等
allowSchemes = ["http", "https"];
valid = isValid(getParameter("redirect_to"), allowSchemes);
if (valid) {
<a href="<%= escapeHTML(getParameter("redirect_to"))%>">
跳转...
</a>
} else {
<a href="/404">
跳转...
</a>
}
通过这个事件,小明学习到了如下知识:
- 做了 HTML 转义,并不等于高枕无忧。
- 对于链接跳转,如 <a href="xxx" 或 location.href="xxx",要检验其内容,禁止以 javascript: 开头的链接,和其他非法的 scheme。
根据上下文采用不同的转义规则 某天,小明为了加快网页的加载速度,把一个数据通过 JSON 的方式内联到 HTML 中:
<script>
var initData = <%= data.toJSON() %>
</script>
插入 JSON 的地方不能使用 escapeHTML(),因为转义 " 后,JSON 格式会被破坏。 但安全组又发现有漏洞,原来这样内联 JSON 也是不安全的:
- 当 JSON 中包含 U+2028 或 U+2029 这两个字符时,不能作为 JavaScript 的字面量使用,否则会抛出语法错误。
- 当 JSON 中包含字符串<script>时,当前的 script 标签将会被闭合,后面的字符串内容浏览器会按照 HTML 进行解析;通过增加下一个 <script> 标签等方法就可以完成注入。
于是我们又要实现一个 escapeEmbedJSON() 函数,对内联 JSON 进行转义。转义规则如下:
| 字符 | 转义后的字符 |
|---|---|
| U+2028 | \U2028 |
| U+2029 | \U2029 |
| < | \u003c |
| 修复后的代码如下: |
<script>
var initData = <%= escapeEmbedJSON(data.toJSON()) %>
通过这个事件,小明学习到了如下知识:
- HTML 转义是非常复杂的,在不同的情况下要采用不同的转义规则。如果采用了错误的转义规则,很有可能会埋下 XSS 隐患。
- 应当尽量避免自己写转义库,而应当采用成熟的、业界通用的转义库。
漏洞总结
小明的例子讲完了,下面我们来系统的看下 XSS 有哪些注入的方法:
- 在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。
- 在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。
- 在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。
- 在标签的 href、src 等属性中,包含 javascript: 等可执行代码。
- 在 onload、onerror、onclick 等事件中,注入不受控制代码。
- 在 style 属性和标签中,包含类似 background-image:url("javascript:..."); 的代码(新版本- 浏览器已经可以防范)。
- 在 style 属性和标签中,包含类似 expression(...) 的 CSS 表达式代码(新版本浏览器已经可以防范)。
总之,如果开发者没有将用户输入的文本进行合适的过滤,就贸然插入到 HTML 中,这很容易造成注入漏洞。攻击者可以利用漏洞,构造出恶意的代码指令,进而利用恶意代码危害数据安全。
2 CSRF攻击
CSRF攻击在百度百科中的解释是: CSRF(Cross-site request forgery跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。 其实就是网站中的一些提交行为,被黑客利用,你在访问黑客的网站的时候,进行的操作,会被操作到其他网站上(如:你所使用的网络银行的网站)。
3 网络劫持攻击
很多的时候,我们的网站不是直接就访问到我们的服务器上的,中间会经过很多层代理,如果在某一个环节,数据被中间代理层的劫持者所截获,他们就能获取到使用你网站的用户的密码等保密数据。比如,我们的用户经常会在各种饭馆里面,连一些奇奇怪怪的wifi,如果这个wifi是黑客所建立的热点wifi,那么黑客就可以结果该用户收发的所有数据。这里,建议站长们网站都使用https进行加密。这样,就算网站的数据能被拿到,黑客也无法解开。
如果你的网站还没有进行https加密的化,则在表单提交部分,最好进行非对称加密--即客户端加密,只有服务端能解开。这样中间的劫持者便无法获取加密内容的真实信息了。
4 控制台注入代码
不知道各位看官有没有注意到天猫官网控制台的警告信息,如图4.1所示,这是为什么呢?因为有的黑客会诱骗用户去往控制台里面粘贴东西(欺负小白用户不懂代码),比如可以在朋友圈贴个什么文章,说:"只要访问天猫,按下F12并且粘贴以下内容,则可以获得xx元礼品"之类的,那么有的用户真的会去操作,并且自己隐私被暴露了也不知道。
图4.1:
天猫这种做法,也是在警告用户不要这么做,看来天猫的前端安全做的也是很到位的。不过,这种攻击毕竟是少数,所以各位看官看一眼就行,如果真的发现有的用户会被这样攻击的话,记得想起天猫的这种解决方案。
5 钓鱼
钓鱼也是一种非常古老的攻击方式了,其实并不太算前端攻击。可毕竟是页面级别的攻击,我们也来一起聊一聊。我相信很多人会有这样的经历,QQ群里面有人发什么兼职啦、什么自己要去国外了房子车子甩卖了,详情在我QQ空间里啦,之类的连接。打开之后发现一个QQ登录框,其实一看域名就知道不是QQ,不过做得非常像QQ登录,不明就里的用户们,就真的把用户名和密码输入了进去,结果没登录到QQ,用户名和密码却给人发过去了
我们平时开发要注意些什么?
- 开发时要提防用户产生的内容,要对用户输入的信息进行层层检测
- 要注意对用户的输出内容进行过滤(进行转义等)
- 重要的内容记得要加密传输(无论是利用https也好,自己加密也好)
- get请求与post请求,要严格遵守规范,不要混用,不要将一些危险的提交使用jsonp完成。
- 对于URL上携带的信息,要谨慎使用。
- 心中时刻记着,自己的网站哪里可能有危险。
毕竟web安全是个很大的面,如果需要了解,还是需要进行专门的学习的。希望这篇聊一聊,可以让各位开发者的网站变得更安全。