CVE-2019-15150: Cross-Site Request Forgery (CSRF) (CWE-352)
value: In the OAuth2 Client extension before 0.4 for MediaWiki, a CSRF vulnerability exists due to the OAuth2 state parameter not being checked in the callback function.
CWE 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.
CVSS v3 base score: 8.8
Published: 2019-08-19T04:15Z
Repository: https://github.com/Schine/MW-OAuth2Client
Language: PHP
Files changed: SpecialOAuth2Client.php, extension.json
Commit date: 2019-08-19 02:38:23 +0200
Commit message: Enforce/verify state parameter of callback

This fixes a security vulnerability where a malicious actor can bypass authentication via a clickjacking attack (CSRF vulnerability).

Signed-off-by: schema <schema@star-made.org>

Diff: diff --git a/SpecialOAuth2Client.php b/SpecialOAuth2Client.php
index 0c71116..a2c32e8 100644
--- a/SpecialOAuth2Client.php
+++ b/SpecialOAuth2Client.php
@@ -90,21 +90,31 @@ class SpecialOAuth2Client extends SpecialPage {
 		// Redirect the user to the authorization URL.
 		$wgOut->redirect( $authorizationUrl );
 	}
 
 	private function _handleCallback(){
+		global $wgRequest;
+
 		try {
+			$storedState = $wgRequest->getSession()->get('oauth2state');
+			// Enforce the `state` parameter to prevent clickjacking/CSRF
+			if(isset($storedState) && $storedState != $_GET['state']) {
+				if(isset($_GET['state'])) {
+					throw new UnexpectedValueException("State parameter of callback does not match original state");
+				} else {
+					throw new UnexpectedValueException("Required state parameter missing");
+				}
+			}
 
 			// Try to get an access token using the authorization code grant.
 			$accessToken = $this->_provider->getAccessToken('authorization_code', [
 				'code' => $_GET['code']
 			]);
 		} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
-
-			// Failed to get the access token or user details.
+			exit($e->getMessage()); // Failed to get the access token or user details.
+		} catch (UnexpectedValueException $e) {
 			exit($e->getMessage());
-
 		}
 
 		$resourceOwner = $this->_provider->getResourceOwner($accessToken);
 		$user = $this->_userHandling( $resourceOwner->toArray() );
 		$user->setCookies();
diff --git a/extension.json b/extension.json
index 1619bed..e11dc35 100644
--- a/extension.json
+++ b/extension.json
@@ -1,8 +1,8 @@
 {
 	"name": "MW-OAuth2Client",
-	"version": "0.3",
+	"version": "0.4",
 	"author": [
 		"[http://dekeijzer.org Joost de Keijzer]",
 		"[https://www.mediawiki.org/wiki/User:Nischayn22 Nischay Nahata]",
 		"[https://star-made.org Schine GmbH]"
 	],

Vulnerable code: 
			// Failed to get the access token or user details.

	"version": "0.3",
Fixed code: 		global $wgRequest;

			$storedState = $wgRequest->getSession()->get('oauth2state');
			// Enforce the `state` parameter to prevent clickjacking/CSRF
			if(isset($storedState) && $storedState != $_GET['state']) {
				if(isset($_GET['state'])) {
					throw new UnexpectedValueException("State parameter of callback does not match original state");
				} else {
					throw new UnexpectedValueException("Required state parameter missing");
				}
			}
			exit($e->getMessage()); // Failed to get the access token or user details.
		} catch (UnexpectedValueException $e) {
	"version": "0.4",