Skip to content

Remove anonymous users and the test database

Severity: highApplies to: MySQL 8.4 LTSApplies to: MariaDB 11.x / 12.xApplies to: Upgraded or legacy installs
The fix
-- Anonymous accounts: no username, no password
SELECT user, host FROM mysql.user WHERE user = '';
DROP USER ''@'localhost';
DROP USER ''@'%'; -- if present
-- The test database, writable by anyone
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE db IN ('test', 'test\\_%');
-- Remote root
SELECT user, host FROM mysql.user WHERE user = 'root' AND host NOT IN ('localhost','127.0.0.1','::1');
FLUSH PRIVILEGES;

Anonymous accounts have an empty username and an empty password. Anyone who can reach the server connects as ''@'localhost' with no credentials at all. Historically MySQL created them at install so that a fresh server was immediately usable, and the account has a nasty second property: because MySQL sorts accounts by specificity, an anonymous entry for '%' can shadow a real named account, so myapp connects as anonymous and then gets permission errors that make no sense.

The test database was created with grants in mysql.db allowing any user — including anonymous — to create tables and write data in it. That’s a foothold: somewhere to stage data, somewhere to write, and on an install with FILE privileges intact, somewhere to build from.

This is mostly about upgraded and legacy servers

Section titled “This is mostly about upgraded and legacy servers”

Be clear about scope, because a fresh install may already be fine:

  • A fresh MySQL 8.4 initialised with mysqld --initialize creates no anonymous users and no test database. Nothing to do.
  • A server upgraded from 5.x, or built from an old image, or provisioned by an automation template written years ago, very often still has them. Upgrades don’t remove accounts.
  • MariaDB on some packagings still creates them.

So the version number doesn’t answer this — the mysql.user table does. That’s a three-second query and worth running even if you’re confident.

mysql_secure_installation is not a complete answer

Section titled “mysql_secure_installation is not a complete answer”

mysql_secure_installation is the standard recommendation and it does handle these. Two caveats worth knowing:

It is interactive and one-shot, so it can’t be part of configuration management, and nothing re-checks afterwards. An account added later by a migration script or a restored dump is invisible to it.

It also only ever ran on the server where someone remembered to run it. On a fleet, “we ran mysql_secure_installation” is a claim about one afternoon, not an ongoing property. The queries in the verify block are what you want in a periodic check.