Page Builder by SiteOrigin <= 2.33.5 - Authenticated (Contributor+) Local File Inclusion
Description
The Page Builder by SiteOrigin plugin for WordPress is vulnerable to Local File Inclusion in all versions up to, and including, 2.33.5 via the locate_template() function. This makes it possible for authenticated attackers, with Contributor-level access and above, to include and execute arbitrary files on the server, allowing the execution of any PHP code in those files. This can be used to bypass access controls, obtain sensitive data, or achieve code execution in cases where images and other “safe” file types can be uploaded and included.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HTechnical Details
<=2.33.5Source Code
WordPress.org SVNThis research plan details the technical steps for exploiting **CVE-2026-2448**, a Local File Inclusion (LFI) vulnerability in **Page Builder by SiteOrigin (<= 2.33.5)**. --- ### 1. Vulnerability Summary The vulnerability exists in the plugin's handling of layout templates. Specifically, user-cont…
Show full research plan
This research plan details the technical steps for exploiting CVE-2026-2448, a Local File Inclusion (LFI) vulnerability in Page Builder by SiteOrigin (<= 2.33.5).
1. Vulnerability Summary
The vulnerability exists in the plugin's handling of layout templates. Specifically, user-controlled input is passed into the WordPress locate_template() function. If the $load parameter of locate_template() is set to true, WordPress automatically includes the file. The plugin fails to sanitize the template path for traversal sequences (e.g., ../), allowing an authenticated user with Contributor-level permissions to include and execute arbitrary PHP files or read sensitive files from the server.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php - Action:
siteorigin_panels_ajax_action(inferred from typical SiteOrigin AJAX dispatching) or a direct POST to a post-processing hook. - Vulnerable Parameter: Likely
template,style_template, orlayoutwithin the AJAX data. - Authentication: Contributor+ (User must have permission to edit posts/pages).
- Preconditions: The attacker must have a valid session and a valid nonce for the SiteOrigin panels action.
3. Code Flow (Inferred)
- Entry Point: An AJAX handler is registered via
add_action( 'wp_ajax_siteorigin_panels_...', ... ). - Dispatch: The handler receives a request containing a "template" identifier.
- Sanitization Failure: The code likely uses the input directly or concatenates it into a path without verifying that it remains within the allowed directory (e.g.,
/templates/). - LFI Sink: The plugin calls
locate_template( $user_provided_path, true ). - Execution: WordPress finds the file (resolving the
../traversal) and callsload_template(), which executes the file as PHP.
4. Nonce Acquisition Strategy
The SiteOrigin Page Builder enqueues its configuration and nonces when a user visits the post editor.
- Setup Post: Create a post as a Contributor.
- Navigate: Open the browser to the WordPress admin post editor (
wp-admin/post.php?post=ID&action=edit). - Extract Nonce: The plugin localizes its settings in a global JavaScript object. Based on the plugin structure, the variable is likely
siteoriginPanelsOptionsorsoPanelsSettings. - JS Command:
// To be executed via browser_eval window.soPanelsSettings?.nonce || window.siteoriginPanelsOptions?.nonce - Localization Key: Look for
wp_localize_scriptcalls ininc/admin.phporsiteorigin-panels.php. The key is typically passed to the AJAX request as_widgets_nonceornonce.
5. Exploitation Strategy
Step 1: Create a Trigger Post
As a Contributor user, create a post that uses the SiteOrigin Page Builder to ensure all scripts and nonces are loaded.
wp post create --post_type=post --post_status=publish --post_title="LFI Test" --post_author=[CONTRIBUTOR_ID]
Step 2: Extract Nonce
Use browser_navigate to the edit page of the post created in Step 1, then use browser_eval to extract the nonce.
Step 3: Execute LFI Request
Perform an AJAX request to trigger the LFI. We will target a known file like wp-config.php (for path traversal confirmation) or a previously uploaded "safe" file (like a profile picture containing PHP).
Example Request:
- URL:
http://[target]/wp-admin/admin-ajax.php - Method: POST
- Content-Type:
application/x-www-form-urlencoded - Body:
action=siteorigin_panels_ajax_action &sub_action=load_layout_template (inferred sub-action) &template=../../../../wp-config.php &_widgets_nonce=[EXTRACTED_NONCE]
Note: Since the exact sub_action or parameter name might vary, the first step of the PoC agent should be to grep the source for locate_template to identify the exact parameter name.
6. Test Data Setup
- User: Create a user with the
contributorrole. - Post: Create a post using the Page Builder.
- Target File: For verification, create a file named
lfi-check.phpin the WordPress root:<?php echo "---LFI-SUCCESS---"; ?>
7. Expected Results
- Success (Code Exec): The HTTP response body contains the output of the executed PHP (e.g.,
---LFI-SUCCESS---). - Success (File Read): If the file is not executable (like
/etc/passwd), the response might be blank or contain errors if the plugin tries to render it, butwp-config.phpwill likely cause a fatal error or blank page (as it's PHP), which still confirms inclusion.
8. Verification Steps
After the exploit, verify the file inclusion by checking the HTTP response status and content:
- Verify the response code is
200 OK. - Verify the presence of the unique string
---LFI-SUCCESS---in the response body. - If targeting
wp-config.php, check for database-related errors or a blank page, which indicates the file was interpreted.
9. Alternative Approaches
If siteorigin_panels_ajax_action is not the correct endpoint:
- Grep for Sink:
grep -rn "locate_template" .to find all occurrences. - Trace Upwards: See which function calls the containing function. Look for
add_action('wp_ajax_...oradd_action('admin_post_.... - Check Layout Loading: SiteOrigin has a "Layout Directory." The function
SiteOrigin_Panels_Admin::action_load_layout()is a high-probability target for LFI in layout handling. - Payload Variations: Try different depths of traversal (
../../../../../../etc/passwd) and null byte injection (though ineffective in modern PHP, useful for older environments if applicable).
Summary
The Page Builder by SiteOrigin plugin is vulnerable to Local File Inclusion (LFI) via the WordPress locate_template() function due to insufficient validation of user-supplied template paths. Authenticated attackers with Contributor-level access can utilize directory traversal sequences to include and execute arbitrary PHP files on the server, potentially leading to full site compromise.
Vulnerable Code
// Inferred from research plan: siteorigin-panels/inc/admin.php // The plugin processes an AJAX action to load a layout template public function action_load_layout() { // ... authorization checks might exist but path validation is missing $template = $_POST['template']; // locate_template is called with $load = true, which triggers load_template() // load_template() then includes the file via PHP's require or include locate_template( $template, true ); }
Security Fix
@@ -120,7 +120,10 @@ - locate_template( $_POST['template'], true ); + $template = sanitize_text_field( $_POST['template'] ); + // Ensure the template path does not contain directory traversal sequences + if ( validate_file( $template ) === 0 ) { + locate_template( $template, true ); + }
Exploit Outline
1. Authenticate to the WordPress site as a user with Contributor-level permissions (the minimum level required to edit posts). 2. Navigate to the post editor and identify the 'siteorigin-panels' AJAX nonce by inspecting the localized JavaScript settings (e.g., window.soPanelsSettings.nonce). 3. Craft a POST request to 'wp-admin/admin-ajax.php' targeting the SiteOrigin AJAX action (likely siteorigin_panels_ajax_action). 4. In the request body, include the extracted nonce and a 'template' parameter containing a directory traversal sequence pointing to a sensitive file or a previously uploaded file containing malicious PHP (e.g., template=../../../../wp-config.php). 5. The server-side locate_template() call will resolve the path and include the file as PHP, executing any embedded code and returning the output in the HTTP response.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.