Skip to content

Commit b1a15b7

Browse files
committed
Grouped backports to the 5.9 branch.
- 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 5.9 branch. Props xknown, jorbin, Vortfu, joehoyle, timothyblynjacobs, peterwilsoncc, ehtis, tykoted, martinkrcho, paulkevan, dd32, antpb, rmccue. git-svn-id: https://develop.svn.wordpress.org/branches/5.9@56875 602fd350-edb4-49c9-b593-d223f7449a82
1 parent e5dd3fa commit b1a15b7

16 files changed

+297
-33
lines changed

src/wp-admin/includes/ajax-actions.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3812,13 +3812,29 @@ function wp_ajax_parse_media_shortcode() {
38123812

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

3815+
// Only process previews for media related shortcodes:
3816+
$found_shortcodes = get_shortcode_tags_in_content( $shortcode );
3817+
$media_shortcodes = array(
3818+
'audio',
3819+
'embed',
3820+
'playlist',
3821+
'video',
3822+
'gallery',
3823+
);
3824+
3825+
$other_shortcodes = array_diff( $found_shortcodes, $media_shortcodes );
3826+
3827+
if ( ! empty( $other_shortcodes ) ) {
3828+
wp_send_json_error();
3829+
}
3830+
38153831
if ( ! empty( $_POST['post_ID'] ) ) {
38163832
$post = get_post( (int) $_POST['post_ID'] );
38173833
}
38183834

38193835
// The embed shortcode requires a post.
38203836
if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3821-
if ( 'embed' === $shortcode ) {
3837+
if ( in_array( 'embed', $found_shortcodes, true ) ) {
38223838
wp_send_json_error();
38233839
}
38243840
} else {

src/wp-admin/includes/class-wp-comments-list-table.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,19 @@ public function single_row( $item ) {
640640

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

643+
$edit_post_cap = $post ? 'edit_post' : 'edit_posts';
644+
if (
645+
current_user_can( $edit_post_cap, $comment->comment_post_ID ) ||
646+
(
647+
empty( $post->post_password ) &&
648+
current_user_can( 'read_post', $comment->comment_post_ID )
649+
)
650+
) {
651+
// The user has access to the post
652+
} else {
653+
return false;
654+
}
655+
643656
echo "<tr id='comment-$comment->comment_ID' class='$the_comment_class'>";
644657
$this->single_row_columns( $comment );
645658
echo "</tr>\n";

src/wp-admin/includes/class-wp-list-table.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,20 @@ protected function comments_bubble( $post_id, $pending_comments ) {
739739
$pending_comments_number
740740
);
741741

742+
$post_object = get_post( $post_id );
743+
$edit_post_cap = $post_object ? 'edit_post' : 'edit_posts';
744+
if (
745+
current_user_can( $edit_post_cap, $post_id ) ||
746+
(
747+
empty( $post_object->post_password ) &&
748+
current_user_can( 'read_post', $post_id )
749+
)
750+
) {
751+
// The user has access to the post and thus can see comments
752+
} else {
753+
return false;
754+
}
755+
742756
if ( ! $approved_comments && ! $pending_comments ) {
743757
// No comments at all.
744758
printf(

src/wp-admin/includes/dashboard.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1085,7 +1085,17 @@ function wp_dashboard_recent_comments( $total_items = 5 ) {
10851085

10861086
echo '<ul id="the-comment-list" data-wp-lists="list:comment">';
10871087
foreach ( $comments as $comment ) {
1088-
_wp_dashboard_recent_comments_row( $comment );
1088+
1089+
$comment_post = get_post( $comment->comment_post_ID );
1090+
if (
1091+
current_user_can( 'edit_post', $comment->comment_post_ID ) ||
1092+
(
1093+
empty( $comment_post->post_password ) &&
1094+
current_user_can( 'read_post', $comment->comment_post_ID )
1095+
)
1096+
) {
1097+
_wp_dashboard_recent_comments_row( $comment );
1098+
}
10891099
}
10901100
echo '</ul>';
10911101

src/wp-admin/includes/user.php

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,8 @@ function admin_created_user_email( $text ) {
599599
* Checks if the Authorize Application Password request is valid.
600600
*
601601
* @since 5.6.0
602+
* @since 6.2.0 Allow insecure HTTP connections for the local environment.
603+
* @since 6.3.2 Validates the success and reject URLs to prevent javascript pseudo protocol being executed.
602604
*
603605
* @param array $request {
604606
* The array of request data. All arguments are optional and may be empty.
@@ -614,24 +616,22 @@ function admin_created_user_email( $text ) {
614616
function wp_is_authorize_application_password_request_valid( $request, $user ) {
615617
$error = new WP_Error();
616618

617-
if ( ! empty( $request['success_url'] ) ) {
618-
$scheme = wp_parse_url( $request['success_url'], PHP_URL_SCHEME );
619-
620-
if ( 'http' === $scheme ) {
619+
if ( isset( $request['success_url'] ) ) {
620+
$validated_success_url = wp_is_authorize_application_redirect_url_valid( $request['success_url'] );
621+
if ( is_wp_error( $validated_success_url ) ) {
621622
$error->add(
622-
'invalid_redirect_scheme',
623-
__( 'The success URL must be served over a secure connection.' )
623+
$validated_success_url->get_error_code(),
624+
$validated_success_url->get_error_message()
624625
);
625626
}
626627
}
627628

628-
if ( ! empty( $request['reject_url'] ) ) {
629-
$scheme = wp_parse_url( $request['reject_url'], PHP_URL_SCHEME );
630-
631-
if ( 'http' === $scheme ) {
629+
if ( isset( $request['reject_url'] ) ) {
630+
$validated_reject_url = wp_is_authorize_application_redirect_url_valid( $request['reject_url'] );
631+
if ( is_wp_error( $validated_reject_url ) ) {
632632
$error->add(
633-
'invalid_redirect_scheme',
634-
__( 'The rejection URL must be served over a secure connection.' )
633+
$validated_reject_url->get_error_code(),
634+
$validated_reject_url->get_error_message()
635635
);
636636
}
637637
}
@@ -660,3 +660,59 @@ function wp_is_authorize_application_password_request_valid( $request, $user ) {
660660

661661
return true;
662662
}
663+
664+
/**
665+
* Validates the redirect URL protocol scheme. The protocol can be anything except http and javascript.
666+
*
667+
* @since 6.3.2
668+
*
669+
* @param string $url - The redirect URL to be validated.
670+
*
671+
* @return true|WP_Error True if the redirect URL is valid, a WP_Error object otherwise.
672+
*/
673+
function wp_is_authorize_application_redirect_url_valid( $url ) {
674+
$bad_protocols = array( 'javascript', 'data' );
675+
if ( empty( $url ) ) {
676+
return true;
677+
}
678+
679+
// Based on https://www.rfc-editor.org/rfc/rfc2396#section-3.1
680+
$valid_scheme_regex = '/^[a-zA-Z][a-zA-Z0-9+.-]*:/';
681+
if ( ! preg_match( $valid_scheme_regex, $url ) ) {
682+
return new WP_Error(
683+
'invalid_redirect_url_format',
684+
__( 'Invalid URL format.' )
685+
);
686+
}
687+
688+
/**
689+
* Filters the list of invalid protocols used in applications redirect URLs.
690+
*
691+
* @since 6.3.2
692+
*
693+
* @param string[] $bad_protocols Array of invalid protocols.
694+
* @param string $url The redirect URL to be validated.
695+
*/
696+
$invalid_protocols = array_map( 'strtolower', apply_filters( 'wp_authorize_application_redirect_url_invalid_protocols', $bad_protocols, $url ) );
697+
698+
$scheme = wp_parse_url( $url, PHP_URL_SCHEME );
699+
$host = wp_parse_url( $url, PHP_URL_HOST );
700+
$is_local = 'local' === wp_get_environment_type();
701+
702+
// validates if the proper URI format is applied to the $url
703+
if ( empty( $host ) || empty( $scheme ) || in_array( strtolower( $scheme ), $invalid_protocols, true ) ) {
704+
return new WP_Error(
705+
'invalid_redirect_url_format',
706+
__( 'Invalid URL format.' )
707+
);
708+
}
709+
710+
if ( 'http' === $scheme && ! $is_local ) {
711+
return new WP_Error(
712+
'invalid_redirect_scheme',
713+
__( 'The URL must be served over a secure connection.' )
714+
);
715+
}
716+
717+
return true;
718+
}

src/wp-includes/Requests/Hooks.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,8 @@ public function dispatch($hook, $parameters = array()) {
6565

6666
return true;
6767
}
68+
69+
public function __wakeup() {
70+
throw new \LogicException( __CLASS__ . ' should never be unserialized' );
71+
}
6872
}

src/wp-includes/Requests/IRI.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,20 @@ public function is_valid() {
705705
return true;
706706
}
707707

708+
public function __wakeup() {
709+
$class_props = get_class_vars( __CLASS__ );
710+
$string_props = array( 'scheme', 'iuserinfo', 'ihost', 'port', 'ipath', 'iquery', 'ifragment' );
711+
$array_props = array( 'normalization' );
712+
foreach ( $class_props as $prop => $default_value ) {
713+
if ( in_array( $prop, $string_props, true ) && ! is_string( $this->$prop ) ) {
714+
throw new UnexpectedValueException();
715+
} elseif ( in_array( $prop, $array_props, true ) && ! is_array( $this->$prop ) ) {
716+
throw new UnexpectedValueException();
717+
}
718+
$this->$prop = null;
719+
}
720+
}
721+
708722
/**
709723
* Set the entire IRI. Returns true on success, false on failure (if there
710724
* are any invalid characters).

src/wp-includes/Requests/Session.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ public function request_multiple($requests, $options = array()) {
229229
return Requests::request_multiple($requests, $options);
230230
}
231231

232+
public function __wakeup() {
233+
throw new \LogicException( __CLASS__ . ' should never be unserialized' );
234+
}
235+
232236
/**
233237
* Merge a request's data with the default data
234238
*

src/wp-includes/class-wp-block-patterns-registry.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,21 @@ public function is_registered( $pattern_name ) {
156156
return isset( $this->registered_patterns[ $pattern_name ] );
157157
}
158158

159+
public function __wakeup() {
160+
if ( ! $this->registered_patterns ) {
161+
return;
162+
}
163+
if ( ! is_array( $this->registered_patterns ) ) {
164+
throw new UnexpectedValueException();
165+
}
166+
foreach ( $this->registered_patterns as $value ) {
167+
if ( ! is_array( $value ) ) {
168+
throw new UnexpectedValueException();
169+
}
170+
}
171+
$this->registered_patterns_outside_init = array();
172+
}
173+
159174
/**
160175
* Utility method to retrieve the main instance of the class.
161176
*

src/wp-includes/class-wp-block-type-registry.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,20 @@ public function is_registered( $name ) {
167167
return isset( $this->registered_block_types[ $name ] );
168168
}
169169

170+
public function __wakeup() {
171+
if ( ! $this->registered_block_types ) {
172+
return;
173+
}
174+
if ( ! is_array( $this->registered_block_types ) ) {
175+
throw new UnexpectedValueException();
176+
}
177+
foreach ( $this->registered_block_types as $value ) {
178+
if ( ! $value instanceof WP_Block_Type ) {
179+
throw new UnexpectedValueException();
180+
}
181+
}
182+
}
183+
170184
/**
171185
* Utility method to retrieve the main instance of the class.
172186
*

0 commit comments

Comments
 (0)