Security
Headlines
HeadlinesLatestCVEs

Headline

CVE-2023-0728: class-wicked-folders-ajax.php in wicked-folders/tags/2.18.16/lib – WordPress Plugin Repository

The Wicked Folders plugin for WordPress is vulnerable to Cross-Site Request Forgery in versions up to, and including, 2.18.16. This is due to missing or incorrect nonce validation on the ajax_save_folder function. This makes it possible for unauthenticated attackers to invoke this function via forged request granted they can trick a site administrator into performing an action such as clicking on a link leading them to perform actions intended for administrators such as changing the folder structure maintained by the plugin.

CVE
#js#wordpress#php#perl#auth

1<?php23// Disable direct load4if ( ! defined( ‘ABSPATH’ ) ) {5 die( '-1’ );6}78final class Wicked_Folders_Ajax {910 private static $instance;1112 private function __construct() {1314 add_action( 'wp_ajax_wicked_folders_save_state’, array( $this, ‘ajax_save_state’ ) );15 add_action( 'wp_ajax_wicked_folders_move_object’, array( $this, ‘ajax_move_object’ ) );16 add_action( 'wp_ajax_wicked_folders_add_folder’, array( $this, ‘ajax_add_folder’ ) );17 add_action( 'wp_ajax_wicked_folders_clone_folder’, array( $this, ‘ajax_clone_folder’ ) );18 add_action( 'wp_ajax_wicked_folders_edit_folder’, array( $this, ‘ajax_edit_folder’ ) );19 add_action( 'wp_ajax_wicked_folders_delete_folder’, array( $this, ‘ajax_delete_folder’ ) );20 add_action( 'wp_ajax_wicked_folders_save_folder’, array( $this, ‘ajax_save_folder’ ) );21 add_action( 'wp_ajax_wicked_folders_save_sort_order’, array( $this, ‘ajax_save_sort_order’ ) );22 add_action( 'wp_ajax_wicked_folders_dismiss_message’, array( $this, ‘ajax_dismiss_message’ ) );23 add_action( 'wp_ajax_wicked_folders_get_child_folders’, array( $this, ‘ajax_get_child_folders’ ) );24 add_action( 'wp_ajax_wicked_folders_unassign_folders’, array( $this, ‘ajax_unassign_folders’ ) );25 add_action( 'wp_ajax_wicked_folders_save_folder_order’, array( $this, ‘ajax_save_folder_order’ ) );26 add_action( 'wp_ajax_wicked_folders_fetch_folders’, array( $this, ‘ajax_fetch_folders’ ) );2728 }2930 public static function get_instance() {31 if ( empty( self::$instance ) ) {32 self::$instance = new Wicked_Folders_Ajax();33 }34 return self::$instance;35 }3637 /**38 * Admin AJAX callback for moving an item to a new folder.39 *40 * @uses Wicked_Folders::move_object41 * @see Wicked_Folders::move_object42 */43 public function ajax_move_object() {4445 $result = array( ‘error’ => false, ‘items’ => array(), ‘folders’ => array() );46 $nonce = isset( $_REQUEST[‘nonce’] ) ? sanitize_text_field( $_REQUEST[‘nonce’] ) : false;47 $object_type = isset( $_REQUEST[‘object_type’] ) ? sanitize_text_field( $_REQUEST[‘object_type’] ) : false;48 $object_id = isset( $_REQUEST[‘object_id’] ) ? array_map( 'absint’, $_REQUEST[‘object_id’] ) : false;49 $destination_object_id = isset( $_REQUEST[‘destination_object_id’] ) ? (int) $_REQUEST[‘destination_object_id’] : false;50 $source_folder_id = isset( $_REQUEST[‘source_folder_id’] ) ? (int) $_REQUEST[‘source_folder_id’] : false;51 $post_type = isset( $_REQUEST[‘post_type’] ) ? sanitize_text_field( $_REQUEST[‘post_type’] ) : false;5253 /*54 if ( ! wp_verify_nonce( $nonce, ‘wicked_folders_move_object’ ) ) {55 $result[‘error’] = true;56 }57 */5859 if ( ! $object_type || ! false === $object_id || ! false === $destination_object_id ) {60 $result[‘error’] = true;61 }6263 if ( ! $result[‘error’] ) {64 foreach ( $object_id as $id ) {65 Wicked_Folders::move_object( $object_type, ( int ) $id, $destination_object_id, $source_folder_id );66 }6768 // Folders are used in response to update item counts69 $result[‘folders’] = Wicked_Folders::get_folders( $post_type );70 }7172 echo json_encode( $result );7374 wp_die();7576 }7778 /**79 * Admin AJAX callback that unassigns folders from an item.80 *81 */82 public function ajax_unassign_folders() {8384 $result = array( ‘error’ => false, ‘items’ => array(), ‘folders’ => array() );85 $nonce = isset( $_REQUEST[‘nonce’] ) ? sanitize_text_field( $_REQUEST[‘nonce’] ) : false;86 $taxonomy = isset( $_REQUEST[‘taxonomy’] ) ? sanitize_key( $_REQUEST[‘taxonomy’] ) : false;87 $object_id = isset( $_REQUEST[‘object_id’] ) ? array_map( 'absint’, $_REQUEST[‘object_id’] ) : false;88 $post_type = Wicked_Folders::get_post_name_from_tax_name( $taxonomy );89 $policy = false;90 $user_id = get_current_user_id();9192 if ( class_exists( ‘Wicked_Folders_Folder_Collection_Policy’ ) ) {93 $policy = Wicked_Folders_Folder_Collection_Policy::get_taxonomy_policy( $taxonomy );94 }9596 /*97 if ( ! wp_verify_nonce( $nonce, ‘wicked_folders_move_object’ ) ) {98 $result[‘error’] = true;99 }100 */101102 if ( ! $taxonomy ) {103 $result[‘error’] = true;104 }105106 if ( ! $result[‘error’] ) {107 foreach ( $object_id as $id ) {108 $folder_ids = array();109110 // If a policy exists for the taxonomy, only unassign folders111 // from the object that the user has assign permission for112 if ( $policy ) {113 $folder_ids = wp_get_object_terms( $id, $taxonomy, array( ‘fields’ => ‘ids’ ) );114115 for ( $i = count( $folder_ids ) - 1; $i > -1; $i-- ) {116 if ( $policy->can_assign( $folder_ids[ $i ], $user_id ) ) {117 unset( $folder_ids[ $i ] );118 }119 }120 }121122 $update_terms_result = wp_set_object_terms( ( int ) $id, $folder_ids, $taxonomy );123124 $result[‘items’][] = array(125 ‘objectId’ => $id,126 ‘taxonomy’ => $taxonomy,127 ‘result’ => $update_terms_result,128 );129 }130131 // Folders are used in response to update item counts132 $result[‘folders’] = Wicked_Folders::get_folders( $post_type );133 }134135 echo json_encode( $result );136137 wp_die();138 }139140 public function ajax_save_state() {141142 $result = array( ‘error’ => false );143 $data = json_decode( file_get_contents( ‘php://input’ ) );144 //$nonce = $data->nonce;145 $screen = $data->screen;146 $state = new Wicked_Folders_Screen_State( $screen, get_current_user_id(), $data->lang );147148 $state->folder = isset( $data->folder->id ) ? $data->folder->id : '0’;149 $state->folder_type = isset( $data->folder->type ) ? $data->folder->type : 'Wicked_Folders_Folder’;150 $state->expanded_folders = $data->expanded;151 $state->tree_pane_width = $data->treePaneWidth;152 $state->orderby = $data->orderby;153 $state->order = $data->order;154 $state->is_folder_pane_visible = $data->isFolderPaneVisible;155 $state->sort_mode = $data->sortMode;156157 if ( isset( $data->hideAssignedItems ) ) {158 $state->hide_assigned_items = $data->hideAssignedItems;159 }160161 $state->save();162163 echo json_encode( $result );164165 wp_die();166167 }168169 public function ajax_add_folder() {170171 $this->ajax_edit_folder();172173 }174175 public function ajax_edit_folder() {176177 $result = array( ‘error’ => false, ‘message’ => __( 'An error occurred. Please try again.’, ‘wicked-folders’ ) );178 $nonce = isset( $_REQUEST[‘nounce’] ) ? sanitize_text_field( $_REQUEST[‘nounce’] ) : false;179 $id = isset( $_REQUEST[‘id’] ) ? ( int ) $_REQUEST[‘id’] : false;180 $name = isset( $_REQUEST[‘name’] ) ? sanitize_text_field( $_REQUEST[‘name’] ) : false;181 $parent = isset( $_REQUEST[‘parent’] ) ? ( int ) $_REQUEST[‘parent’] : false;182 $post_type = isset( $_REQUEST[‘post_type’] ) ? sanitize_key( $_REQUEST[‘post_type’] ) : false;183 $tax_name = Wicked_Folders::get_tax_name( $post_type );184 $url = admin_url( ‘edit.php?post_type=’ . $post_type . ‘&page=’ . $tax_name );185186 //if ( ! wp_verify_nonce( $nonce, ‘wicked_folders_add_folder’ ) ) {187 // $result[‘error’] = true;188 //}189190 if ( ! $name || ! $post_type ) {191 $result[‘message’] = __( 'Invalid name or post type.’, ‘wicked-folders’ );192 $result[‘error’] = true;193 }194195 if ( -1 == $parent || false === $parent ) {196 $parent = 0;197 }198199 if ( ! $result[‘error’] ) {200 if ( $id ) {201 $existing_term = get_term_by( 'name’, $name, $tax_name );202 // Don’t allow terms with the same name at the same level203 if ( $existing_term && $existing_term->parent == $parent ) {204 $term = new WP_Error( ‘term_exists’ );205 } else {206 $term = wp_update_term( $id, $tax_name, array(207 ‘name’ => $name,208 ‘parent’ => $parent,209 ) );210 }211 } else {212 $term = wp_insert_term( $name, $tax_name, array(213 ‘parent’ => $parent,214 ) );215 }216 if ( is_wp_error( $term ) ) {217 if ( isset( $term->errors[‘term_exists’] ) ) {218 $result[‘message’] = __( 'A folder with that name already exists in the selected parent folder. Please enter a different name or select a different parent folder.’, ‘wicked-folders’ );219 } else {220 $result[‘message’] = $term->get_error_message();221 }222 $result[‘error’] = true;223 } else {224 $select = wp_dropdown_categories( array(225 ‘orderby’ => 'name’,226 ‘order’ => 'ASC’,227 ‘show_option_none’ => '— ' . __( 'Parent Folder’, ‘wicked-folders’ ) . ' —’,228 ‘taxonomy’ => $tax_name,229 ‘depth’ => 0,230 ‘hierarchical’ => true,231 ‘hide_empty’ => false,232 ‘selected’ => $parent,233 ‘echo’ => false,234 ‘option_none_value’ => 0,235 ) );236 $result = array(237 ‘error’ => false,238 ‘folderId’ => $term[‘term_id’],239 ‘folderUrl’ => add_query_arg( 'folder’, $term[‘term_id’], $url ),240 ‘select’ => $select,241 );242 }243 }244245 echo json_encode( $result );246247 wp_die();248249 }250251 public function ajax_delete_folder() {252253 // TODO: check nonce254 $result = array( ‘error’ => false );255 $nonce = isset( $_REQUEST[‘nounce’] ) ? sanitize_text_field( $_REQUEST[‘nounce’] ) : false;256 $id = isset( $_REQUEST[‘id’] ) ? ( int ) $_REQUEST[‘id’] : false;257 $post_type = isset( $_REQUEST[‘post_type’] ) ? sanitize_key( $_REQUEST[‘post_type’] ) : false;258 $taxonomy = isset( $_REQUEST[‘taxonomy’] ) ? sanitize_key( $_REQUEST[‘taxonomy’] ) : Wicked_Folders::get_tax_name( $post_type );259260 $delete_result = wp_delete_term( $id, $taxonomy );261262 if ( is_wp_error( $delete_result ) ) {263 $result[‘error’] = true;264 $result[‘message’] = $delete_result->get_error_message();265 }266267 echo json_encode( $result );268269 wp_die();270271 }272273 public function ajax_save_folder() {274275 $response = array( ‘error’ => false );276 //$method = $_SERVER[‘REQUEST_METHOD’];277 $method = isset( $_SERVER[‘HTTP_X_HTTP_METHOD_OVERRIDE’] ) ? $_SERVER[‘HTTP_X_HTTP_METHOD_OVERRIDE’] : 'POST’;278 $method = isset( $_REQUEST[‘_method_override’] ) ? sanitize_text_field( $_REQUEST[‘_method_override’] ) : $method;279 $folder = json_decode( file_get_contents( ‘php://input’ ) );280 $policy = false;281 $user_id = get_current_user_id();282283 if ( ‘DELETE’ == $method ) {284 $folder_id = ( int ) $_REQUEST[‘id’];285 $taxonomy = sanitize_key( $_REQUEST[‘taxonomy’] );286 } else {287 $folder_id = isset( $folder->id ) ? $folder->id : null;288 $taxonomy = $folder->taxonomy;289 }290291 // The Polylang plugin uses the jQuery ajaxPrefilter function to alter292 // AJAX requests which breaks the request (see polylang/js/media.js).293 // The following code checks to see if the Polylang plugin is active and,294 // if so, removes the string added by the Polylang plugin so the request295 // can be processed properly.296 if ( function_exists( ‘is_plugin_active’ ) && ( is_plugin_active( ‘polylang/polylang.php’ ) || is_plugin_active( ‘polylang-pro/polylang.php’ ) ) ) {297 $data = file_get_contents( ‘php://input’ );298 $data = preg_replace( '/^pll_post_id=([0-9|undefined]*)?&/’, '’, $data );299 $data = preg_replace( '/&pll_ajax_backend=1/’, '’, $data );300 $folder = json_decode( $data );301 }302303 // Similar issue with Anything Order by Terms plugin; adds a screen_id304 // parameter (see anything-order-by-terms/modules/base/script.js) which305 // breaks the request306 if ( function_exists( ‘is_plugin_active’ ) && is_plugin_active( ‘anything-order-by-terms/anything-order.php’ ) ) {307 $data = file_get_contents( ‘php://input’ );308 $data = preg_replace( '/&screen_id=([A-Z\-\_0-9]*)/i’, '’, $data );309 $folder = json_decode( $data );310 }311312 if ( class_exists( ‘Wicked_Folders_Folder_Collection_Policy’ ) ) {313 $policy = Wicked_Folders_Folder_Collection_Policy::get_taxonomy_policy( $taxonomy );314315 // If there’s a security policy, enforce it316 if ( $policy ) {317 if (318 ( ‘POST’ == $method && false == $policy->can_create( $user_id ) ) ||319 ( ‘PUT’ == $method && false == $policy->can_edit( $folder_id, $user_id ) ) ||320 ( ‘DELETE’ == $method && false == $policy->can_delete( $folder_id, $user_id ) )321 ) {322 $response[‘message’] = __( 'Permission denied.’, ‘wicked-folders’ );323 $response[‘error’] = true;324325 status_header( 400 );326327 echo json_encode( $response );328329 die();330 }331 }332 }333334 // Insert folder335 if ( ‘POST’ == $method ) {336 // TODO: Refactor. We should be working with a proper folder object337 // that is initalized from the JSON in the request and then338 // serialized as JSON339 $term = wp_insert_term( $folder->name, $folder->taxonomy, array(340 ‘parent’ => $folder->parent,341 ‘slug’ => Wicked_Folders_Term_Folder::generate_unique_slug( $folder->name, $folder->taxonomy ),342 ) );343344 if ( ! is_wp_error( $term ) ) {345 $owner_data = get_userdata( $user_id );346 $folder->id = ( string ) $term[‘term_id’];347 $folder->ownerId = $user_id;348 $folder->ownerName = isset( $owner_data->data->display_name ) ? $owner_data->data->display_name : '’;349350 add_term_meta( $term[‘term_id’], 'wf_owner_id’, $user_id );351 }352 }353354 // Update folder355 if ( ‘PUT’ == $method ) {356 $term = wp_update_term( $folder->id, $folder->taxonomy, array(357 ‘name’ => $folder->name,358 ‘parent’ => $folder->parent,359 ) );360361 update_term_meta( $folder->id, 'wf_owner_id’, ( int ) $folder->ownerId );362 }363364 // Delete folder365 if ( ‘DELETE’ == $method ) {366 $term = wp_delete_term( ( int ) $_REQUEST[‘id’], sanitize_key( $_REQUEST[‘taxonomy’] ) );367 // Delete the sort meta for the folder368 delete_metadata( 'post’, 0, ‘_wicked_folder_order__’ . sanitize_key( $_REQUEST[‘taxonomy’] ) . ‘__’ . sanitize_text_field( $_REQUEST[‘id’] ), false, true );369 }370371 if ( is_wp_error( $term ) ) {372 if ( isset( $term->errors[‘term_exists’] ) ) {373 $response[‘message’] = __( 'A folder with that name already exists in the selected parent folder. Please enter a different name or select a different parent folder.’, ‘wicked-folders’ );374 } else {375 $response[‘message’] = $term->get_error_message();376 }377 $response[‘error’] = true;378 status_header( 400 );379 echo json_encode( $response );380 die();381 } else {382 echo json_encode( $folder );383 }384385 wp_die();386387 }388389 public function ajax_clone_folder() {390 $folders = array();391 $id = isset( $_REQUEST[‘id’] ) ? ( int ) $_REQUEST[‘id’] : false;392 $post_type = isset( $_REQUEST[‘post_type’] ) ? sanitize_key( $_REQUEST[‘post_type’] ) : false;393 $parent = isset( $_REQUEST[‘parent’] ) ? ( int ) $_REQUEST[‘parent’] : false;394 $clone_children = isset( $_REQUEST[‘clone_children’] ) && ‘true’ == $_REQUEST[‘clone_children’] ? true : false;395 $taxonomy = Wicked_Folders::get_tax_name( $post_type );396 $user_id = get_current_user_id();397398 try {399 if ( class_exists( ‘Wicked_Folders_Folder_Collection_Policy’ ) ) {400 $policy = Wicked_Folders_Folder_Collection_Policy::get_taxonomy_policy( $taxonomy );401402 // If there’s a security policy, enforce it403 if ( $policy ) {404 // Require edit permission to clone folder405 if ( ! $policy->can_edit( $id, $user_id ) ) {406 throw new Exception( __( 'Permission denied.’, ‘wicked-folders’ ) );407 }408 }409 }410411 $folder = Wicked_Folders::get_folder( $id, $post_type );412 $folders = $folder->clone_folder( $clone_children, $parent );413414 echo json_encode( $folders );415 } catch ( Exception $e ) {416 status_header( 400 );417418 echo esc_html( $e->getMessage() );419420 die();421 }422423 wp_die();424 }425426 public function ajax_save_sort_order() {427428 global $wpdb;429430 $new_order = array();431 $screen = sanitize_text_field( $_REQUEST[‘screen’] );432 $folder_id = sanitize_text_field( $_REQUEST[‘folder_id’] );433 $post_type = sanitize_text_field( $_REQUEST[‘post_type’] );434 $taxonomy = sanitize_text_field( $_REQUEST[‘taxonomy’] );435 $object_ids = array_map( 'absint’, $_REQUEST[‘object_ids’] );436 $order = sanitize_text_field( $_REQUEST[‘order’] );437 $orderby = sanitize_text_field( $_REQUEST[‘orderby’] );438 $page_number = ( int ) $_REQUEST[‘page_number’];439 $items_per_page = ( int ) $_REQUEST[‘items_per_page’];440 $sort_key = ‘_wicked_folder_order__’ . $taxonomy . ‘__’ . $folder_id;441 $before = $items_per_page * ( $page_number - 1 );442 $after = $before + $items_per_page;443444 // Initialize folder order. This will ensure that every post in the folder445 // has an order meta key so that we can update later446 Wicked_Folders::initalize_folder_order( $folder_id, $taxonomy );447448 // Get IDs of posts assigned to the folder ordered the same way as they449 // were prior to changing the sort order450 $q = array(451 ‘post_type’ => $post_type,452 ‘posts_per_page’ => -1,453 ‘fields’ => 'ids’,454 ‘order’ => $order,455 ‘orderby’ => $orderby,456 ‘tax_query’ => array(457 array(458 ‘taxonomy’ => $taxonomy,459 ‘field’ => 'term_id’,460 ‘terms’ => ( int ) $folder_id,461 )462 )463 );464465 if ( ‘wicked_folder_order’ == $orderby ) {466 $q[‘orderby’] = array(467 ‘meta_value_num’ => $order,468 ‘title’ => 'ASC’,469 );470 $q[‘meta_key’] = $sort_key;471 }472473 $post_ids = get_posts( $q );474475 $n = count( $post_ids );476477 // Get the order of posts on previous pages478 for ( $i = 0; $i < $before; $i++ ) {479 $new_order[] = $post_ids[ $i ];480 }481482 // Append the new order of posts for the current page483 $new_order = array_merge( $new_order, $object_ids );484485 // Add posts from subsequent pages486 for ( $i = $after; $i < $n; $i++ ) {487 $new_order[] = $post_ids[ $i ];488 }489490 // Get the current sort orders491 $current_order = $wpdb->get_results( $wpdb->prepare( "SELECT post_id, meta_value FROM {$wpdb->prefix}postmeta WHERE meta_key = %s ORDER BY post_id", $sort_key ), OBJECT_K );492493 foreach ( $new_order as $index => $post_id ) {494 $sort = ( $n - $index ) * -1;495 // Only update posts where the sort order has changed496 if ( $sort != $current_order[ $post_id ]->meta_value ) {497 $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}postmeta SET meta_value = %s WHERE post_id = %d AND meta_key = %s", $sort, $post_id, $sort_key ) );498 }499 }500501 }502503 public function ajax_dismiss_message() {504 $result = array( ‘error’ => false );505 $dismissed_messages = ( array ) get_user_option( ‘wicked_folders_dismissed_messages’ );506 $dismissed_messages[] = $_POST[‘key’];507508 update_user_meta( get_current_user_id(), 'wicked_folders_dismissed_messages’, $dismissed_messages );509510 echo json_encode( $result );511512 wp_die();513 }514515 public function ajax_get_child_folders() {516 global $wpdb;517518 $folders = array();519 $folder_type = sanitize_text_field( $_REQUEST[‘folder_type’] );520 $folder_id = sanitize_key( $_REQUEST[‘folder_id’] );521 $post_type = sanitize_key( $_REQUEST[‘post_type’] );522523 $folder = Wicked_Folders::get_dynamic_folder( $folder_type, $folder_id, $post_type );524525 if ( $folder ) {526 $folder->fetch();527 $folders = $folder->get_child_folders();528 }529530 echo json_encode( $folders );531532 wp_die();533 }534535 public function ajax_save_folder_order() {536 global $wpdb;537538 $result = array( ‘error’ => false );539 $folders = isset( $_REQUEST[‘folders’] ) && is_array( $_REQUEST[‘folders’ ] ) ? array_map( array( $this, ‘sanitize_folder_order_param’ ), $_REQUEST[‘folders’] ) : array();540 $order_field_exists = Wicked_Folders::get_instance()->term_order_field_exists();541542 foreach ( $folders as $folder ) {543 update_term_meta( $folder[‘id’], 'wf_order’, ( int ) $folder[‘order’] );544545 // Update wp_terms.term_order if the field exists. This field is546 // used by the Category Order and Taxonomy Terms Order plugin so547 // this should ensure that the folders appear in the expected order548 // for users who use this plugin549 if ( $order_field_exists ) {550 $wpdb->update(551 $wpdb->terms,552 array( ‘term_order’ => $folder[‘order’] ),553 array( ‘term_id’ => ( int ) $folder[‘id’] ),554 array( ‘%d’ ),555 array( ‘%d’ )556 );557 }558 }559560 echo json_encode( $result );561562 wp_die();563 }564565 public function ajax_fetch_folders() {566 $folders = array();567 $taxonomy = isset( $_GET[‘taxonomy’] ) ? sanitize_text_field( $_GET[‘taxonomy’] ) : false;568569 if ( $taxonomy ) {570 $post_type = Wicked_Folders::get_post_name_from_tax_name( $taxonomy );571572 $folders = Wicked_Folders::get_folders( $post_type, $taxonomy );573 }574575 echo json_encode( $folders );576577 wp_die();578 }579580 /**581 * Sanitizes the value of an entry within a folder order array.582 */583 public function sanitize_folder_order_param( $value ) {584 return array(585 ‘id’ => ( int ) $value[‘id’],586 ‘order’ => ( int ) $value[‘order’],587 );588 }589}

CVE: Latest News

CVE-2023-50976: Transactions API Authorization by oleiman · Pull Request #14969 · redpanda-data/redpanda