Jumpserver安全一窥:Sep系列漏洞深度解析

Jumpserver是中国国内公司开发的一个开源项目,在开源堡垒机领域一家独大。在2023年9月官方集中修复了一系列安全问题,其中涉及到如下安全漏洞:

  • JumpServer 重置密码验证码可被计算推演的漏洞,CVE编号为CVE-2023-42820

  • JumpServer 重置密码验证码可被暴力破解的漏洞,CVE编号为CVE-2023-43650

  • JumpServer 认证用户跨目录任意文件读取漏洞,CVE编号为CVE-2023-42819

  • JumpServer 全局开启公钥认证后,用户可以使用公钥创建访问Token的漏洞,CVE编号为CVE-2023-43652

  • JumpServer 认证用户开启MFA后,可以使用SSH公钥认证的逻辑缺陷漏洞,CVE编号为CVE-2023-42818

  • JumpServer 认证用户连接MongoDB数据库,可执行任意系统命令的远程执行漏洞,CVE编号为CVE-2023-43651

  • Jumpserver Session录像任意下载漏洞,CVE编号为CVE-2023-42442

虽然涉及到数个漏洞,也不乏高危严重问题,但对于一个商业化运营的国产开源项目来说,官方透明公开的态度还是值得点赞的。另外官方写的漏洞通告也很详细,那么我们就根据漏洞通告的内容依旧补丁来逐一解析一下这些漏洞的详情吧。

CVE-2023-42820:伪随机数种子泄漏造成用户接管漏洞

这个漏洞是这一系列漏洞里最严重的问题了,未授权的攻击者可以利用该漏洞推算出没有开启多因子验证(MFA)的账号的“重置密码Token”,进而修改该账号的密码。

这句话很绕,简单来说就是用户点击忘记密码时系统会生成一个随机字符串作为Token并发送到用户邮箱,但由于一个有趣的安全问题,导致这个随机字符串Token可以被推算出来,造成漏洞。

漏洞的核心是随机数种子泄露导致的,而这段逻辑并不是来自于Jumpserver,而是其依赖的一个第三方项目django-simple-captcha。

这个django-simple-captcha库和Django reCAPTCHA可以说是Django生态中唯二常用的验证码生成库了,但因为中国用户无法使用reCAPTCHA,所以它基本就是国内的唯一之选。包括我自己的博客也在使用其作为图形验证码依赖:

64ff458209b16db1c8663df6e0dad104.png

今年我写了一篇文章《用ChatGPT帮我检查广告评论》,起因就是当时遇到了大量垃圾评论,我怀疑是我的验证码被绕过了,但由于自己没有去看django-simple-captcha的代码,当时只猜测是攻击者“识别”了验证码内容,但现在看来也可能是由于随机数种子漏洞导致的。

对,使用django-simple-captcha后,你的进程随机数种子将泄露给用户。

我们来看看django-simple-captcha的工作流程。首先,开发者需要为需要验证码的Django表单(forms)增加一个CaptchaField字段:

class UserForm(forms.Form):
    username = forms.CharField(...)
    captcha = CaptchaField(widget=CustomCaptchaTextInput, label=_('Captcha'))
    ...

渲染表单的时候,CaptchaField内部会生成随机的验证字符串(challenge)和答案(response)。challenge可以是传统的4个字符的文本,也可以是我博客或Jumpserver中使用的四则运算,这个通过配置来定义。

然后,challenge和response会按照如下算法生成一个唯一的hashkey:

randrange = random.SystemRandom().randrange
key_ = (
    smart_text(randrange(0, MAX_RANDOM_KEY))
    + smart_text(time.time())
    + smart_text(self.challenge, errors="ignore")
    + smart_text(self.response, errors="ignore")
).encode("utf8")
self.hashkey = hashlib.sha1(key_).hexdigest()

这个hashkey将返回给用户,用户通过这个hashkey和下面的captcha_image视图生成验证码图片。

在页面中展示验证码时,django-simple-captcha提供了一个captcha_image视图,开发者需要将其加入url routers中:

f302d549f67a420661d2096fd283573d.png

captcha_image视图只接收一个参数,即为用户传入的key

def captcha_image(request, key, scale=1):
    if scale == 2 and not settings.CAPTCHA_2X_IMAGE:
        raise Http404
    try:
        store = CaptchaStore.objects.get(hashkey=key)
    except CaptchaStore.DoesNotExist:
        # HTTP 410 Gone status so that crawlers don't index these expired urls.
        return HttpResponse(status=410)

    random.seed(key)  # Do not generate different images for the same key
    #...

这里的key就是前面计算的验证码hashkey,作用是通过它查找到数据库中对应的验证码。如果验证码存在,则将key传给random.seed函数并执行后续操作,后面的代码多次调用了伪随机数相关函数,用于给验证码图片生成旋转、噪点。

问题就出在这里了,开发者使用random.seed(key)的目的是将后续操作中的随机因素固定,保证key相同的情况下生成的验证码图片完全相同。但因为key是一个用户已知的值,那么用户就可以用于预测后续生成的所有的伪随机数。

这个过程中有两点值得注意:

  • 我们并不可以“篡改”或“控制”随机数种子,而只可以“查看”随机数种子,因为key的生成过程是不可控的

  • 在调用random.seed(key)以前验证码就已经生成好了,这里设置随机数种子的目的只是为了让验证码图片中的旋转和噪点保持一致

这两点并不影响我们的漏洞利用,因为攻击者已知了此时的随机数种子,就可以预测后续所有的伪随机数生成函数的结果——不仅包括django-simple-captcha后续生成的验证码,也包括jumpserver中其他使用了伪随机数的场景。

这个漏洞的另一个核心点就是Jumpserver内部使用伪随机数来生成找回密码时的Token。虽然Python官方文档中明确申明不要以安全为目的使用random模块,并且在提供了secrets模块作为替代选项:

Warning The pseudo-random generators of this module should not be used for security purposes. For security or cryptographic uses, see the secrets module.

但是不幸的是Jumpserver仍然使用的random模块来生成Token:

54be3d578780a906b02e120760c35c67.png

那么答案就呼之欲出了,使用第三方模块django-simple-captcha提供的方式获取固定在进程中的伪随机数种子,然后马上请求UserResetPasswordSendCodeApi生成找回密码使用的code,通过预测这个code修改目标用户密码。

漏洞利用过程没有太复杂,只是需要注意几个点:

  • Jumpserver以多进程负债均衡的方式运行,而每次请求只会固定当前进程的伪随机数种子,所以我们需要尝试发送数个相同的请求将所有进程中的种子固定

  • 进入找回密码步骤本身也需要输入验证码,如果编写自动化程序,也可以尝试使用这个方法来预测验证码

为了让读者更加理解漏洞的本质,我编写了半自动的脚本来简化步骤,这部分内容已上线Vulhub:https://github.com/vulhub/vulhub/tree/master/jumpserver/CVE-2023-42820,可以参阅复现。

这里再说下修复方法,我推荐如下三种情况:

  • 如果不关注原random_string函数函数中的功能,可以简单将其替换成Django内置的django.utils.crypto.get_random_string

  • 如果仍想保留原random_string函数函数中的功能,

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值