Compromise of 2FA secrets and backup codes possible through read-only SQLi – (miniOrange <= 5.5.82)

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


The plugin stores users’ emergency backup codes as plain text in the database. Furthermore, users’ TOTP secret keys are encrypted, but the encryption keys are stored in the same database as the encrypted ciphertexts.
An attacker that can obtain one of the seemingly never-ending read-only SQL-Injections will be able to bypass all 2FA checks for all users indefinitely.

Proof of concept

Compromise of TOTP secrets

The plugin uses OpenSSL to encrypt users’ TOTP secret keys before it stores them in the database.

function mo_GAuth_set_secret($user_id,$secret){
  global $Mo2fdbQueries;
  update_user_meta( $user_id, 'mo2f_get_auth_rnd_string', $key);

  // EDITOR: $key is ultimately passed into openssl_encrpyt. 
  update_user_meta( $user_id, 'mo2f_gauth_key', $secret);

For this, the plugin generates a unique encryption key per user.
However, the encryption keys are stored as plaintext alongside the ciphertexts (encrypted TOTP secrets), which defeats the very purpose of encrypting TOTP secrets in the first place.

Using a read-only SQLi, an attacker can indefinitely bypass all two-factor authentication checks by running the following SQL query:

SELECT user_id, meta_value, meta_key FROM wp_usermeta WHERE meta_key = 'mo2f_get_auth_rnd_string' OR meta_key = 'mo2f_gauth_key'

Compromise of emergency codes

The plugin stores 2FA emergency codes encrypted using OpenSSL.
However, the plugin also stores the encryption key in the “wp_options” table.

$key = get_option( 'mo2f_encryption_key' );
$codes_encrypt = MO2f_Utility::encrypt_data($str1, $key);
update_user_meta($id,'chqwetcsdvnvd', $codes_encrypt);

Using an obscure key like “chqwetcsdvnvd” to store the backup codes in the “wp_usermeta” does not make this implementation any safer.

An attacker can compromise all emergency codes for all users by running the following SQL query (through his read-only SQLi):

SELECT user_id, meta_value, meta_key FROM wp_usermeta WHERE meta_key = 'chqwetcsdvnvd'

SELECT option_value FROM wp_options WHERE option_name = 'mo2f_encryption_key'


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 *