From 023fee5c93e1211cddc4a8c01a04d6c6e3d823b8 Mon Sep 17 00:00:00 2001
From: Daimona Eaytoy <daimona.wiki@gmail.com>
Date: Mon, 8 Feb 2021 13:36:18 +0100
Subject: [PATCH] SECURITY: Avoid info leaks in ApiAbuseFilterCheckMatch

There are various info leaks for both deleted rc rows, and suppressed
AbuseLog entries.

Bug: T223654
Change-Id: I4900b1be73323599d74e3164447f81eded094d75
---
 includes/AbuseFilterChangesList.php |  1 +
 includes/Api/CheckMatch.php         | 36 ++++++++++++++++++++++++++---
 2 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/includes/AbuseFilterChangesList.php b/includes/AbuseFilterChangesList.php
index bcaee22a..bb979f5d 100644
--- a/includes/AbuseFilterChangesList.php
+++ b/includes/AbuseFilterChangesList.php
@@ -37,6 +37,7 @@ class AbuseFilterChangesList extends OldChangesList {
 		if ( (int)$rc->getAttribute( 'rc_deleted' ) !== 0 ) {
 			$s .= ' ' . $this->msg( 'abusefilter-log-hidden-implicit' )->parse();
 			if ( !$this->userCan( $rc, RevisionRecord::SUPPRESSED_ALL ) ) {
+				// Remember to keep this in sync with the CheckMatch API
 				return;
 			}
 		}
diff --git a/includes/Api/CheckMatch.php b/includes/Api/CheckMatch.php
index ed128363..397f6746 100644
--- a/includes/Api/CheckMatch.php
+++ b/includes/Api/CheckMatch.php
@@ -5,9 +5,13 @@ namespace MediaWiki\Extension\AbuseFilter\Api;
 use ApiBase;
 use ApiResult;
 use FormatJson;
+use LogEventsList;
 use LogicException;
+use LogPage;
 use MediaWiki\Extension\AbuseFilter\AbuseFilterServices;
+use MediaWiki\Extension\AbuseFilter\Special\SpecialAbuseLog;
 use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
+use MediaWiki\Revision\RevisionRecord;
 use RecentChange;
 
 class CheckMatch extends ApiBase {
@@ -16,11 +20,12 @@ class CheckMatch extends ApiBase {
 	 */
 	public function execute() {
 		$afPermManager = AbuseFilterServices::getPermissionManager();
+		$user = $this->getUser();
 		$params = $this->extractRequestParams();
 		$this->requireOnlyOneParameter( $params, 'vars', 'rcid', 'logid' );
 
 		// "Anti-DoS"
-		if ( !$afPermManager->canUseTestTools( $this->getUser() ) ) {
+		if ( !$afPermManager->canUseTestTools( $this->getUser() ) ) {
 			$this->dieWithError( 'apierror-abusefilter-canttest', 'permissiondenied' );
 		}
 
@@ -35,14 +40,33 @@ class CheckMatch extends ApiBase {
 				$this->dieWithError( [ 'apierror-nosuchrcid', $params['rcid'] ] );
 			}
 
+			$type = (int)$rc->getAttribute( 'rc_type' );
+			$deletedValue = $rc->getAttribute( 'rc_deleted' );
+			if (
+				(
+					$type === RC_LOG &&
+					!LogEventsList::userCanBitfield(
+						$deletedValue,
+						LogPage::SUPPRESSED_ACTION | LogPage::SUPPRESSED_USER,
+						$user
+					)
+				) || (
+					$type !== RC_LOG &&
+					!RevisionRecord::userCanBitfield( $deletedValue, RevisionRecord::SUPPRESSED_ALL, $user )
+				)
+			) {
+				// T223654 - Same check as in AbuseFilterChangesList
+				$this->dieWithError( 'apierror-permissiondenied-generic', 'deletedrc' );
+			}
+
 			// @phan-suppress-next-line PhanTypeMismatchArgumentNullable T240141
-			$varGenerator = AbuseFilterServices::getVariableGeneratorFactory()->newRCGenerator( $rc, $this->getUser() );
+			$varGenerator = AbuseFilterServices::getVariableGeneratorFactory()->newRCGenerator( $rc, $user );
 			$vars = $varGenerator->getVars();
 		} elseif ( $params['logid'] ) {
 			$dbr = wfGetDB( DB_REPLICA );
 			$row = $dbr->selectRow(
 				'abuse_filter_log',
-				'afl_var_dump',
+				'*',
 				[ 'afl_id' => $params['logid'] ],
 				__METHOD__
 			);
@@ -51,6 +75,12 @@ class CheckMatch extends ApiBase {
 				$this->dieWithError( [ 'apierror-abusefilter-nosuchlogid', $params['logid'] ], 'nosuchlogid' );
 			}
 
+			if ( !$afPermManager->canSeeHiddenLogEntries( $user ) && SpecialAbuseLog::isHidden( $row ) ) {
+				// T223654 - Same check as in SpecialAbuseLog. Both the visibility of the AbuseLog entry
+				// and the corresponding revision are checked.
+				$this->dieWithError( 'apierror-permissiondenied-generic', 'deletedabuselog' );
+			}
+
 			$vars = AbuseFilterServices::getVariablesBlobStore()->loadVarDump( $row->afl_var_dump );
 		}
 		if ( $vars === null ) {
