1. 同源策略
URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们同源。
浏览器的同源策略,限制了来自不同源的document
或脚本,对当前document
读取或设置某些属性。从一个域上加载的脚本不允许访问另外一个域的文档属性。
如果没有同源限制存在,浏览器中cookie等其他数据可以任意读取,不同域下DOM可以任意操作,ajax可以任意请求。如果浏览了恶意网站那么就会泄漏这些隐私数据
在浏览器中,<script>
、<img>
、<iframe>
、<link>
等标签都可以加载跨域资源,而不受同源限制。
2. CSRF
CSRF(Cross Site Request Forgery)跨站请求伪造是攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
CSRF 利用的是网站对用户网页浏览器的信任。
一个典型的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执行了自己定义的操作。
常见的攻击类型
- GET类型的CSRF
- POST类型的CSRF
- 链接类型的CSRF
防护措施
-
验证
Referer
根据 HTTP 协议,在 HTTP 头中有一个字段叫Referer
,它记录了该 HTTP 请求的来源地址。通过验证Referer
,可以检查请求是否来自合法的"源"。 -
验证
Token
服务端随机生成token,保存在服务端session中,同时保存到客户端中,客户端发送请求时,把token带到HTTP请求头或参数中,服务端接收到请求,验证请求中的token与session中的是否一致。如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。 -
禁止第三方网站携带本网站的cookie信息
设置same-site属性,same-site属性有两个值,Strict(所有的第三方请求都不能携带本网站的cookie)和Lax(链接可以,但是form表单提交和ajax请求不行)
-
验证码
通过增加网站A的验证手段,例如增加图形验证码或短信验证码等等,只有通过验证的请求才算合法。但是这种方案拥有两个局限性,一个是增加开发成本,另外一个是降低用户体验。
注:CSRF(通常)发生在第三方域名,不能获取到Cookie等信息,只是使用。
3. XSS
XSS攻击全称跨站脚本攻击(Cross-Site Scripting),为区别CSS,安全领域叫做XSS。
攻击者通过在目标网站上注入恶意脚本并运行,获取用户敏感信息如 Cookie、SessionID等,影响网站与用户数据安全。
XSS 利用的是用户对指定网站的信任。
XSS攻击分类
反射型
反射型XSS只是把用户输入的数据“反射”给浏览器,攻击者往往需要诱使用户操作一个恶意链接,才能攻击成功。
存储型
恶意代码保存到目标网站服务器中,这种攻击具有较强稳定性和持久性,比较常见场景是在博客,论坛、OA、CRM等社交网站上。
攻击流程:
- 攻击者提交一条包含XSS代码的留言或其他数据到数据库
- 当目标用户查询时,XSS的内容会从服务器解析之后加载出来
- 浏览器将恶意代码当作正常脚本解析执行,可以执行浏览器所具有权限下的命令
DOM
通过恶意脚本修改页面的DOM结构,是纯粹发生在客户端的攻击。
存储型XSS的恶意代码存在数据库里,反射型XSS的恶意代码存在URL里。
DOM型XSS攻击中,取出和执行恶意代码由浏览器端完成,属于前端JS自身的安全漏洞,而其他两种XSS都属于服务端的安全漏洞
防护措施
-
开启浏览器自带防御机制
-
XSS攻击代码过滤(js下的js-xss,JAVA下的XSS HTMLFilter)
-
输出检查
利用转义库对 HTML 模板各处节点进行充分的转义。
-
输入内容长度限制
对输入的内容限定合理长度,可以增加XSS攻击的难度
-
使用
HttpOnly
通过设置HttpOnly
,浏览器可以禁止页面的JavaScript访问带有HttpOnly
属性的Cookie
。它的目的并非是为了对抗XSS,而是对抗XSS之后的Cookie
劫持攻击。HttpOnly
是在Set-Cookie
的时候进行标记的
Set-Cookie: <name>=<value>[; <Max-Age>=<age>] [; expires=<date>][; domain=<domain_name>] [; path=<some_path>][; secure][; HttpOnly]
-
开启CSP网页安全政策
通过HTTP头信息的
Content-Security-Policy
字段在网页中设置
<meta>
标签<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">
注:不论是 Cookie 还是 token 都无法XSS。
4. CORS
CORS(Cross-origin resource sharing)是一个W3C标准,全称是"跨域资源共享"。它允许浏览器向跨源服务器,发出XMLHttpRequest
请求,从而克服了AJAX只能同源使用的限制。
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
简单请求
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin
字段。
如果Origin
指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。
Access-Control-Allow-Origin: http://api.bob.com
# 是否允许发送Cookie
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
非简单请求
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin
头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin
头信息字段。
# 预检"请求的HTTP头信息。
# 这个请求是用来询问的
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
# CORS请求会用到哪些HTTP方法
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
# 确认允许跨源请求,就可以以下回应
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
# "预检"请求之后,浏览器的正常CORS请求。
PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
# 响应
Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8
注:CORS与JSONP的使用目的相同,但是比JSONP更强大。
JSONP只支持GET
请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
5. 重放攻击 Replay Attacks
重放攻击是攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程。
主机A给主机B发送的报文被攻击者C截获了,然后C伪装成A给B发送其截获来的报文,而B会误以为C就是A,就把回应报文发送给了C。虽然A是加密的,但C根本不用破译。
防范措施
防止重放攻击的方法是使用不重数。需保存某个很短时间段内的所有随机数,而且时间戳的同步也不需要太精确;使用挑战一应答机制和一次性口令机制。
-
加随机数
认证双方不需要时间同步,双方记住使用过的随机数,如发现报文中有以前使用过的随机数,就认为是重放攻击。需要额外保存使用过的随机token值,则需要保存和查询开销
-
加流水号
双方在报文中添加一个逐步递增的整数,则只要收到一个不连续的流水号报文(太大或者太小)就认为有重放威胁
-
加时间戳
不用额外保存其他信息。缺点是认证双方需要准确的时间同步,同步越好,受攻击的可能性就越小。
6. 重复提交请
以下情况都会导致表单重复提交,造成数据重复,增加服务器负载,严重甚至会造成服务器宕机。
- 由于用户误操作,多次点击表单提交按钮。
- 由于网速等原因造成页面卡顿,用户重复刷新提交页面。
- 黑客或恶意用户使用postman等工具重复恶意提交表单(攻击网站)。
防范措施
-
通过JavaScript屏蔽提交按钮(不推荐)
js代码很容易被绕过。比如用户通过刷新页面方式,或使用postman等工具绕过前段页面仍能重复提交表单。
-
数据库增加约束
约束能有效避免数据库重复插入相同数据。但无法阻止恶意用户重复提交表单(攻击网站),服务器大量执行sql插入语句,增加服务器和数据库负荷。
-
session防止表单重复提交
服务器返回表单页面时,会先生成一个subToken保存于session,并把该subToen传给表单页面。首次提交表单时session的subToken与表单携带的subToken一致走正常流程,删除subToken,当再次提交表单时由于session的subToken为空则不通过。
-
Aop自定义实现
实现原理:
- 自定义防止重复提交标记(@AvoidRepeatableCommit)。
- 对需要防止重复提交的Congtroller里的mapping方法加上该注解。
- 新增Aspect切入点,为@AvoidRepeatableCommit加入切入点。
- 每次提交表单时,Aspect都会保存当前key到reids(须设置过期时间)。
- 重复提交时Aspect会判断当前redis是否有该key,若有则拦截。
import java.lang.annotation.*; /** * 自定义标签 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AvoidRepeatableCommit { long timeout() default 30000 ; }
@Aspect @Component public class AvoidRepeatableCommitAspect { @Autowired private RedisTemplate redisTemplate; @Around("@annotation(com.xwolf.boot.annotation.AvoidRepeatableCommit)") public Object around(ProceedingJoinPoint point) throws Throwable { HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest(); String ip = IPUtil.getIP(request); //获取注解 MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); //目标类、方法 String className = method.getDeclaringClass().getName(); String name = method.getName(); String ipKey = String.format("%s#%s",className,name); int hashCode = Math.abs(ipKey.hashCode()); String key = String.format("%s_%d",ip,hashCode); log.info("ipKey={},hashCode={},key={}",ipKey,hashCode,key); AvoidRepeatableCommit avoidRepeatableCommit = method.getAnnotation(AvoidRepeatableCommit.class); long timeout = avoidRepeatableCommit.timeout(); if (timeout < 0){ //过期时间5分钟 timeout = 60*5; } String value = (String) redisTemplate.opsForValue().get(key); if (StringUtils.isNotBlank(value)){ return "请勿重复提交"; } redisTemplate.opsForValue().set(key, UUIDUtil.uuid(),timeout,TimeUnit.MILLISECONDS); //执行方法 Object object = point.proceed(); return object; } }
7. 点击劫持
第三方网站通过iframe内嵌某一个网站,并且将iframe设置为透明不可见,将其覆盖在其他经过伪装的DOM上,伪装的可点击DOM(按钮等)与实际内嵌网站的可点击DOM位置相同,当用户点击伪装的DOM时,实际上点击的是iframe中内嵌的网页的DOM从而触发请求操作
特点:用户自己做了点击操作;用户毫不知情
防护措施
-
Javascript禁止内嵌
<script> if (top.location != window.location) { //如果不相等,说明使用了iframe,可进行相关的操作 } </script>
但是这种方式并不是万能的,因为iframe标签中的属性sandbox属性是可以禁用内嵌网页的脚本的:
<iframe sandbox='allow-forms' src='...'></iframe>
-
设置http响应头 X-Frame-Options:有三个值 DENY(禁止内嵌) SAMEORIGIN(只允许同域名页面内嵌) ALLOW-FROM(指定可以内嵌的地址)
能在所有的web服务器端预设好X-Frame-Options字段值是最理想的状态
8.加密算法
加密技术是对信息进行编码和解码的技术。编码是把可读信息(又称明文)译成代码形式(又称密文),其逆过程就是解码。加密技术的要点是加密算法,加密算法可以分为三类:
对称加密,如AES
、DES
、3DES
将明文分成N个组,然后使用密钥对各个组进行加密,形成各自的密文,最后把所有的分组密文进行合并,形成最终的密文。
优势:算法公开、计算量小、加密速度快、加密效率高
缺陷:双方都使用同样密钥,安全性得不到保证
非对称加密,如RSA、DSA
同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端。
私钥加密,持有私钥、公钥才可以解密;公钥加密,持有私钥才可以解密;
优点:安全,难以破解
缺点:算法比较耗时
不可逆加密 MD5、SHA1、HMAC
加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,无法根据密文推算出明文。