前端安全相关
主要介绍一些前端安全相关的东西。
常见的一些前端攻击及其应对
xss 跨站脚本攻击
xss攻击是比较常见的攻击手段,对于前端而言,该攻击主要是通过页面上一些漏洞,向页面注入一段js脚本,来获取用户cookie或者模拟用户发送请求。
攻击原理
攻击者检测到页面上可以注入执行脚本的漏洞,于是将正常数据替换成恶意脚本达到攻击目的。
比如:
url:xxxx?aaa=111
我们想把111插入box里面
var box = document.querySelector(".box")
var span = document.createElement('span')
span.innerHTML = decodeURI(window.location.search.split('?')[1].split('aaa=')[1])
box.append(span)
如果攻击者把aaa=111替换成特殊的代码,让自己的脚本执行。
xss url: xxxx?aaa=<img src="aaa.png" onerror="alert(111111)" alt="">
此时<img src="aaa.png" onerror="alert(111111)" alt="">就会被添加进页面,而aaa.png是不存在的,就会执行onerror事件,把恶意脚本放入里面,这时只要用户打开上面的链接,xss攻击就完成了。
攻击类型
- 持久攻击:是用户输入的已经存入数据库了,比如论坛帖子等,当其他用户访问到该用户的攻击页面,攻击完成。
- 反射攻击:一般如同上面的一样,构造一个链接让用户点击完成攻击。
- dom型:根据用户的输入来动态构造一个DOM节点,引导用户输入代码构建节点。
后果
攻击者可用恶意脚本获取用户敏感信息,如cookie,获取用户账号控制权,或是直接在用户页面发起请求,伪造用户请求。
解决方式
首先任何xss攻击都是要通过脚本当注入展示才生效的,大多数时候我们只需在最后的展示阶段把脚本给干掉就行了。
- 既然是通过脚本进行攻击,那么我们把用户输入的内容或者其他内容在添加进入页面时进行转义,比如
<
转义为<
当然<
是html特殊字符串,在页面上显示还是<
。 - 除了富文本等需要html标签的,尽量不要用不安全的方式添加文档,如v-html及innerHTML,这些把文档都会视为html代码,可用v-text或innerText等代替。当然大多数靠谱等富文本都有自己的xss过滤。
- 不要相信用户的输入,用户的输入全部要进行xss过滤,可以用第三方插件如xss.js等进行过滤。同理后端也不要相信前端等传输数据,因为攻击者可以绕过前端直接进行请求,后端也要对接收对数据进行过滤后存入数据库。
- X-XSS-Protection,服务器添加xss header头,如nginx,浏览器检测到xss攻击时阻止其加载。
- 前后端都要做xss防范,后端主要防范持久攻击,前端则是另外两个攻击。
csrf 跨站点请求伪造
攻击者伪造正常用户到身份,发送恶意请求。
攻击原理
正常用户访问正常网站,用户完成登录,用户到cookie存储了下来,这时攻击者给用户发送一个钓鱼网站,该用户点击之后打开页面或者打开页面后点击该页面的按钮攻击就完成了。
怎么完成攻击的?
首先用户的cookie是存储在正常网站下的,钓鱼网站获取不了用户cookie,所以不能直接在钓鱼网站中发起正常请求的,假如有一个get请求是xxxxx?account=aaa&amount=1000&for=bbb
,这个请求把aaa用户的1000块钱转给bbb,攻击者发现该网站没有进行csrf防范,于是把xxxxx?account=aaa&amount=1000&for=bbb
这个链接放到钓鱼网站中:
<a href="xxxxx?account=aaa&amount=1000&for=bbb">点我</a> or
<img src="xxxxx?account=aaa&amount=1000&for=bbb" alt="">
img的话会直接发起请求,a标签的话需要用户点击。
倘如该用户的cookie没有过期且服务器没有做csrf防范,这时该请求会判定为用户正常请求(该请求有该用户cookie),被服务器正常处理,伪造用户请求的攻击完成。
后果
不能获取用户账号控制权,但能伪造成用户,发起一系列请求。
解决方式
我们知道了csrf的攻击原理,就是发起一个带cookie的简单请求,伪造成正常用户,首先这个请求是简单的,要么是form表单提交要么是一个链接,故不能在该请求上添加自定义header头等信息,另外HTTP请求头中有一个Referer字段,标注了该请求发起地址,可通过这个地址校验是否是正常网站发起请求。注意,钓鱼网站中用ajax请求请求正常网站的接口是不会带上cookie的,只能通过上面方式来进行伪造请求。
- 在http请求中添加自定义header头,后端接收请求后校验该请求是否带有header头。
- 后端接收请求后校验Request的Referer字段是否为正常网址(Referer为请求发起页面地址,可设置meta头
<meta name="referrer" content="origin" />
将其设置为域名)。 - 在发起请求时带上一个后端生成的token。
sql注入
sql注入是通过请求参数带上sql命令,让后端接收后使用sql语句来获取数据库信息及数据库权限,需后端对请求参数进行过滤。
http安全头
X-XSS-Protection
示列:nginx-> X-XSS-Protection: 1; mode=block
浏览器开启xss过滤。
Content Security Policy
示列:html-> <meta http-equiv="Content-Security-Policy" content="script-src 'self'">
nginx-> add_header Content-Security-Policy "child-src https:"
只允许脚本从本源加载。
SCP
策略,允许站点管理者控制用户代理能够为指定的页面加载哪些资源,可以帮助防止xss攻击。
X-Frame-Options
示列:nginx-> add_header X-Frame-Options sameorigin always
指示允许一个页面可否在frame展示。
X-Content-Type-Options
示列:nginx-> add_header X-Content-Type-Options nosniff
禁用了客户端的 MIME 类型嗅探行为,外部加载文档一定要符合MIME标准类型,否则会被阻止加载。
请求类型是"style" 但是 MIME 类型不是 "text/css",
请求类型是"script" 但是 MIME 类型不是 JavaScript MIME 类型。
其他安全说明
https与http
https对我们传输的数据进行加密解密展示,故https比http要慢,在http下无论如何都可能会被劫持流量,无论怎么加密都能被破解登录,前端真正安全就是上https,那么前端加密到底有没有意义呢,我个人觉得保护用户隐私,增加攻击成本上来看都是有积极意义的。
cookie
http请求是无状态的,服务器识别不了用户是谁,一般来说用cookie和session来让服务器认出用户是谁,session是存储在服务器端的,一般用户较少可以使用这种方式,安全性也较高,但是总的来说还是cookie比较常见,同时一些攻击也会造成cookie的泄漏,比如xss攻击可以通过脚本注入的方式从document.cookie里面直接拿。
措施:
- 尽量不要将明文密码直接存入cookie中,避免攻击者能获取到明文密码。
- HttpOnly,个人建议cookie的设置交由后端在Response中设置,并且开启
httpOnly
(开启之后该cookie不能通过js脚本操控,包括获取,修改和设置,只能于Response修改),这样就算遇到xss也不会暴露用户的cookie。 - 后端签发的token不要带有任何敏感信息,实际上加密解密用户的id,账号就行了。
至于前端如何知道HttpOnly成功设置,可以采取双cookie,或者设置再获取的方式判断是否存在:
function getOnlyCookit(key = TokenKey) {
Cookies.set(key, '111111', {
expires: defaultData
})
if (Cookies.get(key) === undefined) {
return true
} else {
removeCookit()
return false
}
}
加密与破解
加密方式多种多样,当然也有对应的破解方式,加密和破解是一个对立的存在,现在的加密和解密的算法都是公之于众的,但是不要试图自己写一个加密解密的方法应用于项目中,毕竟各类的加密方式都是密码学家数学家搞出来的,经得起时间的考验,破解你的比起破解他们的纬度不是一个层面上的。
- 对称加密和非对称加密
对称加密:同一个密钥进行加密和解密,计算量小,加密速度快,加密效率高。
非对称加密:公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密,速度慢。
- 密码的存储
假如用户输入的密码是123456,如果存入数据库也是123456的话,那么假如数据库泄漏,那么大量用户的信息和账号都会曝光,故千万不要将明文密码存入数据库,一般来说后端拿到明文密码通过某种加密把加密后的值存入数据库,用户登录时,把加密后的值和数据库里面的值进行对比,如果一样就成功登录,但是不要以为万无一失了,还有下面都方式进行破解。
- 加盐与破解
常见的破解方式有字典破解与暴力破解等,但是都是效率较低等方式,加密不一定要通过解密拿到原始值,可以通过彩虹表等方式查到对应值,比如著名等md5加密,md5加密是不可逆等,但是相同的字段加密后的值都是一样的,那么可以通过穷举法生成大量的md5值,对比发现一样的反过来找到加密前的值,所以将密码直接加密成md5放入数据库也是有破解的风险。其他加密方式也一样,只要破解了一个那么同样的方式破解后面的简单得多了。
密码都有破解的风险,我们要做的不只是防止被破解,还要增加破解的成本,可以在生成密码时通过加盐的方式生成一个随机字符串和原密码混在一起进行加密,将盐值和加密密码一起存入数据库中,用户修改密码也会重新生成盐值,那么除非知道如何混淆及数据库泄漏同时发生(源代码和数据库都让别人知道了,那还玩个蛋),才会出现大面积被破解都情况,这样查表的方式就无效了,就算破解了一个用户的密码,用同样的方式破解另外的也不灵,降低了被大量破解的风险。
- 撞库
我们自己的安全措施做得很好了,但是还是有一部分用户的信息被泄漏了,但是经过排查,我们的数据库和代码并没有泄露出去,但是为啥用户却泄露了呢?
我们大部分人都有一个习惯,账号密码都用自己所熟悉的,那么会造成一个后果,就是我们的淘宝,微博,微信等等的账号和密码都是一样的,当然大厂的安全措施是有保障的,每个厂商的加密方式也不同,但是你不能保证用户所注册的每个软件网站安全性很高,万一出现了某个傻X网站直接用明文存储账号信息,或者账号密码都被破解了,恰好数据库又被泄漏了出去,那么攻击者拿到数据库到处用账号和密码登录,这便是撞库。别人的账号密码都是对的呀,我们也不知道登录的是不是用户,总之用户发现账号泄漏了,这个锅你得背上。
由上可知,撞库是无法避免的,且账号密码都是正确的,那么如何避免损失呢?当然市面上已经有了常见当应对措施:
- 限制ip访问登录接口次数(不是特别建议,撞库会影响正常用户)。
- 登录错误添加验证码输入。
- 记录用户登录ip,或者其他的信息,当用户登录时当ip地址差异较大或者其他信息有差异,阻止其直接登录,通过手机短信邮箱等有效手段进行二次校验登录。
- 在进行敏感操作时除了密码校验外,增加手机短信邮箱等验证。
- 记录账号风险,出现撞库的话提示用户,建议用户更改密码。
总之,记住一句话,一切攻击都是有成本的,当这个收益小于成本时,攻击就会得不偿失,我们应对攻击时不仅要考虑防止攻击,还要考虑如何提高攻击者当成本。
近期评论