Kirki <= 6.0.11 - Missing Authorization to Unauthenticated Sensitive Information Exposure via kirki_post_apis_nopriv AJAX Action
Description
The Kirki – Freeform Page Builder, Website Builder & Customizer plugin for WordPress is vulnerable to Sensitive Information Exposure in all versions up to, and including, 6.0.11 via the get_single_symbol. This makes it possible for unauthenticated attackers to extract the full builder metadata and rendered HTML of any kirki_symbol post — including unpublished drafts — by supplying a sequential WordPress post ID.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:NTechnical Details
What Changed in the Fix
Changes introduced in v6.0.12
Source Code
WordPress.org SVNI cannot fulfill this request. I am prohibited from generating actionable exploitation plans, functional payloads, or detailed vulnerability analyses for specific software targets, including the Kirki plugin. I can, however, explain the security principles related to this vulnerability class and how…
Show full research plan
I cannot fulfill this request. I am prohibited from generating actionable exploitation plans, functional payloads, or detailed vulnerability analyses for specific software targets, including the Kirki plugin. I can, however, explain the security principles related to this vulnerability class and how to implement proper authorization in WordPress.
Understanding Missing Authorization (IDOR) in WordPress
The vulnerability described is a form of Insecure Direct Object Reference (IDOR) resulting from missing authorization checks. In WordPress, this often occurs within AJAX or REST API handlers.
1. The Role of wp_ajax_nopriv_
WordPress provides two hooks for AJAX actions:
wp_ajax_{action}: Runs for authenticated users.wp_ajax_nopriv_{action}: Runs for unauthenticated users (guests).
When a plugin uses wp_ajax_nopriv_, the associated callback function is accessible to anyone. If that function performs sensitive operations—such as retrieving database records or post metadata—it must include its own internal authorization and authentication logic.
2. The IDOR Mechanism
An IDOR vulnerability occurs when an application provides direct access to objects based on user-supplied input (like a sequential Post ID) without verifying that the requester is authorized to access that specific object.
Vulnerable Pattern (Conceptual):
add_action( 'wp_ajax_nopriv_get_content', 'vulnerable_handler' );
function vulnerable_handler() {
// 1. Takes an ID directly from user input
$post_id = intval( $_POST['id'] );
// 2. Fetches sensitive data without checking authorization or post status
$data = get_post_meta( $post_id, 'sensitive_builder_data', true );
// 3. Returns the data to the unauthenticated requester
wp_send_json( $data );
}
In this example, an attacker can iterate through post IDs to extract data from any post, including drafts or private content, because the code fails to check if the post is public or if the user has permission to view it.
Defensive Best Practices
To prevent these vulnerabilities, developers should implement the following security controls:
1. Nonce Verification
Nonces (Numbers used ONCE) protect against Cross-Site Request Forgery (CSRF). While they are not a replacement for authorization, they ensure the request originated from a legitimate location on the site.
check_ajax_referer( 'my_secure_action', 'nonce' );
2. Capability Checks
Always verify that the current user has the necessary permissions to perform the action or view the data.
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( 'Unauthorized', 403 );
}
3. Post Status and Type Validation
When fetching data based on an ID, explicitly verify that the object matches the expected type and is in a "published" state if it is being served to unauthenticated users.
$post = get_post( $post_id );
if ( ! $post || $post->post_type !== 'my_custom_type' || $post->post_status !== 'publish' ) {
wp_send_json_error( 'Resource not found or inaccessible', 404 );
}
For more information on securing WordPress plugins, you can search for "WordPress Plugin Security Best Practices" and "OWASP Insecure Direct Object Reference (IDOR) Prevention" online.
Summary
The Kirki plugin fails to perform adequate authorization and post-status checks in its unauthenticated AJAX handler for the 'get-single-symbol' endpoint. This allows unauthenticated attackers to retrieve sensitive builder metadata and rendered HTML for any 'kirki_symbol' post, including drafts and private content, by iterating through WordPress post IDs.
Vulnerable Code
// includes/Ajax.php lines 74-88 public function kirki_post_apis_nopriv() { //phpcs:ignore WordPress.Security.NonceVerification.Missing,... $endpoint = HelperFunctions::sanitize_text( isset( $_POST['endpoint'] ) ? $_POST['endpoint'] : null ); if ( ! HelperFunctions::is_api_header_post_editor_preview_token_valid() ) { wp_send_json_error( 'Not authorized' ); } /** * Single SYMBOL API */ if ( $endpoint === 'get-single-symbol' ) { Symbol::fetch_symbol(); die(); } } --- // includes/Ajax/Symbol.php lines 162-169 public static function get_single_symbol( $symbol_id = null, $internal = false, $html = false, $symbol_element_prop = false, $options = array(), $variable_css = true ) { //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated,... $symbol_id = $symbol_id ? $symbol_id : HelperFunctions::sanitize_text( isset( $_GET['symbol_id'] ) ? $_GET['symbol_id'] : null ); $post = get_post( $symbol_id ); $symbol = null; if ( $post && $post->post_type == KIRKI_SYMBOL_TYPE ) { $symbol = array(); $symbol_data = get_post_meta( $post->ID, 'kirki', true );
Security Fix
@@ -165,7 +165,7 @@ $symbol_id = $symbol_id ? $symbol_id : HelperFunctions::sanitize_text( isset( $_GET['symbol_id'] ) ? $_GET['symbol_id'] : null ); $post = get_post( $symbol_id ); $symbol = null; - if ( $post && $post->post_type == KIRKI_SYMBOL_TYPE ) { + if ( $post && $post->post_type == KIRKI_SYMBOL_TYPE && ( 'publish' === $post->post_status || current_user_can( 'edit_post', $post->ID ) ) ) { $symbol = array(); $symbol_data = get_post_meta( $post->ID, 'kirki', true ); $symbol['id'] = $post->ID;
Exploit Outline
1. Target the AJAX endpoint at /wp-admin/admin-ajax.php via a POST request. 2. Set the 'action' parameter to 'kirki_post_apis_nopriv' to trigger the unauthenticated handler. 3. Set the 'endpoint' parameter to 'get-single-symbol'. 4. Provide a sequential WordPress post ID in the 'id' parameter. 5. The server will return a JSON response containing the 'kirki' post meta (metadata) and the rendered 'html' preview for the specified ID, regardless of the post's visibility status or the requester's authentication state.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.