CVE-2024-1995: Insufficient Information (NVD-CWE-noinfo)
value: The Smart Custom Fields plugin for WordPress is vulnerable to unauthorized access of data due to a missing capability check on the relational_posts_search() function in all versions up to, and including, 4.2.2. This makes it possible for authenticated attackers, with subscrber-level access and above, to retrieve post content that is password protected and/or private.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-03-20T02:15Z
Repository: https://github.com/inc2734/smart-custom-fields
Language: PHP
Files changed: classes/fields/class.field-related-posts.php, classes/fields/class.field-related-terms.php
Commit date: 2024-03-16 23:20:17 +0900
Commit message: Security fix

Diff: diff --git a/classes/fields/class.field-related-posts.php b/classes/fields/class.field-related-posts.php
index 95f0234..c779950 100644
--- a/classes/fields/class.field-related-posts.php
+++ b/classes/fields/class.field-related-posts.php
@@ -75,60 +75,74 @@ class Smart_Custom_Fields_Field_Related_Posts extends Smart_Custom_Fields_Field_
 	/**
 	 * Process that loading post when clicking post load button.
 	 */
 	public function relational_posts_search() {
 		check_ajax_referer( SCF_Config::NAME . '-relation-post-types', 'nonce' );
+
 		$_posts = array();
 
 		$post_types = filter_input( INPUT_POST, 'post_types' );
 		if ( $post_types ) {
-			$post_type = explode( ',', $post_types );
-			$args      = array(
-				'post_type'      => $post_type,
-				'order'          => 'ASC',
-				'orderby'        => 'ID',
-				'posts_per_page' => -1,
-				'post_status'    => 'any',
-			);
+			$post_type              = explode( ',', $post_types );
+			$retrievable_post_types = array();
 
-			$click_count = filter_input( INPUT_POST, 'click_count' );
-			if ( $click_count ) {
-				$posts_per_page = get_option( 'posts_per_page' );
-				$offset         = $click_count * $posts_per_page;
-				$args           = array_merge(
-					$args,
-					array(
-						'offset'         => $offset,
-						'posts_per_page' => $posts_per_page,
-					)
-				);
+			foreach ( $post_type as $_post_type ) {
+				$post_type_object = get_post_type_object( $_post_type );
+
+				if ( current_user_can( $post_type_object->cap->edit_posts ) ) {
+					$retrievable_post_types[] = $_post_type;
+				}
 			}
 
-			$s = filter_input( INPUT_POST, 's' );
-			if ( $s ) {
-				$args = array_merge(
-					$args,
-					array(
-						's' => $s,
-					)
+			if ( $retrievable_post_types ) {
+				$args = array(
+					'post_type'      => $retrievable_post_types,
+					'order'          => 'ASC',
+					'orderby'        => 'ID',
+					'posts_per_page' => -1,
+					'post_status'    => 'any',
 				);
-			}
 
-			$field_name = sanitize_text_field( filter_input( INPUT_POST, 'field_name' ) );
+				$click_count = filter_input( INPUT_POST, 'click_count' );
+				if ( $click_count ) {
+					$posts_per_page = get_option( 'posts_per_page' );
+					$offset         = $click_count * $posts_per_page;
+					$args           = array_merge(
+						$args,
+						array(
+							'offset'         => $offset,
+							'posts_per_page' => $posts_per_page,
+						)
+					);
+				}
+
+				$s = filter_input( INPUT_POST, 's' );
+				if ( $s ) {
+					$args = array_merge(
+						$args,
+						array(
+							's' => $s,
+						)
+					);
+				}
+
+				$field_name = sanitize_text_field( filter_input( INPUT_POST, 'field_name' ) );
 
-			/**
-			 * This filter will be always applied when it queries posts in related posts field.
-			 */
-			$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args', $args, $field_name, $post_type );
+				/**
+				* This filter will be always applied when it queries posts in related posts field.
+				*/
+				$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args', $args, $field_name, $post_type );
 
-			/**
-			 * This filter will only be applied when getting posts via ajax call, therefore it won't be applied for the first load.
-			 */
-			$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args_ajax_call', $args, $field_name, $post_type );
+				/**
+				* This filter will only be applied when getting posts via ajax call, therefore it won't be applied for the first load.
+				*/
+				$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args_ajax_call', $args, $field_name, $post_type );
 
-			$_posts = get_posts( $args );
+				$_posts = get_posts( $args );
+			}
 		}
+
 		header( 'Content-Type: application/json; charset=utf-8' );
 		echo wp_json_encode( $_posts );
 		die();
 	}
 
@@ -142,37 +156,52 @@ class Smart_Custom_Fields_Field_Related_Posts extends Smart_Custom_Fields_Field_
 	public function get_field( $index, $value ) {
 		$name      = $this->get_field_name_in_editor( $index );
 		$disabled  = $this->get_disable_attribute( $index );
 		$post_type = $this->get( 'post-type' );
 		$limit     = $this->get( 'limit' );
-		if ( ! $post_type ) {
-			$post_type = array( 'post' );
-		}
-		if ( ! preg_match( '/^\d+$/', $limit ) ) {
-			$limit = '';
-		}
+
+		$choices_posts  = array();
 		$posts_per_page = get_option( 'posts_per_page' );
 
-		$args = array(
-			'post_type'      => $post_type,
-			'order'          => 'ASC',
-			'orderby'        => 'ID',
-			'posts_per_page' => $posts_per_page,
-			'post_status'    => 'any',
-		);
+		if ( $post_type ) {
+			$retrievable_post_types = array();
+
+			foreach ( $post_type as $_post_type ) {
+				$post_type_object = get_post_type_object( $_post_type );
 
-		/**
-		 * This filter will be always applied when it queries posts in related posts field.
-		 */
-		$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args', $args, $name, $post_type );
-		/**
-		 * This filter will only be applied in the first load, therefore it won't be applied when getting posts via ajax call.
-		 */
-		$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args_first_load', $args, $name, $post_type );
-
-		// Get posts to show in the first load.
-		$choices_posts = get_posts( $args );
+				if ( current_user_can( $post_type_object->cap->edit_posts ) ) {
+					$retrievable_post_types[] = $_post_type;
+				}
+			}
+
+			if ( $retrievable_post_types ) {
+				if ( ! preg_match( '/^\d+$/', $limit ) ) {
+					$limit = '';
+				}
+
+				$args = array(
+					'post_type'      => $retrievable_post_types,
+					'order'          => 'ASC',
+					'orderby'        => 'ID',
+					'posts_per_page' => $posts_per_page,
+					'post_status'    => 'any',
+				);
+
+				/**
+				* This filter will be always applied when it queries posts in related posts field.
+				*/
+				$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args', $args, $name, $post_type );
+
+				/**
+				* This filter will only be applied in the first load, therefore it won't be applied when getting posts via ajax call.
+				*/
+				$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args_first_load', $args, $name, $post_type );
+
+				// Get posts to show in the first load.
+				$choices_posts = get_posts( $args );
+			}
+		}
 
 		$choices_li = array();
 		foreach ( $choices_posts as $_post ) {
 			$post_title = get_the_title( $_post->ID );
 			if ( empty( $post_title ) ) {
diff --git a/classes/fields/class.field-related-terms.php b/classes/fields/class.field-related-terms.php
index 231246b..1cac386 100644
--- a/classes/fields/class.field-related-terms.php
+++ b/classes/fields/class.field-related-terms.php
@@ -75,50 +75,63 @@ class Smart_Custom_Fields_Field_Related_Terms extends Smart_Custom_Fields_Field_
 	/**
 	 * Process that loading post when clicking post load button.
 	 */
 	public function relational_terms_search() {
 		check_ajax_referer( SCF_Config::NAME . '-relation-taxonomies', 'nonce' );
+
 		$_terms = array();
-		$args   = array();
 
 		$taxonomies = filter_input( INPUT_POST, 'taxonomies' );
 		if ( $taxonomies ) {
-			$taxonomies = explode( ',', $taxonomies );
-			$args       = array(
-				'taxonomy'     => $taxonomies,
-				'order'        => 'ASC',
-				'orderby'      => 'ID',
-				'number'       => '',
-				'hide_empty'   => false,
-				'hierarchical' => false,
-			);
+			$taxonomies             = explode( ',', $taxonomies );
+			$retrievable_taxonomies = array();
 
-			$click_count = filter_input( INPUT_POST, 'click_count' );
-			if ( $click_count ) {
-				$number = get_option( 'posts_per_page' );
-				$offset = $click_count * $number;
-				$args   = array_merge(
-					$args,
-					array(
-						'offset' => $offset,
-						'number' => $number,
-					)
-				);
+			foreach ( $taxonomies as $_taxonomy ) {
+				$tax = get_taxonomy( $_taxonomy );
+
+				if ( current_user_can( $tax->cap->manage_terms ) ) {
+					$retrievable_taxonomies[] = $_taxonomy;
+				}
 			}
 
-			$search = filter_input( INPUT_POST, 'search' );
-			if ( $search ) {
-				$args = array_merge(
-					$args,
-					array(
-						'search' => $search,
-					)
+			if ( $retrievable_taxonomies ) {
+				$args = array(
+					'taxonomy'     => $retrievable_taxonomies,
+					'order'        => 'ASC',
+					'orderby'      => 'ID',
+					'number'       => '',
+					'hide_empty'   => false,
+					'hierarchical' => false,
 				);
-			}
 
-			$_terms = get_terms( $args );
+				$click_count = filter_input( INPUT_POST, 'click_count' );
+				if ( $click_count ) {
+					$number = get_option( 'posts_per_page' );
+					$offset = $click_count * $number;
+					$args   = array_merge(
+						$args,
+						array(
+							'offset' => $offset,
+							'number' => $number,
+						)
+					);
+				}
+
+				$search = filter_input( INPUT_POST, 'search' );
+				if ( $search ) {
+					$args = array_merge(
+						$args,
+						array(
+							'search' => $search,
+						)
+					);
+				}
+
+				$_terms = get_terms( $args );
+			}
 		}
+
 		header( 'Content-Type: application/json; charset=utf-8' );
 		echo wp_json_encode( $_terms );
 		die();
 	}
 
@@ -132,30 +145,45 @@ class Smart_Custom_Fields_Field_Related_Terms extends Smart_Custom_Fields_Field_
 	public function get_field( $index, $value ) {
 		$name       = $this->get_field_name_in_editor( $index );
 		$disabled   = $this->get_disable_attribute( $index );
 		$taxonomies = $this->get( 'taxonomy' );
 		$limit      = $this->get( 'limit' );
-		if ( ! $taxonomies ) {
-			$taxonomies = array( 'category' );
-		}
-		if ( ! preg_match( '/^\d+$/', $limit ) ) {
-			$limit = '';
+
+		$choices_terms = array();
+		$number        = get_option( 'posts_per_page' );
+
+		if ( $taxonomies ) {
+			$retrievable_taxonomies = array();
+
+			foreach ( $taxonomies as $_taxonomy ) {
+				$tax = get_taxonomy( $_taxonomy );
+
+				if ( current_user_can( $tax->cap->manage_terms ) ) {
+					$retrievable_taxonomies[] = $_taxonomy;
+				}
+			}
+
+			if ( $retrievable_taxonomies ) {
+				if ( ! preg_match( '/^\d+$/', $limit ) ) {
+					$limit = '';
+				}
+
+				// choicse
+				$choices_terms = get_terms(
+					array(
+						'taxonomy'     => $taxonomies,
+						'order'        => 'ASC',
+						'orderby'      => 'ID',
+						'hide_empty'   => false,
+						'hierarchical' => false,
+						'number'       => $number,
+					)
+				);
+			}
 		}
-		$number = get_option( 'posts_per_page' );
 
-		// choicse
-		$choices_terms = get_terms(
-			array(
-				'taxonomy'     => $taxonomies,
-				'order'        => 'ASC',
-				'orderby'      => 'ID',
-				'hide_empty'   => false,
-				'hierarchical' => false,
-				'number'       => $number,
-			)
-		);
-		$choices_li    = array();
+		$choices_li = array();
 		foreach ( $choices_terms as $_term ) {
 			$term_name = $_term->name;
 			if ( empty( $term_name ) ) {
 				$term_name = '&nbsp;';
 			}

Vulnerable code: 			$post_type = explode( ',', $post_types );
			$args      = array(
				'post_type'      => $post_type,
				'order'          => 'ASC',
				'orderby'        => 'ID',
				'posts_per_page' => -1,
				'post_status'    => 'any',
			);
			$click_count = filter_input( INPUT_POST, 'click_count' );
			if ( $click_count ) {
				$posts_per_page = get_option( 'posts_per_page' );
				$offset         = $click_count * $posts_per_page;
				$args           = array_merge(
					$args,
					array(
						'offset'         => $offset,
						'posts_per_page' => $posts_per_page,
					)
				);
			$s = filter_input( INPUT_POST, 's' );
			if ( $s ) {
				$args = array_merge(
					$args,
					array(
						's' => $s,
					)
			}
			$field_name = sanitize_text_field( filter_input( INPUT_POST, 'field_name' ) );
			/**
			 * This filter will be always applied when it queries posts in related posts field.
			 */
			$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args', $args, $field_name, $post_type );
			/**
			 * This filter will only be applied when getting posts via ajax call, therefore it won't be applied for the first load.
			 */
			$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args_ajax_call', $args, $field_name, $post_type );
			$_posts = get_posts( $args );
		if ( ! $post_type ) {
			$post_type = array( 'post' );
		}
		if ( ! preg_match( '/^\d+$/', $limit ) ) {
			$limit = '';
		}
		$args = array(
			'post_type'      => $post_type,
			'order'          => 'ASC',
			'orderby'        => 'ID',
			'posts_per_page' => $posts_per_page,
			'post_status'    => 'any',
		);
		/**
		 * This filter will be always applied when it queries posts in related posts field.
		 */
		$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args', $args, $name, $post_type );
		/**
		 * This filter will only be applied in the first load, therefore it won't be applied when getting posts via ajax call.
		 */
		$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args_first_load', $args, $name, $post_type );

		// Get posts to show in the first load.
		$choices_posts = get_posts( $args );
		$args   = array();
			$taxonomies = explode( ',', $taxonomies );
			$args       = array(
				'taxonomy'     => $taxonomies,
				'order'        => 'ASC',
				'orderby'      => 'ID',
				'number'       => '',
				'hide_empty'   => false,
				'hierarchical' => false,
			);
			$click_count = filter_input( INPUT_POST, 'click_count' );
			if ( $click_count ) {
				$number = get_option( 'posts_per_page' );
				$offset = $click_count * $number;
				$args   = array_merge(
					$args,
					array(
						'offset' => $offset,
						'number' => $number,
					)
				);
			$search = filter_input( INPUT_POST, 'search' );
			if ( $search ) {
				$args = array_merge(
					$args,
					array(
						'search' => $search,
					)
			}
			$_terms = get_terms( $args );
		if ( ! $taxonomies ) {
			$taxonomies = array( 'category' );
		}
		if ( ! preg_match( '/^\d+$/', $limit ) ) {
			$limit = '';
		$number = get_option( 'posts_per_page' );
		// choicse
		$choices_terms = get_terms(
			array(
				'taxonomy'     => $taxonomies,
				'order'        => 'ASC',
				'orderby'      => 'ID',
				'hide_empty'   => false,
				'hierarchical' => false,
				'number'       => $number,
			)
		);
		$choices_li    = array();
Fixed code: 
			$post_type              = explode( ',', $post_types );
			$retrievable_post_types = array();
			foreach ( $post_type as $_post_type ) {
				$post_type_object = get_post_type_object( $_post_type );

				if ( current_user_can( $post_type_object->cap->edit_posts ) ) {
					$retrievable_post_types[] = $_post_type;
				}
			if ( $retrievable_post_types ) {
				$args = array(
					'post_type'      => $retrievable_post_types,
					'order'          => 'ASC',
					'orderby'        => 'ID',
					'posts_per_page' => -1,
					'post_status'    => 'any',
				$click_count = filter_input( INPUT_POST, 'click_count' );
				if ( $click_count ) {
					$posts_per_page = get_option( 'posts_per_page' );
					$offset         = $click_count * $posts_per_page;
					$args           = array_merge(
						$args,
						array(
							'offset'         => $offset,
							'posts_per_page' => $posts_per_page,
						)
					);
				}

				$s = filter_input( INPUT_POST, 's' );
				if ( $s ) {
					$args = array_merge(
						$args,
						array(
							's' => $s,
						)
					);
				}

				$field_name = sanitize_text_field( filter_input( INPUT_POST, 'field_name' ) );
				/**
				* This filter will be always applied when it queries posts in related posts field.
				*/
				$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args', $args, $field_name, $post_type );
				/**
				* This filter will only be applied when getting posts via ajax call, therefore it won't be applied for the first load.
				*/
				$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args_ajax_call', $args, $field_name, $post_type );
				$_posts = get_posts( $args );
			}


		$choices_posts  = array();
		if ( $post_type ) {
			$retrievable_post_types = array();

			foreach ( $post_type as $_post_type ) {
				$post_type_object = get_post_type_object( $_post_type );
				if ( current_user_can( $post_type_object->cap->edit_posts ) ) {
					$retrievable_post_types[] = $_post_type;
				}
			}

			if ( $retrievable_post_types ) {
				if ( ! preg_match( '/^\d+$/', $limit ) ) {
					$limit = '';
				}

				$args = array(
					'post_type'      => $retrievable_post_types,
					'order'          => 'ASC',
					'orderby'        => 'ID',
					'posts_per_page' => $posts_per_page,
					'post_status'    => 'any',
				);

				/**
				* This filter will be always applied when it queries posts in related posts field.
				*/
				$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args', $args, $name, $post_type );

				/**
				* This filter will only be applied in the first load, therefore it won't be applied when getting posts via ajax call.
				*/
				$args = apply_filters( SCF_Config::PREFIX . 'custom_related_posts_args_first_load', $args, $name, $post_type );

				// Get posts to show in the first load.
				$choices_posts = get_posts( $args );
			}
		}

			$taxonomies             = explode( ',', $taxonomies );
			$retrievable_taxonomies = array();
			foreach ( $taxonomies as $_taxonomy ) {
				$tax = get_taxonomy( $_taxonomy );

				if ( current_user_can( $tax->cap->manage_terms ) ) {
					$retrievable_taxonomies[] = $_taxonomy;
				}
			if ( $retrievable_taxonomies ) {
				$args = array(
					'taxonomy'     => $retrievable_taxonomies,
					'order'        => 'ASC',
					'orderby'      => 'ID',
					'number'       => '',
					'hide_empty'   => false,
					'hierarchical' => false,
				$click_count = filter_input( INPUT_POST, 'click_count' );
				if ( $click_count ) {
					$number = get_option( 'posts_per_page' );
					$offset = $click_count * $number;
					$args   = array_merge(
						$args,
						array(
							'offset' => $offset,
							'number' => $number,
						)
					);
				}

				$search = filter_input( INPUT_POST, 'search' );
				if ( $search ) {
					$args = array_merge(
						$args,
						array(
							'search' => $search,
						)
					);
				}

				$_terms = get_terms( $args );
			}


		$choices_terms = array();
		$number        = get_option( 'posts_per_page' );

		if ( $taxonomies ) {
			$retrievable_taxonomies = array();

			foreach ( $taxonomies as $_taxonomy ) {
				$tax = get_taxonomy( $_taxonomy );

				if ( current_user_can( $tax->cap->manage_terms ) ) {
					$retrievable_taxonomies[] = $_taxonomy;
				}
			}

			if ( $retrievable_taxonomies ) {
				if ( ! preg_match( '/^\d+$/', $limit ) ) {
					$limit = '';
				}

				// choicse
				$choices_terms = get_terms(
					array(
						'taxonomy'     => $taxonomies,
						'order'        => 'ASC',
						'orderby'      => 'ID',
						'hide_empty'   => false,
						'hierarchical' => false,
						'number'       => $number,
					)
				);
			}
		$choices_li = array();