Remediation Guides

MySQL Unpassworded Account Check (Nessus 10481): How to Find and Fix Passwordless MySQL and MariaDB Accounts

26 September 2026 10 min read

MySQL Unpassworded Account Check (Nessus plugin 10481) means the scanner logged in to your MySQL or MariaDB server over the network with an account that has no password. Fix it by setting passwords with ALTER USER, dropping anonymous and remote root accounts (mysql_secure_installation covers much of this), then binding the server to a private address and firewalling TCP 3306.

This is a configuration problem, not a software bug, so no update or patch clears it. Only changing the accounts, and ideally the network exposure, does.

What the scanner is actually detecting

Plugin 10481 is a remote check in the Nessus Databases family. Nessus connects to the MySQL port and tries to log in without a password. If the server accepts the login, the plugin reports the finding with Tenable’s synopsis: “The remote database server can be accessed without a password.” It needs no credentials, which is why it appears in network-only scans too; see credentialed vs uncredentialed scanning for how these two kinds of checks differ.

Field Value
Plugin ID 10481
Name MySQL Unpassworded Account Check
Family / type Databases / remote
Severity High (CVSS v3 base 7.3, CVSS v2 base 7.5)
CVE references CVE-2002-1809, CVE-2004-1532
Tenable solution Set a password on the affected account, or disable it

The accounts that usually trigger it are:

  • A root account reachable from other hosts, such as root@’%’, with an empty password. The MySQL manual notes that initializing a server with mysqld –initialize-insecure creates root@localhost with no password, and a remote root account created later without IDENTIFIED BY has none either.
  • Anonymous accounts (empty user name). MariaDB’s mariadb-install-db creates them unless it runs with –skip-test-db. An anonymous account with host ‘%’ accepts any user name, so it matches whatever name the scanner tries.
  • Application or monitoring accounts created with CREATE USER and no IDENTIFIED BY clause.

Real-world risk, stated honestly

The impact depends on which account has no password and what it is allowed to do.

  • Root or another administrative account: anyone who can reach the port gets full control of every database. They can read and change data, create accounts to keep access, and use the FILE privilege within the limits of secure_file_priv. Fix this case immediately.
  • Application account: exposure equals that account’s grants, which often means read and write access to the whole application schema.
  • Anonymous account with default grants: on MariaDB, the default anonymous grants only cover the test and test_% databases, so direct data exposure is limited. The subtler issue is documented in the MySQL manual: ”@’localhost’ sorts ahead of a named account such as ‘jeffrey’@’%’, so a named user connecting locally can be authenticated as the anonymous account with the wrong privileges.

The finding also tells you the scanner reached the database port. A server listening only on 127.0.0.1 would not produce it. If the scanner was outside your perimeter, treat the port as part of your external attack surface and restrict it first.

How to confirm it on the host

Reproduce the scanner’s test from a machine on the scanner’s network. When no password option is given, the mysql client sends no password, and –no-defaults (given first) stops it reading one from option files.

# Try root with no password (use the address and port from the finding)
mysql --no-defaults -h 192.0.2.20 -P 3306 -u root -e "SELECT CURRENT_USER();"

# Try a user name that does not exist: success means an anonymous account matched
mysql --no-defaults -h 192.0.2.20 -P 3306 -u nosuchuser -e "SELECT CURRENT_USER();"

If either command returns a row, the finding is confirmed. CURRENT_USER() shows the account the server actually matched; a blank user name such as @% means an anonymous account let you in.

Then, as an administrator on the server, list accounts with an empty authentication string:

-- MySQL 5.7 and later
SELECT user, host, plugin, account_locked
FROM mysql.user
WHERE authentication_string = '';

-- MariaDB (mysql.user is a compatibility view from 10.4): look for rows where both columns are empty
SELECT user, host, plugin, password, authentication_string FROM mysql.user;

-- Anonymous accounts, and root accounts reachable from other hosts
SELECT user, host FROM mysql.user
WHERE user = ''
   OR (user = 'root' AND host NOT IN ('localhost', '127.0.0.1', '::1'));

Read the plugin column before acting. Accounts using auth_socket (MySQL) or unix_socket (MariaDB) have no stored password by design: they accept only local socket connections where the operating system user matches, and they refuse TCP/IP. MariaDB 10.4 and later authenticate root@localhost this way by default. Locked reserved accounts (run SHOW CREATE USER and look for ACCOUNT LOCK) cannot log in either; leave them alone.

Finally, check how the server listens and whether it was started without grant tables:

SHOW GLOBAL VARIABLES WHERE Variable_name IN ('bind_address', 'skip_networking', 'port');
sudo ss -ltnp | grep ':3306'
ps -eo args | grep -i '[s]kip-grant'

With –skip-grant-tables, every login succeeds. The MySQL 8.4 manual states the server also enables skip_networking in that mode, but a server left in maintenance mode is worth ruling out.

How to fix it

Step 1: record the current state

Save each affected account’s definition and grants, and back up the server option file. Before dropping an account, check whether stored objects use it as their definer:

SHOW CREATE USER 'root'@'%';
SHOW GRANTS FOR 'root'@'%';

SELECT 'routine', ROUTINE_SCHEMA, ROUTINE_NAME FROM information_schema.ROUTINES WHERE DEFINER = 'root@%'
UNION ALL
SELECT 'view', TABLE_SCHEMA, TABLE_NAME FROM information_schema.VIEWS WHERE DEFINER = 'root@%'
UNION ALL
SELECT 'trigger', TRIGGER_SCHEMA, TRIGGER_NAME FROM information_schema.TRIGGERS WHERE DEFINER = 'root@%'
UNION ALL
SELECT 'event', EVENT_SCHEMA, EVENT_NAME FROM information_schema.EVENTS WHERE DEFINER = 'root@%';

MySQL 8.4 refuses to drop an account that is the definer of stored programs or views (unless you hold SET_ANY_DEFINER or ALLOW_NONEXISTENT_DEFINER), and objects that run in definer context can fail once the account is gone.

Step 2, option A: run the secure installation script

# MySQL
sudo mysql_secure_installation

# MariaDB (the old mysql_secure_installation name also works)
sudo mariadb-secure-installation

Both scripts prompt for each action: set a root password, remove root accounts accessible from outside the local host, remove anonymous accounts, and remove the test database. They do not touch other named accounts, so an application account without a password survives. Run the query above again afterwards.

Step 2, option B: fix the accounts with SQL

-- 1. Remove anonymous accounts (use the exact host values your query returned)
DROP USER IF EXISTS ''@'localhost';
DROP USER IF EXISTS ''@'%';

-- 2. Give every remaining password-based account a long random password
ALTER USER 'root'@'localhost' IDENTIFIED BY 'replace-with-a-long-random-password';
ALTER USER 'app'@'10.0.20.%' IDENTIFIED BY 'replace-with-another-long-random-password';

-- 3. Remove root access from other hosts
DROP USER 'root'@'%';

-- 4. Unsure whether an account is still used? Lock it instead of dropping it
ALTER USER 'legacy'@'%' ACCOUNT LOCK;
  • Use ALTER USER and DROP USER, not UPDATE on mysql.user. Account management statements reload the grant tables immediately, so FLUSH PRIVILEGES is not needed. On MariaDB 10.4 and later, mysql.user is a view anyway.
  • Current MySQL releases (not MariaDB) also accept IDENTIFIED BY RANDOM PASSWORD, which generates a password and returns it once.
  • If people need remote administrative access, create a named account limited to the admin subnet, for example ‘dba_jdoe’@’10.0.5.%’, instead of keeping root@’%’.
  • On MariaDB 10.4 and later, root@localhost with unix_socket does not need a password. If you must switch it to password login, MariaDB documents ALTER USER root@localhost IDENTIFIED VIA mysql_native_password USING PASSWORD(“…”), which replaces socket authentication.

Step 3: stop exposing the port

MySQL’s default bind_address is *, meaning every interface. Find the option file that sets it, then set it to 127.0.0.1 for local-only use, or to the private address the application servers use:

grep -Rin 'bind.address' /etc/my.cnf /etc/my.cnf.d /etc/mysql 2>/dev/null
[mysqld]
bind-address = 10.0.10.5
sudo systemctl restart mysql     # MySQL on Debian/Ubuntu
sudo systemctl restart mysqld    # MySQL on RHEL family
sudo systemctl restart mariadb   # MariaDB

Then allow TCP 3306 only from the hosts that need it:

# firewalld
sudo firewall-cmd --permanent --remove-service=mysql
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.20.0/24" port port="3306" protocol="tcp" accept'
sudo firewall-cmd --reload

# ufw (also delete any broader 3306 rule listed by "ufw status numbered")
sudo ufw allow from 10.0.20.0/24 to any port 3306 proto tcp

On Windows, set bind-address in the [mysqld] section of the my.ini the service uses (for installer-created services, sc.exe qc on the service name shows it in the –defaults-file argument), restart the service, and scope the inbound rule:

Get-NetFirewallPortFilter | Where-Object LocalPort -eq 3306 | Get-NetFirewallRule
New-NetFirewallRule -DisplayName "MySQL from app subnet" -Direction Inbound -Protocol TCP -LocalPort 3306 -RemoteAddress 10.0.20.0/24 -Action Allow

How to verify the fix and rescan

  1. Repeat the two remote mysql commands. Expect ERROR 1045 (28000): Access denied … (using password: NO), or a connection error if the firewall now blocks your test host.
  2. Repeat the account query. Only socket-authenticated or locked accounts should have an empty authentication string.
  3. Confirm bind_address and the ss -ltnp output show the intended address.
  4. Rescan with the same Nessus scanner and policy. If the firewall now blocks that scanner, the plugin cannot test the accounts at all, so keep the account query output as evidence that the accounts themselves are fixed.
  5. Restart or recycle application connection pools and confirm they reconnect with the new credentials.

What can break and how to roll back

  • Applications without a password: they fail on their next new connection. Password changes do not affect sessions already connected, so failures can appear hours later when a pool recycles. Update the application’s secret in the same change window.
  • Clients silently matched to an anonymous account: after the anonymous account is gone, they authenticate as their named account, which may need a password and have different privileges.
  • Remote root users: backup jobs, monitoring agents and admin tools that used root@’%’.
  • Definer objects: views, routines, triggers and events owned by a dropped account.
  • Other interfaces: replication, backup or admin clients that connected through an address the server no longer listens on.

To roll back, unlock a locked account with ALTER USER … ACCOUNT UNLOCK. Recreate a dropped account from the saved SHOW CREATE USER and SHOW GRANTS output, but give it a password rather than restoring the empty one, or the finding returns. Restore the backed-up option file and restart the service to undo the bind-address change.

Common false positive reasons

This plugin completes a real login, so a report is usually accurate. Disputes tend to come from one of these:

  • A different listener: a second instance, a container publishing 3306, a database proxy or another MySQL-compatible product answers on the reported port. Check which process owns it with ss -ltnp.
  • A maintenance window: the server was running with –skip-grant-tables for a password reset when the scan ran.
  • Stale results: the account was fixed after the scan, or the address now belongs to a different host behind NAT or a load balancer.
  • Your own query, not the scanner: auth_socket and unix_socket rows look passwordless in mysql.user but cannot be used over the network.

FAQ

Does mysql_secure_installation clear Nessus 10481 on its own?

Only when the passwordless account is root or anonymous. It leaves other named accounts unchanged, so rerun the empty authentication_string query afterwards.

Is a root account using auth_socket or unix_socket “unpassworded”?

No. These plugins accept only local Unix socket connections from the matching operating system user and refuse TCP/IP, so a network scanner cannot use them.

Is blocking port 3306 enough?

It removes remote exposure and usually makes the finding disappear, but the account still has no password for anyone on the host or on an allowed network. Fix both.

Why does one scan report it and another does not?

Usually reachability: the scanners sit in different network zones. Tenable also lists global_settings/supplied_logins_only as an excluded KB item for this plugin, so a scan configured to use only supplied credentials skips it.

Tracking this finding across many hosts

Passwordless database accounts can come back when new servers are built from the same image or script. If you use SITEY, you can upload the .nessus export, have its AI draft a host-specific remediation script that runs only after a person approves it, deploy it through SITEY agents on Linux or Windows servers, and re-test each Nessus finding to confirm it is closed. Duplicates are merged per scanner, not across scanners.

Sources

SITEY closes the loop, not just the report.Discover, validate, fix and verify in your own infrastructure.

See pricing