From 13666047538e4ca3f060c4e8a435256e1d7c4fa0 Mon Sep 17 00:00:00 2001 From: Ike Hecht Date: Sun, 23 Aug 2026 00:06:20 -0400 Subject: [PATCH] SLOP-394: require edit token for ?unreview= write (CSRF) Special:PageStatistics?unreview= rewrote the acting user\x27s wl_notificationtimestamp on any GET with no token check, and wrote the timestamp value unvalidated. Require the user\x27s edit token and embed it in the ReviewHandler right-click fallback URL. --- includes/ReviewHandler.php | 3 ++- specials/SpecialPageStatistics.php | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/includes/ReviewHandler.php b/includes/ReviewHandler.php index d07bb04..285de52 100644 --- a/includes/ReviewHandler.php +++ b/includes/ReviewHandler.php @@ -128,7 +128,8 @@ public function getTemplate() { // used if user right-clicks link and opens in new tab $unReviewURL = SpecialPage::getTitleFor( 'PageStatistics' )->getInternalURL( [ 'page' => $this->title->getPrefixedText(), - 'unreview' => $this->initial + 'unreview' => $this->initial, + 'token' => $this->user->getEditToken() ] ); $unReviewLink = Xml::element( diff --git a/specials/SpecialPageStatistics.php b/specials/SpecialPageStatistics.php index a7a6186..0c06048 100644 --- a/specials/SpecialPageStatistics.php +++ b/specials/SpecialPageStatistics.php @@ -39,6 +39,10 @@ public function execute( $parser = null ) { $unReviewTimestamp = $wgRequest->getVal( 'unreview' ); if ( $unReviewTimestamp ) { + // state-changing GET action: require an edit token (CSRF protection) + if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'token' ) ) ) { + throw new PermissionsError( 'editmywatchlist' ); + } $rh = new ReviewHandler( $wgUser, $this->mTitle, $wgRequest ); $rh->resetNotificationTimestamp( $unReviewTimestamp ); $wgOut->addModuleStyles( [ 'ext.watchanalytics.reviewhandler.styles' ] );