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

松哥最近在研究 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)。

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

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

  • 缓存攻击(Cache Side-Channel Attacks),通过获取对缓存的访问权而获取缓存内的一些敏感信息,例如攻击者获取云端主机物理主机的访问权而获取存储器的访问权。
  • 计时攻击(Timing attack),通过设备运算的用时来推断出所使用的运算操作,或者通过对比运算的时间推定数据位于哪个存储设备,或者利用通信的时间差进行数据窃取。
  • 基于功耗监控的旁路攻击,同一设备不同的硬件电路单元的运作功耗也是不一样的,因此一个程序运行时的功耗会随着程序使用哪一种硬件电路单元而变动,据此推断出数据输出位于哪一个硬件单元,进而窃取数据。
  • 电磁攻击(Electromagnetic attack),设备运算时会泄漏电磁辐射,经过得当分析的话可解析出这些泄漏的电磁辐射中包含的信息(比如文本、声音、图像等),这种攻击方式除了用于密码学攻击以外也被用于非密码学攻击等窃听行为,如TEMPEST 攻击。
  • 声学密码分析(Acoustic cryptanalysis),通过捕捉设备在运算时泄漏的声学信号捉取信息(与功率分析类似)。
  • 差别错误分析,隐密数据在程序运行发生错误并输出错误信息时被发现。
  • 数据残留(Data remanence),可使理应被删除的敏感数据被读取出来(例如冷启动攻击)。
  • 软件初始化错误攻击,现时较为少见,行锤攻击(Row hammer)是该类攻击方式的一个实例,在这种攻击实现中,被禁止访问的存储器位置旁边的存储器空间如果被频繁访问将会有状态保留丢失的风险。
  • 光学方式,即隐密数据被一些视觉光学仪器(如高清晰度相机、高清晰度摄影机等设备)捕捉。

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

而上面 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. 一个诡异的登录问题
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352