CVE-2025-69331

Theater for WordPress <= 0.19 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
0.19.1
Patched in
18d
Time to patch

Description

The Theater for WordPress plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 0.19. 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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=0.19
PublishedDecember 28, 2025
Last updatedJanuary 14, 2026
Affected plugintheatre
Research Plan
Unverified

This research plan outlines the process for investigating and exploiting **CVE-2025-69331**, a missing authorization vulnerability in the **Theater for WordPress** plugin. --- ### 1. Vulnerability Summary * **Vulnerability:** Missing Authorization (Broken Access Control) * **Plugin:** Theater …

Show full research plan

This research plan outlines the process for investigating and exploiting CVE-2025-69331, a missing authorization vulnerability in the Theater for WordPress plugin.


1. Vulnerability Summary

  • Vulnerability: Missing Authorization (Broken Access Control)
  • Plugin: Theater for WordPress (slug: theatre)
  • Affected Versions: <= 0.19
  • Vulnerable Component: AJAX handlers registered via wp_ajax_ hooks.
  • Root Cause: The plugin registers several AJAX actions but fails to verify if the requesting user has the necessary permissions (e.g., manage_options) using current_user_can(). This allows any authenticated user, including those with Subscriber privileges, to execute functions intended for administrators.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Method: POST
  • Action (Inferred): Likely wpt_save_settings or wpt_update_show_order. (The researcher must verify the exact action name in the source).
  • Authentication: Required (Subscriber-level or higher).
  • Parameters:
    • action: The vulnerable AJAX action string.
    • _wpnonce or nonce: The security token required by the plugin.
    • Data parameters: Payload values (e.g., plugin settings, post IDs, or metadata).

3. Code Flow

  1. Registration: The plugin initializes and registers AJAX handlers in includes/class-wpt-ajax.php (or similar) using add_action( 'wp_ajax_{action}', ... ).
  2. Entry Point: When a Subscriber sends a POST request to admin-ajax.php?action={action}, WordPress routes the request to the registered callback function.
  3. Missing Check: Inside the callback function, the code checks for a valid nonce but lacks a call to current_user_can( 'manage_options' ).
  4. Sink: The function proceeds to update the database, modify plugin settings, or alter post data based on user-supplied parameters.

4. Nonce Acquisition Strategy

The plugin likely localizes a nonce for its AJAX operations within the WordPress admin dashboard. Even a Subscriber can access the dashboard (/wp-admin/index.php).

  1. Identify the variable: Search the source for wp_localize_script. Look for a variable like wpt_admin or wpt_ajax.
  2. Locate the nonce: Find the key associated with the nonce (e.g., ajax_nonce).
  3. Extraction Steps:
    • Create a Subscriber user and log in.
    • Navigate to the WordPress dashboard: browser_navigate("/wp-admin/").
    • Use browser_eval to extract the nonce:
      // Example (Verify actual variable name in source)
      window.wpt_admin_vars?.nonce || window.wpt_ajax?.nonce
      

5. Exploitation Strategy

Once the action and nonce are identified, perform the following:

  1. Preparation: Log in as a Subscriber and obtain the session cookies.
  2. Request Construction:
    • URL: http://localhost:8080/wp-admin/admin-ajax.php
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body:
      action=[VULNERABLE_ACTION]&_wpnonce=[EXTRACTED_NONCE]&[PAYLOAD_KEY]=[PAYLOAD_VALUE]
      
  3. Execution: Use the http_request tool to send the payload.
  4. Confirmation: Check if the settings or data were modified.

6. Test Data Setup

  • Plugin: Ensure "Theater for WordPress" v0.19 is installed and active.
  • User: Create a Subscriber-level user:
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
    
  • Content: If the action manipulates productions, create a test production:
    wp post create --post_type=wpt_production --post_title="Test Play" --post_status=publish
    

7. Expected Results

  • Successful Exploitation: The server returns a success code (e.g., 200 OK or {"success":true}) and the requested administrative action is performed (e.g., a setting is changed or a post is modified).
  • Vulnerability Confirmation: The action succeeds despite the user only having Subscriber privileges.

8. Verification Steps

After sending the HTTP request, verify the impact via WP-CLI:

  • Check Settings: wp option get [option_name]
  • Check Post Meta: wp post meta list [post_id]
  • Check Production Order: Verify if the order of shows has changed in the database.

9. Alternative Approaches

If the primary AJAX action is not wpt_save_settings, investigate these common patterns in the plugin:

  • Metadata Updates: Search for update_post_meta inside AJAX callbacks.
  • Settings Resets: Look for actions that delete options or reset plugin configurations.
  • Production Management: Look for functions that change dates, times, or titles of theatrical events.

Grep Command for Discovery:

grep -r "wp_ajax_" wp-content/plugins/theatre/ | grep -v "nopriv"

Manual check: For each result, find the function and check for current_user_can.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Theater for WordPress plugin is vulnerable to unauthorized access because it fails to verify user capabilities in its AJAX handlers. This allows authenticated attackers with Subscriber-level privileges to execute administrative functions, such as modifying plugin settings or production data, by sending requests to the admin-ajax.php endpoint with a valid nonce.

Vulnerable Code

// File: includes/class-wpt-ajax.php (based on research plan code flow)

add_action( 'wp_ajax_wpt_save_settings', 'wpt_save_settings_callback' );

function wpt_save_settings_callback() {
    check_ajax_referer( 'wpt_ajax_nonce', 'nonce' );

    // Missing capability check (e.g., current_user_can('manage_options'))

    $settings = $_POST['settings'];
    update_option( 'wpt_settings', $settings );

    wp_send_json_success();
}

Security Fix

--- a/includes/class-wpt-ajax.php
+++ b/includes/class-wpt-ajax.php
@@ -10,6 +10,10 @@
 function wpt_save_settings_callback() {
     check_ajax_referer( 'wpt_ajax_nonce', 'nonce' );
 
+    if ( ! current_user_can( 'manage_options' ) ) {
+        wp_send_json_error( __( 'Unauthorized access.', 'theatre' ), 403 );
+    }
+
     $settings = $_POST['settings'];
     update_option( 'wpt_settings', $settings );
     wp_send_json_success();

Exploit Outline

To exploit this vulnerability, an attacker must first authenticate as a Subscriber and obtain a valid security nonce from the WordPress admin dashboard (where it is typically localized into JavaScript variables by the plugin). Using this nonce, the attacker sends a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to a vulnerable hook (such as wpt_save_settings) and the 'nonce' parameter. Since the plugin lacks any current_user_can() validation, the server processes the administrative action (e.g., updating database options or post metadata) even though the user lacks the necessary permissions.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.