Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/kirki/ComponentLibrary/controller/CompLibFormHandler.php
+++ b/kirki/ComponentLibrary/controller/CompLibFormHandler.php
@@ -17,18 +17,18 @@
protected $namespace = KIRKI_COMPONENT_LIBRARY_APP_PREFIX . '/v1';
public function __construct() {
- $this->init_rest_api_endpoint( 'kirki-login', WP_REST_Server::CREATABLE, array( $this, 'handle_login' ) );
- $this->init_rest_api_endpoint( 'kirki-register', WP_REST_Server::CREATABLE, array( $this, 'handle_register' ) );
- $this->init_rest_api_endpoint( 'kirki-forgot-password', WP_REST_Server::CREATABLE, array( $this, 'handle_forgot_password' ) );
- $this->init_rest_api_endpoint( 'kirki-change-password', WP_REST_Server::CREATABLE, array( $this, 'handle_change_password' ) );
- $this->init_rest_api_endpoint( 'kirki-retrieve-username', WP_REST_Server::CREATABLE, array( $this, 'handle_retrieve_username' ) );
- $this->init_rest_api_endpoint( 'kirki-comment', WP_REST_Server::CREATABLE, array( $this, 'handle_post_comment' ) );
+ $this->init_rest_api_endpoint( 'kirki-login', WP_REST_Server::CREATABLE, array( $this, 'handle_login' ), array( $this, 'guest_permissions_check' ) );
+ $this->init_rest_api_endpoint( 'kirki-register', WP_REST_Server::CREATABLE, array( $this, 'handle_register' ), array( $this, 'guest_permissions_check' ) );
+ $this->init_rest_api_endpoint( 'kirki-forgot-password', WP_REST_Server::CREATABLE, array( $this, 'handle_forgot_password' ), array( $this, 'guest_permissions_check' ) );
+ $this->init_rest_api_endpoint( 'kirki-change-password', WP_REST_Server::CREATABLE, array( $this, 'handle_change_password' ), array( $this, 'guest_permissions_check' ) );
+ $this->init_rest_api_endpoint( 'kirki-retrieve-username', WP_REST_Server::CREATABLE, array( $this, 'handle_retrieve_username' ), array( $this, 'guest_permissions_check' ) );
+ $this->init_rest_api_endpoint( 'kirki-comment', WP_REST_Server::CREATABLE, array( $this, 'handle_post_comment' ), array( $this, 'comment_permissions_check' ) );
}
- public function init_rest_api_endpoint( $endpoint, $methods, $callback ) {
+ public function init_rest_api_endpoint( $endpoint, $methods, $callback, $permission_callback = null ) {
add_action(
'rest_api_init',
- function () use ( $endpoint, $methods, $callback ) {
+ function () use ( $endpoint, $methods, $callback, $permission_callback ) {
register_rest_route(
$this->namespace,
'/' . $endpoint,
@@ -36,7 +36,7 @@
array(
'methods' => $methods,
'callback' => $callback,
- 'permission_callback' => array( $this, 'get_item_permissions_check' ),
+ 'permission_callback' => $permission_callback ? $permission_callback : array( $this, 'get_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( $methods ),
),
'schema' => array( $this, 'get_item_schema' ),
@@ -50,6 +50,21 @@
return true;
}
+ public function guest_permissions_check( $request ) {
+ return true;
+ }
+
+ public function comment_permissions_check( $request ) {
+ if ( ! is_user_logged_in() && get_option( 'default_comment_status' ) !== 'open' ) {
+ return new WP_Error(
+ 'rest_forbidden',
+ __( 'You must be logged in to post comments.' ),
+ array( 'status' => 401 )
+ );
+ }
+ return true;
+ }
+
private function wp_unique_username( $username, $suffix = 1 ) {
$original_username = $username;
while ( username_exists( $username ) ) {
@@ -58,29 +73,146 @@
return $username;
}
+ private function validate_meta_field( $field_name ) {
+ $allowed_meta_fields = apply_filters( 'kirki_allowed_registration_meta_fields', array(
+ 'first_name',
+ 'last_name',
+ 'phone',
+ 'company',
+ 'address',
+ 'city',
+ 'state',
+ 'country',
+ 'zip',
+ ) );
+
+ if ( ! in_array( $field_name, $allowed_meta_fields, true ) ) {
+ return false;
+ }
+
+ if ( preg_match( '/[^a-z0-9_-]/i', $field_name ) ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Verify that the submitted emailSubject + emailBody were signed by the server
+ * at page-render time and have not been tampered with.
+ *
+ * IMPORTANT: $body_raw must be the raw JSON string as received from the request —
+ * never a re-encoded array. Re-encoding can produce different output than the
+ * original wp_json_encode() call, breaking the HMAC comparison.
+ *
+ * @param string $subject The email subject string.
+ * @param string $body_raw The raw emailBody JSON string from the request.
+ * @param string $signature The HMAC signature to verify against.
+ * @return bool
+ */
+ private function verify_email_template_signature( $subject, $body_raw, $signature ) {
+ if ( empty( $signature ) ) {
+ return false;
+ }
+
+ // Use the raw string directly — same as what was signed in ElementGenerator.
+ // Do NOT json_decode then re-encode here.
+ $payload = $subject . '|' . $body_raw;
+ $secret = AUTH_KEY . AUTH_SALT;
+ $expected = hash_hmac( 'sha256', $payload, $secret );
+
+ return hash_equals( $expected, $signature );
+ }
+
+ /**
+ * Build the email body from a verified emailBody definition.
+ * Chip values are resolved from a fixed server-controlled map.
+ *
+ * @param array $email_body_array
+ * @param array $chip_data
+ * @return string
+ */
+ private function build_email_body( array $email_body_array, array $chip_data ) {
+ $email_body = '';
+ foreach ( $email_body_array as $body_data ) {
+ if ( ! isset( $body_data['type'], $body_data['value'] ) ) {
+ continue;
+ }
+ if ( $body_data['type'] === 'text' ) {
+ $email_body .= $body_data['value'];
+ } elseif ( $body_data['type'] === 'chip' && isset( $chip_data[ $body_data['value'] ] ) ) {
+ $email_body .= $chip_data[ $body_data['value'] ];
+ }
+ }
+ return $email_body;
+ }
+
public function handle_post_comment( $request ) {
- $form_data = $request->get_body_params();
- $transiet_name = $this->validate_nonce( 'kirki-comment' );
+ $form_data = $request->get_body_params();
+ $transient_name = $this->validate_nonce( 'kirki-comment' ); // note: typo fix from $transiet_name
+
+ $comment = isset( $form_data['comment'] ) ? sanitize_text_field( $form_data['comment'] ) : '';
+ $post_id = isset( $form_data['post_id'] ) ? absint( $form_data['post_id'] ) : 0;
+ $comment_parent = isset( $form_data['comment_parent'] ) ? absint( $form_data['comment_parent'] ) : 0;
+ $user_id = get_current_user_id();
+ $user = $user_id ? get_user_by( 'ID', $user_id ) : null;
- $name = isset( $form_data['name'] ) ? sanitize_text_field( $form_data['name'] ) : '';
- $email = isset( $form_data['email'] ) ? sanitize_email( $form_data['email'] ) : '';
- $comment = isset( $form_data['comment'] ) ? sanitize_text_field( $form_data['comment'] ) : '';
- $post_id = isset( $form_data['post_id'] ) ? sanitize_text_field( $form_data['post_id'] ) : 0;
- $comment_parent = isset( $form_data['comment_parent'] ) ? sanitize_text_field( $form_data['comment_parent'] ) : 0;
- $date = gmdate( 'Y-m-d H:i:s' );
- $user_id = get_current_user_id();
- $user = get_user_by( 'ID', $user_id );
- if ( $user ) {
+ // Resolve author identity.
+ if ( $user ) {
$name = $user->get( 'display_name' );
$email = $user->get( 'user_email' );
- }
+ } else {
+ // Anonymous commenter: require name + valid email supplied in the form.
+ $name = isset( $form_data['name'] ) ? sanitize_text_field( $form_data['name'] ) : '';
+ $email = isset( $form_data['email'] ) ? sanitize_email( $form_data['email'] ) : '';
+
+ if ( empty( $name ) || empty( $email ) || ! is_email( $email ) ) {
+ return new WP_REST_Response(
+ array( 'message' => 'Name and a valid email address are required.' ),
+ 400
+ );
+ }
+ }
+
+ $existing_comment_id = isset( $form_data['comment_id'] ) ? absint( $form_data['comment_id'] ) : 0;
+ $is_edit = $existing_comment_id !== 0;
+ $collection_type = isset( $form_data['collection_type'] ) ? sanitize_text_field( $form_data['collection_type'] ) : '';
+
+ // -----------------------------------------------------------------------
+ // EDIT PATH
+ // -----------------------------------------------------------------------
+ if ( $is_edit ) {
+ // FIX 1: Editing always requires an authenticated session.
+ if ( ! is_user_logged_in() ) {
+ return new WP_REST_Response(
+ array( 'message' => 'You must be logged in to edit a comment.' ),
+ 401
+ );
+ }
+
+ $existing_comment = get_comment( $existing_comment_id );
+
+ if ( ! $existing_comment ) {
+ return new WP_REST_Response(
+ array( 'message' => 'Comment not found.' ),
+ 404
+ );
+ }
- $existing_comment_id = isset( $form_data['comment_id'] ) ? sanitize_text_field( $form_data['comment_id'] ) : 0;
- $is_edit = 0 == $existing_comment_id ? false : true;
- $collection_type = isset( $form_data['collection_type'] ) ? sanitize_text_field( $form_data['collection_type'] ) : '';
+ // FIX 1 (cont.): strict ownership — user_id 0 must never match.
+ $is_owner = ( $user_id !== 0 && (int) $existing_comment->user_id === $user_id );
+ $is_moderator = current_user_can( 'moderate_comments' );
+
+ if ( ! $is_owner && ! $is_moderator ) {
+ return new WP_REST_Response(
+ array( 'message' => 'You are not authorized to edit this comment.' ),
+ 403
+ );
+ }
- global $wpdb;
- if ( $is_edit ) {
+ $date = gmdate( 'Y-m-d H:i:s' );
+
+ global $wpdb;
$wpdb->update(
$wpdb->comments,
array(
@@ -90,6 +222,7 @@
),
array( 'comment_ID' => $existing_comment_id )
);
+
apply_filters(
'kirki_comment_added-' . $collection_type,
array(
@@ -98,44 +231,60 @@
'form_data' => $form_data,
)
);
- } else {
- $comment_data = array(
- 'comment_post_ID' => $post_id,
- 'user_id' => $user_id,
- 'comment_author' => $name,
- 'comment_author_email' => $email,
- 'comment_content' => $comment,
- 'comment_parent' => $comment_parent,
- 'comment_approved' => 1,
- 'comment_date' => $date,
- 'comment_date_gmt' => get_gmt_from_date( $date ),
- );
- $comment_data = apply_filters( 'kirki_comment-' . $collection_type, $comment_data );
- $wpdb->insert( $wpdb->comments, $comment_data );
- $comment_id = (int) $wpdb->insert_id;
- apply_filters(
- 'kirki_comment_added-' . $collection_type,
- array(
+
+ delete_transient( $transient_name );
+ return new WP_REST_Response( array( 'message' => 'Comment updated.' ), 200 );
+ }
+
+ // -----------------------------------------------------------------------
+ // INSERT PATH
+ // -----------------------------------------------------------------------
+
+ // FIX 2: Build the comment array without hardcoding comment_approved=1,
+ // then route through wp_new_comment() so WordPress moderation, spam
+ // filters (Akismet, etc.), and flood checks all apply normally.
+ $comment_data = array(
+ 'comment_post_ID' => $post_id,
+ 'user_id' => $user_id,
+ 'comment_author' => $name,
+ 'comment_author_email' => $email,
+ 'comment_author_IP' => isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '',
+ 'comment_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '',
+ 'comment_content' => $comment,
+ 'comment_parent' => $comment_parent,
+ );
+
+ $comment_data = apply_filters( 'kirki_comment-' . $collection_type, $comment_data );
+
+ // wp_new_comment() runs duplicate/flood/spam checks, fires hooks, and
+ // respects the site's moderation settings.
+ $comment_id = wp_new_comment( $comment_data, true ); // true = return WP_Error on failure
+
+ if ( is_wp_error( $comment_id ) ) {
+ return new WP_REST_Response(
+ array( 'message' => $comment_id->get_error_message() ),
+ 400
+ );
+ }
+
+ if ( ! $comment_id ) {
+ return new WP_REST_Response(
+ array( 'message' => 'Failed to add comment.' ),
+ 400
+ );
+ }
+
+ apply_filters(
+ 'kirki_comment_added-' . $collection_type,
+ array(
'comment_ID' => $comment_id,
'user_id' => $user_id,
'form_data' => $form_data,
- )
- );
- }
+ )
+ );
- // Check if the comment was added successfully.
- if ( $comment_id ) {
- $response = array(
- 'message' => 'Comment Added',
- );
- delete_transient( $transiet_name );
- return new WP_REST_Response( $response, 200 );
- } else {
- $response = array(
- 'message' => 'Invalid form data',
- );
- return new WP_REST_Response( $response, 400 );
- }
+ delete_transient( $transient_name );
+ return new WP_REST_Response( array( 'message' => 'Comment added.' ), 200 );
}
@@ -154,9 +303,9 @@
$username = $user->get( 'user_login' );
} else {
$response = array(
- 'message' => 'User not found',
+ 'message' => 'Invalid username or password',
);
- return new WP_REST_Response( $response, 404 );
+ return new WP_REST_Response( $response, 401 );
}
}
@@ -174,9 +323,9 @@
if ( is_wp_error( $user ) ) {
$response = array(
- 'message' => $user->errors[ array_key_first( $user->errors ) ],
+ 'message' => 'Invalid username or password',
);
- return new WP_REST_Response( $response, 500 );
+ return new WP_REST_Response( $response, 401 );
}
$response = array(
'message' => 'User logged in',
@@ -227,7 +376,9 @@
foreach ( $form_data as $name => $value ) {
if ( $name !== 'username' && $name !== 'email' && $name !== 'password' && $name !== 'confirm_password' ) {
- $user_data['meta_input'][ KIRKI_COMPONENT_LIBRARY_APP_PREFIX . '_' . $name ] = $value;
+ if ( $this->validate_meta_field( $name ) ) {
+ $user_data['meta_input'][ KIRKI_COMPONENT_LIBRARY_APP_PREFIX . '_' . $name ] = sanitize_text_field( $value );
+ }
}
}
@@ -270,7 +421,7 @@
$user = get_user_by( 'email', $email );
if ( ! $user ) {
- return new WP_REST_Response( array( 'message' => 'User not found' ), 404 );
+ return new WP_REST_Response( array( 'message' => 'If an account exists with this email, you will receive a password reset link.' ), 200 );
}
$username = $user->get( 'user_login' );
@@ -285,17 +436,17 @@
if ( ! $user ) {
$response = array(
- 'message' => 'User not found',
+ 'message' => 'If an account exists with this information, you will receive a password reset link.',
);
- return new WP_REST_Response( $response, 404 );
+ return new WP_REST_Response( $response, 200 );
}
$user_email = $user->get( 'user_email' );
if($email !== $user_email) {
$response = array(
- 'message' => 'Invalid email address',
+ 'message' => 'If an account exists with this information, you will receive a password reset link.',
);
- return new WP_REST_Response( $response, 404 );
+ return new WP_REST_Response( $response, 200 );
}
$email = $user_email;
@@ -319,27 +470,29 @@
'reset_link' => "$url?action=rp&key=$key&login=" . rawurlencode( $username ),
);
- $email_subject = isset( $form_data['emailSubject'] ) ? sanitize_text_field( $form_data['emailSubject'] ) : '';
- $email_body = '';
+ $email_subject = isset( $form_data['emailSubject'] ) ? $form_data['emailSubject'] : '';
+ $email_body_raw = isset( $form_data['emailBody'] ) ? $form_data['emailBody'] : '[]';
+ $email_signature = isset( $form_data['emailSignature'] ) ? $form_data['emailSignature'] : '';
- if ( isset( $form_data['emailBody'] ) ) {
- $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 .= $body_data['value'];
- } elseif ( isset( $body_data['type'] ) && isset( $body_data['value'] ) && $body_data['type'] === 'chip' ) {
- $email_body .= $chip_data[ $body_data['value'] ];
- }
- }
+ if ( ! $this->verify_email_template_signature( $email_subject, $email_body_raw, $email_signature ) ) {
+ wp_send_json_error( array( 'message' => 'Invalid request' ), 400 );
+ exit;
}
+ $email_body_array = json_decode( $email_body_raw, true );
+ if ( ! is_array( $email_body_array ) ) {
+ $email_body_array = array();
+ }
+
+ $email_body = $this->build_email_body( $email_body_array, $chip_data );
+
$email_body = nl2br( $email_body );
$headers = array( 'Content-Type: text/html; charset=UTF-8' );
// Send custom email.
apply_filters( 'kirki_element_smtp', '' );
- $sent = wp_mail( $email, $email_subject, $email_body, $headers );
+ $sent = wp_mail( $email, sanitize_text_field( $email_subject ), $email_body, $headers );
if ( $sent ) {
$response = array(
@@ -407,7 +560,7 @@
$user = get_user_by( 'email', $email );
if ( ! $user ) {
- wp_send_json_error( array( 'message' => 'No user found with that email address.' ), 404 );
+ wp_send_json_success( array( 'message' => 'If an account exists with this email, you will receive your username.' ) );
exit;
}
@@ -419,26 +572,28 @@
'sitename' => get_bloginfo( 'name' ),
);
- $email_subject = isset( $form_data['emailSubject'] ) ? sanitize_text_field( $form_data['emailSubject'] ) : '';
- $email_body = '';
+ $email_subject = isset( $form_data['emailSubject'] ) ? $form_data['emailSubject'] : '';
+ $email_body_raw = isset( $form_data['emailBody'] ) ? $form_data['emailBody'] : '[]';
+ $email_signature = isset( $form_data['emailSignature'] ) ? $form_data['emailSignature'] : '';
- if ( isset( $form_data['emailBody'] ) ) {
- $email_body = json_decode( $form_data['emailBody'], true );
- foreach ( $email_body as $key => $body_data ) {
- if ( isset( $body_data['type'] ) && isset( $body_data['value'] ) && $body_data['type'] === 'text' ) {
- $email_body = $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'] ];
- }
- }
+ if ( ! $this->verify_email_template_signature( $email_subject, $email_body_raw, $email_signature ) ) {
+ wp_send_json_error( array( 'message' => 'Invalid request' ), 400 );
+ exit;
+ }
+
+ $email_body_array = json_decode( $email_body_raw, true );
+ if ( ! is_array( $email_body_array ) ) {
+ $email_body_array = array();
}
+ $email_body = $this->build_email_body( $email_body_array, $chip_data );
+
$email_body = nl2br( $email_body );
$headers = array( 'Content-Type: text/html; charset=UTF-8' );
apply_filters( 'kirki_element_smtp', '' );
- $email_sent = wp_mail( $email, $email_subject, $email_body, $headers );
+ $email_sent = wp_mail( $email, sanitize_text_field( $email_subject ), $email_body, $headers );
if ( ! $email_sent ) {
wp_send_json_error( array( 'message' => 'Failed to send email. Please try again later.' ), 500 );
@@ -450,6 +605,13 @@
exit;
}
+ /**
+ * Validate the nonce from the request header and return true on success.
+ * Exits with an error response on failure.
+ *
+ * @param string $element_name
+ * @return true
+ */
public function validate_nonce( $element_name ) {
$nonce = isset( $_SERVER['HTTP_X_WP_ELEMENT_NONCE'] )
? sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_WP_ELEMENT_NONCE'] ) )
--- a/kirki/ComponentLibrary/controller/ElementGenerator.php
+++ b/kirki/ComponentLibrary/controller/ElementGenerator.php
@@ -40,22 +40,57 @@
private function add_element_config() {
- $id = $this->element['id'];
- if (
- $this->element['name'] === 'kirki-login' || $this->element['name'] === 'kirki-register' ||
- $this->element['name'] === 'kirki-forgot-password' || $this->element['name'] === 'kirki-change-password' ||
- $this->element['name'] === 'kirki-retrieve-username' || $this->element['name'] === 'kirki-comment'
- ) {
- $nonce = $this->add_nonce_to_element( $this->element );
- $this->component_lib_forms[ $id ] = array_merge(
- $this->properties['attributes'],
- $this->setting,
- array(
- 'name' => $this->element['name'],
- 'nonce' => $nonce,
- )
- );
- }
+ $id = $this->element['id'];
+ if (
+ $this->element['name'] === 'kirki-login' || $this->element['name'] === 'kirki-register' ||
+ $this->element['name'] === 'kirki-forgot-password' || $this->element['name'] === 'kirki-change-password' ||
+ $this->element['name'] === 'kirki-retrieve-username' || $this->element['name'] === 'kirki-comment'
+ ) {
+ $nonce = $this->add_nonce_to_element( $this->element );
+ $config = array_merge(
+ $this->properties['attributes'],
+ $this->setting,
+ array(
+ 'name' => $this->element['name'],
+ 'nonce' => $nonce,
+ )
+ );
+
+ // SECURITY FIX: Sign the email template so the REST handler can verify
+ // it was not tampered with — without any extra DB queries.
+ // Always generate a signature for these element types, even when
+ // settings are empty, so the client always has a value to send.
+ if ( in_array( $this->element['name'], array( 'kirki-forgot-password', 'kirki-retrieve-username' ), true ) ) {
+ if ( ! isset( $config['emailSubject'] ) ) {
+ $config['emailSubject'] = '';
+ }
+ if ( ! isset( $config['emailBody'] ) ) {
+ $config['emailBody'] = array();
+ }
+ $config['emailSignature'] = $this->sign_email_template(
+ $config['emailSubject'],
+ $config['emailBody']
+ );
+ }
+
+ $this->component_lib_forms[ $id ] = $config;
+ }
+ }
+
+ /**
+ * Produce an HMAC signature over the admin-configured email template.
+ * Uses WordPress AUTH_KEY + AUTH_SALT so it is server-secret and
+ * never reproducible by an external attacker.
+ *
+ * @param string $subject
+ * @param array|string $body
+ * @return string Hex HMAC-SHA256 signature.
+ */
+ private function sign_email_template( $subject, $body ) {
+ $body_string = is_array( $body ) ? wp_json_encode( $body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) : (string) $body;
+ $payload = $subject . '|' . $body_string;
+ $secret = AUTH_KEY . AUTH_SALT;
+ return hash_hmac( 'sha256', $payload, $secret );
}
public function generate_common_element( $hide = false, $children_html = false ) {
--- a/kirki/includes/Ajax.php
+++ b/kirki/includes/Ajax.php
@@ -337,6 +337,9 @@
}
if ( $endpoint === 'get-wp-single-post' ) {
+ if ( ! HelperFunctions::has_access( KIRKI_ACCESS_LEVELS['FULL_ACCESS'] ) ) {
+ wp_send_json_error( 'Not authorized', 401 );
+ }
$post_id = (int) HelperFunctions::sanitize_text( isset( $_GET['post_id'] ) ? $_GET['post_id'] : null );
$post = get_post( $post_id );
@@ -424,15 +427,18 @@
Symbol::fetch_list( false, true );
}
- if ($endpoint === 'get-page-custom-section') {
- $type = HelperFunctions::sanitize_text(isset($_GET['type']) ? $_GET['type'] : '');
- wp_send_json(HelperFunctions::get_page_custom_section($type, true));
- }
+ if ($endpoint === 'get-page-custom-section') {
+ $type = HelperFunctions::sanitize_text(isset($_GET['type']) ? $_GET['type'] : '');
+ wp_send_json(HelperFunctions::get_page_custom_section($type, true));
+ }
/**
* GET Single prebuilt html API
*/
if ( $endpoint === 'get-pre-built-html' ) {
+ if ( ! HelperFunctions::has_access( KIRKI_ACCESS_LEVELS['FULL_ACCESS'] ) ) {
+ wp_send_json_error( 'Not authorized', 401 );
+ }
Symbol::get_pre_built_html_using_url();
}
@@ -549,6 +555,9 @@
* AUTHOR LIST
*/
if ( 'get-authors' === $endpoint ) {
+ if ( ! HelperFunctions::has_access( KIRKI_ACCESS_LEVELS['FULL_ACCESS'] ) ) {
+ wp_send_json_error( 'Not authorized', 401 );
+ }
WordpressData::get_author_list();
}
@@ -557,6 +566,9 @@
*/
if ( 'get-roles' === $endpoint ) {
+ if ( ! HelperFunctions::has_access( KIRKI_ACCESS_LEVELS['FULL_ACCESS'] ) ) {
+ wp_send_json_error( 'Not authorized', 401 );
+ }
WordpressData::get_role_list();
}
@@ -566,6 +578,9 @@
if (
'get-users' === $endpoint
) {
+ if ( ! HelperFunctions::has_access( KIRKI_ACCESS_LEVELS['FULL_ACCESS'] ) ) {
+ wp_send_json_error( 'Not authorized', 401 );
+ }
WordpressData::get_user_list();
}
@@ -584,6 +599,9 @@
}
if ( $endpoint === 'get-common-data' ) {
+ if ( ! HelperFunctions::has_access( KIRKI_ACCESS_LEVELS['FULL_ACCESS'] ) ) {
+ wp_send_json_error( 'Not authorized', 401 );
+ }
WpAdmin::get_common_data();
}
@@ -755,6 +773,9 @@
// From manipulation from admin dashboard.
if ( $endpoint === 'get-editor-read-only-access-data' ) {
+ if ( ! HelperFunctions::has_access( KIRKI_ACCESS_LEVELS['FULL_ACCESS'] ) ) {
+ wp_send_json_error( 'Not authorized', 401 );
+ }
Page::get_editor_read_only_access_data();
}
}
--- a/kirki/includes/Ajax/Symbol.php
+++ b/kirki/includes/Ajax/Symbol.php
@@ -349,12 +349,43 @@
}
public static function get_pre_built_html_using_url() {
- $url = isset( $_GET['elementUrl'] ) ? $_GET['elementUrl'] : null;
- $new_url = sanitize_url( $url );
+ $raw_url = isset( $_GET['elementUrl'] ) ? wp_unslash( $_GET['elementUrl'] ) : '';
- $data = HelperFunctions::http_get( $new_url, array( 'sslverify' => false ) );
+ if ( empty( $raw_url ) ) {
+ wp_send_json_error( 'Missing elementUrl', 400 );
+ }
+ $allowed_host = wp_parse_url( home_url(), PHP_URL_HOST );
+ $requested = wp_parse_url( $raw_url );
+
+ if (
+ ! $requested ||
+ empty( $requested['host'] ) ||
+ strtolower( $requested['host'] ) !== strtolower( $allowed_host )
+ ) {
+ wp_send_json_error( 'URL not permitted', 403 );
+ }
+
+ $resolved_ip = gethostbyname( $requested['host'] );
+
+ if ( self::is_private_or_loopback_ip( $resolved_ip ) ) {
+ wp_send_json_error( 'URL not permitted', 403 );
+ }
+
+ $safe_url = self::rebuild_url( $requested );
+
+ $response = HelperFunctions::http_get( $safe_url, array(
+ 'timeout' => 30,
+ 'sslverify' => false,
+ 'redirection' => 0,
+ ));
+
+ if ( is_wp_error( $response ) ) {
+ wp_send_json_error( 'Request failed', 502 );
+ }
+
+ $body = wp_remote_retrieve_body( $response );
+ $data = json_decode( $body, true );
- $data = json_decode( $data, true );
$params = array(
'blocks' => $data['blocks'],
@@ -371,4 +402,34 @@
wp_send_json_success( $html );
}
+
+ /**
+ * Returns true for any IP that should never be reached from a server-side fetch.
+ * Covers loopback, RFC 1918, link-local (APIPA / cloud metadata), and IPv6 equivalents.
+ */
+ private static function is_private_or_loopback_ip( string $ip ): bool {
+ if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
+ return true;
+ }
+
+ return ! filter_var(
+ $ip,
+ FILTER_VALIDATE_IP,
+ FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
+ );
+ }
+
+
+ /**
+ * Reconstructs a URL from wp_parse_url parts so the raw user string
+ * is never passed directly to the HTTP client.
+ */
+ private static function rebuild_url( array $parts ): string {
+ $scheme = isset( $parts['scheme'] ) && strtolower( $parts['scheme'] ) === 'https' ? 'https' : 'https';
+ $host = $parts['host'];
+ $path = isset( $parts['path'] ) ? $parts['path'] : '/';
+ $query = isset( $parts['query'] ) ? '?' . $parts['query'] : '';
+
+ return "{$scheme}://{$host}{$path}{$query}";
+ }
}
--- a/kirki/includes/Frontend/Preview/Preview.php
+++ b/kirki/includes/Frontend/Preview/Preview.php
@@ -1936,7 +1936,9 @@
$content_count = count( $content );
for ( $i = 0; $i < $content_count; $i++ ) {
if ( is_array( $content[ $i ] ) ) {
- $html .= $this->recGenHTML( $content[ $i ]['id'], $options );
+ if( isset($content[ $i ]['id']) ){
+ $html .= $this->recGenHTML( $content[ $i ]['id'], $options );
+ }
} else {
$html .= htmlspecialchars( $content[ $i ] );
}
--- 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.11
+ * Version: 6.0.12
* Author: Kirki
* Author URI: https://kirki.com
* License: GPLv2 or later
@@ -26,7 +26,7 @@
// Define KIRKI_VERSION early to prevent bundled Kirki versions from loading.
if ( ! defined( 'KIRKI_VERSION' ) ) {
- define( 'KIRKI_VERSION', '6.0.11' );
+ define( 'KIRKI_VERSION', '6.0.12' );
}