本記事では、特定のCTFウェブチャレンジ「easy_web」を題材に、ファイルインクルードとコマンド実行の脆弱性を悪用した攻略手法を解説します。課題のURLは、http://xxx/index.php?img=...&cmd= のような形式でした。
イメージパラメータ (img) の解析
まず、img パラメータに注目します。このパラメータはエンコードされた文字列を含んでおり、デコードするとファイル名に変換されることが判明しました。デコードロジックは以下の通りです。
$encoded_input = $_GET['img'];
$decoded_filename = hex2bin(base64_decode(base64_decode($encoded_input)));
// 例: "TXpVek5UTTFNbVUzTURabE5qYz0" をデコードすると "555.png"
// "TmprMlpUWTBOalUzT0RKbE56QTJPRGN3" をデコードすると "index.php" になります。
このデコードされたファイル名は、以下のPHPコードによって処理されます。
$file_to_access = hex2bin(base64_decode(base64_decode($_GET['img'])));
// 英数字とピリオド以外の文字を除去
$file_to_access = preg_replace("/[^a-zA-Z0-9.]+/", "", $file_to_access);
// "flag" が含まれるファイル名の場合、アクセスを拒否
if (preg_match("/flag/i", $file_to_access)) {
echo '<img src ="./ctf3.jpeg">';
die("アクセス禁止:フラグはここにはありません");
} else {
// ファイルの内容をBase64エンコードして表示
$file_content_base64 = base64_encode(file_get_contents($file_to_access));
echo "<img src='data:image/gif;base64," . $file_content_base64 . "'></img>";
echo "<br>";
}
このコードから、以下のことがわかります。
imgパラメータは、二重のBase64デコードとHexデコードを経てファイル名に変換されます。- ファイル名から英数字とピリオド以外の文字が除去されます (
preg_replace)。 - デコードされたファイル名に "flag" (大文字小文字を問わず) が含まれている場合、直接ファイルの内容を読み取ることができません。これは、フラグファイルへの直接アクセスを防ぐためのフィルターです。
この挙動から、ファイルインクルードの脆弱性があると考えられます。例えば、index.php の内容を取得するために、index.php をエンコードした文字列を img パラメータに渡すことで、そのBase64エンコードされたソースコードを取得できます。
コマンドパラメータ (cmd) の解析
次に、cmd パラメータに注目します。このパラメータは、PHPコード内でバッククォート (` `) を使って実行されるため、コマンドインジェクションの脆弱性が存在します。
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`; // $cmd をバッククォートで実行し、結果を出力
} else {
echo ("md5 is funny ~");
}
しかし、コマンドは以下の2つの条件をクリアした場合のみ実行されます。
- MD5ハッシュ衝突のバイパス:
$_POST['a']と$_POST['b']が異なる文字列であるにもかかわらず、それらのMD5ハッシュが厳密に一致する (`md5($_POST['a']) === md5($_POST['b'])`) 必要があります。これは、異なる入力から同一のMD5ハッシュを生成する「MD5衝突」を利用します。 - コマンドブラックリストのバイパス:
cmdパラメータには非常に厳格なブラックリストが適用されており、多くのコマンドや特殊文字がフィルタリングされます。
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[\]{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~"); // フィルタリングされた場合
echo "<br>";
}
この正規表現は、ls, cat, sh といった基本的なコマンドだけでなく、多くの特殊文字 (', ", バッククォート, ;, |, & (数字が続かない場合), $, \, \\, 改行コード、タブ、スペース文字、括弧など) も厳しくフィルターしています。
攻略手順
1. MD5ハッシュ衝突の解決
MD5ハッシュ衝突の条件 (`(string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])`) を満たすため、既知のMD5衝突ペアを利用します。これらのペアは、異なる入力でありながら同一のMD5ハッシュを生成します。
例えば、POSTリクエストで a=[衝突ペアのバイナリデータ1]&b=[衝突ペアのバイナリデータ2] を送信することで、条件をクリアできます。
2. コマンドブラックリストのバイパス
MD5条件をクリアし、コマンドを実行できるようになったら、次にコマンドブラックリストを回避してフラグファイルを読み取る方法を見つけます。
上記の正規表現を確認すると、多くのファイル読み取りコマンド(cat, more, less, head, tail など)がブロックされています。また、\ (バックスラッシュ) や \\ (二重バックスラッシュ) もフィルタリングされているため、バックスラッシュを用いたコマンドの難読化は機能しません。
しかし、sort コマンドはブラックリストに含まれていません。sort コマンドはテキストファイルの内容をソートして表示する機能があり、これをファイル読み取りに利用できます。
まずは、ルートディレクトリの内容を確認してフラグファイルの名前を特定します。ls -F / や dir / のようなコマンドがブラックリストに直接記載されていない場合、これらを使用してファイル一覧を取得できる可能性があります。
例えば、dir / を実行して、フラグファイルが /flag_xxxxxxxx.txt のような名前であることがわかったとします。次に、このファイルを sort コマンドで読み取ります。
# フラグファイル名を特定する例 (cmdパラメータにURLエンコードして指定)
cmd=dir%20/
# 特定したフラグファイルの内容を読み取る例
cmd=sort%20/path/to/flag_file_name
これらのペイロードを、MD5衝突が成功するPOSTリクエストと組み合わせて送信することで、フラグの取得に成功します。