Landing Page Builder <= 1.5.3.4 - Authenticated (Author+) Stored Cross-Site Scripting
Description
The Landing Page Builder plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.5.3.4 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with author-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=1.5.3.4What Changed in the Fix
Changes introduced in v1.5.3.5
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-24620 (Landing Page Builder Stored XSS) ## 1. Vulnerability Summary The **Landing Page Builder** plugin (versions <= 1.5.3.4) contains a stored Cross-Site Scripting (XSS) vulnerability within its AJAX data saving mechanism. The `ULPB_Ajax_Requests::ulpb_admin_…
Show full research plan
Exploitation Research Plan: CVE-2026-24620 (Landing Page Builder Stored XSS)
1. Vulnerability Summary
The Landing Page Builder plugin (versions <= 1.5.3.4) contains a stored Cross-Site Scripting (XSS) vulnerability within its AJAX data saving mechanism. The ULPB_Ajax_Requests::ulpb_admin_ajax function, which handles page content updates, fails to sanitize specific keys within the JSON-encoded payload. An attacker with Editor-level access (or Author, depending on specific capability mappings) can inject arbitrary JavaScript into a landing page.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
ulpb_admin_data - HTTP Method:
POST - Parameter:
defaults(A JSON-encoded string containing page data). - Authentication: Required. The code checks
current_user_can('editor') || current_user_can('administrator'). - Nonce Requirement: None. Line 160 of
admin/classes/ajax-requests-class.phpexplicitly sets$nonce = false;and the subsequent checkif ( $nonce )will never trigger adie.
3. Code Flow
- Entry Point: The AJAX action
wp_ajax_ulpb_admin_datais registered inULPB_Ajax_Requests::_hooks()and callsulpb_admin_ajax(). - Method Detection: The function checks
$_SERVER['REQUEST_METHOD']. IfPOST, it proceeds to line 178. - Data Parsing:
$_POST['defaults']is captured.stripslashes()is applied.json_decode(..., true)converts the string into an associative array$data.
- Vulnerable Sanitization Logic:
- At line 204, the plugin defines
cleanDataArray(&$value, $key, $allowed_html). - At line 214, it checks an allowlist:
$contentKeysArr = array('accContent', 'widgetTextContent', 'wltc','fbTextContent','formCustomHTML','POcustomCSS','POcustomJS','widgetContent', ...);. - Sink: If the
$keymatches any string in this array, no sanitization is performed. The code branch is empty:if(in_array($key, $contentKeysArr)){ }. - If the key is not in the list, it uses
esc_textarea($value).
- At line 204, the plugin defines
- Storage: The
$datais processed and eventually stored usingupdate_post_meta($page_id, 'ULPB_DATA', $data). - Execution: When a user views the landing page associated with the
page_id, the raw content fromwidgetTextContentorPOcustomJSis rendered in the HTML, executing the script.
4. Nonce Acquisition Strategy
Based on the source code analysis of admin/classes/ajax-requests-class.php:
- Result: No nonce is required.
- Verification: Line 160:
$nonce = false;. Line 161:if ( $nonce ) { die( 'Invalid Nonce' ); }. This check is logically impossible to trigger.
5. Exploitation Strategy
The goal is to use an Editor account to update a landing page with a malicious payload.
Step-by-Step Plan
- Login: Authenticate as an Editor.
- Create/Identify Page: Create a new Landing Page (post type
ulpb_post) or identify an existing one and capture itsID. - Craft Payload: Create a JSON structure for the
defaultsparameter.- Key:
pageID(must match the target page ID). - Key:
widgetTextContent(contains the XSS payload). - Key:
pageOptions(required to satisfy logic like$data['pageOptions']['pageSeoName']).
- Key:
- Submit Update: Send a POST request to
admin-ajax.php. - Trigger: Navigate to the public URL of the Landing Page.
Target HTTP Request
POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
action=ulpb_admin_data&defaults={"pageID":"[TARGET_PAGE_ID]","postType":"ulpb_post","pageStatus":"publish","pageOptions":{"pageSeoName":"XSS Test","loadWpFooterTwo":"true"},"widgetTextContent":"<script>alert(document.domain)</script>"}
6. Test Data Setup
- Target User: Create an Editor user (e.g.,
attacker_editor). - Target Page:
wp post create --post_type=ulpb_post --post_title="Vulnerable Page" --post_status=publish --post_author=[EDITOR_ID] - Capture ID: Store the ID of the created post.
7. Expected Results
- The AJAX response should be empty or a success indicator (the code does not show a specific JSON return before exiting).
- When navigating to
/?post_type=ulpb_post&p=[ID], the browser should executealert(document.domain).
8. Verification Steps
- Database Check: Verify the payload is stored in
post_meta.wp post meta get [ID] ULPB_DATA - HTML Inspection: Fetch the page content and check for the raw script tag.
# Using http_request to fetch the page and grep for the script # Response should contain: <script>alert(document.domain)</script>
9. Alternative Approaches
If widgetTextContent is blocked or handled differently by the frontend renderer, try these keys from the identified contentKeysArr:
POcustomJS: This is explicitly meant for JavaScript and is likely included in a<script>block.- Payload for
POcustomJS:alert('XSS_FROM_CUSTOM_JS')(Note: do not include tags if the plugin wraps it).
- Payload for
formCustomHTML: Useful if the page uses a form builder widget.- Payload:
<img src=x onerror=alert(1)>
- Payload:
btnLink: If the button link is not sanitized, usejavascript:alert(1).
Alternative Payload for POcustomJS
{
"pageID": "[ID]",
"postType": "ulpb_post",
"pageOptions": {"pageSeoName": "JS Test"},
"POcustomJS": "alert('XSS')"
}
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.