CVE-2025-11977

HappyForms <= 1.26.12 - Authenticated (Admin+) Local File Inclusion

mediumImproper Control of Filename for Include/Require Statement in PHP Program ('PHP Remote File Inclusion')
6.6
CVSS Score
6.6
CVSS Score
medium
Severity
1.26.13
Patched in
1d
Time to patch

Description

The Happyforms – Form Builder for WordPress: Drag & Drop Contact Forms, Surveys, Payments & Multipurpose Forms plugin for WordPress is vulnerable to Local File Inclusion in all versions up to, and including, 1.26.12 via the happyforms_get_form_partial() function. This makes it possible for authenticated attackers, with Administrator-level access and above, to include and execute arbitrary .php 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 .php file types can be uploaded and included.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H
Attack Vector
Network
Attack Complexity
High
Privileges Required
High
User Interaction
None
Scope
Unchanged
High
Confidentiality
High
Integrity
High
Availability

Technical Details

Affected versions<=1.26.12
PublishedJuly 9, 2026
Last updatedJuly 10, 2026
Affected pluginhappyforms

What Changed in the Fix

Changes introduced in v1.26.13

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan targets a Local File Inclusion (LFI) vulnerability in the Happyforms plugin (versions <= 1.26.12). The vulnerability resides in the `happyforms_get_form_partial()` function, which fails to properly sanitize template paths before inclusion. ### 1. Vulnerability Summary * **Vulne…

Show full research plan

This research plan targets a Local File Inclusion (LFI) vulnerability in the Happyforms plugin (versions <= 1.26.12). The vulnerability resides in the happyforms_get_form_partial() function, which fails to properly sanitize template paths before inclusion.

1. Vulnerability Summary

  • Vulnerability: Authenticated (Admin+) Local File Inclusion (LFI).
  • Vulnerable Function: happyforms_get_form_partial() (likely located in inc/classes/class-happyforms.php or core/helpers/helper-misc.php).
  • Reason: The function accepts a user-provided parameter (representing a template or partial name) and uses it directly in an include or require statement without sufficient sanitization or path validation. This allows an administrator to use directory traversal (../) to include arbitrary .php files.
  • Impact: Execution of arbitrary PHP code (RCE) if an attacker can upload a .php file (e.g., via the media library) or inclusion of sensitive files that execute logic (like wp-config.php).

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php or a specific admin page (e.g., the Form Builder/Customizer).
  • Action: Likely happyforms_get_form_partial.
  • Parameter: Typically template, part, or partial (to be confirmed via grep).
  • Authentication: Administrator-level access is required.
  • Preconditions: The plugin must be active.

3. Code Flow (Manual Discovery)

The researcher/agent must first locate the vulnerable sink:

  1. Search for the function:
    grep -rn "function happyforms_get_form_partial" .
  2. Trace the Sink: Inside the function, look for include, include_once, require, or require_once. Identify the variable being passed.
  3. Find the Entry Point: Search for where this function is called or registered as an AJAX action:
    grep -rn "happyforms_get_form_partial" .
    Look for add_action( 'wp_ajax_...', ... ).

4. Nonce Acquisition Strategy

Happyforms heavily utilizes the WordPress Customizer and AJAX. Admin nonces are typically localized for the dashboard.

  1. Identify the Script/Nonce: Search for wp_localize_script in the codebase to find the handle.
    grep -rn "wp_localize_script" .
  2. Likely Variable: In Happyforms, nonces are often attached to window.happyforms_admin or window.happyforms_customizer.
  3. Acquisition Steps:
    • Create a test form (if none exist) to ensure the builder scripts load: wp eval "HappyForms()->initialize_plugin();" (or use WP-CLI to create a form).
    • Navigate to the Happyforms admin page: browser_navigate("http://localhost:8080/wp-admin/admin.php?page=happyforms").
    • Extract the nonce using browser_eval:
      // Hypothetical identifiers based on Happyforms naming conventions
      window.happyforms_admin?.nonce || window.happyforms_settings?.nonce
      

5. Exploitation Strategy

Step 1: Discover Parameters

Assuming the function is an AJAX handler, use http_request to test parameters.

  • Action: happyforms_get_form_partial
  • Target Parameter: (Discovered in Step 3, e.g., partial)

Step 2: Test Path Traversal

Attempt to include a known WordPress file to confirm the LFI. We will target wp-config (which won't display source but will confirm execution/inclusion if no error occurs vs a 404 for a non-existent file).

Request (Example):

POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded

action=happyforms_get_form_partial&partial=../../../../wp-config&nonce=[NONCE]

Step 3: RCE via Media Library (If applicable)

  1. Upload a file named poc.php (containing <?php echo "VULN_SUCCESS"; ?>) to the WordPress Media Library.
  2. Identify the path (usually wp-content/uploads/YYYY/MM/poc.php).
  3. Include it via the LFI:
    partial=../../../../wp-content/uploads/2025/01/poc (Note: The plugin likely appends .php).

6. Test Data Setup

  1. Login as Admin: Use the login_as_admin tool.
  2. Create a Form: Ensure at least one Happyforms form exists to populate admin interfaces.
    wp post create --post_type=happyform --post_title="Exploit Test" --post_status=publish
  3. Locate Plugin Path: Confirm plugin is at wp-content/plugins/happyforms/.

7. Expected Results

  • Success: The HTTP response body contains the output of the included PHP file (e.g., "VULN_SUCCESS") or a lack of "File not found" errors when targeting valid system files.
  • Evidence: If including wp-config.php, the response may be empty (200 OK) because wp-config.php defines constants but produces no output. Including a non-existent file should trigger a PHP warning/error if WP_DEBUG is on, or a plugin-specific error message.

8. Verification Steps

  1. Error Log Check: Check wp-content/debug.log for "failed to open stream" errors which indicate the inclusion path attempted:
    tail -n 20 /var/www/html/wp-content/debug.log
  2. PHP Execution: If a shell was uploaded, verify the existence of a file created by the shell.

9. Alternative Approaches

  • Wrapper Inclusion: If direct inclusion is blocked by .php extension enforcement, try php://filter/convert.base64-encode/resource=wp-config to read the configuration.
    • Note: If the plugin appends .php, the payload would be php://filter/convert.base64-encode/resource=../../../../wp-config (the filter will look for wp-config.php).
  • Template Directives: Check if the vulnerability is accessible via the [happyforms] shortcode processing if it dynamically loads parts based on attributes.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Happyforms plugin for WordPress is vulnerable to Local File Inclusion (LFI) in versions up to and including 1.26.12 through the happyforms_get_form_partial() function. This allows authenticated administrators to include and execute arbitrary PHP files on the server by exploiting insufficient sanitization of template paths containing directory traversal sequences.

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/happyforms/1.26.12/happyforms.php /home/deploy/wp-safety.org/data/plugin-versions/happyforms/1.26.13/happyforms.php
--- /home/deploy/wp-safety.org/data/plugin-versions/happyforms/1.26.12/happyforms.php	2025-11-18 19:13:52.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/happyforms/1.26.13/happyforms.php	2026-03-30 19:53:50.000000000 +0000
@@ -5,7 +5,7 @@
  * Plugin URI:  https://happyforms.io
  * Description: Form builder to get in touch with visitors, grow your email list and collect payments.
  * Author:      Happyforms
- * Version:     1.26.12
+ * Version:     1.26.13
  * Author URI:  https://happyforms.io
  * Upgrade URI: https://happyforms.io/upgrade
  */
@@ -22,7 +22,7 @@
 /**
  * The current version of the plugin.
  */
-define( 'HAPPYFORMS_VERSION', '1.26.12' );
+define( 'HAPPYFORMS_VERSION', '1.26.13' );
 
 if ( ! function_exists( 'happyforms_get_version' ) ):
 
@@ -143,7 +143,7 @@
 /**
  * Start general admin and frontend hooks.
  */
-add_action( 'plugins_loaded', array( HappyForms(), 'initialize_plugin' ) );
+add_action( 'init', array( HappyForms(), 'initialize_plugin' ), 0 );
 
 /**
  * Start Customize screen specific hooks.
--- /home/deploy/wp-safety.org/data/plugin-versions/happyforms/1.26.12/readme.txt	2025-11-18 19:13:52.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/happyforms/1.26.13/readme.txt	2026-03-30 19:53:50.000000000 +0000
@@ -3,9 +3,9 @@
 Contributors: happyforms
 Tags: contact form, WordPress form plugin, forms, form builder, custom form
 Requires at least: 5.0
-Tested up to: 6.8.3
+Tested up to: 6.9.4
 Requires PHP: 7.0
-Stable tag: 1.26.12
+Stable tag: 1.26.13
 License: GPLv2 or later
 License URI: http://www.gnu.org/licenses/gpl-2.0.html
 
@@ -289,6 +289,13 @@
 
 == Changelog ==
 
+= March 30, 2026 =
+
+Version 1.26.13 changelog:
+
+* Update: Compatibility with WordPress 6.9.4.
+* Fixed: Translation loading notice triggered on WordPress 6+.
+
 = November 18, 2025 =
 
 Version 1.26.12 changelog:
@@ -649,6 +656,11 @@
 
 == Upgrade Notice ==
 
+= 1.26.13 =
+
+* Update: Compatibility with WordPress 6.9.4.
+* Fixed: Translation loading notice triggered on WordPress 6+.
+
 = 1.26.12 =

Exploit Outline

1. Authenticate as a user with Administrator-level access to the WordPress site. 2. Obtain a valid security nonce from the Happyforms admin dashboard, typically localized in the builder or customizer scripts (e.g., from `window.happyforms_admin.nonce`). 3. Trigger an AJAX request (POST) to `wp-admin/admin-ajax.php` with the `action` parameter set to `happyforms_get_form_partial`. 4. Provide a parameter (likely `partial` or `template`) containing a directory traversal payload like `../../../../wp-config` (targeting `wp-config.php` assuming the plugin appends the extension) or a path to a malicious PHP file previously uploaded to the Media Library. 5. Upon processing, the plugin will use the provided path in an `include` or `require` statement, executing the targeted PHP file in the context of the WordPress environment.

Check if your site is affected.

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