CVE-2026-34903

Ocean Extra <= 2.5.3 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
2.5.4
Patched in
9d
Time to patch

Description

The Ocean Extra plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 2.5.3. 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<=2.5.3
PublishedApril 7, 2026
Last updatedApril 15, 2026
Affected pluginocean-extra

What Changed in the Fix

Changes introduced in v2.5.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2026-34903 - Ocean Extra Missing Authorization ## 1. Vulnerability Summary The **Ocean Extra** plugin (<= 2.5.3) contains a missing authorization vulnerability in its Onboarding/Demo Import logic. Specifically, the class `OE_Onboarding_Site_Templates_Import_Data` registers seve…

Show full research plan

Research Plan: CVE-2026-34903 - Ocean Extra Missing Authorization

1. Vulnerability Summary

The Ocean Extra plugin (<= 2.5.3) contains a missing authorization vulnerability in its Onboarding/Demo Import logic. Specifically, the class OE_Onboarding_Site_Templates_Import_Data registers several AJAX actions that lack capability checks (current_user_can). While these actions verify a WordPress nonce for CSRF protection, the nonce is available to any authenticated user (including Subscribers) in the WordPress admin dashboard. This allows a Subscriber-level attacker to trigger administrative actions such as downloading template files, deleting specific posts, and importing demo data.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: oceanwp_onboarding_import_data (registered in includes/onboarding/class/import-data.php)
  • Vulnerable Parameters:
    • action: oceanwp_onboarding_import_data
    • nonce: The owp-onboarding nonce.
    • importType: content, customizer, widgets, or form.
  • Authentication: Authenticated (Subscriber and above).
  • Preconditions: The plugin must be active, and a valid nonce must be retrieved from the admin area.

3. Code Flow

  1. Nonce Generation: OE_Onboarding_Manager::localize_script() (in includes/onboarding/start.php) creates a nonce:
    'nonce' => wp_create_nonce( 'owp-onboarding' ),
    
  2. Nonce Exposure: This nonce is localized to the script oe-onboarding via wp_localize_script under the object name oeOnboardingLoc. The scripts are enqueued via the admin_enqueue_scripts hook, which runs for any user logged into the WordPress dashboard.
  3. AJAX Registration: In includes/onboarding/class/import-data.php, the __construct() method registers the AJAX handlers:
    add_action( 'wp_ajax_oceanwp_onboarding_import_data', array( $this, 'onboarding_import_data' ) );
    
  4. Vulnerable Handler: The function onboarding_import_data() performs a nonce check but fails to perform an authorization check:
    public function onboarding_import_data() {
        if ( !isset($_POST['nonce']) || !wp_verify_nonce( sanitize_key($_POST['nonce']), 'owp-onboarding' ) ) {
            wp_send_json_error([...]);
        }
        // ... (Missing current_user_can('manage_options')) ...
        $import_type = isset($_POST['importType']) ? sanitize_text_field($_POST['importType']) : '';
        // ...
        switch ($import_type) {
            case 'content':
                $result = $this->import_content($template);
                break;
            // ...
        }
    }
    
  5. Sink: import_content($template) (in the same file) proceeds to delete standard WordPress posts:
    $sample_page      = get_page_by_path( 'sample-page', OBJECT, 'page' );
    $hello_world_post = get_page_by_path( 'hello-world', OBJECT, 'post' );
    if ( ! is_null( $sample_page ) ) {
        wp_delete_post( $sample_page->ID, true );
    }
    

4. Nonce Acquisition Strategy

The nonce is localized in the WordPress admin dashboard for all authenticated users.

  1. Navigation: Navigate to /wp-admin/index.php as a Subscriber.
  2. Extraction: Use browser_eval to extract the nonce from the global JavaScript object oeOnboardingLoc.
    • JS Command: window.oeOnboardingLoc?.nonce

5. Exploitation Strategy

  1. Auth: Log in as a Subscriber-level user.
  2. Setup: The handler requires the option ocean_installing_template_data to be set. Since the focus is demonstrating the lack of authorization on the AJAX handler, we will simulate the "Template Selection" step by setting this option via CLI, or verify if the handler reaches the wp_verify_nonce check.
  3. Request: Send a POST request to admin-ajax.php.
    • URL: http://localhost:8888/wp-admin/admin-ajax.php
    • Method: POST
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body:
      action=oceanwp_onboarding_import_data&nonce=[NONCE]&importType=content
      
  4. Action: The execution will trigger the import_content method. This method automatically attempts to delete the default "Hello World" post and "Sample Page".

6. Test Data Setup

  1. User: Create a user with the subscriber role.
  2. Target Content: Ensure a post with slug hello-world exists (standard in default WP).
  3. State Setup: Set the required option for the importer to proceed:
    wp option update ocean_installing_template_data '{"slug":"coach","content":true}' --format=json
    
    (Note: This simulates the attacker or a previous admin selecting a demo template to import).

7. Expected Results

  • The AJAX request should return a JSON response.
  • If the import fails due to connectivity to demos.oceanwp.org, the import_content logic will still execute the wp_delete_post calls before the external fetch, confirming unauthorized modification of site content.
  • The "Hello World" post will be permanently deleted from the database.

8. Verification Steps

  1. Check Post Existence:
    wp post list --name="hello-world" --post_type=post
    
    If the post is no longer listed, the unauthorized action was successful.
  2. Verify Capability Check: Attempt the same request as an unauthenticated user; it should fail the nonce check. Attempt as a Subscriber; it should pass the nonce check and proceed to the logic.

9. Alternative Approaches

If importType=content fails or is blocked by environmental factors:

  • Action: download_selected_template_data
  • Body: action=download_template_data&security=[NONCE]
  • Effect: This calls download_template_files, which makes remote requests (SSRF-lite) and updates the option ocean_downloaded_demo_path.
  • Verification: wp option get ocean_downloaded_demo_path to see if it was updated by the Subscriber's request.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Ocean Extra plugin for WordPress is vulnerable to unauthorized data modification and administrative action execution due to missing capability checks on several AJAX handlers in the onboarding and demo import logic. This allows authenticated attackers with Subscriber-level access to trigger demo data imports, download template files, or delete default site content like posts and pages.

Vulnerable Code

// includes/onboarding/class/import-data.php line 60
        public function download_selected_template_data() {

            check_ajax_referer('owp-onboarding', 'security');

            $template = get_option('ocean_installing_template_data');
---
// includes/onboarding/class/import-data.php line 131
        public function onboarding_import_data() {

            if ( !isset($_POST['nonce']) || !wp_verify_nonce( sanitize_key($_POST['nonce']), 'owp-onboarding' ) ) {
                wp_send_json_error(array(
                    'message' => __('Nonce verification failed.', 'ocean-extra')
                ));
            }

            $template = get_option('ocean_installing_template_data');
---
// includes/onboarding/start.php line 228
			return apply_filters(
				'ocean_onboarding_localize',
				[
					'options' => oe_onboarding_wizard_options(),
					'childThemeStatus' => $child_theme_status,
					'siteUrl' => esc_url(site_url()),
					'homeUrl' => esc_url(home_url()),
					'adminUrl' => esc_url(admin_url()),
					'nonce' => wp_create_nonce( 'owp-onboarding' ),
					'ajax_url' => admin_url( 'admin-ajax.php' ),

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/ocean-extra/2.5.3/includes/onboarding/class/import-data.php /home/deploy/wp-safety.org/data/plugin-versions/ocean-extra/2.5.4/includes/onboarding/class/import-data.php
--- /home/deploy/wp-safety.org/data/plugin-versions/ocean-extra/2.5.3/includes/onboarding/class/import-data.php	2026-02-16 09:30:20.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/ocean-extra/2.5.4/includes/onboarding/class/import-data.php	2026-03-09 09:02:10.000000000 +0000
@@ -64,6 +64,15 @@
 
             check_ajax_referer('owp-onboarding', 'security');
 
+            if ( ! current_user_can( 'manage_options' ) ) {
+                wp_send_json_error(
+                    array(
+                        'message' => __( 'You do not have permission to perform this action.', 'ocean-extra' )
+                    ),
+                    403
+                );
+            }
+
             $template = get_option('ocean_installing_template_data');
 
             if (empty($template)) {
@@ -132,6 +141,15 @@
                 ));
             }
 
+            if ( ! current_user_can( 'manage_options' ) ) {
+                wp_send_json_error(
+                    array(
+                        'message' => __( 'You do not have permission to perform this action.', 'ocean-extra' )
+                    ),
+                    403
+                );
+            }
+
             $template = get_option('ocean_installing_template_data');
 
             if (empty($template)) {
@@ -385,6 +403,15 @@
                 ));
             }
 
+            if ( ! current_user_can( 'manage_options' ) ) {
+                wp_send_json_error(
+                    array(
+                        'message' => __( 'You do not have permission to perform this action.', 'ocean-extra' )
+                    ),
+                    403
+                );
+            }
+
             $template = get_option('ocean_installing_template_data');
 
             if ( empty( $template ) ) {

Exploit Outline

An attacker with Subscriber-level privileges can exploit this vulnerability through the following steps: 1. Log in to the WordPress admin dashboard as a Subscriber. 2. Locate the global JavaScript object `oeOnboardingLoc` (localized by the plugin for all authenticated users) and extract the value of the `nonce` key, which uses the action string 'owp-onboarding'. 3. Send an AJAX POST request to `/wp-admin/admin-ajax.php` with the action set to `oceanwp_onboarding_import_data` or `download_template_data`. 4. Include the extracted nonce in the `nonce` or `security` parameter respectively. 5. To delete standard content, use the `importType=content` parameter. The server-side logic in `import_content` will proceed to call `wp_delete_post` for the 'sample-page' and 'hello-world' slugs without verifying if the user has administrative permissions.

Check if your site is affected.

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