网络安全
XSS
XSS(Cross site scripting) 跨站脚本攻击
网站存在漏洞,允许恶意用户注入恶意代码
例子,网站允许 img src 路径的拼接;写入可执行脚本
innerHTML 容易引发跨站脚本攻击
CSRF
什么是 CSRF
CSRF(Cross-site request forgery) 跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的
一个典型的 CSRF 攻击有着如下的流程:
- 受害者登录a.com,并保留了登录凭证(Cookie)。
- 攻击者引诱受害者访问了b.com。
- b.com 向 a.com 发送了一个请求:a.com/act=xx。浏览器会默认携带 a.com 的Cookie
- a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求
- a.com以受害者的名义执行了act=xx
- 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作
CSRF的特点
- 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生
- 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据
- 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”
- 跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪
CSRF通常是跨域的,因为外域通常更容易被攻击者掌控。但是如果本域下有容易被利用的功能,比如可以发图和链接的论坛和评论区,攻击可以直接在本域下进行,而且这种攻击更加危险
防护策略
CSRF通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对CSRF的防护能力来提升安全性
前面提到了 CSRF
的两个特点
- CSRF(通常)发生在第三方域名
- CSRF攻击者不能获取到Cookie等信息,只是使用
针对这两点,我们可以专门制定防护策略
- 阻止不明外域的访问
- 同源检测
- Samesite Cookie
- 提交时要求附加本域才能获取的信息
- CSRF Token(所有用户的请求再携带一个CSRF攻击者无法获取到的Token,服务器通过校验请求是否携带正确的Token,来把正常的请求和攻击的请求区分开)
- 双重Cookie验证
前面讲到CSRF的另一个特征是,攻击者无法直接窃取到用户的信息(Cookie,Header,网站内容等),仅仅是冒用Cookie中的信息。
而CSRF攻击之所以能够成功,是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的Token。服务器通过校验请求是否携带正确的Token,来把正常的请求和攻击的请求区分开,也可以防范CSRF的攻击。
CSRF Token的防护策略分为三个步骤
-
将CSRF Token输出到页面中
首先,用户打开页面的时候,服务器需要给这个用户生成一个Token,该Token通过加密算法对数据进行加密,一般Token都包括随机字符串和时间戳的组合,显然在提交时Token不能再放在Cookie中了,否则又会被攻击者冒用。因此,为了安全起见Token最好还是存在服务器的Session中,之后在每次页面加载时,使用JS遍历整个DOM树,对于DOM中所有的a和form标签后加入Token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的HTML代码,这种方法就没有作用,还需要程序员在编码时手动添加Token。
-
页面提交的请求携带这个Token
对于GET请求,Token将附在请求地址之后,这样URL 就变成 http://url?csrftoken=tokenvalue。 而对于 POST 请求来说,要在 form 的最后加上: 这样,就把Token以参数的形式加入请求了
-
服务器验证Token是否正确
当用户从客户端得到了Token,再次提交给服务器的时候,服务器需要判断Token的有效性,验证过程是先解密Token,对比加密字符串以及时间戳,如果加密字符串一致且时间未过期,那么这个Token就是有效的。
这种方法要比之前检查Referer或者Origin要安全一些,Token可以在产生并放于Session之中,然后在每次请求时把Token从Session中拿出,与请求中的Token进行比对,但这种方法的比较麻烦的在于如何把Token以参数的形式加入请求。
下面将以Java为例,介绍一些CSRF Token的服务端校验逻辑,代码如下:
HttpServletRequest req = (HttpServletRequest)request;
HttpSession s = req.getSession();
// 从 session 中得到 csrftoken 属性
String sToken = (String)s.getAttribute(“csrftoken”);
if(sToken == null){
// 产生新的 token 放入 session 中
sToken = generateToken();
s.setAttribute(“csrftoken”,sToken);
chain.doFilter(request, response);
} else {
// 从 HTTP 头中取得 csrftoken
String xhrToken = req.getHeader(“csrftoken”);
// 从请求参数中取得 csrftoken
String pToken = req.getParameter(“csrftoken”);
if(sToken != null && xhrToken != null && sToken.equals(xhrToken)){
chain.doFilter(request, response);
}else if(sToken != null && pToken != null && sToken.equals(pToken)){
chain.doFilter(request, response);
}else{
request.getRequestDispatcher(“error.jsp”).forward(request,response);
}
}这个Token的值必须是随机生成的,这样它就不会被攻击者猜到,考虑利用Java应用程序的java.security.SecureRandom类来生成足够长的随机标记,替代生成算法包括使用256位BASE64编码哈希,选择这种生成算法的开发人员必须确保在散列数据中使用随机性和唯一性来生成随机标识。通常,开发人员只需为当前会话生成一次Token。在初始生成此Token之后,该值将存储在会话中,并用于每个后续请求,直到会话过期。当最终用户发出请求时,服务器端必须验证请求中Token的存在性和有效性,与会话中找到的Token相比较。如果在请求中找不到Token,或者提供的值与会话中的值不匹配,则应中止请求,应重置Token并将事件记录为正在进行的潜在CSRF攻击。
分布式校验
在大型网站中,使用Session存储CSRF Token会带来很大的压力。访问单台服务器session是同一个。但是现在的大型网站中,我们的服务器通常不止一台,可能是几十台甚至几百台之多,甚至多个机房都可能在不同的省份,用户发起的HTTP请求通常要经过像Ngnix之类的负载均衡器之后,再路由到具体的服务器上,由于Session默认存储在单机服务器内存中,因此在分布式环境下同一个用户发送的多次HTTP请求可能会先后落到不同的服务器上,导致后面发起的HTTP请求无法拿到之前的HTTP请求存储在服务器中的Session数据,从而使得Session机制在分布式环境下失效,因此在分布式集群中CSRF Token需要存储在Redis之类的公共存储空间。由于使用Session存储,读取和验证CSRF Token会引起比较大的复杂度和性能问题,目前很多网站采用Encrypted Token Pattern方式。这种方法的Token是一个计算出来的结果,而非随机生成的字符串。这样在校验时无需再去读取存储的Token,只用再次计算一次即可。这种Token的值通常是使用UserID、时间戳和随机数,通过加密的方法生成。这样既可以保证分布式服务的Token一致,又能保证Token不容易被破解。在token解密成功之后,服务器可以访问解析值,Token中包含的UserID和时间戳将会被拿来被验证有效性,将UserID与当前登录的UserID进行比较,并将时间戳与当前时间进行比较。
双重Cookie验证
在会话中存储CSRF Token比较繁琐,而且不能在通用的拦截上统一处理所有的接口那么另一种防御措施是使用双重提交Cookie。利用CSRF攻击不能获取到用户Cookie的特点,(攻击者并利用而没有获取),我们可以要求Ajax和表单请求携带一个Cookie中的值。
双重Cookie采用以下流程:
-
在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串(例如csrfcookie=v8g9e4ksfhw)。
-
在前端向后端发起请求时,取出Cookie,并添加到URL的参数中(接上例POST https://www.a.com/comment?csrfcookie=v8g9e4ksfhw)。// 读取并使用Cookie中的信息,这是csrf攻击者做不到的
-
后端接口验证Cookie中的字段与URL参数中的字段是否一致,不一致则拒绝。此方法相对于CSRF Token就简单了许多。可以直接通过前后端拦截的的方法自动化实现。后端校验也更加方便,只需进行请求中字段的对比,而不需要再进行查询和存储Token。
clickjacking
ClickJacking(点击劫持)是由互联网安全专家罗伯特·汉森和耶利米·格劳斯曼在2008年首创的。ClickJacking是一种视觉欺骗攻击手段,在web端就是iframe嵌套一个透明不可见的页面,让用户在不知情(被欺骗)的情况下,点击攻击者想要欺骗用户点击的位置。(用户点击之后,就可以打开用户的摄像头或者干一些其他操作…)
X-Frame-Options HTTP 响应头是用来给浏览器 指示允许一个页面 可否在 <frame>
, <iframe>
, <embed>
或者 <object>
中展现的标记。站点可以通过 确保网站没有被嵌入到别人的站点 里面,从而避免 clickjacking
攻击
X-Frame-Options: deny
X-Frame-Options: sameorigin
X-Frame-Options: allow-from https://example.com/
X-Frame-Options 有三个可能的值:
-
deny
表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许。
-
sameorigin
表示该页面可以在相同域名页面的 frame 中展示。
-
allow-from uri
表示该页面可以在指定来源的 frame 中展示。
Man-in-the-Middle attach, MITM 中间人攻击
请求或者响应在传输途中,遭遇攻击者拦截并篡改内容共的攻击称为中间人攻击
使用HTTPS而不是HTTP,虽然有使用HTTP协议确定报文完整性的方法,但事实上并不便捷、可靠(其中常用MD5和SHA-1等散列值校验的方法,以及用来确认文件的数字前面方法)
但是,这些都无法依靠浏览器自动帮用户检查
使用HTTPS,SSL的摘要功能提供了完整性的保护(HTTPS = HTTP + 加密 + 认证 + 完整性保护)