Site takeover through stolen API credentials in combination with SQLi – (miniOrange <= 5.5.82)

| in

Affected pluginminiOrange
Active installs20,000+
Vulnerable version<= 5.5.82
Audited version5.5.82
Fully patched version
Recommended remediationRemoval of the plugin


The plugin uses remote APIs in almost all authentication-related contexts. In addition, the plugin authenticates itself using information stored exclusively as plaintext in the database.

An attacker, armed with a read-only SQLi, can trivially gain access to the API credentials and use them to take over the site in many ways.

Proof of concept

The plugin uses the Mo2f_Api class to communicate with its remote authentication server.

It uses HTTP basic authentication using HTTP headers, which are provided by calling the Mo2f_Api::get_http_header_array method.

    function get_http_header_array() {

        $customerKey = get_option( 'mo2f_customerKey' );
        $apiKey      = get_option( 'mo2f_api_key' );

        /* Current time in milliseconds since midnight, January 1, 1970 UTC. */
        $currentTimeInMillis = Mo2f_Api::get_timestamp();

        /* Creating the Hash using SHA-512 algorithm */
        $stringToHash = $customerKey . $currentTimeInMillis . $apiKey;;
        $hashValue = hash( "sha512", $stringToHash );

        $headers = array(
            "Content-Type" => "application/json",
            "Customer-Key" => $customerKey,
            "Timestamp" => $currentTimeInMillis,
            "Authorization" => $hashValue

        return $headers;

An attacker can obtain both “mo2fa_customerKey” and “mo2f_api_key”
by running the following SQL query through his read-only SQLi:

SELECT option_name, option_value FROM wp_options WHERE option_name = 'mo2f_customerKey' OR option_name = 'mo2f_api_key'

There are dozens of attacker vectors from there on, a couple of examples include:

  • Creating new (miniOrange) users.
  • Changing authentication methods for users.
  • Updating emergency codes for users.
  • Fetching all available user info that miniOranage has about users.

Proposed patch

1. Use API tokens for authentication and store the API tokens encrypted.

2. Implement a secure storage mechanism for retrieving the encryption key.

In WordPress, the options are, from most to least preferred:

  • Reading the encryption key from a file whose name is passed as an environment variable. (Plays great with docker secrets).
  • Reading the encryption key from an environment variable.
  • Reading the encryption key from a constant defined in the wp-config.php file.

A sample implementation could look like this:

final class Secrets {

  public static function get(string $secret_name) :string
    $contents = @file_get_contents($secret_name);
      return $contents;
    if(isset($_SERVER[$secret_name])) {
      return $_SERVER[$secret_name];
    $value = @constant($secret_name);

	if(!$value) {
		throw BrokenEnvironmentException::forMissingSecret($secret_name);
    return $value;



Vendor contactedSeptember 12, 2022
First ResponseSeptember 16, 2022
Fully patched at
Publicly disclosedApril 24, 2023


Leave a Reply

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