VAITP Dataset

← Back to the dataset

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.

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 ::