Hardcoded Database Credentials in PHP

Critical Risk Secrets Exposure
phpdatabaseconnection-stringsmysqlpostgresqlpdomysqlicredentialshardcoded-secrets

What it is

PHP applications that hardcode database connection strings containing usernames, passwords, and sensitive connection parameters directly in source code expose database credentials to anyone with access to the codebase, version control history, or server files, potentially leading to unauthorized database access and data breaches.

<?php
// VULNERABLE: Hardcoded database credentials

class DatabaseConnection {
    private $pdo;
    
    public function __construct() {
        try {
            // VULNERABLE: Credentials hardcoded in source
            $dsn = 'mysql:host=prod-db.company.com;dbname=company_app;charset=utf8mb4';
            $username = 'app_user';
            $password = 'ProductionPassword123!';  // VULNERABLE!
            
            $this->pdo = new PDO($dsn, $username, $password, [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            ]);
            
        } catch (PDOException $e) {
            die('Connection failed: ' . $e->getMessage());
        }
    }
    
    public function getConnection() {
        return $this->pdo;
    }
}

// VULNERABLE: Config file with hardcoded credentials
// config/database.php
return [
    'connections' => [
        'mysql' => [
            'host'     => 'prod-mysql.internal',
            'port'     => '3306',
            'database' => 'company_app',
            'username' => 'app_user',
            'password' => 'ProdSecret2024!',  // VULNERABLE!
        ],
    ],
];
<?php
// SECURE: Credentials from environment variables

class DatabaseConnection {
    private $pdo;
    
    public function __construct() {
        try {
            // SECURE: Load credentials from environment
            $host = getenv('DB_HOST') ?: 'localhost';
            $dbname = getenv('DB_NAME') ?: '';
            $username = getenv('DB_USERNAME') ?: '';
            $password = getenv('DB_PASSWORD') ?: '';
            
            if (empty($dbname) || empty($username) || empty($password)) {
                throw new Exception('Database credentials not configured');
            }
            
            $dsn = "mysql:host={$host};dbname={$dbname};charset=utf8mb4";
            
            $this->pdo = new PDO($dsn, $username, $password, [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => false,
            ]);
            
        } catch (PDOException $e) {
            // SECURE: Don't expose connection details in errors
            error_log('Database connection failed: ' . $e->getMessage());
            throw new Exception('Database connection error');
        }
    }
    
    public function getConnection() {
        return $this->pdo;
    }
}

// SECURE: Config file using environment variables
// config/database.php
return [
    'connections' => [
        'mysql' => [
            'host'     => getenv('DB_HOST'),
            'port'     => getenv('DB_PORT') ?: '3306',
            'database' => getenv('DB_NAME'),
            'username' => getenv('DB_USERNAME'),
            'password' => getenv('DB_PASSWORD'),
        ],
    ],
];

// .env file (NOT committed to version control)
// DB_HOST=prod-mysql.internal
// DB_PORT=3306
// DB_NAME=company_app
// DB_USERNAME=app_user
// DB_PASSWORD=SecurePasswordFromSecretsManager

💡 Why This Fix Works

The vulnerable code hardcodes database credentials directly in source files, exposing them to anyone with code access or version control history. The secure version loads credentials from environment variables using getenv(), validates they are present, and provides example .env configuration. The .env file should never be committed to version control.

Why it happens

PHP application code contains database connection instantiation with plaintext credentials directly in new PDO(), mysqli_connect(), or pg_connect() calls. Developers write code like new PDO('mysql:host=localhost;dbname=app', 'root', 'password123') directly in controller files, model classes, or bootstrap scripts. These hardcoded credentials are visible to anyone with file system access, get cached in opcache, appear in error messages and stack traces, and remain in the codebase permanently unless explicitly removed from all copies.

Root causes

Hardcoded Database Credentials in Connection Strings

PHP application code contains database connection instantiation with plaintext credentials directly in new PDO(), mysqli_connect(), or pg_connect() calls. Developers write code like new PDO('mysql:host=localhost;dbname=app', 'root', 'password123') directly in controller files, model classes, or bootstrap scripts. These hardcoded credentials are visible to anyone with file system access, get cached in opcache, appear in error messages and stack traces, and remain in the codebase permanently unless explicitly removed from all copies.

Credentials Embedded in Configuration Files

Database passwords and connection details stored in config.php, database.php, settings.php, or similar configuration files within the application directory. These configuration files typically define constants or arrays like define('DB_PASSWORD', 'prod_password') or $config['db']['password'] = 'secret'. Configuration files are often deployed to web-accessible directories, may be served by misconfigured web servers, get included in backup archives, and are frequently shared among development team members via insecure channels.

Database Passwords Committed to Version Control

Configuration files containing plaintext database credentials committed to Git, SVN, or other version control systems. Even if credentials are later removed or replaced with environment variables, they remain in version control history indefinitely, accessible via git log, git show, or repository browsing tools. Credentials in Git history persist across all clones, in forked repositories, in CI/CD systems that checkout code, and in developer local machines, creating widespread credential exposure.

Development Connection Strings Copied to Production

Developers create database connections with test credentials during local development and deploy the same configuration files to production environments without updating connection strings. Applications use single config.php files across all environments or lack environment detection logic. Production deployments inherit development database credentials, localhost connection strings pointing to wrong servers, or weak default passwords, leading to production systems attempting connections with incorrect or insecure credentials that may be logged or exposed.

Missing Environment Variable Usage

PHP applications don't leverage environment variables (via $_ENV, $_SERVER, or getenv()) for database configuration management. Developers unfamiliar with twelve-factor app methodology or environment-based configuration hardcode credentials instead of reading from environment. Without environment variable usage, applications can't separate secrets from code, can't use platform-provided secret injection (like Docker secrets or Kubernetes ConfigMaps), and require code changes for credential rotation.

DSN Strings with Embedded Credentials

Data Source Name (DSN) strings constructed with embedded username and password components like mysql://username:password@host/database used in database abstraction layers or ORM configurations. DSN strings with credentials appear in PDO constructor calls, framework configuration files (Laravel's .env, Symfony's database.yaml), and database connection pool configurations. These DSN strings are often logged, passed through multiple layers of code, and may be exposed in error messages when connection fails.

Fixes

1

Store Database Credentials in Environment Variables

Configure PHP applications to read database credentials from environment variables using getenv('DB_PASSWORD'), $_ENV['DB_PASSWORD'], or $_SERVER['DB_PASSWORD']. Set environment variables through web server configuration (Apache SetEnv, Nginx fastcgi_param), php-fpm pool configuration, Docker environment variables, or Kubernetes ConfigMaps/Secrets. Construct database connections as new PDO('mysql:host=' . getenv('DB_HOST'), getenv('DB_USER'), getenv('DB_PASSWORD')). This separates secrets from code, enables environment-specific configuration without code changes, and prevents credentials from being committed to version control.

2

Use .env Files with Environment-Specific Configurations

Implement .env file support using libraries like vlucas/phpdotenv for PHP applications or framework-native solutions (Laravel's .env, Symfony's .env.local). Create .env.example files with dummy values to commit to version control, while keeping actual .env files with real credentials excluded via .gitignore. Structure environment files for each deployment stage: .env.development, .env.staging, .env.production. Load environment variables at application bootstrap using Dotenv::createImmutable(__DIR__)->load() to inject configuration into $_ENV without hardcoding secrets.

3

Never Commit Credentials to Version Control

Add comprehensive .gitignore rules to exclude configuration files containing credentials: config.php, database.php, .env, .env.local, secrets/*, credentials.json. Use git-secrets or similar pre-commit hooks to scan for credential patterns before commits. If credentials were previously committed, use tools like BFG Repo-Cleaner or git-filter-branch to purge them from Git history entirely. After cleaning history, rotate all exposed credentials immediately. Implement repository secret scanning in CI/CD pipelines to detect accidental credential commits.

4

Integrate Dedicated Secrets Management Systems

Replace environment variables with dedicated secrets management platforms like AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, or Google Secret Manager. Use SDK clients (aws/aws-sdk-php, hashicorp/vault-php-sdk) to fetch secrets at runtime with proper authentication and caching. Configure secret rotation policies (automatic 30-90 day rotation for database passwords). Implement least-privilege IAM policies granting applications read-only access to specific secrets. Secrets managers provide encryption-at-rest, encryption-in-transit, audit logs, versioning, and centralized credential management across multiple applications.

5

Implement Environment-Specific Configuration Management

Create separate configuration management for development, staging, and production environments using environment detection logic or deployment-specific config files. Use environment variable APP_ENV to determine which configuration to load. Implement configuration classes that validate required environment variables are set and provide sensible defaults for non-sensitive settings. For frameworks, leverage native environment configuration (Laravel's config/database.php with env() helper, Symfony's environment variable processors). Never share production credentials with development environments.

6

Establish Credential Rotation and Access Auditing

Implement regular database credential rotation schedules (quarterly for production, monthly for high-security environments). Use secrets management platforms that support automatic rotation with minimal application downtime. Enable database audit logging to track connection attempts, failed authentications, and unusual query patterns. Monitor for credential usage from unexpected IP addresses or geographic locations. Implement principle of least privilege by creating database users with minimal required permissions rather than using root or admin accounts. Document credential rotation procedures and maintain an inventory of all database credentials across environments.

Detect This Vulnerability in Your Code

Sourcery automatically identifies hardcoded database credentials in php and many other security issues in your codebase.