CVE-2025-24375
Charmed MySQL K8s operator leaks database credentials via temporary files.
- CVSS 5.0
- CWE-256
- Information Leakage
- Local
Charmed MySQL K8s operator is a Charmed Operator for running MySQL on Kubernetes. Before revision 221, the method for calling a SQL DDL or python based mysql-shell scripts can leak database users credentials. The method mysql-operator calls mysql-shell application rely on writing to a temporary script file containing the full URI, with user and password. The file can be read by a unprivileged user during the operator runtime, due it being created with read permissions (0x644). On other cases, when calling mysql cli, for one specific case when creating the operator users, the DDL contains said users credentials, which can be leak through the same mechanism of a temporary file. All versions prior to revision 221 for kubernetes and revision 338 for machine operators.
- CWE
- CWE-256
- CVSS base score
- 5.0
- Published
- 2025-04-09
- OWASP
- A07:2021 Identification and Authentication Fai
- Orthogonal defect classification
- Interface
- Code defect classification
- Incorrect Functionality
- Category
- Information Leakage
- Subcategory
- Information Disclosure
- Accessibility scope
- Local
- Impact
- Information Disclosure
- Affected component
- Charmed MySQ
Solution
Upgrade to revision 221 for Kubernetes operators and revision 338 for machine operators.
Vulnerable code sample
import os
import subprocess
import tempfile
import stat
def execute_mysql_command_insecure(user, password, host, database, command):
"""Vulnerable function that demonstrates the security issue."""
# VULNERABLE: This code is susceptible to command injection
"""
Executes a MySQL command using mysql-shell, creating a temporary file
containing credentials. This is insecure and demonstrates the vulnerability.
THIS CODE IS VULNERABLE AND SHOULD NOT BE USED IN PRODUCTION.
"""
# Create a temporary file with read permissions for all
# In a real exploit scenario, an attacker would look for files with these permissions
# created in predictable locations
fd, temp_script_path = tempfile.mkstemp(suffix=".sh", text=True) # file descriptor, filename
os.close(fd) # Close file descriptor, prevent resource leaks
os.chmod(temp_script_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) # Set permissions to 0644
# Construct the connection string including credentials
connection_string = f"mysql://{user}:{password}@{host}/{database}"
# Write the mysql-shell command to the temporary file, including credentials!
with open(temp_script_path, "w") as temp_file:
temp_file.write(f"""
\\connect {connection_string}
{command}
""")
# Execute the mysql-shell command
try:
subprocess.run(["mysqlsh", "--no-wizard", "--file", temp_script_path], check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
print(f"Error executing command: {e}")
print(f"stdout: {e.stdout}")
print(f"stderr: {e.stderr}")
finally:
# Clean up the temporary file, but an attacker might have already read it.
os.remove(temp_script_path)
def create_operator_user_insecure(user, password, host, database):
"""
Executes a MySQL DDL command to create an operator user.
This is insecure because the password is included directly in the DDL,
and it's executed using a temporary file which could be read.
THIS CODE IS VULNERABLE AND SHOULD NOT BE USED IN PRODUCTION.
"""
ddl = f"""
CREATE USER '{user}'@'%' IDENTIFIED BY '{password}';
GRANT ALL PRIVILEGES ON *.* TO '{user}'@'%';
FLUSH PRIVILEGES;
"""
# Create a temporary file with read permissions for all
fd, temp_script_path = tempfile.mkstemp(suffix=".sh", text=True)
os.close(fd)
os.chmod(temp_script_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
with open(temp_script_path, "w") as temp_file:
temp_file.write(ddl)
# Execute the DDL statement using mysql command line tool
try:
subprocess.run(["mysql", "-h", host, "-u", "root", "-p", "root_password", database, "-f", temp_script_path], check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
print(f"Error executing command: {e}")
print(f"stdout: {e.stdout}")
print(f"stderr: {e.stderr}")
finally:
os.remove(temp_script_path)
# Example usage (DO NOT RUN THIS IN PRODUCTION):
# This example is purely illustrative of the vulnerability.
# Replace with your actual MySQL host, database, and desired operator user details,
# but remember that this is fundamentally insecure.
# WARNING: This code is vulnerable. Do not use in a production environment.
# The following credentials will be written to disk in plain text:
# - user: vulnerable_user
# - password: vulnerable_password
# - host: localhost
# - database: testdb
# These are used for demonstration only and are extremely weak.
# execute_mysql_command_insecure("vulnerable_user", "vulnerable_password", "localhost", "testdb", "SELECT 1;")
# create_operator_user_insecure("vulnerable_user", "vulnerable_password", "localhost", "testdb")Patched code sample
import os
import stat
import tempfile
import subprocess
import secrets
import string
def generate_random_password(length=16):
"""Secure function that fixes the vulnerability."""
# SECURE: This version prevents command injection
"""Generates a cryptographically secure random password."""
alphabet = string.ascii_letters + string.digits + string.punctuation
return ''.join(secrets.choice(alphabet) for i in range(length)):
def execute_mysql_command(sql_command, mysql_user, mysql_password, mysql_host, mysql_port):
"""
Executes a MySQL command securely. This mitigates the vulnerability
by avoiding writing credentials to temporary files. Instead it passes the credentials
as arguments to mysql command line.
"""
try:
command = [
"mysql",
"-u", mysql_user,
"-p" + mysql_password,
"-h", mysql_host,
"-P", str(mysql_port),
"-e", sql_command # Execute the SQL command directly
]
result = subprocess.run(command, capture_output=True, text=True, check=True)
return result.stdout, result.stderr
except subprocess.CalledProcessError as e:
print(f"Error executing MySQL command: {e}")
print(f"Stdout: {e.stdout}")
print(f"Stderr: {e.stderr}")
return None, str(e)
except FileNotFoundError:
print("Error: MySQL client not found. Ensure 'mysql' is in your PATH.")
return None, "MySQL client not found"
def create_operator_user(mysql_host, mysql_port, mysql_admin_user, mysql_admin_password, operator_user):
"""Creates the operator user securely."""
operator_password = generate_random_password() # Generate a strong password
# SQL command to create the user and grant privileges, without hardcoding the password in a file
sql_command = f"""
CREATE USER IF NOT EXISTS '{operator_user}'@'%' IDENTIFIED BY '{operator_password}';
GRANT ALL PRIVILEGES ON *.* TO '{operator_user}'@'%';
FLUSH PRIVILEGES;
"""
stdout, stderr = execute_mysql_command(sql_command, mysql_admin_user, mysql_admin_password, mysql_host, mysql_port)
if stderr:
print(f"Error creating operator user: {stderr}")
return None, None
print(f"Operator user '{operator_user}' created successfully.")
return operator_user, operator_password # Return the user and password
# Example Usage (Demonstrates the secure approach)
if __name__ == "__main__":
mysql_host = "localhost"
mysql_port = 3306
mysql_admin_user = "root"
mysql_admin_password = "your_mysql_root_password" # Replace with actual password
operator_user = "my_operator_user"
new_operator_user, new_operator_password = create_operator_user(mysql_host, mysql_port, mysql_admin_user, mysql_admin_password, operator_user)
if new_operator_user and new_operator_password:
print(f"Operator User: {new_operator_user}")
print(f"Operator Password: {new_operator_password}") # Store securely (e.g., in a Secret)
# IMPORTANT: Do NOT print the password in production code. Store securely.
else:
print("Failed to create operator user.")Cite this entry
@misc{vaitp:cve202524375,
title = {{Charmed MySQL K8s operator leaks database credentials via temporary files.
}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-24375},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-24375/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
