前言
jumpserver 前不久出了一个密码重置漏洞 CVE-2023-42820
在当天我就复现了这个漏洞 这个随机数的案例非常有趣 这个漏洞出现在了一个很难想到的位置 是一个由第三方依赖库引起的问题
漏洞分析
验证码库中的一个问题
分析一下修复后的版本差异 发现了一个有趣的commit
生成随机字符串的时候增加重置了随机数种子的步骤
https://github.com/jumpserver/jumpserver/commit/ce645b1710c5821119f313e1b3d801470565aac0
我原本以为这个漏洞可能是 攻击者可以收集到足够随机数导致伪随机数预测的问题
但是我发现了同commit账号下的这个给第三方验证码库django-simple-captcha的PR
https://github.com/mbi/django-simple-captcha/pull/221
在这个pr里同样也有一处设置种子的步骤
继续审计 发现了这个验证码库的奇葩逻辑
下面是我画的一张django-simple-captcha 库的流程图
完整的的验证码流程有3个请求
第一个请求 生成验证码
服务器会生成随机challenge 和一个随机字符串key 储存到数据库 把包含key的验证码url返回
第二个请求 访问验证码url 获取 验证码图片/语音
服务器会将key 设置为随机数seed 之后根据key从数据库查找challenge 根据配置文件随机生成噪点和干扰线 渲染成验证码图片
第三个请求 验证验证码
之后服务器会根据key从数据库查找challenge 进行eval后与提交的答案比较 返回比较结果
在这里很容易看到一个不对劲的步骤
这个库居然把作为随机数seed 的key直接返回给用户 作为验证码的索引
:::info
/captcha/image/{key}/
:::
也就是说访问一次验证码图片 当前进程的全局seed 就会被设置为key 同时我们也拿到了这个key
现在 我们就可以利用这个key预测之后生成的随机数
重置用户密码
现在我们可以控制随机数的生成 那么怎么利用这个特性呢?
jumpserver的密码重置功能默认是开启的
jumpserver重置验证码时会向邮箱发送包含随机字符串验证码的一封邮件 (因为代码上的问题 没有正确配置邮件服务器时也能正常生成验证码)
在这里我们可以通过拿到的key预测这个验证码
构造EXP
在利用过程中 我们需要知道当设置种子后又生成了多少个随机数
分析验证码生成部分的代码 找到验证码生成的过程
把随机数生成的部分抽出 之后放入之后的EXP中
这里有几点需要注意
在验证码字符的旋转过程 会导致随机数生成的次数变化 有一个随机数生成循环会随验证码的长度而变化
因为jumpserver使用的验证码类型为数学计算验证码 所以生成的验证码位数在 4-5位
另外出现在CAPTCHA_PUNCTUATION内的验证码字符不会当做单独的一个字符
也就是说如果验证码为 5-1=时 5- 会被认为是一个字符
所以 实际上循环范围应该是3-5
之后按照jumpserver中的实现生成验证码字符串
自动化EXP
手工利用这个漏洞还是有点麻烦 所以我们要实现一个自动化利用的exp
EXP暂时不公开
这里就交给读者自己实现EXP了 内容涉及CSRF 和 表单处理的内容 自己实现一遍应该会学到很多
下面给出一点点提示
自动化利用我们得绕过图片验证码
在重置密码的时候需要完成一个图片验证码
我们通过之前的漏洞来固定seed 时生成的图片验证码也可预测
(这一步中的随机数生成次数与之前的有些差异 通过调试分析代码可以简单解决)
最终 我实现了一个高成功率的自动化利用脚本 可以在3s内完成 包括图片验证码绕过的 自动密码重置脚本
详细步骤:
- 进入重置密码流程
- 访问 /core/auth/password/forget/previewing/ 生成图片验证码url1
- 多次访问验证码url1 让seed污染大部分的 workers 提高成功概率 让seed固定
- 访问 /core/auth/password/forget/previewing/ 生成新图片验证码url2
- 根据seed 预测图片验证码 解决验证码
- 多次访问验证码url1 让seed污染大部分的 workers 提高成功概率 让seed固定
- 发送邮箱验证码
- 根据seed 预测验证码 尝试验证 获取重置链接
- 重置密码
漏洞修复
django-simple-captcha 发布了 0.5.19版本 通过重置种子 修复了这个问题
https://github.com/mbi/django-simple-captcha/blob/master/CHANGES
jumpserver >= v2.28.19, >= v3.6.5 临时修复了这个问题
https://github.com/jumpserver/jumpserver/security/advisories/GHSA-7prv-g565-82qp
django-simple-captcha vulnerability