<返回更多

什么是计时攻击?Spring Boot 中该如何防御?

2020-09-02    
加入收藏

松哥最近在研究 Spring Security 源码,发现了很多好玩的代码,抽空写几篇文章和小伙伴们分享一下。

很多人吐槽 Spring Security 比 Shiro 重量级,这个重量级不是凭空来的,重量有重量的好处,就是它提供了更为强大的防护功能。

比如松哥最近看到的一段代码:

protected final UserDetails retrieveUser(String username,
  UsernamePasswordAuthenticationToken authentication)
  throws AuthenticationException {
 prepareTimingAttackProtection(); try {
  UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
  if (loadedUser == null) {
   throw new InternalAuthenticationServiceException(
     "UserDetailsService returned null, which is an interface contract violation");
  }  return loadedUser;
 } catch (UsernameNotFoundException ex) {
  mitigateAgainstTimingAttack(authentication);  throw ex;
 } catch (InternalAuthenticationServiceException ex) {
  throw ex;
 } catch (Exception ex) {
  throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
 }}

这段代码位于 DaoAuthenticationProvider 类中,为了方便大家理解,我来简单说下这段代码的上下文环境。

当用户提交用户名密码登录之后,Spring Security 需要根据用户提交的用户名去数据库中查询用户,这块如果大家不熟悉,可以参考松哥之前的文章:

  1. Spring Security 如何将用户数据存入数据库?
  2. Spring Security+Spring Data Jpa 强强联手,安全管理只有更简单!

查到用户对象之后,再去比对从数据库中查到的用户密码和用户提交的密码之间的差异。具体的比对工作,可以参考Spring Boot 中密码加密的两种姿势!一文。

而上面这段代码就是 Spring Security 根据用户登录时传入的用户名去数据库中查询用户,并将查到的用户返回。方法中还有一个 authentication 参数,这个参数里边保存了用户登录时传入的用户名/密码信息。

那么这段代码有什么神奇之处呢?

我们来一行一行分析。

源码梳理

1

首先方法一进来调用了 prepareTimingAttackProtection 方法,从方法名字上可以看出,这个是为计时攻击的防御做准备,那么什么又是计时攻击呢?别急,松哥一会来解释。我们先来吧流程走完。prepareTimingAttackProtection 方法的执行很简单,如下:

private void prepareTimingAttackProtection() {
 if (this.userNotFoundEncodedPassword == null) {
  this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
 }}

该方法就是将常量 USER_NOT_FOUND_PASSWORD 使用 passwordEncoder 编码之后(如果不了解 passwordEncoder,可以参考 Spring Boot 中密码加密的两种姿势!一文),将编码结果赋值给 userNotFoundEncodedPassword 变量。

2

接下来调用 loadUserByUsername 方法,根据登录用户传入的用户名去数据库中查询用户,如果查到了,就将查到的对象返回。

3

如果查询过程中抛出 UsernameNotFoundException 异常,按理说直接抛出异常,接下来的密码比对也不用做了,因为根据用户名都没查到用户,这次登录肯定是失败的,没有必要进行密码比对操作!

但是大家注意,在抛出异常之前调用了 mitigateAgainstTimingAttack 方法。这个方法从名字上来看,有缓解计时攻击的意思。

我们来看下该方法的执行流程:

private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
 if (authentication.getCredentials() != null) {
  String presentedPassword = authentication.getCredentials().toString();  this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
 }}

可以看到,这里首先获取到登录用户传入的密码即 presentedPassword,然后调用 passwordEncoder.matches 方法进行密码比对操作,本来该方法的第二个参数是数据库查询出来的用户密码,现在数据库中没有查到用户,所以第二个参数用 userNotFoundEncodedPassword 代替了,userNotFoundEncodedPassword 就是我们一开始调用 prepareTimingAttackProtection 方法时赋值的变量。这个密码比对,从一开始就注定了肯定会失败,那为什么还要比对呢?

计时攻击

这就引入了我们今天的主题--计时攻击。

计时攻击是旁路攻击的一种,在密码学中,旁道攻击又称侧信道攻击、边信道攻击(Side-channel attack)。

这种攻击方式并非利用加密算法的理论弱点,也不是暴力破解,而是从密码系统的物理实现中获取的信息。例如:时间信息、功率消耗、电磁泄露等额外的信息源,这些信息可被用于对系统的进一步破解。

旁路攻击有多种不同的分类:

所有的攻击类型都利用了加密/解密系统在进行加密/解密操作时算法逻辑没有被发现缺陷,但是通过物理效应提供了有用的额外信息(这也是称为“旁路”的缘由),而这些物理信息往往包含了密钥、密码、密文等隐密数据。

而上面 Spring Security 中的那段代码就是为了防止计时攻击。

具体是怎么做的呢?假设 Spring Security 从数据库中没有查到用户信息就直接抛出异常了,没有去执行 mitigateAgainstTimingAttack 方法,那么黑客经过大量的测试,再经过统计分析,就会发现有一些登录验证耗时明显少于其他登录,进而推断出登录验证时间较短的都是不存在的用户,而登录耗时较长的是数据库中存在的用户。

现在 Spring Security 中,通过执行 mitigateAgainstTimingAttack 方法,无论用户存在或者不存在,登录校验的耗时不会有明显差别,这样就避免了计时攻击。

可能有小伙伴会说,passwordEncoder.matches 方法执行能耗费多少时间呀?这要看你怎么计时了,时间单位越小,差异就越明显:毫秒(ms)、微秒(µs)、奈秒(ns)、皮秒(ps)、飛秒(fs)、阿秒(as)、仄秒(zs)。

另外,Spring Security 为了安全,passwordEncoder 中引入了一个概念叫做自适应单向函数,这种函数故意执行的很慢并且消耗大量系统资源,所以非常有必要进行计时攻击防御。

关于自适应单向函数,这是另外一个故事了,松哥抽空再和小伙伴们聊~

好啦,今天就先和小伙伴们聊这么多,小伙伴们决定有收获的话,记得点个在看鼓励下松哥哦~

不知不觉,Spring Security 系列已经连载了 49 篇啦,感兴趣的小伙伴也可以看看本系列其他文章哦:

  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 自带防火墙!你都不知道自己的系统有多安全!
  16. 什么是会话固定攻击?Spring Boot 中要如何防御会话固定攻击?
  17. 集群化部署,Spring Security 要如何处理 session 共享?
  18. 松哥手把手教你在 SpringBoot 中防御 CSRF 攻击!so easy!
  19. 要学就学透彻!Spring Security 中 CSRF 防御源码解析
  20. Spring Boot 中密码加密的两种姿势!
  21. Spring Security 要怎么学?为什么一定要成体系的学习?
  22. Spring Security 两种资源放行策略,千万别用错了!
  23. 松哥手把手教你入门 Spring Boot + CAS 单点登录
  24. Spring Boot 实现单点登录的第三种方案!
  25. Spring Boot+CAS 单点登录,如何对接数据库?
  26. Spring Boot+CAS 默认登录页面太丑了,怎么办?
  27. 用 Swagger 测试接口,怎么在请求头中携带 Token?
  28. Spring Boot 中三种跨域场景总结
  29. Spring Boot 中如何实现 HTTP 认证?
  30. Spring Security 中的四种权限控制方式
  31. Spring Security 多种加密方案共存,老破旧系统整合利器!
  32. 神奇!自己 new 出来的对象一样也可以被 Spring 容器管理!
  33. Spring Security 配置中的 and 到底该怎么理解?
  34. 一文搞定 Spring Security 异常处理机制!
  35. 写了这么多年代码,这样的登录方式还是头一回见!
  36. Spring Security 竟然可以同时存在多个过滤器链?
  37. Spring Security 可以同时对接多个用户表?
  38. 在 Spring Security 中,我就想从子线程获取用户登录信息,怎么办?
  39. 深入理解 FilterChainProxy【源码篇】
  40. 深入理解 SecurityConfigurer 【源码篇】
  41. 深入理解 HttpSecurity【源码篇】
  42. 深入理解 AuthenticationManagerBuilder 【源码篇】
  43. 花式玩 Spring Security ,这样的用户定义方式你可能没见过!
  44. 深入理解 WebSecurityConfigurerAdapter【源码篇】
  45. 盘点 Spring Security 框架中的八大经典设计模式
  46. Spring Security 初始化流程梳理
  47. 为什么你使用的 Spring Security OAuth 过期了?松哥来和大家捋一捋!
  48. 一个诡异的登录问题
声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>