Skip to content

Commit 7e02ddf

Browse files
committed
Grouped backports to the 5.7 branch.
- Comments: Prevent users who can not see a post from seeing comments on it. - Shortcodes: Restrict media shortcode ajax to certain type. - REST API: Ensure no-cache headers are sent when methods are overridden. - REST API: Limit `search_columns` for users without `list_users`. - Prevent unintended behavior when certain objects are unserialized. - Application Passwords: Prevent the use of some pseudo protocols in application passwords. Merges [56833], [56834], [56835], [56836], [56837], and [56838] to the 5.7 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.7@56886 602fd350-edb4-49c9-b593-d223f7449a82
1 parent e1f5752 commit 7e02ddf

16 files changed

+298
-34
lines changed

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

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

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

3768+
// Only process previews for media related shortcodes:
3769+
$found_shortcodes = get_shortcode_tags_in_content( $shortcode );
3770+
$media_shortcodes = array(
3771+
'audio',
3772+
'embed',
3773+
'playlist',
3774+
'video',
3775+
'gallery',
3776+
);
3777+
3778+
$other_shortcodes = array_diff( $found_shortcodes, $media_shortcodes );
3779+
3780+
if ( ! empty( $other_shortcodes ) ) {
3781+
wp_send_json_error();
3782+
}
3783+
37683784
if ( ! empty( $_POST['post_ID'] ) ) {
37693785
$post = get_post( (int) $_POST['post_ID'] );
37703786
}
37713787

37723788
// The embed shortcode requires a post.
37733789
if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3774-
if ( 'embed' === $shortcode ) {
3790+
if ( in_array( 'embed', $found_shortcodes, true ) ) {
37753791
wp_send_json_error();
37763792
}
37773793
} 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
@@ -622,6 +622,19 @@ public function single_row( $item ) {
622622
}
623623
$this->user_can = current_user_can( 'edit_comment', $comment->comment_ID );
624624

625+
$edit_post_cap = $post ? 'edit_post' : 'edit_posts';
626+
if (
627+
current_user_can( $edit_post_cap, $comment->comment_post_ID ) ||
628+
(
629+
empty( $post->post_password ) &&
630+
current_user_can( 'read_post', $comment->comment_post_ID )
631+
)
632+
) {
633+
// The user has access to the post
634+
} else {
635+
return false;
636+
}
637+
625638
echo "<tr id='comment-$comment->comment_ID' class='$the_comment_class'>";
626639
$this->single_row_columns( $comment );
627640
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
@@ -735,6 +735,20 @@ protected function comments_bubble( $post_id, $pending_comments ) {
735735
$pending_comments_number
736736
);
737737

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

src/wp-admin/includes/dashboard.php

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

10531053
echo '<ul id="the-comment-list" data-wp-lists="list:comment">';
10541054
foreach ( $comments as $comment ) {
1055-
_wp_dashboard_recent_comments_row( $comment );
1055+
1056+
$comment_post = get_post( $comment->comment_post_ID );
1057+
if (
1058+
current_user_can( 'edit_post', $comment->comment_post_ID ) ||
1059+
(
1060+
empty( $comment_post->post_password ) &&
1061+
current_user_can( 'read_post', $comment->comment_post_ID )
1062+
)
1063+
) {
1064+
_wp_dashboard_recent_comments_row( $comment );
1065+
}
10561066
}
10571067
echo '</ul>';
10581068

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: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,8 @@ public function dispatch($hook, $parameters = array()) {
6565

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

src/wp-includes/Requests/IRI.php

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

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

src/wp-includes/Requests/Session.php

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

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

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

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

127+
public function __wakeup() {
128+
if ( ! $this->registered_patterns ) {
129+
return;
130+
}
131+
if ( ! is_array( $this->registered_patterns ) ) {
132+
throw new UnexpectedValueException();
133+
}
134+
foreach ( $this->registered_patterns as $value ) {
135+
if ( ! is_array( $value ) ) {
136+
throw new UnexpectedValueException();
137+
}
138+
}
139+
$this->registered_patterns_outside_init = array();
140+
}
141+
127142
/**
128143
* Utility method to retrieve the main instance of the class.
129144
*

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

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

155+
public function __wakeup() {
156+
if ( ! $this->registered_block_types ) {
157+
return;
158+
}
159+
if ( ! is_array( $this->registered_block_types ) ) {
160+
throw new UnexpectedValueException();
161+
}
162+
foreach ( $this->registered_block_types as $value ) {
163+
if ( ! $value instanceof WP_Block_Type ) {
164+
throw new UnexpectedValueException();
165+
}
166+
}
167+
}
168+
155169
/**
156170
* Utility method to retrieve the main instance of the class.
157171
*

0 commit comments

Comments
 (0)