CVE-2021-46252 (published 2022-02-15T23:15Z)

A Cross-Site Request Forgery (CSRF) in RequirementsBypassPage.php of Scratch Wiki scratch-confirmaccount-v3 allows attackers to modify account request requirement bypasses.

Severity: MEDIUM
CVSS v2 base score: 4.3 (AV:N/AC:M/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 6.5, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-352
cwe_name: Cross-Site Request Forgery (CSRF)
description: The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
extended_description: When a web server is designed to receive a request from a client without any mechanism for verifying that it was intentionally sent, then it might be possible for an attacker to trick a client into making an unintentional request to the web server which will be treated as an authentic request. This can be done via a URL, image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.
         

Fix information:
repo_url: https://github.com/InternationalScratchWiki/scratch-confirmaccount-v3
commit_details: repo_url: https://github.com/InternationalScratchWiki/scratch-confirmaccount-v3
author: apple502j
author_date: 2022-01-05 02:09:16+09:00
msg: Fix CSRF when adding requirements bypass (#155)
repository: repo_url: https://github.com/InternationalScratchWiki/scratch-confirmaccount-v3
repo_name: InternationalScratchWiki/scratch-confirmaccount-v3
description: A new in-house account request system for the Scratch Wikis.
repo_language: PHP
owner: InternationalScratchWiki
file_changes: filename: SpecialConfirmAccounts.php
change_type: ModificationType.MODIFY
diff: @@ -362,7 +362,7 @@ class SpecialConfirmAccounts extends SpecialPage {
 			$this->handleUnblockFormSubmission($request, $output, $session);
 		} else if ($request->getText('bypassAddUsername') || $request->getText('bypassRemoveUsername')) { //TODO: refactor to move all the subpages into their own files
 			$bypassPage = new RequirementsBypassPage($this);
-			$bypassPage->handleFormSubmission();
+			$bypassPage->handleFormSubmission($session);
 		}
 	}
 
@@ -445,7 +445,7 @@ class SpecialConfirmAccounts extends SpecialPage {
 			return $this->blocksPage($par, $request, $output, $session);
 		} else if (strpos($par, wfMessage('scratch-confirmaccount-requirements-bypasses-url')->text()) === 0) {
 			$bypassPage = new RequirementsBypassPage($this);
-			return $bypassPage->render();
+			return $bypassPage->render($session);
 		} else if ($request->getText('username')) {
 			return $this->searchByUsername($request->getText('username'), $request, $output);
 		} else if (isset(statuses[$par])) {

programming_language: PHP
filename: RequirementsBypassPage.php
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,8 @@
 <?php
 require_once __DIR__ . '/../database/DatabaseInteractions.php';
+require_once __DIR__ . '/../common.php';
+
+use MediaWiki\Session\Session;
 
 class RequirementsBypassPage {
     private $pageContext;
@@ -14,8 +17,13 @@ class RequirementsBypassPage {
         $this->pageContext = $pageContext;
     }
 
-    function handleFormSubmission() {
-        $request = $request = $this->pageContext->getRequest();
+    function handleFormSubmission(Session &$session) {
+        $request = $this->pageContext->getRequest();
+        
+        if (isCSRF($session, $request->getText('csrftoken'))) {
+    			$this->pageContext->getOutput()->showErrorPage('error', 'scratch-confirmaccount-csrf');
+    			return;
+    		}
 
         $dbw = getTransactableDatabase('scratch-confirmaccount-bypasses');
 
@@ -27,10 +35,10 @@ class RequirementsBypassPage {
 
         commitTransaction($dbw, 'scratch-confirmaccount-bypasses');
 
-        $this->render();
+        $this->render($session);
     }
 
-    function showAddBypassForm() {
+    function showAddBypassForm(Session &$session) {
         $output = $this->pageContext->getOutput();
         $request = $this->pageContext->getRequest();
         
@@ -39,6 +47,10 @@ class RequirementsBypassPage {
                 'action' => SpecialPage::getTitleFor('ConfirmAccounts', wfMessage('scratch-confirmaccount-requirements-bypasses-url')->text())->getFullURL(),
                 'method' => 'post',
                 'items' => [
+                    new OOUI\HiddenInputWidget([
+                        'name' => 'csrftoken',
+                        'value' => setCSRFToken($session)
+                    ]),
                     new OOUI\ActionFieldLayout(
                         new OOUI\TextInputWidget( [
                             'name' => 'bypassAddUsername',
@@ -56,7 +68,7 @@ class RequirementsBypassPage {
         );
     }
 
-    function showBypassesList() {
+    function showBypassesList(Session &$session) {
         $output = $this->pageContext->getOutput();
 
         $dbr = getReadOnlyDatabase();
@@ -77,6 +89,7 @@ class RequirementsBypassPage {
 
             $table .= Html::openElement('td');
             $table .= Html::openElement('form', ['action' => SpecialPage::getTitleFor('ConfirmAccounts', wfMessage('scratch-confirmaccount-requirements-bypasses-url')->text())->getFullURL(), 'method' => 'post']);
+            $table .= Html::element('input', ['type' => 'hidden', 'name' => 'csrftoken', 'value' => setCSRFToken($session)]);
             $table .= Html::element('input', ['type' => 'hidden', 'name' => 'bypassRemoveUsername', 'value' => $username]);
             $table .= Html::element('input', ['type' => 'submit', 'value' => wfMessage('scratch-confirmaccount-requirements-bypasses-remove')->text()]);
             $table .= Html::closeElement('form');
@@ -90,12 +103,12 @@ class RequirementsBypassPage {
         $output->addHTML($table);
     }
 
-    function render() {
+    function render(Session &$session) {
         $output = $this->pageContext->getOutput();
 
         $output->enableOOUI();
 
-        $this->showAddBypassForm();
-        $this->showBypassesList();
+        $this->showAddBypassForm($session);
+        $this->showBypassesList($session);
     }
 }
\ No newline at end of file

programming_language: PHP