Exposure of encryption secrets in world-readable .txt file (WP 2FA <= 2.3.0)

| in

Affected pluginWP 2FA
Active installs30.000+
Vulnerable version<= 2.3.0
Audited version2.0.0
Fully patched version
Recommended remediationRemoval of the plugin


The plugin will, under certain conditions, log all users’ 2FA secrets to a world-readable .txt file in the “wp-uploads” directory.

Proof of concept

The plugin contains debugging functionality (disabled by default) which can be enabled using the following code:

define('WP_DEBUG', true);

add_filter('wp_2fa_logging_enabled', fn() => true);

If a user (or the plugin author providing support on production sites) enables
the debugging functionality, the plugin will start to log all users’ TOTP secrets to a .txt file in the wp-uploads directory.

This happens when a user configures (or changes) his 2FA settings.
The relevant code is in the Open_SSL::encrypt method, which will be called before a user’s TOTP secret is stored in the database.

The method looks like this:

public static function encrypt( string $text ): string {
    Debugging::log( 'Encrypting a text: ' . $text );
    if ( self::is_ssl_available() ) {
        $iv   = self::secure_random( self::BLOCK_BYTE_SIZE );
        $key  = \openssl_digest( \base64_decode( WP2FA::get_secret_key() ), self::DIGEST_ALGORITHM, true ); //phpcs:ignore
        $text = \openssl_encrypt(
        $text = \base64_encode( $iv . $text ); //phpcs:ignore
    Debugging::log( 'Encrypted text: ' . $text );
    return $text;

Debugging::log is called with the plaintext strings (TOTP secret keys) as method arguments at the start and at the end of this method.

public static function log( $message ) {
    if ( self::is_logging_enabled() ) {
        self::write_to_log( self::get_log_timestamp() . "\n" . $message . "\n" . __( 'Current memory usage: ', 'wp-2fa' ) . memory_get_usage( true ) . "\n" );

The vendor knows that writing log files to the “wp-uploads” directory is dangerous, as this directory will be world-readable on most WordPress hosts. For that reason, the plugin creates a .htacces file in the same directory where logs are written to.

private static function write_to_log( $data, $override = false ) {
    $logging_dir_path = self::get_logging_dir_path();
    if ( ! is_dir( $logging_dir_path ) ) {
    $log_file_name = gmdate( 'Y-m-d' );
    return self::write_to_file( 'wp-2fa-debug-' . $log_file_name . '.log', $data, $override );

This protection is insufficient since most WordPress hosts run NGINX with PHP-fpm instead of Apache.

If we follow the code path of the Debuggin::get_logging_dir_path method, it becomes clear that, ultimately, the file path of the log file will have the following pattern:


Most NGINX configurations that WordPress hosts use will happily render this file in the browser.

An attacker can scan for the presence of these log files and grep the contents for the prefix “Encrypting a text.”

Suppose a secret is found in the log files. In that case, the associated user account can be compromised since TOTP authentication relies entirely on the assumption that an attacker can never access the secret shared between the web server and the user’s 2FA application.

Proposed patch

The logging of TOTP secrets or other text that is meant to be stored encrypted should be removed without replacement.


Vendor contactedMay 30, (through WPScan)
First Response
Fully patched at
Publicly disclosedApril 24, 2023


  • WPScan and the vendor considered this vulnerability a security enhancement instead.
  • The vendor created several releases without addressing this simple fix.

Leave a Reply

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