漏洞分析 | GitLab任意用户密码重置漏洞(CVE-2023-7028)

GitLab是一个基于Web的Git仓库管理工具。它提供了一套完整的代码管理工具,包括版本控制、代码审查、问题跟踪、持续集成等功能。通过GitLab,团队可以在一个统一的平台上进行代码的协作和管理。

2024年1月11日,Gitlab官方披露CVE-2023-7028 GitLab 任意用户密码重置漏洞,官方评级严重。攻击者可利用忘记密码功能,构造恶意请求获取密码重置链接从而重置密码。

官方已发布安全更新,建议升级至最新版本,若无法升级,建议利用安全组功能设置Gitlab仅对可信地址开放。

▌漏洞复现

1. 靶场环境搭建

1)使用docker执行以下命令。

2)配置SMTP邮件发送功能。

进入docker容器,修改/etc/gitlab/gitlab.rb文件,在文件末尾添加以下内容。注意替换成自己的邮箱配置。

重新加载配置文件。

gitlab-ctl reconfigure

3)测试邮箱配置是否正确。

检查自己的邮箱,查看是否收到邮件。

4)Gitlab 设置默认root用户的账号密码。

5)Gitlab设置默认root用户的邮箱。

在浏览器上使用上面设置的账号密码登陆。

进入profile设置页面,设置root用户的邮箱地址,然后拉到页面下方,点击Update profile settings,输入密码保存。

这里利用burpsuite生成域名并组成的地址:root@wwwm3saic0gloubgkfl2p989208uwkk9.oastify.com(注意不能使用admin用户,否则会收不到邮件)。

复制邮件里的链接,并填入到浏览器中进行访问,即可确认邮件地址。

▌漏洞利用

利用工具:

  • 浏览器
  • BurpSuite

具体步骤:

1)使用BurpSuite开启抓包,在Gitlab登陆页面点击Forget your password?访问密码重置页面。

2)BurpSuite开启拦截,页面中输入某用户的邮件地址:root@wwwm3saic0gloubgkfl2p989208uwkk9.oastify.com,并点击Reset password。

3)BurpSuite拦截到数据包后,解码请求体。

4)使用Dnslog功能生成一个临时邮件地址:hacker@086qfwmmo4sp0ynkwjx61dkde4kz8pwe.oastify.com,并将数据包修改为以下内容:

5)将修改后的内容进行url编码并发送。

6)观察dnslog日志,可以看到,root@wwwm3saic0gloubgkfl2p989208uwkk9.oastify.com和hacker@086qfwmmo4sp0ynkwjx61dkde4kz8pwe.oastify.com同时收到了这封邮件。

root用户收到的邮件:

hacker收到的邮件:

7)两者收到的密码重置链接是一样的,此时在浏览器上打开该链接,输入新的密码。

点击Change your password,页面跳转到首页并提示修改成功。

▌漏洞分析

通过流量抓包,我们发现当填完邮箱地址并点击Reset password按钮时,浏览器会使用POST方法调用/users/password接口。

并且根据GitLab的官方通告,我们可以了解到该功能是在16.1.0版本中引入的。所以我们可以通过以上信息找到该接口的源码及具体实现。首先,我们在gitlab仓库中将分支切换到v16.1.0-ee,并且搜索/users/password,找到该接口的路由规则。

通过路由规则,我们找到了app/controllers/passwords_controller.rb,但在controller中我们并没有找到create方法的具体实现,但是我们找到了以下关键信息。

上面这些关键信息中,我们发现PasswordsController继承自Devise::PasswordsController。Devise 是一个用于 Ruby on Rails 应用程序的开源认证(authentication)和授权(authorization)库。它简化了用户身份验证和管理、注册和会话管理等方面的开发任务。

Gemfile.lock中记录了依赖Devise的版本号。

我们在Github的Devise仓库中找到了PasswordsController模块中create方法的实现。

在create方法中有一行这样的代码。

从这段代码中调用的方法名称中就能看出来就是发送设置密码指令的意思,具体就是发送一封包含重置密码链接都邮件的功能。而resource_params就是我们的请求参数,即邮件地址。我们分别在Devise库和Gitlab源码中找到了send_reset_password_instructions方法的实现。

Devise库lib/devise/models/recoverable.rb中:

Gitlab源码app/models/concerns/recoverable_by_any_email.rb中:

从代码注释中我们可以看到,Gitlab覆盖了Devise库中的send_reset_password_instructions方法。

而且在Gitlab的recoverable_by_any_email.rb中其实存在两个send_reset_password_instructions方法,使用class_methods do … end包裹起来的方法属于模块的类方法,它通过类调用(即上面所述的resource_class),这个方法主要用于用户请求密码重置时处理发送密码重置指令的逻辑。

在该方法中通过by_email_with_errors方法获取用户实例,然后通过用户实例调用了在类外部同名的实例方法。

而此次漏洞产生的原因就在by_email_with_errors方法中,该方法用于检查邮箱对应的用户是否存在,并检查该用户是否确认该邮箱地址,若确认则会返回用户实例,该方法中调用了find_by_any_email(email, confirmed: true)去获取email参数对应的用户,我们在app/models/user.rb文件中发现了该函数的定义:

可以看出find_by_any_email方法调用了by_any_email方法,而by_any_email方法支持传入email数组。所以在传入find_by_any_email方法的email变量中传入一个数组,并且这个数组中包含一个经过验证的邮箱,该方法就能返回一个有效的用户记录。

最后通过recoverable.send_reset_password_instructions(to: email)发送密码重置邮件。

▌塞讯验证规则

针对该漏洞的攻击模拟已经加入到塞讯安全度量验证平台中,您可以在塞讯安全度量验证平台中搜索关键词“CVE-2023-7028”“GitLab”获取相关攻击模拟验证动作,从而验证您的安全防御体系是否能够有效应对该漏洞,平台以业界独有方式确保您的验证过程安全无害。

参考来源:

https://about.gitlab.com/releases/2024/01/11/critical-security-release-gitlab-16-7-2-released/
https://gitlab.com/gitlab-org/gitlab
https://github.com/heartcombo/devise