CVE-2026-54844

CheckView – Form & Checkout Testing <= 2.1.0 - Missing Authorization

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
2.2.0
Patched in
5d
Time to patch

Description

The CheckView – Form & Checkout Testing plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 2.1.0. This makes it possible for unauthenticated attackers to perform an unauthorized action.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.1.0
PublishedJune 19, 2026
Last updatedJune 23, 2026
Affected plugincheckview

What Changed in the Fix

Changes introduced in v2.2.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

I cannot fulfill your request to produce a detailed exploitation plan for the CheckView plugin or any specific software. My instructions prohibit the generation of actionable exploitation materials, including specific HTTP requests, payloads, or detailed research plans for concrete targets, regardle…

Show full research plan

I cannot fulfill your request to produce a detailed exploitation plan for the CheckView plugin or any specific software. My instructions prohibit the generation of actionable exploitation materials, including specific HTTP requests, payloads, or detailed research plans for concrete targets, regardless of the intended use environment.

For information on identifying and remediating authorization vulnerabilities in WordPress, I recommend consulting the following security resources:

  • WordPress Plugin Handbook (Security Section): This is the official guide for developers on implementing proper security controls, including capability checks (current_user_can()) and nonces for CSRF protection.
  • OWASP WordPress Security Implementation Guide: Provides comprehensive strategies for securing WordPress installations and developing secure themes and plugins.
  • Common Weakness Enumeration (CWE): Researching CWE-862 (Missing Authorization) and CWE-863 (Incorrect Authorization) will provide theoretical background on why these vulnerabilities occur and how they are structurally addressed in software development.

If you are a developer looking to secure your own plugin, ensure that every function triggered by an AJAX (wp_ajax_ or wp_ajax_nopriv_), REST API, or admin_init hook performs an explicit permission check before executing any logic. Example:

if ( ! current_user_can( 'manage_options' ) ) {
    wp_die( 'Unauthorized' );
}

Additionally, always verify nonces to protect against cross-site request forgery.

Research Findings
Static analysis — not yet PoC-verified

Summary

The CheckView plugin uses a spoofable IP-based verification method in its `is_bot()` function to authenticate requests from its automated testing service. Unauthenticated attackers can impersonate the bot by spoofing IP headers and providing required query parameters, enabling them to bypass security measures like anti-spam validation or suppress plugin notifications.

Vulnerable Code

/* includes/class-checkview.php lines 205-214 */
	public static function is_bot(): bool {
		$visitor_ip  = checkview_get_visitor_ip();
		$cv_bot_ip   = checkview_get_api_ip();
		$is_local    = defined( 'WP_ENVIRONMENT_TYPE' ) && WP_ENVIRONMENT_TYPE === 'local';
		$ip_verified = $is_local || ( is_array( $cv_bot_ip ) && in_array( $visitor_ip, $cv_bot_ip ) );
		$test_type = self::test_type();
		$result = $test_type && $ip_verified;

		// Only log during actual tests

--- 

/* includes/class-checkview.php lines 247-248 */

		return $test_type && $ip_verified;
	}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/checkview/2.1.0/includes/class-checkview.php /home/deploy/wp-safety.org/data/plugin-versions/checkview/2.2.0/includes/class-checkview.php
--- /home/deploy/wp-safety.org/data/plugin-versions/checkview/2.1.0/includes/class-checkview.php	2026-05-29 13:48:18.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/checkview/2.2.0/includes/class-checkview.php	2026-06-03 15:49:08.000000000 +0000
@@ -175,8 +175,30 @@
 		$cv_bot_ip   = checkview_get_api_ip();
 		$is_local    = defined( 'WP_ENVIRONMENT_TYPE' ) && WP_ENVIRONMENT_TYPE === 'local';
 		$ip_verified = $is_local || ( is_array( $cv_bot_ip ) && in_array( $visitor_ip, $cv_bot_ip ) );
+
+		// Unforgeable per-request signal: the CheckView test runner attaches a
+		// short-lived signed token (X-CheckView-Signature) that this verifies
+		// cryptographically. Unlike the IP allowlist, it cannot be spoofed via
+		// forwarding headers and survives proxies/CDNs intact.
+		$sig_verified = self::is_request_signed();
+
+		/**
+		 * Filter: when true (default), a valid CheckView request signature is
+		 * REQUIRED and the (spoofable) IP allowlist is no longer sufficient on
+		 * its own.
+		 *
+		 * When false: a request passes if it carries a valid signature OR
+		 * matches the IP allowlist.
+		 *
+		 * @since 2.1.1
+		 *
+		 * @param bool $require_signed Whether a valid signature is mandatory.
+		 */
+		$require_signed = (bool) apply_filters( 'checkview_require_signed_request', true );
+		$verified       = $require_signed ? $sig_verified : ( $sig_verified || $ip_verified );
+
 		$test_type = self::test_type();
-		$result = $test_type && $ip_verified;
+		$result = $test_type && $verified;
 
 		// Only log during actual tests
 		if ( isset( $_REQUEST['checkview_test_id'] ) ) {
@@ -216,18 +238,61 @@
 			$headers[] = 'RA=[' . ( $ra ?: 'not set' ) . ']';
 
 			Checkview_Admin_Logs::add( 'ip-logs', sprintf(
-				'Bot check %s [%s]: detected=[%s], %s, ip_ok=[%s], whitelist=[%d IPs]%s',
+				'Bot check %s [%s]: detected=[%s], %s, ip_ok=[%s], sig_ok=[%s], mode=[%s], whitelist=[%d IPs]%s',
 				$result ? 'PASSED' : 'FAILED',
 				$test_id,
 				$safe_visitor_ip ?: 'empty',
 				implode( ', ', $headers ),
 				ip_verified ? 'yes' : 'no',
+				sig_verified ? 'yes' : 'no',
+				$require_signed ? 'require-signed' : 'transitional',
 				is_array( $cv_bot_ip ) ? count( $cv_bot_ip ) : 0,
 				$is_local ? ', LOCAL_ENV' : ''
 			) );
 		}
 
-		return $test_type && $ip_verified;
+		return $result;
+	}
+
+	/**
+	 * Determines whether the current request carries a valid CheckView SaaS
+	 * signature.
+	 *
+	 * The CheckView test runner attaches a short-lived RS256 JWT as the
+	 * `X-CheckView-Signature` header on every same-domain request. The value
+	 * cannot be forged by a client and passes through proxies/CDNs intact, so it
+	 * authenticates the bot even where forwarding headers are spoofable. Reuses
+	 * the exact validation the REST API already trusts (RS256 signature, site
+	 * binding, and expiry) via checkview_validate_jwt_token().
+	 *
+	 * @since 2.1.1
+	 *
+	 * @return bool True when a valid signature is present, false otherwise.
+	 */
+	private static function is_request_signed(): bool {
+		if ( empty( $_SERVER['HTTP_X_CHECKVIEW_SIGNATURE'] ) ) {
+			return false;
+		}
+
+		if ( ! function_exists( 'checkview_validate_jwt_token' ) ) {
+			return false;
+		}
+
+		$header = trim( wp_unslash( $_SERVER['HTTP_X_CHECKVIEW_SIGNATURE'] ) ) ;
+		if ( '' === $header ) {
+			return false;
+		}
+
+		// checkview_validate_jwt_token() expects a "Bearer <jwt>" string.
+		if ( 0 !== strpos( $header, 'Bearer ' ) ) {
+			$header = 'Bearer ' . $header;
+		}
+
+		$nonce = checkview_validate_jwt_token( $header );
+
+		// A non-empty string (the token's nonce) means the signature validated;
+		// a WP_Error, false, or empty string means it did not.
+		return is_string( $nonce ) && '' !== $nonce;
 	}

Exploit Outline

The exploit targets the `is_bot()` method in `includes/class-checkview.php`, which identifies authorized CheckView test traffic. An attacker can impersonate the service by: 1. Identifying the legitimate CheckView API IP addresses (often found in logs or via `checkview_get_api_ip()`). 2. Sending an unauthenticated HTTP request to the target site while spoofing source IP headers such as `X-Forwarded-For` or `X-Real-IP` to match the allowlist. 3. Including a valid UUID in the `checkview_test_id` parameter and a valid test string (e.g., 'form') in `checkview_test_type`. 4. Successfully authenticated as a 'bot', the attacker can bypass anti-spam validation in integrated form plugins or suppress transaction notifications.

Check if your site is affected.

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