Page MenuHomePhabricator

CVE-2024-40601: Classic CSRF in MediaWikiChat's API modules
Closed, ResolvedPublicSecurity

Description

MediaWikiChat 's API modules are currently marked as POST-only, but they make no use of an anti-CSRF token, as is the standard security practise. This, naturally, leaves them open to CSRF.

Proposed and lightly tested patch (please excuse the line numbers not matching; this is based on top of a patch for T189417 that is yet to be merged, and I also have a lot of other, unrelated, uncommitted changes locally -- but you get the idea anyway from this patch):

diff --git a/MediaWikiChat.js b/MediaWikiChat.js
index febc2be..5570523 100644
--- a/MediaWikiChat.js
+++ b/MediaWikiChat.js
@@ -560,10 +560,10 @@ var MediaWikiChat = {
                $( '#mwchat-users #' + userE + ' .mwchat-useritem-kicklink' ).click( function() {
                        var parent = $( this ).parent().parent();
 
-                       $.ajax( {
-                               type: 'POST',
-                               url: mw.config.get( 'wgScriptPath' ) + '/api.php',
-                               data: { 'action': 'chatkick', 'id': parent.attr( 'data-id' ), 'format': 'json' }
+                       ( new mw.Api() ).postWithToken( 'csrf', {
+                               'action': 'chatkick',
+                               'id': parent.attr( 'data-id' ),
+                               'format': 'json'
                        } ).done( function() {
                                MediaWikiChat.getNew();
                        } );
@@ -603,15 +603,11 @@ var MediaWikiChat = {
                var toid = $( this ).parents( '.mwchat-useritem' ).attr( 'data-id' );
 
                if ( e.which == 13 ) {
-                       $.ajax( {
-                               type: 'POST',
-                               url: mw.config.get( 'wgScriptPath' ) + '/api.php',
-                               data: {
-                                       'action': 'chatsendpm',
-                                       'message': $( this )[0].value,
-                                       'id': toid,
-                                       'format': 'json'
-                               }
+                       ( new mw.Api() ).postWithToken( 'csrf', {
+                               'action': 'chatsendpm',
+                               'message': $( this )[0].value,
+                               'id': toid,
+                               'format': 'json'
                        } ).done( function() {
                                MediaWikiChat.getNew();
                                MediaWikiChat.restartInterval();
@@ -763,14 +759,10 @@ $( function() {
                                parseInt( $( '#mwchat-loading' ).attr( 'data-queue' ) ) + 1 )
                        .animate( { opacity: $( '#mwchat-loading' ).attr( 'data-queue' ) } );
 
-                       $.ajax( {
-                               type: 'POST',
-                               url: mw.config.get( 'wgScriptPath' ) + '/api.php',
-                               data: {
-                                       'action': 'chatsend',
-                                       'message': message,
-                                       'format': 'json'
-                               }
+                       ( new mw.Api() ).postWithToken( 'csrf', {
+                               'action': 'chatsend',
+                               'message': message,
+                               'format': 'json'
                        } ).done( function( msg ) {
                                MediaWikiChat.getNewReply( msg );
                                $( '#mwchat-loading' ).attr(
diff --git a/extension.json b/extension.json
index 543b1ca..8a51fa6 100644
--- a/extension.json
+++ b/extension.json
@@ -1,6 +1,6 @@
 {
        "name": "MediaWikiChat",
-       "version": "2.23.0",
+       "version": "2.24.0",
        "author": [
                "Adam Carter/UltrasonicNXT"
        ],
@@ -99,7 +100,12 @@
                                "chat-idle-minutes", "chat-idle-hours", "chat-idle-more", "chat-today", "chat-message-from",
                                "chat-private-message-from", "chat-mentioned-by"
                        ],
-                       "dependencies": [ "mediawiki.jqueryMsg", "mediawiki.user", "mediawiki.util" ]
+                       "dependencies": [
+                               "mediawiki.api",
+                               "mediawiki.jqueryMsg",
+                               "mediawiki.user",
+                               "mediawiki.util"
+                       ]
                },
                "ext.mediawikichat.site": {
                        "class": "MediaWiki\\ResourceLoader\\WikiModule",
diff --git a/includes/api/ChatKickAPI.php b/includes/api/ChatKickAPI.php
index 1c3e258..2ca15d3 100644
--- a/includes/api/ChatKickAPI.php
+++ b/includes/api/ChatKickAPI.php
@@ -58,6 +58,16 @@ class ChatKickAPI extends ApiBase {
                return true;
        }
 
+       /** @inheritDoc */
+       public function needsToken() {
+               return 'csrf';
+       }
+
+       /** @inheritDoc */
+       public function isWriteMode() {
+               return true;
+       }
+
        /** @inheritDoc */
        public function getAllowedParams() {
                return [
@@ -74,9 +84,4 @@ class ChatKickAPI extends ApiBase {
                        'action=chatkick&id=1' => 'apihelp-chatkick-example-1'
                ];
        }
-
-       /** @inheritDoc */
-       public function mustBePosted() {
-               return true;
-       }
 }
diff --git a/includes/api/ChatSendAPI.php b/includes/api/ChatSendAPI.php
index 61f712a..3c2219a 100644
--- a/includes/api/ChatSendAPI.php
+++ b/includes/api/ChatSendAPI.php
@@ -78,6 +80,15 @@ class ChatSendAPI extends ApiBase {
                }
        }
 
+       /** @inheritDoc */
+       public function needsToken() {
+               return 'csrf';
+       }
+
+       /** @inheritDoc */
+       public function isWriteMode() {
+               return true;
+       }
        /** @inheritDoc */
        public function getAllowedParams() {
                return [
@@ -94,9 +105,4 @@ class ChatSendAPI extends ApiBase {
                        'action=chatsend&message=Hello%20World!' => 'apihelp-chatsend-example-1'
                ];
        }
-
-       /** @inheritDoc */
-       public function mustBePosted() {
-               return true;
-       }
 }
diff --git a/includes/api/ChatSendPMAPI.php b/includes/api/ChatSendPMAPI.php
index 647fcd7..0c2beab 100644
--- a/includes/api/ChatSendPMAPI.php
+++ b/includes/api/ChatSendPMAPI.php
@@ -76,6 +76,16 @@ class ChatSendPMAPI extends ApiBase {
                }
        }
 
+       /** @inheritDoc */
+       public function needsToken() {
+               return 'csrf';
+       }
+
+       /** @inheritDoc */
+       public function isWriteMode() {
+               return true;
+       }
+
        /** @inheritDoc */
        public function getAllowedParams() {
                return [
@@ -97,9 +107,4 @@ class ChatSendPMAPI extends ApiBase {
                                => 'apihelp-chatsendpm-example-1'
                ];
        }
-
-       /** @inheritDoc */
-       public function mustBePosted() {
-               return true;
-       }
 }

Details

Risk Rating
Medium
Author Affiliation
Wikimedia Communities
Related Changes in Gerrit:

Event Timeline

ashley added projects: MediaWikiChat, Vuln-CSRF.
ashley moved this task from Backlog to Bugs on the MediaWikiChat board.
sbassett subscribed.

I think a public pull request could made for this (since it's non-Wikimedia-deployed and hosted on github)

sbassett changed the task status from Open to In Progress.Apr 23 2024, 3:17 PM
sbassett triaged this task as Medium priority.
sbassett changed Author Affiliation from N/A to Wikimedia Communities.
sbassett added a project: security-bug.
sbassett changed Risk Rating from N/A to Medium.

@ashley Since MediaWikiChat is not deployed in WMF production, this patch can be pushed through github.

MWC is hosted on WMF gerrit, it hasn't been hosted on GitHub since 2017 or so.

MWC is hosted on WMF gerrit, it hasn't been hosted on GitHub since 2017 or so.

Ok, great. Sometimes the mediawiki.org extension documentation can be extremely confusing.

Bawolff changed the visibility from "Custom Policy" to "Public (No Login Required)".May 1 2024, 11:01 PM
Bawolff changed the edit policy from "Custom Policy" to "All Users".
mmartorana renamed this task from Classic CSRF in MediaWikiChat's API modules to CVE-2024-40601: Classic CSRF in MediaWikiChat's API modules.Jul 8 2024, 5:37 PM