Skip to content

Commit

Permalink
Grouped backports to the 6.0 branch.
Browse files Browse the repository at this point in the history
- REST API: Limit `search_columns` for users without `list_users`.
- Comments: Prevent users who can not see a post from seeing comments on it.
- Application Passwords: Prevent the use of some pseudo protocols in application passwords.
- Restrict media shortcode ajax to certain type
- REST API: Ensure no-cache headers are sent when methods are overriden.
- Prevent unintended behavior when certain objects are unserialized.

Merges [56833], [56834], [56835], [56836], [56837], and [56838] to the 6.0 branch.
Props xknown, jorbin, Vortfu, joehoyle, timothyblynjacobs, peterwilsoncc, ehtis, tykoted, martinkrcho, paulkevan, dd32, antpb, rmccue.


git-svn-id: https://develop.svn.wordpress.org/branches/6.0@56870 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
joemcgill committed Oct 12, 2023
1 parent 3e39582 commit b74d49c
Show file tree
Hide file tree
Showing 16 changed files with 297 additions and 33 deletions.
18 changes: 17 additions & 1 deletion src/wp-admin/includes/ajax-actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -3822,13 +3822,29 @@ function wp_ajax_parse_media_shortcode() {

$shortcode = wp_unslash( $_POST['shortcode'] );

// Only process previews for media related shortcodes:
$found_shortcodes = get_shortcode_tags_in_content( $shortcode );
$media_shortcodes = array(
'audio',
'embed',
'playlist',
'video',
'gallery',
);

$other_shortcodes = array_diff( $found_shortcodes, $media_shortcodes );

if ( ! empty( $other_shortcodes ) ) {
wp_send_json_error();
}

if ( ! empty( $_POST['post_ID'] ) ) {
$post = get_post( (int) $_POST['post_ID'] );
}

// The embed shortcode requires a post.
if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
if ( 'embed' === $shortcode ) {
if ( in_array( 'embed', $found_shortcodes, true ) ) {
wp_send_json_error();
}
} else {
Expand Down
13 changes: 13 additions & 0 deletions src/wp-admin/includes/class-wp-comments-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,19 @@ public function single_row( $item ) {

$this->user_can = current_user_can( 'edit_comment', $comment->comment_ID );

$edit_post_cap = $post ? 'edit_post' : 'edit_posts';
if (
current_user_can( $edit_post_cap, $comment->comment_post_ID ) ||
(
empty( $post->post_password ) &&
current_user_can( 'read_post', $comment->comment_post_ID )
)
) {
// The user has access to the post
} else {
return false;
}

echo "<tr id='comment-$comment->comment_ID' class='$the_comment_class'>";
$this->single_row_columns( $comment );
echo "</tr>\n";
Expand Down
14 changes: 14 additions & 0 deletions src/wp-admin/includes/class-wp-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,20 @@ protected function comments_bubble( $post_id, $pending_comments ) {
$pending_comments_number
);

$post_object = get_post( $post_id );
$edit_post_cap = $post_object ? 'edit_post' : 'edit_posts';
if (
current_user_can( $edit_post_cap, $post_id ) ||
(
empty( $post_object->post_password ) &&
current_user_can( 'read_post', $post_id )
)
) {
// The user has access to the post and thus can see comments
} else {
return false;
}

if ( ! $approved_comments && ! $pending_comments ) {
// No comments at all.
printf(
Expand Down
12 changes: 11 additions & 1 deletion src/wp-admin/includes/dashboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -1085,7 +1085,17 @@ function wp_dashboard_recent_comments( $total_items = 5 ) {

echo '<ul id="the-comment-list" data-wp-lists="list:comment">';
foreach ( $comments as $comment ) {
_wp_dashboard_recent_comments_row( $comment );

$comment_post = get_post( $comment->comment_post_ID );
if (
current_user_can( 'edit_post', $comment->comment_post_ID ) ||
(
empty( $comment_post->post_password ) &&
current_user_can( 'read_post', $comment->comment_post_ID )
)
) {
_wp_dashboard_recent_comments_row( $comment );
}
}
echo '</ul>';

Expand Down
80 changes: 68 additions & 12 deletions src/wp-admin/includes/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,8 @@ function admin_created_user_email( $text ) {
* Checks if the Authorize Application Password request is valid.
*
* @since 5.6.0
* @since 6.2.0 Allow insecure HTTP connections for the local environment.
* @since 6.3.2 Validates the success and reject URLs to prevent javascript pseudo protocol being executed.
*
* @param array $request {
* The array of request data. All arguments are optional and may be empty.
Expand All @@ -621,24 +623,22 @@ function admin_created_user_email( $text ) {
function wp_is_authorize_application_password_request_valid( $request, $user ) {
$error = new WP_Error();

if ( ! empty( $request['success_url'] ) ) {
$scheme = wp_parse_url( $request['success_url'], PHP_URL_SCHEME );

if ( 'http' === $scheme ) {
if ( isset( $request['success_url'] ) ) {
$validated_success_url = wp_is_authorize_application_redirect_url_valid( $request['success_url'] );
if ( is_wp_error( $validated_success_url ) ) {
$error->add(
'invalid_redirect_scheme',
__( 'The success URL must be served over a secure connection.' )
$validated_success_url->get_error_code(),
$validated_success_url->get_error_message()
);
}
}

if ( ! empty( $request['reject_url'] ) ) {
$scheme = wp_parse_url( $request['reject_url'], PHP_URL_SCHEME );

if ( 'http' === $scheme ) {
if ( isset( $request['reject_url'] ) ) {
$validated_reject_url = wp_is_authorize_application_redirect_url_valid( $request['reject_url'] );
if ( is_wp_error( $validated_reject_url ) ) {
$error->add(
'invalid_redirect_scheme',
__( 'The rejection URL must be served over a secure connection.' )
$validated_reject_url->get_error_code(),
$validated_reject_url->get_error_message()
);
}
}
Expand Down Expand Up @@ -667,3 +667,59 @@ function wp_is_authorize_application_password_request_valid( $request, $user ) {

return true;
}

/**
* Validates the redirect URL protocol scheme. The protocol can be anything except http and javascript.
*
* @since 6.3.2
*
* @param string $url - The redirect URL to be validated.
*
* @return true|WP_Error True if the redirect URL is valid, a WP_Error object otherwise.
*/
function wp_is_authorize_application_redirect_url_valid( $url ) {
$bad_protocols = array( 'javascript', 'data' );
if ( empty( $url ) ) {
return true;
}

// Based on https://www.rfc-editor.org/rfc/rfc2396#section-3.1
$valid_scheme_regex = '/^[a-zA-Z][a-zA-Z0-9+.-]*:/';
if ( ! preg_match( $valid_scheme_regex, $url ) ) {
return new WP_Error(
'invalid_redirect_url_format',
__( 'Invalid URL format.' )
);
}

/**
* Filters the list of invalid protocols used in applications redirect URLs.
*
* @since 6.3.2
*
* @param string[] $bad_protocols Array of invalid protocols.
* @param string $url The redirect URL to be validated.
*/
$invalid_protocols = array_map( 'strtolower', apply_filters( 'wp_authorize_application_redirect_url_invalid_protocols', $bad_protocols, $url ) );

$scheme = wp_parse_url( $url, PHP_URL_SCHEME );
$host = wp_parse_url( $url, PHP_URL_HOST );
$is_local = 'local' === wp_get_environment_type();

// validates if the proper URI format is applied to the $url
if ( empty( $host ) || empty( $scheme ) || in_array( strtolower( $scheme ), $invalid_protocols, true ) ) {
return new WP_Error(
'invalid_redirect_url_format',
__( 'Invalid URL format.' )
);
}

if ( 'http' === $scheme && ! $is_local ) {
return new WP_Error(
'invalid_redirect_scheme',
__( 'The URL must be served over a secure connection.' )
);
}

return true;
}
4 changes: 4 additions & 0 deletions src/wp-includes/Requests/Hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,8 @@ public function dispatch($hook, $parameters = array()) {

return true;
}

public function __wakeup() {
throw new \LogicException( __CLASS__ . ' should never be unserialized' );
}
}
14 changes: 14 additions & 0 deletions src/wp-includes/Requests/IRI.php
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,20 @@ public function is_valid() {
return true;
}

public function __wakeup() {
$class_props = get_class_vars( __CLASS__ );
$string_props = array( 'scheme', 'iuserinfo', 'ihost', 'port', 'ipath', 'iquery', 'ifragment' );
$array_props = array( 'normalization' );
foreach ( $class_props as $prop => $default_value ) {
if ( in_array( $prop, $string_props, true ) && ! is_string( $this->$prop ) ) {
throw new UnexpectedValueException();
} elseif ( in_array( $prop, $array_props, true ) && ! is_array( $this->$prop ) ) {
throw new UnexpectedValueException();
}
$this->$prop = null;
}
}

/**
* Set the entire IRI. Returns true on success, false on failure (if there
* are any invalid characters).
Expand Down
4 changes: 4 additions & 0 deletions src/wp-includes/Requests/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ public function request_multiple($requests, $options = array()) {
return Requests::request_multiple($requests, $options);
}

public function __wakeup() {
throw new \LogicException( __CLASS__ . ' should never be unserialized' );
}

/**
* Merge a request's data with the default data
*
Expand Down
15 changes: 15 additions & 0 deletions src/wp-includes/class-wp-block-patterns-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,21 @@ public function is_registered( $pattern_name ) {
return isset( $this->registered_patterns[ $pattern_name ] );
}

public function __wakeup() {
if ( ! $this->registered_patterns ) {
return;
}
if ( ! is_array( $this->registered_patterns ) ) {
throw new UnexpectedValueException();
}
foreach ( $this->registered_patterns as $value ) {
if ( ! is_array( $value ) ) {
throw new UnexpectedValueException();
}
}
$this->registered_patterns_outside_init = array();
}

/**
* Utility method to retrieve the main instance of the class.
*
Expand Down
14 changes: 14 additions & 0 deletions src/wp-includes/class-wp-block-type-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,20 @@ public function is_registered( $name ) {
return isset( $this->registered_block_types[ $name ] );
}

public function __wakeup() {
if ( ! $this->registered_block_types ) {
return;
}
if ( ! is_array( $this->registered_block_types ) ) {
throw new UnexpectedValueException();
}
foreach ( $this->registered_block_types as $value ) {
if ( ! $value instanceof WP_Block_Type ) {
throw new UnexpectedValueException();
}
}
}

/**
* Utility method to retrieve the main instance of the class.
*
Expand Down
34 changes: 34 additions & 0 deletions src/wp-includes/class-wp-theme.php
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,28 @@ public function parent() {
return isset( $this->parent ) ? $this->parent : false;
}

/**
* Perform reinitialization tasks.
*
* Prevents a callback from being injected during unserialization of an object.
*
* @return void
*/
public function __wakeup() {
if ( $this->parent && ! $this->parent instanceof self ) {
throw new UnexpectedValueException();
}
if ( $this->headers && ! is_array( $this->headers ) ) {
throw new UnexpectedValueException();
}
foreach ( $this->headers as $value ) {
if ( ! is_string( $value ) ) {
throw new UnexpectedValueException();
}
}
$this->headers_sanitized = array();
}

/**
* Adds theme data to cache.
*
Expand Down Expand Up @@ -1771,4 +1793,16 @@ private static function _name_sort( $a, $b ) {
private static function _name_sort_i18n( $a, $b ) {
return strnatcasecmp( $a->name_translated, $b->name_translated );
}

private static function _check_headers_property_has_correct_type( $headers ) {
if ( ! is_array( $headers ) ) {
return false;
}
foreach ( $headers as $key => $value ) {
if ( ! is_string( $key ) || ! is_string( $value ) ) {
return false;
}
}
return true;
}
}
22 changes: 22 additions & 0 deletions src/wp-includes/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -2377,6 +2377,7 @@ function gallery_shortcode( $attr ) {
$attachments[ $val->ID ] = $_attachments[ $key ];
}
} elseif ( ! empty( $atts['exclude'] ) ) {
$post_parent_id = $id;
$attachments = get_children(
array(
'post_parent' => $id,
Expand All @@ -2389,6 +2390,7 @@ function gallery_shortcode( $attr ) {
)
);
} else {
$post_parent_id = $id;
$attachments = get_children(
array(
'post_parent' => $id,
Expand All @@ -2401,6 +2403,17 @@ function gallery_shortcode( $attr ) {
);
}

if ( ! empty( $post_parent_id ) ) {
$post_parent = get_post( $post_parent_id );

// terminate the shortcode execution if user cannot read the post or password-protected
if (
( ! is_post_publicly_viewable( $post_parent->ID ) && ! current_user_can( 'read_post', $post_parent->ID ) )
|| post_password_required( $post_parent ) ) {
return '';
}
}

if ( empty( $attachments ) ) {
return '';
}
Expand Down Expand Up @@ -2727,6 +2740,15 @@ function wp_playlist_shortcode( $attr ) {
$attachments = get_children( $args );
}

if ( ! empty( $args['post_parent'] ) ) {
$post_parent = get_post( $id );

// terminate the shortcode execution if user cannot read the post or password-protected
if ( ! current_user_can( 'read_post', $post_parent->ID ) || post_password_required( $post_parent ) ) {
return '';
}
}

if ( empty( $attachments ) ) {
return '';
}
Expand Down
1 change: 1 addition & 0 deletions src/wp-includes/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,7 @@ function rest_cookie_check_errors( $result ) {
$result = wp_verify_nonce( $nonce, 'wp_rest' );

if ( ! $result ) {
add_filter( 'rest_send_nocache_headers', '__return_true', 20 );
return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie check failed' ), array( 'status' => 403 ) );
}

Expand Down
Loading

0 comments on commit b74d49c

Please sign in to comment.