写在前面

在前面《自动踢掉登录用户》一文中,我们通过使用SpringSecurity中的Session并发控制实现了像QQ一样的功能,即当用户在一台设备上登录成功,之后会自动踢掉另一台设备上的已登录。

其实SpringSecurity中的Session功能非常强大,本篇要学的就是是什么是会话固定攻击以及SpringSecurity中如何防御会话固定攻击,此时就需要使用到Session。

项目实例化

第一步,使用IDEA创建一个名为fixation-attack的SpringBoot工程,并在其pom.xml依赖文件中添加如下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

第二步,在application.yml配置文件中新增用户信息:

1
2
3
4
5
spring:
security:
user:
name: envy
password: 1234

第三步,新建controller包,并在里面新建一个HelloController类,里面的代码如下:

1
2
3
4
5
6
7
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello,world!";
}
}

第四步,启动项目进行测试。用户访问/hello接口就会跳转到/login页面,输入正确的用户名和密码后,点击登录即可完成登录,页面显示既定的hello,world!信息。

HttpSession

在学习会话固定攻击之前,首先来学习什么是HttpSessionHttpSession是一个服务端的概念,服务端生成的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
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.sessionManagement()
.sessionFixation().migrateSession();
}
}

注意这个sessionFixation选项有4个值,分别为migrateSessionnonechangeSessionIdnewSession,如下所示:

其中migrateSession表示在登录成功之后,创建一个新的会话,并将旧session中的信息复制到新的session中,SpringSecurity中默认使用此种方式。none表示不做任何事,即继续使用旧的session。changeSessionId表示session不会变化,仅仅修改sessionid的值,这其实就使用到了Servlet容器提供的防御会话固定攻击。newSession表示登录后创建一个新的session。

前面也说了SpringSecurity中默认使用的是migrateSession,也就是说当用户匿名访问的时候使用的是一个sessionid,当用户成功登录之后会使用另一个sessionid,这样就可以有效避免会话固定攻击。

无论使用何种方案,上述三种均能有效防御会话固定攻击。

文章小结

通过本文的学习,我们对SpringSecurity中固定会话攻击有了一个清晰的认识,同时也学会了如何防御固定会话攻击。