diff --git a/api.wordpress.org/public_html/dotorg/github/activity.php b/api.wordpress.org/public_html/dotorg/github/activity.php index f8ca79e7cc..7ab9d5f1cc 100644 --- a/api.wordpress.org/public_html/dotorg/github/activity.php +++ b/api.wordpress.org/public_html/dotorg/github/activity.php @@ -43,7 +43,16 @@ function get_signed_payload_or_die() { $payload = file_get_contents( 'php://input' ); // Validate that the request came from GitHub. - $sent_signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256']; + $sent_signature = (string) filter_var( + wp_unslash( $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '' ), + FILTER_VALIDATE_REGEXP, + [ + 'options' => [ + 'regexp' => '/^sha256=[0-9a-f]{64}\z/i', + 'default' => '', + ], + ] + ); $expected_signature = 'sha256=' . hash_hmac( 'sha256', $payload, constant( 'GH_ACTIVITY_WEBHOOK_SECRET' ) ); if ( ! hash_equals( $expected_signature, $sent_signature ) ) { @@ -71,7 +80,7 @@ function github_user_to_user_id( $user ) { return $user_id ? intval( $user_id ) : false; } -$event = $_SERVER['HTTP_X_GITHUB_EVENT']; +$event = sanitize_key( wp_unslash( $_SERVER['HTTP_X_GITHUB_EVENT'] ?? '' ) ); $payload = get_signed_payload_or_die(); // Ignore anything on private repos. @@ -163,6 +172,7 @@ function github_user_to_user_id( $user ) { case 'issues': if ( ! in_array( $payload->action, [ 'opened', 'edited', 'closed', 'deleted' ] ) ) { header( 'HTTP/1.0 422 Unprocessable Entity', true, 422 ); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Plain-text response body; the endpoint sends Content-Type: text/plain. die( "NO; $event:{$payload->action} not required." ); } @@ -215,6 +225,7 @@ function github_user_to_user_id( $user ) { if ( ! in_array( $payload->action, [ 'opened', 'reopened', 'edited', 'closed' ] ) ) { header( 'HTTP/1.0 422 Unprocessable Entity', true, 422 ); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Plain-text response body; the endpoint sends Content-Type: text/plain. die( "NO; $event:{$payload->action} not required." ); } diff --git a/api.wordpress.org/public_html/dotorg/helpscout/common.php b/api.wordpress.org/public_html/dotorg/helpscout/common.php index 92fc0c12f3..ef30866683 100644 --- a/api.wordpress.org/public_html/dotorg/helpscout/common.php +++ b/api.wordpress.org/public_html/dotorg/helpscout/common.php @@ -22,18 +22,27 @@ function load_wordpress( $wp_init_host = '' ) { * Retrieve the incoming payload, and verify it's from HelpScout. */ function get_request() { - global $HTTP_RAW_POST_DATA; - // HelpScout sends json data in the POST, so grab it from the input directly. - $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' ); - - // Check the signature matches. - if ( ! is_from_helpscout( $HTTP_RAW_POST_DATA, $_SERVER['HTTP_X_HELPSCOUT_SIGNATURE'] ?? '' ) ) { + $raw_post_data = file_get_contents( 'php://input' ); + + // Check the signature matches. It's a base64 encoded HMAC, allowing for the URL-safe alphabet. + $signature = (string) filter_var( + wp_unslash( $_SERVER['HTTP_X_HELPSCOUT_SIGNATURE'] ?? '' ), + FILTER_VALIDATE_REGEXP, + [ + 'options' => [ + 'regexp' => '!^[A-Za-z0-9+/=_-]+\z!', + 'default' => '', + ], + ] + ); + + if ( ! is_from_helpscout( $raw_post_data, $signature ) ) { exit; } // get the info from HS. - return json_decode( $HTTP_RAW_POST_DATA ); + return json_decode( $raw_post_data ); } // function to verify signature from HelpScout diff --git a/api.wordpress.org/public_html/dotorg/helpscout/plugins-themes.php b/api.wordpress.org/public_html/dotorg/helpscout/plugins-themes.php index 5ca6231eb9..810056609b 100644 --- a/api.wordpress.org/public_html/dotorg/helpscout/plugins-themes.php +++ b/api.wordpress.org/public_html/dotorg/helpscout/plugins-themes.php @@ -66,7 +66,7 @@ ] ); if ( $post_ids ) { - echo '

' . ucwords( $type ) . ' mentioned in this email:

'; + echo '

' . esc_html( ucwords( $type ) ) . ' mentioned in this email:

'; display_items( $post_ids ); @@ -84,7 +84,7 @@ $items = get_user_items( $user ); if ( $items ) { $url = add_query_arg( [ 'post_type' => $repo_post_types[ $type ], 'author' => $user->ID ], admin_url( 'edit.php' ) ); - echo '

' . ucwords( $type ) . ' owned by this user:

'; + echo '

' . esc_html( ucwords( $type ) ) . ' owned by this user:

'; display_items( $items ); diff --git a/api.wordpress.org/public_html/dotorg/helpscout/webhook.php b/api.wordpress.org/public_html/dotorg/helpscout/webhook.php index c2ff332d00..4a6be57bc8 100644 --- a/api.wordpress.org/public_html/dotorg/helpscout/webhook.php +++ b/api.wordpress.org/public_html/dotorg/helpscout/webhook.php @@ -8,7 +8,7 @@ // $request is the validated HelpScout request. $request = get_request(); -$event = $_SERVER['HTTP_X_HELPSCOUT_EVENT'] ?? ''; +$event = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_HELPSCOUT_EVENT'] ?? '' ) ); // Warm the caches. get_email_thread( $request->id, true ); diff --git a/api.wordpress.org/public_html/dotorg/trac/mentions-handler.php b/api.wordpress.org/public_html/dotorg/trac/mentions-handler.php index 151b1008a4..cd93618418 100644 --- a/api.wordpress.org/public_html/dotorg/trac/mentions-handler.php +++ b/api.wordpress.org/public_html/dotorg/trac/mentions-handler.php @@ -1,4 +1,14 @@ Temporarily Unavailable'; wp_cache_set( $cache_key, $output, 'trac-oembed', MINUTE_IN_SECONDS ); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static HTML markup defined above; this endpoint serves text/html. die( $output ); } @@ -324,4 +335,5 @@ function send() { wp_cache_set( $cache_key, $data, 'trac-oembed', HOUR_IN_SECONDS ); +// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Complete HTML document built by DOMDocument above; this endpoint serves text/html and escaping it would destroy the markup. echo $data; \ No newline at end of file diff --git a/api.wordpress.org/public_html/dotorg/trac/pr/class-trac.php b/api.wordpress.org/public_html/dotorg/trac/pr/class-trac.php index 6cb9e65a62..adfdda2d31 100644 --- a/api.wordpress.org/public_html/dotorg/trac/pr/class-trac.php +++ b/api.wordpress.org/public_html/dotorg/trac/pr/class-trac.php @@ -148,6 +148,7 @@ protected function _api( $method, $params ) { $json = $this->trac_json_deobjectify( $json ); } elseif ( $json && isset( $json->error ) ) { + // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Discarded by every catch site or logged as plain text; escaping would corrupt the log line. throw new \Exception( 'JSON Error: ' . $json->error->code . ' ' . $json->error->message ); } elseif ( ! $json ) { throw new \Exception( 'Trac API Error: Trac Unavailable.' ); diff --git a/api.wordpress.org/public_html/dotorg/trac/pr/index.php b/api.wordpress.org/public_html/dotorg/trac/pr/index.php index 5010171352..ec89771b31 100644 --- a/api.wordpress.org/public_html/dotorg/trac/pr/index.php +++ b/api.wordpress.org/public_html/dotorg/trac/pr/index.php @@ -1,15 +1,23 @@ [ + 'regexp' => '/^sha1=[0-9a-f]{40}\z/i', + 'default' => '', + ], + ] + ); $expected_signature = 'sha1=' . hash_hmac( 'sha1', $HTTP_RAW_POST_DATA, GH_PRBOT_WEBHOOK_SECRET ); if ( ! hash_equals( $expected_signature, $sent_signature ) ) { @@ -33,11 +55,12 @@ function verify_signature() { $payload = json_decode( $HTTP_RAW_POST_DATA ); -if ( ! empty( $_GET['trac'] ) ) { - define( 'WEBHOOK_TRAC_HINT', $_GET['trac'] ); +$trac_hint = sanitize_key( wp_unslash( $_GET['trac'] ?? '' ) ); +if ( $trac_hint ) { + define( 'WEBHOOK_TRAC_HINT', $trac_hint ); } -switch ( $_SERVER['HTTP_X_GITHUB_EVENT'] ) { +switch ( sanitize_key( wp_unslash( $_SERVER['HTTP_X_GITHUB_EVENT'] ?? '' ) ) ) { // Pull Request case 'pull_request':