Page MenuHomePhabricator

0001-SECURITY-Send-original-client-info-on-x-wiki-request.patch

Authored By
taavi
Dec 22 2021, 2:12 PM
Size
7 KB
Referenced Files
None
Subscribers
None

0001-SECURITY-Send-original-client-info-on-x-wiki-request.patch

From 94bfc05acc553ade067892869cb41d087a38f254 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Taavi=20V=C3=A4=C3=A4n=C3=A4nen?= <hi@taavi.wtf>
Date: Thu, 21 Oct 2021 14:19:39 +0300
Subject: [PATCH] SECURITY: Send original client info on x-wiki requests
Bug: T285116
---
includes/EchoHooks.php | 12 +++++++-----
includes/ForeignWikiRequest.php | 34 ++++++++++++++++++++++++++-------
includes/NotifUser.php | 10 ++++++----
includes/api/ApiCrossWiki.php | 2 +-
4 files changed, 41 insertions(+), 17 deletions(-)
diff --git a/includes/EchoHooks.php b/includes/EchoHooks.php
index 410a6ea2..febd036d 100644
--- a/includes/EchoHooks.php
+++ b/includes/EchoHooks.php
@@ -893,16 +893,18 @@ class EchoHooks implements RecentChange_saveHook {
$markAsReadIds = array_map( 'intval', $markAsReadIds );
// Look up the notifications on the foreign wiki
$notifUser = MWEchoNotifUser::newFromUser( $user );
- $notifInfo = $notifUser->getForeignNotificationInfo( $markAsReadIds, $markAsReadWiki );
+ $notifInfo = $notifUser->getForeignNotificationInfo( $markAsReadIds, $markAsReadWiki, $request );
foreach ( $notifInfo as $id => $info ) {
$subtractions[$info['section']]++;
}
// Schedule a deferred update to mark these notifications as read on the foreign wiki
- DeferredUpdates::addCallableUpdate( static function () use ( $user, $markAsReadIds, $markAsReadWiki ) {
- $notifUser = MWEchoNotifUser::newFromUser( $user );
- $notifUser->markReadForeign( $markAsReadIds, $markAsReadWiki );
- } );
+ DeferredUpdates::addCallableUpdate(
+ static function () use ( $user, $markAsReadIds, $markAsReadWiki, $request ) {
+ $notifUser = MWEchoNotifUser::newFromUser( $user );
+ $notifUser->markReadForeign( $markAsReadIds, $markAsReadWiki, $request );
+ }
+ );
}
}
diff --git a/includes/ForeignWikiRequest.php b/includes/ForeignWikiRequest.php
index cce5ff8b..475f86ff 100644
--- a/includes/ForeignWikiRequest.php
+++ b/includes/ForeignWikiRequest.php
@@ -49,14 +49,21 @@ class EchoForeignWikiRequest {
/**
* Execute the request
+ * @param WebRequest|null $originalRequest Original request data to be sent with these requests
* @return array[] [ wiki => result ]
*/
- public function execute() {
+ public function execute( ?WebRequest $originalRequest = null ) {
if ( !$this->canUseCentralAuth() ) {
return [];
}
- $reqs = $this->getRequestParams( $this->method, [ $this, 'getQueryParams' ] );
+ $reqs = $this->getRequestParams(
+ $this->method,
+ function ( string $wiki ) use ( $originalRequest ) {
+ return $this->getQueryParams( $wiki, $originalRequest );
+ },
+ $originalRequest
+ );
return $this->doRequests( $reqs );
}
@@ -121,10 +128,11 @@ class EchoForeignWikiRequest {
* This method fetches the tokens for all requested wikis at once and caches the result.
*
* @param string $wiki Name of the wiki to get a token for
+ * @param WebRequest|null $originalRequest Original request data to be sent with these requests
* @suppress PhanTypeInvalidCallableArraySize getRequestParams can take an array, too (phan bug)
* @return string Token, or empty string if an unable to retrieve the token.
*/
- protected function getCsrfToken( $wiki ) {
+ protected function getCsrfToken( $wiki, ?WebRequest $originalRequest ) {
if ( $this->csrfTokens === null ) {
$this->csrfTokens = [];
$reqs = $this->getRequestParams( 'GET', [
@@ -133,7 +141,7 @@ class EchoForeignWikiRequest {
'type' => $this->tokenType,
'format' => 'json',
'centralauthtoken' => $this->getCentralAuthToken( $this->user ),
- ] );
+ ], $originalRequest );
$responses = $this->doRequests( $reqs );
foreach ( $responses as $w => $response ) {
if ( isset( $response['query']['tokens']['csrftoken'] ) ) {
@@ -156,9 +164,10 @@ class EchoForeignWikiRequest {
* @param string $method 'GET' or 'POST'
* @param array|callable $params Associative array of query string / POST parameters,
* or a callback that takes a wiki name and returns such an array
+ * @param WebRequest|null $originalRequest Original request data to be sent with these requests
* @return array[] Array of request parameters to pass to doRequests(), keyed by wiki name
*/
- protected function getRequestParams( $method, $params ) {
+ protected function getRequestParams( $method, $params, ?WebRequest $originalRequest ) {
$apis = EchoForeignNotifications::getApiEndpoints( $this->wikis );
if ( !$apis ) {
return [];
@@ -172,6 +181,16 @@ class EchoForeignWikiRequest {
'url' => $api['url'],
$queryKey => is_callable( $params ) ? $params( $wiki ) : $params
];
+
+ if ( $originalRequest ) {
+ $reqs[$wiki]['headers'] = [
+ 'X-Forwarded-For' => $originalRequest->getIP(),
+ 'User-Agent' => (
+ $originalRequest->getHeader( 'User-Agent' )
+ . ' (via EchoForeignWikiRequest MediaWiki/' . MW_VERSION . ')'
+ ),
+ ];
+ }
}
return $reqs;
@@ -179,9 +198,10 @@ class EchoForeignWikiRequest {
/**
* @param string $wiki Wiki name
+ * @param WebRequest|null $originalRequest Original request data to be sent with these requests
* @return array
*/
- protected function getQueryParams( $wiki ) {
+ protected function getQueryParams( $wiki, ?WebRequest $originalRequest ) {
$extraParams = [];
if ( $this->wikiParam ) {
// Only request data from that specific wiki, or they'd all spawn
@@ -189,7 +209,7 @@ class EchoForeignWikiRequest {
$extraParams[$this->wikiParam] = $wiki;
}
if ( $this->method === 'POST' ) {
- $extraParams['token'] = $this->getCsrfToken( $wiki );
+ $extraParams['token'] = $this->getCsrfToken( $wiki, $originalRequest );
}
return [
diff --git a/includes/NotifUser.php b/includes/NotifUser.php
index b7d1bbda..42a8bce0 100644
--- a/includes/NotifUser.php
+++ b/includes/NotifUser.php
@@ -386,8 +386,9 @@ class MWEchoNotifUser {
*
* @param int[] $eventIds Event IDs to mark as read
* @param string $wiki Wiki name
+ * @param WebRequest|null $originalRequest Original request data to be sent with these requests
*/
- public function markReadForeign( array $eventIds, $wiki ) {
+ public function markReadForeign( array $eventIds, $wiki, ?WebRequest $originalRequest = null ) {
$foreignReq = new EchoForeignWikiRequest(
$this->mUser,
[
@@ -398,7 +399,7 @@ class MWEchoNotifUser {
'wikis',
'csrf'
);
- $foreignReq->execute();
+ $foreignReq->execute( $originalRequest );
}
/**
@@ -406,9 +407,10 @@ class MWEchoNotifUser {
*
* @param int[] $eventIds Event IDs to look up. Only unread notifications can be found.
* @param string $wiki Wiki name
+ * @param WebRequest|null $originalRequest Original request data to be sent with these requests
* @return array[] Array of notification data as returned by api.php, keyed by event ID
*/
- public function getForeignNotificationInfo( array $eventIds, $wiki ) {
+ public function getForeignNotificationInfo( array $eventIds, $wiki, ?WebRequest $originalRequest = null ) {
$foreignReq = new EchoForeignWikiRequest(
$this->mUser,
[
@@ -421,7 +423,7 @@ class MWEchoNotifUser {
[ $wiki ],
'notwikis'
);
- $foreignResults = $foreignReq->execute();
+ $foreignResults = $foreignReq->execute( $originalRequest );
$list = $foreignResults[$wiki]['query']['notifications']['list'] ?? [];
$result = [];
diff --git a/includes/api/ApiCrossWiki.php b/includes/api/ApiCrossWiki.php
index b632a065..ddc7d549 100644
--- a/includes/api/ApiCrossWiki.php
+++ b/includes/api/ApiCrossWiki.php
@@ -36,7 +36,7 @@ trait ApiCrossWiki {
$this->getModulePrefix() . 'wikis',
$tokenType !== false ? $tokenType : null
);
- return $foreignReq->execute();
+ return $foreignReq->execute( $this->getRequest() );
}
/**
--
2.34.1

File Metadata

Mime Type
text/x-diff
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
9309725
Default Alt Text
0001-SECURITY-Send-original-client-info-on-x-wiki-request.patch (7 KB)

Event Timeline