CVE-2020-28039 (published 2020-11-02T21:15Z)

is_protected_meta in wp-includes/meta.php in WordPress before 5.5.2 allows arbitrary file deletion because it does not properly determine whether a meta key is considered protected.

Severity: MEDIUM
CVSS v2 base score: 6.4 (AV:N/AC:L/Au:N/C:N/I:P/A:P)
CVSS v3 base score: 9.1, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/WordPress/wordpress-develop
commit_details: repo_url: https://github.com/WordPress/wordpress-develop
author: Jonathan Desrosiers
author_date: 2020-10-29 18:05:21+00:00
msg: Meta: Sanitize meta key before checking protection status.

Props zieladam, peterwilsoncc, xknown, whyisjake.
Merges [49377,49381] to trunk.

git-svn-id: https://develop.svn.wordpress.org/trunk@49387 602fd350-edb4-49c9-b593-d223f7449a82
repository: repo_url: https://github.com/WordPress/wordpress-develop
repo_name: WordPress/wordpress-develop
description: WordPress Develop, Git-ified. Synced from git://develop.git.wordpress.org/, including branches and tags! This repository is just a mirror of the WordPress subversion repository. Please include a link to a pre-existing ticket on https://core.trac.wordpress.org/ with every pull request.
repo_language: PHP
owner: WordPress
file_changes: filename: meta.php
change_type: ModificationType.MODIFY
diff: @@ -1159,7 +1159,8 @@ function _get_meta_table( $type ) {
  * @return bool Whether the meta key is considered protected.
  */
 function is_protected_meta( $meta_key, $meta_type = '' ) {
-	$protected = ( '_' === $meta_key[0] );
+	$sanitized_key = preg_replace( "/[^\x20-\x7E\p{L}]/", '', $meta_key );
+	$protected     = strlen( $sanitized_key ) > 0 && ( '_' === $sanitized_key[0] );
 
 	/**
 	 * Filters whether a meta key is considered protected.

programming_language: PHP
filename: isProtectedMeta.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @group meta
+ * @covers ::is_protected_meta
+ */
+class Tests_Meta_isProtectedMeta extends WP_UnitTestCase {
+
+	/**
+	 * @dataProvider protected_data
+	 */
+	public function test_protected( $key ) {
+		$this->assertTrue( is_protected_meta( $key ) );
+	}
+
+	public function protected_data() {
+		$protected_keys = array(
+			array( '_wp_attachment' ),
+		);
+		for ( $i = 0, $max = 31; $i < $max; $i ++ ) {
+			$protected_keys[] = array( chr( $i ) . '_wp_attachment' );
+		}
+		for ( $i = 127, $max = 159; $i <= $max; $i ++ ) {
+			$protected_keys[] = array( chr( $i ) . '_wp_attachment' );
+		}
+		$protected_keys[] = array( chr( 95 ) . '_wp_attachment' );
+
+		return $protected_keys;
+	}
+
+	/**
+	 * @dataProvider unprotected_data
+	 */
+	public function test_unprotected( $key ) {
+		$this->assertFalse( is_protected_meta( $key ) );
+	}
+
+	public function unprotected_data() {
+		$unprotected_keys = array(
+			array( 'singleword' ),
+			array( 'two_words' ),
+			array( 'ąŌ_not_so_protected_meta' ),
+		);
+
+		for ( $i = 32, $max = 94; $i <= $max; $i ++ ) {
+			$unprotected_keys[] = array( chr( $i ) . '_wp_attachment' );
+		}
+		for ( $i = 96, $max = 126; $i <= $max; $i ++ ) {
+			$unprotected_keys[] = array( chr( $i ) . '_wp_attachment' );
+		}
+
+		return $unprotected_keys;
+	}
+
+}

programming_language: PHP