Appearance
前言
最近线上项目被他人攻击,对网站危害极大。之前对前端安全知识只是大致了解,简单应用。为了更详细的了解与应用前端安全知识,因此查找与前端安全相关材料抱着求学的心态系统地学习前端安全知识,在这里做个简单的总结,希望在日常开发中不断预防和修复漏洞。
典型的前端安全有XSS攻击和CSRF攻击,本篇主要从这两方面阐述。
XSS的介绍
XSS全称Cross-Site Scripting(跨站脚本攻击),是一种代码注入攻击。攻击者在目标网站上注入恶意脚本,能在用户的浏览器上运行。利用恶意脚本,攻击者可以获取用户敏感信息,如:Cookie等,进行攻击危害数据。
XSS存在的原因
在URL参数场景,用户输入提交给服务端的URL内容,没有进行充分的过滤。如果将所有不合法的参数和输入内容做充分的过滤,就不会导致在用户浏览器中执行攻击者的脚本。
只做URL参数过滤和用户提交内容过滤是不行的。因为攻击者可以使用各种方式绕过服务端的过滤,最典型的是对URL参数进行各种编码,比如: escape/encodeURI/encodeURIComponent
/8进制/10进制/16进制绕过过滤。
XSS攻击方式
①、一个搜索页面,点击搜索页面url添加上了关键词,页面显示与关键词相关的内容。
<input type="text" value="<%= getParams(keyword)%>" />
<button>搜索</button>
<div>显示搜索的内容:<%= getParams(keyword)%> </div>
<input type="text" value="<%= getParams(keyword)%>" />
<button>搜索</button>
<div>显示搜索的内容:<%= getParams(keyword)%> </div>
- 附:文中<%= ... %>语法参考EJS语法。
文本框值没做任何处理,项目就上线了。某天收到一个神秘链接:
http://www.xxx.com/search?keyword=<script>alert('xss')</script>
http://www.xxx.com/search?keyword=<script>alert('xss')</script>
浏览器发送请求http://www.xxx.com/search?keyword=<script>alert('xss')</script>
时,后端会解析URL中的请求参数keyword的值,得到<script>alert('xss')</script>
,浏览器最终在页面搜索结果区域显示与关键词相关内容:
<div>显示搜索的内容:<script>alert('xss')</script> </div>
<div>显示搜索的内容:<script>alert('xss')</script> </div>
对于<script>alert('xss')</script>
浏览器无法分辨是否属于恶意代码,因此会执行脚本。
对于这种攻击方式,可以告诉浏览器这段内容是文本就可以了。可以选择进行转义字符:
<input type="text" value="<%= esHTML(getParams(keyword))%>" />
<button>搜索</button>
<div>显示搜索的内容:<%= esHTML(getParams(keyword))%> </div>
<input type="text" value="<%= esHTML(getParams(keyword))%>" />
<button>搜索</button>
<div>显示搜索的内容:<%= esHTML(getParams(keyword))%> </div>
转义规则:
字符 | 转义后的字符 |
---|---|
' | ' |
& | & |
< | < |
> | > |
/ | / |
" | " |
经过转义后:
<input type="text" value="<script>alert('xss')<script>" />
<button>搜索</button>
<div>显示搜索的内容:
<script>alert('xss')<script>
</div>
<input type="text" value="<script>alert('xss')<script>" />
<button>搜索</button>
<div>显示搜索的内容:
<script>alert('xss')<script>
</div>
通过转义字符恶意脚本被转义,浏览器不会执行恶意脚本,搜索到的与关键词相关内容也正常在页面中显示出来。有些情况只做HTML转义,并不是高枕无忧,比如下面场景。
②、超链接场景,页面正常显示:
<a href="http://www.xxx.com/search?to=javascript:alert('xss');">click</a>
<a href="http://www.xxx.com/search?to=javascript:alert('xss');">click</a>
用户一旦进行点击,浏览器就会执行代码;这种场景如果使用上述转义字符规则:
<a href="http://www.xxx.com/search?to=javascript:alert('xss');">click</a>
<a href="http://www.xxx.com/search?to=javascript:alert('xss');">click</a>
显然是不可行的。因为href跟的是正确属性,浏览器无法分辨属性值是否属于恶意代码,用户点击恶意脚本会执行。
对于这种形式可以设置白名单预防攻击:
var allowHTTP = ['http', 'https'];
var isAllow = isAllowFunc(allowHTTP, to);
if(!!isAllow) {
<a href="跳转到相应页面">...</a>
}else{
<a href="/404.html">...</a>
}
var allowHTTP = ['http', 'https'];
var isAllow = isAllowFunc(allowHTTP, to);
if(!!isAllow) {
<a href="跳转到相应页面">...</a>
}else{
<a href="/404.html">...</a>
}
通过该方式可知针对超链接跳转方式,如:location.href="xxx"或者<a href="xxx"></a>;
甚至包括<script>,<style>,<img>
等标签的src属性值,可以选择白名单校验禁止以javascript:
开头的链接 (包括javascript大小写的拼写) 和其他非法的scheme。
③、有些情况把JSON数据直接写在HTML中:
<script> var data = data.toJSON(); </script>
<script> var data = data.toJSON(); </script>
这种方式JSON也存在风险,并且插入的JSON不能进行转义。因为转义 " 后JSON格式被破坏。
因此需要实现一个对JSON数据进行转义的方法。但是需要注意的是:
当JSON中包含U+2028或U+2029这两个字符时,不能作为 JavaScript 的字面量使用,否则会抛出语法错误。
当JSON中包含字符串
</script>
时,当前的script标签将会被闭合,后面的字符串内容浏览器会按照HTML进行解析;通过增加下一个<script>
标签等方法就可以完成注入。
转义规则:
字符 | 转义后的字符 |
---|---|
U+2028 | \u2028 |
U+2029 | \u2029 |
< | \u003c |
修改后的代码:
<script> var data = esJSON(data.toJSON()); </script>
<script> var data = esJSON(data.toJSON()); </script>
通过以上可知:
HTML转义是非常复杂的,在不同的场景要采用不同的转义规则。如果采用的方式不正确,很可能埋下XSS隐患。因此尽量避免自己写转义库,应采用成熟,通用的转义库。
归纳注入方式
- 在HTML中内嵌恶意内容以script标签形式注入。
- 在HTML中拼接的JavaScript数据突破了原本的限制(字符串,变量等)。
- 在标签属性中,恶意内容包含引号,突破属性值的限制,注入其他属性或标签。
- 在标签style,href,src等属性中,包含JavaScript等可执行代码。
- 在onload,onclick等事件中注入不受控制的代码。
- 在style属性和标签中,包含background: url('JavaScript...')类似代码。
- 在style属性和标签中,包含expression(...)类似CSS表达式代码。
XSS攻击分类
根据攻击的来源,可以将XSS攻击分以下类型:
- 存储型攻击
该攻击常见于论坛博主发贴,网站用户评论,网站用户留言等场景,攻击者将恶意脚本存储到目标网站上。
实现方式: 1、攻击者将恶意脚本提交到目标网站数据库中。 2、用户打开该网站浏览时,浏览器发送请求恶意脚本从数据库中读取,拼接在页面HTML中。 3、浏览器接收到响应后解析执行。 4、混在其中的恶意脚本也被执行,恶意脚本窃取用户数据,如:Cookie,发送到攻击者网站。
- 反射型攻击
该攻击常见于网站搜索,跳转等场景,通过URL传递参数,需要用户主动打开恶意的URL才会生效。攻击者将恶意脚本存储到URL里。
实现方式: 1、攻击者构造出特殊的URL并添加恶意脚本。 2、用户打开带有恶意脚本的URL,服务端将恶意脚本从URL中取出,拼接在HTML中返回给浏览器。 3、浏览器接收到响应后解析执行。 4、混在其中的恶意脚本也被执行,恶意脚本窃取用户数据,如:Cookie,发送到攻击者网站。
- DOM型攻击
该攻击常发生于用户的输入来动态的构造一个DOM节点场景,如果没有对用户的输入进行过滤,很有可能造成XSS攻击。基于DOM的XSS取出恶意脚本和执行恶意脚本由浏览器端完成,服务端不参与。
实现方式: 1、攻击者构造出特殊的URL并添加恶意脚本。 2、用户打开带有恶意脚本的URL。 3、浏览器接收到响应后解析执行,前端JavaScript取出URL中的恶意脚本并执行。 4、恶意脚本窃取用户数据,如:Cookie,发送到攻击者网站。
XSS防御策略
- 客户端对用户有明确的输入类型,例如:数字,URL,手机号,邮箱等内容进行安全符转义,服务端对提交的内容进行安全转义。
- 对输入和URL参数进行过滤,对输出进行编码;和白名单结合。
- 避免拼接HTML。如果框架允许,使用createElement,setAttribute等之类的方法实现,或者采用比较成熟的框架,如:Vue/React。
- 要警惕插入位置为DOM属性,链接等位置。
- 尽量不要使用onClick="fn('')"等形式内联事件的写法。可以通过addEventListener事件绑定更安全。
- 服务端渲染开启模版引擎自带的HTML转义功能。
- 增加验证码功能,防止脚本冒充用户提交危险操作。
- 限制用户输入长度,增加攻击的难度。
- 设置Http-Only Cookie 禁止JavaScript读取敏感Cookie,攻击者完成XSS注入也无法窃取Cookie。
- 避免第三方跨域提交内容到服务端。
CSRF的介绍
CSRF全称Cross-Site Request Forgery(跨站请求伪造),攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
CSRF的特点
- 攻击一般在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。
- 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作,而不是直接窃取数据。
- 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是冒用。
- 跨站请求可以用各种方式,如:图片URL,超链接,Form等。部分请求方式可以直接嵌入在第三方论坛中,难以追踪。
CSRF通常是跨域的,因为外域通常更容易被攻击者掌握。反之,在本域下进行,如:可以发链接的评论,攻击反而更加危险。
CSRF攻击分类
- GET类型的CSRF
该攻击方式只需要一个HTTP请求,一般是这样:
<img src="http://www.xxx.com?time=100&keyword=get" alt="" />
<img src="http://www.xxx.com?time=100&keyword=get" alt="" />
受害者在访问这个图片页面后,浏览器自动向:http://www.xxx.com?account=danger&time=100&keyword=get
发送一次HTTP请求。www.xxx.com
会包含受害者登录信息的一次跨域请求。
- POST类型的CSRF。
该攻击方式通常使用的是一个自动提交的表单:
<form action="http://www.xxx.com" method="POST">
<input type="hidden" name="time" value="100" />
</form>
<script> document.forms[0].submit(); </script>
<form action="http://www.xxx.com" method="POST">
<input type="hidden" name="time" value="100" />
</form>
<script> document.forms[0].submit(); </script>
用户访问该页面,表单会自动提交,完成了模拟POST操作。一般在网站上传功能上很有可能是发起攻击的来源。
- 链接类型的CSRF。
该攻击方式通常发生在论坛发布图片嵌入恶意链接,或者以广告的形式诱导用户点击,如:
<a href="http://www.xxx.com/csrf">充1元送1000</a>
<a href="http://www.xxx.com/csrf">充1元送1000</a>
这种方式是用户之前登录了信任网站A,并且保存了登录状态。只要用户主动访问这个页面,攻击者就能攻击成功。
CSRF防御策略
- 添加token验证
本站点的接口请求前在头部添加token用于鉴别身份,第三方站点不能获取头部token。
该方式弊端:token鉴权对服务器压力较大;页面form提交,超链接不能形成统一的token增加入口,造成部分疏漏。
- 服务端通过Referer Header和Origin Header进行同源验证。
<a href="http://www.xxx.com/csrf" referrerpolicy="no-referrer">...</a>
<a href="http://www.xxx.com/csrf" referrerpolicy="no-referrer">...</a>
该方式弊端: 1、可以部分修改或隐藏Referer。 2、在低版本浏览器下对Referer和Origin不是很稳定。 3、在一些浏览器或操作会丢失Origin头部,如:302重定向。 4、HTTPS跳转到HTTP,所有浏览器Referer都会丢失。
- 禁止第三方网站获取Cookie。
可以设置Chrome的SameSite属性,而SameSite兼容性不好。
- 利用双重Cookie认证。
每个请求的参数都添加scrfCookie='随机数' 防御参数,并在Cookie中混入该防御参数值,服务端请求头部的Cookie中的Cookie参数和请求参数所带的该参数进行对比。
该方式弊端: 如果前后端代码分离,前端和后端接口不同源,如:前端为www.a.com
,后端为api.a.com
,前端要拿到后端接口下的Cookie,必须将Cookie放在a.com
下才能保证子域下都可以获取到,然而这样会增加XSS攻击风险。
总结
本篇到这里算是结束了,从以上内容可以知晓:防范XSS是需要前端和后端共同参与的,针对DOM型攻击需要前端完成,选择适合的转义库,在不同的上下文调用不同的转义规则;防范CSRF,自动防御策略需要使用同源检测(Referer和Origin验证),主动防御策略增加Token验证或双重Cookie验证,以及配合Samesite Cookie。为了更好的防御,最佳实践应结合防御措施优缺点和Web应用程序自身情况选择适合方案。文章可能存在一些没有说清楚的地方或者有错的地方,欢迎指正~
参考资料: