前两天和大家聊了 Spring Security 中的 session 并发问题,和小伙伴们聊了如何像 QQ 一样,用户在一台设备上登录成功之后,就会自动踢掉另一台设备上的登录。

当然,Spring Security 中,关于 session 的功能不仅仅是这些,之前和大家说我们学习 Spring Security,也是学习各种各样的网络攻击与防御策略,今天松哥就来和大家聊一个简单的:什么是会话固定攻击以及 Spring Security 中如何防止会话固定攻击。

本文是 Spring Security 系列的第 16 篇,阅读本系列前面的文章有助于更好的理解本文:

  1. 挖一个大坑,Spring Security 开搞!
  2. 松哥手把手带你入门 Spring Security,别再问密码怎么解密了
  3. 手把手教你定制 Spring Security 中的表单登录
  4. Spring Security 做前后端分离,咱就别做页面跳转了!统统 JSON 交互
  5. Spring Security 中的授权操作原来这么简单
  6. Spring Security 如何将用户数据存入数据库?
  7. Spring Security+Spring Data Jpa 强强联手,安全管理只有更简单!
  8. Spring Boot + Spring Security 实现自动登录功能
  9. Spring Boot 自动登录,安全风险要怎么控制?
  10. 在微服务项目中,Spring Security 比 Shiro 强在哪?
  11. SpringSecurity 自定义认证逻辑的两种方式(高级玩法)
  12. Spring Security 中如何快速查看登录用户 IP 地址等信息?
  13. Spring Security 自动踢掉前一个登录用户,一个配置搞定!
  14. Spring Boot + Vue 前后端分离项目,如何踢掉已登录用户?
  15. Spring Security 自带防火墙!你都不知道自己的系统有多安全!

# 1.HttpSession

看前面文章的评论,我发现有的小伙伴对 HttpSession 还不太熟悉,所以在讲会话固定攻击之前,先来和大家说一说 HttpSession。

HttpSession 是一个服务端的概念,服务端生成的 HttpSession 都会有一个对应的 sessionid,这个 sessionid 会通过 cookie 传递给前端,前端以后发送请求的时候,就带上这个 sessionid 参数,服务端看到这个 sessionid 就会把这个前端请求和服务端的某一个 HttpSession 对应起来,形成“会话”的感觉。

浏览器关闭并不会导致服务端的 HttpSession 失效,想让服务端的 HttpSession 失效,要么手动调用 HttpSession#invalidate 方法;要么等到 session 自动过期;要么重启服务端。

但是为什么有的人会感觉浏览器关闭之后 session 就失效了呢?这是因为浏览器关闭之后,保存在浏览器里边的 sessionid 就丢了(默认情况下),所以当浏览器再次访问服务端的时候,服务端会给浏览器重新分配一个 sessionid ,这个 sessionid 和之前的 HttpSession 对应不上,所以用户就会感觉 session 失效。

注意前面我用了一个默认情况下,也就是说,我们可以通过手动配置,让浏览器重启之后 sessionid 不丢失,但是这样会带来安全隐患,所以一般不建议。

以 Spring Boot 为例,服务端生成 sessionid 之后,返回给前端的响应头是这样的:

在服务端的响应头中有一个 Set-Cookie 字段,该字段指示浏览器更新 sessionid,同时大家注意还有一个 HttpOnly 属性,这个表示通过 JS 脚本无法读取到 Cookie 信息,这样能有效的防止 XSS 攻击。

下一次浏览器再去发送请求的时候,就会自觉的携带上这个 jsessionid 了:

大家先对 HttpSession 有一个大致的了解,接下来我们再来看会话固定攻击。

# 2.会话固定攻击

什么是会话固定攻击?英文叫做 session fixation attack。

正常来说,只要你不关闭浏览器,并且服务端的 HttpSession 也没有过期,那么维系服务端和浏览器的 sessionid 是不会发生变化的,而会话固定攻击,则是利用这一机制,借助受害者用相同的会话 ID 获取认证和授权,然后利用该会话 ID 劫持受害者的会话以成功冒充受害者,造成会话固定攻击。

一般来说,会话固定攻击的流程是这样,以淘宝为例:

  1. 攻击者自己可以正常访问淘宝网站,在访问的过程中,淘宝网站给攻击者分配了一个 sessionid。
  2. 攻击者利用自己拿到的 sessionid 构造一个淘宝网站的链接,并把该链接发送给受害者。
  3. 受害者使用该链接登录淘宝网站(该链接中含有 sessionid),登录成功后,一个合法的会话就成功建立。
  4. 攻击者利用手里的 sessionid 冒充受害者。

在这个过程中,如果淘宝网站支持 URL 重写,那么攻击还会变得更加容易。

什么是 URL 重写?就是用户如果在浏览器中禁用了 cookie,那么 sessionid 自然也用不了了,所以有的服务端就支持把 sessionid 放在请求地址中:

http://www.taobao.com;jsessionid=xxxxxx

如果服务端支持这种 URL 重写,那么对于攻击者来说,按照上面的攻击流程,构造一个这种地址简直太简单不过了。

不过这种请求地址大家在 Spring Security 中应该很少见到(原因请见下文),但是在 Shiro 中可能多多少少有见过。

# 3.如何防御

这个问题的根源在 sessionid 不变,如果用户在未登录时拿到的是一个 sessionid,登录之后服务端给用户重新换一个 sessionid,就可以防止会话固定攻击了。

如果你使用了 Spring Security ,其实是不用担心这个问题的,因为 Spring Security 中默认已经做了防御工作了。

Spring Security 中的防御主要体现在三个方面:

首先就是上篇文章讲的 StrictHttpFirewall,请求地址中有 ; 请求会被直接拒绝。

另一方面就是响应的 Set-Cookie 字段中有 HttpOnly 属性,这种方式避免了通过 XSS 攻击来获取 Cookie 中的会话信息进而达成会话固定攻击。

第三点则是让 sessionid 变一下。既然问题是由于 sessionid 不变导致的,那我就让 sessionid 变一下。

具体配置如下:

可以看到,在这里,我们有四个选项:

  1. migrateSession 表示在登录成功之后,创建一个新的会话,然后讲旧的 session 中的信息复制到新的 session 中,默认即此
  2. none 表示不做任何事情,继续使用旧的 session。
  3. changeSessionId 表示 session 不变,但是会修改 sessionid,这实际上用到了 Servlet 容器提供的防御会话固定攻击。
  4. newSession 表示登录后创建一个新的 session。

默认的 migrateSession ,在用户匿名访问的时候是一个 sessionid,当用户成功登录之后,又是另外一个 sessionid,这样就可以有效避免会话固定攻击。

这三种方案,可以让我们有效避免会话固定攻击!

# 4.小结

说了这么多,大家发现,如果你使用了 Spring Security,其实你什么都不用做,Spring Security 已经帮我们做好了会话固定攻击的防御工作,Spring Security 之强大,可见一斑。是不是非常 nice!

好啦,今天就和大家聊这样一个简单的话题,如果小伙伴们觉得有收获,记得点个在看鼓励下松哥哦~