防御固定会话攻击
写在前面
在前面《自动踢掉登录用户》一文中,我们通过使用SpringSecurity中的Session并发控制实现了像QQ一样的功能,即当用户在一台设备上登录成功,之后会自动踢掉另一台设备上的已登录。
其实SpringSecurity中的Session功能非常强大,本篇要学的就是是什么是会话固定攻击以及SpringSecurity中如何防御会话固定攻击,此时就需要使用到Session。
项目实例化
第一步,使用IDEA创建一个名为fixation-attack
的SpringBoot工程,并在其pom.xml依赖文件中添加如下依赖:
1 | <dependencies> |
第二步,在application.yml
配置文件中新增用户信息:
1 | spring: |
第三步,新建controller包,并在里面新建一个HelloController
类,里面的代码如下:
1 | @RestController |
第四步,启动项目进行测试。用户访问/hello
接口就会跳转到/login
页面,输入正确的用户名和密码后,点击登录即可完成登录,页面显示既定的hello,world!信息。
HttpSession
在学习会话固定攻击之前,首先来学习什么是HttpSession
。HttpSession
是一个服务端的概念,服务端生成的HttpSession
都会有一个对应的sessionid,之后这个sessionid会通过cookie传递给前端,前端以后发生请求的时候会携带这个sessionid,然后服务端会将该sessionid和服务器的某一个HttpSession
对应起来,进而形成会话。
前面说过关闭浏览器并不会导致服务端的HttpSession
失效,如果想让服务端的HttpSession
失效,开发者可以通过手动调用HttpSession#invalidate
方法,或者等到session过期时间让它自动过期,再有就是重启服务端。
但还是有人觉得,关闭浏览器之后session也就失效了?原因在于默认情况下关闭浏览器会导致保存在浏览器中的sessionid丢失,这样当浏览器重新打开并访问服务端时,服务端会给浏览器重新分配一个sessionid,这个重新分配的sessionid肯定和之前的HttpSession
是对应不上的,因此就产生了session失效的错觉。
前面也着重说明了是在默认情况下,言外之意开发者可以通过手动配置,使得浏览器重启之后的sessionid不丢失,毫无疑问这种配置可能会带来一定的安全隐患,因此不太建议开发者修改默认的配置。
以之前初始化项目中用户访问/hello
接口为例,当服务端生成sessionid之后,返回给前端的响应头如下图所示:
可以看到在服务端的响应头中有一个Set-Cookie
字段,该字段指示浏览器更新sessionid,同时请注意它还有一个HttpOnly
属性,这个表示通过JS脚本时无法获取到Cookie信息的,这样可以有效防止XSS攻击。
之后当浏览器再去发送请求的时候,就会自觉携带这个jsessionid,如下所示:
以上就是HttpSession相关的基础内容,接下来开始学习会话固定攻击的相关内容。
会话固定攻击
一般来说,只要开发者不关闭浏览器,且服务端的HttpSession
也没有过期,那么维系服务端和浏览器的sessionid是不变的,而会话固定攻击(session fixation attack
)则是利用这一机制,借助受害者用相同的会话ID来获取认证和授权,然后利用该会话ID来劫持受害者的会话,以此冒充受害者,进而造成会话固定攻击。
这里以淘宝网站为例,介绍一般的会话固定攻击流程,如下所示:
(1)攻击者自己可以正常访问淘宝,在访问过程中,淘宝网给攻击者分配了一个sessionid;
(2)攻击者利用自己拿到的sessid来构造一个淘宝网站的链接,并将该链接发送给受害者;
(3)受害者使用该链接登录淘宝网,由于该链接中存在sessionid,因此可以成功登录,之后一个合法的会话就成功建立了;
(4)攻击者利用手中获取的session来冒充受害者,进而进行登录。
如果淘宝网还支持URL重写,那么此时攻击将变得更加容易。所谓的URL重写,是指如果用户在浏览器中禁用了Cookie,那么sessionid也无法使用,此时有的服务器就支持将sessionid放在请求地址中,如下所示:
1 | http://www.taobao.com;jsessionid=xxxxxxxxxx |
因此如果服务端支持类似上述的URL重写,那么对于攻击者来说,按照上面的攻击流程来构造类似的请求地址是非常简单的。但是这种请求地址在SpringSecurity中默认是无法使用的,前面我们说过SpringSecurity默认禁止请求URL地址中包含分号。
防御方法
从上面的会话固定攻击流程中可以看出上述问题的核心在于sessionid是固定不变的,也就是说如果用户在未登录时拿到的是一个sessionid,而登录之后服务端重新给用户分配一个sessionid,这样两个不同的sessionid就能防止固定会话攻击的发生。
由于本项目在初始化的时候就使用了SpringSecurity,因此开发者无需担心会话固定攻击,因为SpringSecurity默认对此进行了防御。
那么问题来说,SpringSecurity是如何防御会话固定攻击的呢?其实主要体现在如下三个方面:
(1)SpringSecurity的StrictHttpFirewall
中的setAllowSemicolon
方法默认不允许请求地址中出现分号;
(2)在前一篇提到过,响应的Set-Cookie字段中有HttpOnly属性,这种方式避免通过XSS攻击来获取Cookie中的会话信息进而形成会话固定攻击。
(3)既然问题的核心是sessionid不发生变化,那么就让sessionid变化一下即可。
解决办法如下:新建一个config包,并在里面新建一个SecurityConfig类,里面的代码如下所示:
1 | @Configuration |
注意这个sessionFixation
选项有4个值,分别为migrateSession
、none
、changeSessionId
和newSession
,如下所示:
其中migrateSession
表示在登录成功之后,创建一个新的会话,并将旧session中的信息复制到新的session中,SpringSecurity中默认使用此种方式。none
表示不做任何事,即继续使用旧的session。changeSessionId
表示session不会变化,仅仅修改sessionid的值,这其实就使用到了Servlet容器提供的防御会话固定攻击。newSession
表示登录后创建一个新的session。
前面也说了SpringSecurity中默认使用的是migrateSession
,也就是说当用户匿名访问的时候使用的是一个sessionid,当用户成功登录之后会使用另一个sessionid,这样就可以有效避免会话固定攻击。
无论使用何种方案,上述三种均能有效防御会话固定攻击。
文章小结
通过本文的学习,我们对SpringSecurity中固定会话攻击有了一个清晰的认识,同时也学会了如何防御固定会话攻击。