Database audit fix

How to Find and Secure Passwordless MySQL Accounts

A MySQL account whose authentication_string is empty while its plugin is a password plugin logs in with no secret at all, from any host its grant allows. Give each one a password with ALTER USER, lock it, or drop it.

Hiren KalariyaLast reviewed: Aug 30, 2026Check mysql-passwordless-accounts
High
severity
Yes
needs root or sudo
3
results it can return
3 of 8
checks in this audit

Every threshold on this page is transcribed from the database-security-authentication audit script that CtrlOps runs, and a build check fails if the two ever disagree. See all 8 Authentication checks, or how the audit runs.

High severityNeeds sudo

What this check reads

Queries mysql.user for rows with authentication_string='' (and, on old servers, password=''), excluding socket-auth plugins (auth_socket, unix_socket) and the built-in locked system accounts (mariadb.sys, mysql.sys, mysql.session, mysql.infoschema, debian-sys-maint). The first query references the pre-5.7.6 password column; if that errors the script silently retries without it. Output is user@host pairs, joined by GROUP_CONCAT into a single string. Empty output, or the literal string NULL, is the PASS; one surviving pair is enough to FAIL, and every pair found is listed in the message.

When it applies

Runs only when MySQL/MariaDB is detected (HAS_MY=1) and reachable as root over the socket (MYOK=1); otherwise it SKIPs. It queries mysql.user live. Accounts using auth_socket/unix_socket are excluded by design, as are the built-in system accounts mariadb.sys, mysql.sys, mysql.session, mysql.infoschema and debian-sys-maint. Anything else with an empty authentication string is reported, including PAM/LDAP accounts that legitimately store no hash.

What each result means

Result thresholds this check applies
ResultWhenWhat it means
FAILOne or more non-system, non-socket accounts have an empty authentication stringThe accounts are listed as user@host and can be used with no password at all. Set a password, lock them, or drop them.
PASSNoneEvery named account authenticates with a password or through socket auth.
SKIPNo root socket accessNothing was checked. The account table was never queried, so passwordless accounts are neither confirmed nor ruled out.

Why it matters

CIS MySQL "Ensure passwords are not stored in the global configuration / Ensure no users have an empty password". An empty authentication_string with a password plugin (mysql_native_password, caching_sha2_password) means any client that can reach the socket or port logs in with no secret.

Why it fails, and when it is wrong

  • Accounts created with CREATE USER 'app'@'%'; and never given a password.
  • Accounts migrated with IDENTIFIED WITH mysql_native_password but an empty hash after a botched dump/restore.
  • Accounts using PAM/LDAP plugins (authentication_pam, authentication_ldap_*) legitimately have an empty authentication_string and will be reported. Add the plugin name to the exclusion list or accept the finding.
  • account_locked='Y' accounts (e.g. definer-only accounts) also have empty strings. The script excludes the standard ones by name but not custom locked accounts. Check with SELECT user,host,plugin,account_locked FROM mysql.user.

How to fix it

ALTER USER 'app'@'%' IDENTIFIED BY 'long-random-password';
-- or, for accounts that should never log in:
ALTER USER 'definer'@'localhost' ACCOUNT LOCK;
-- or remove them:
DROP USER 'stale'@'%';

Verify the fix

sudo mysql -N -e "SELECT CONCAT(user,'@',host) FROM mysql.user
  WHERE authentication_string=''
    AND plugin NOT IN ('auth_socket','unix_socket')
    AND user NOT IN ('','mariadb.sys','mysql.sys','mysql.session','mysql.infoschema','debian-sys-maint')"
# expected: no rows

# Confirm a fixed account really needs its password now:
mysql -u app -h 127.0.0.1 -e 'SELECT 1'      # no -p
# expected: ERROR 1045

Debugging

MYOK=0; see the same note under MySQL Anonymous Users.

Check its plugin: SELECT user,host,plugin,account_locked FROM mysql.user WHERE user='<name>';. PAM/LDAP plugins (authentication_pam, authentication_ldap_*) and custom locked accounts have an empty authentication_string by design. The script's exclusion list is fixed, so these must be accepted as known findings.

The message is built from GROUP_CONCAT; a literal string NULL is treated as "none". If you see odd output, run the query above directly to see the real rows.

The script first runs a query that also references the legacy password column and silently retries without it if that errors. If both queries fail you get PASS with no accounts listed; verify by hand with the query above adapted to your column layout.

Sources

How the script reads this

Next

Re-run the Authentication audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 8 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.

All 8 Authentication fixes
Audit your fleet

Find every one of these on every server, in one click

CtrlOps runs this audit over your existing SSH connection - no agents, no scripts to manage. $7/user/month after a 1 month free trial - no credit card required.

✓ Start instantly·✓ No credit card·✓ No sneaky autorenewals