CVE-2026-2448

Page Builder by SiteOrigin <= 2.33.5 - Authenticated (Contributor+) Local File Inclusion

highImproper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
8.8
CVSS Score
8.8
CVSS Score
high
Severity
2.34.0
Patched in
1d
Time to patch

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:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
High
Confidentiality
High
Integrity
High
Availability

Technical Details

Affected versions<=2.33.5
PublishedMarch 2, 2026
Last updatedMarch 3, 2026
Affected pluginsiteorigin-panels

Source Code

WordPress.org SVN
Research Plan
Unverified

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-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, or layout within 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)

  1. Entry Point: An AJAX handler is registered via add_action( 'wp_ajax_siteorigin_panels_...', ... ).
  2. Dispatch: The handler receives a request containing a "template" identifier.
  3. 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/).
  4. LFI Sink: The plugin calls locate_template( $user_provided_path, true ).
  5. Execution: WordPress finds the file (resolving the ../ traversal) and calls load_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.

  1. Setup Post: Create a post as a Contributor.
  2. Navigate: Open the browser to the WordPress admin post editor (wp-admin/post.php?post=ID&action=edit).
  3. Extract Nonce: The plugin localizes its settings in a global JavaScript object. Based on the plugin structure, the variable is likely siteoriginPanelsOptions or soPanelsSettings.
  4. JS Command:
    // To be executed via browser_eval
    window.soPanelsSettings?.nonce || window.siteoriginPanelsOptions?.nonce
    
  5. Localization Key: Look for wp_localize_script calls in inc/admin.php or siteorigin-panels.php. The key is typically passed to the AJAX request as _widgets_nonce or nonce.

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

  1. User: Create a user with the contributor role.
  2. Post: Create a post using the Page Builder.
  3. Target File: For verification, create a file named lfi-check.php in 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, but wp-config.php will 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:

  1. Verify the response code is 200 OK.
  2. Verify the presence of the unique string ---LFI-SUCCESS--- in the response body.
  3. 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:

  1. Grep for Sink: grep -rn "locate_template" . to find all occurrences.
  2. Trace Upwards: See which function calls the containing function. Look for add_action('wp_ajax_... or add_action('admin_post_....
  3. 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.
  4. Payload Variations: Try different depths of traversal (../../../../../../etc/passwd) and null byte injection (though ineffective in modern PHP, useful for older environments if applicable).
Research Findings
Static analysis — not yet PoC-verified

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

--- a/siteorigin-panels/inc/admin.php
+++ b/siteorigin-panels/inc/admin.php
@@ -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.