SEC.AUTH.ROLE_ANONYMOUS — An account with no name is an account for anyone
- Category: security
- Severity: high
- Level: 0
- Confidence: deterministic
- Downtime class: none — the finding is about an account, not about a statement
- Stability: stable
- Suites: audit
- Applies to: MySQL 8.4
The part that misreads
''@'localhost' looks like an account nobody uses. It is an account anybody can use.
MySQL matches an incoming connection against account rows by user name and host, and a row with an empty user name matches any name the server does not otherwise know. So a client connecting from a matching host under a name that does not exist is not refused — it is authenticated as this account, with whatever privileges this account holds.
Every mistyped user name lands here. Every stale credential from that host lands here. Every probe that guesses a name lands here.
Bad
-- An empty user name. Not unused — it matches ANY name the server
-- does not otherwise know, from a host matching its pattern.
CREATE USER ''@'localhost' IDENTIFIED BY 'a real secret';
Good
-- Give the account a name, so it can only be used by something that knows it.
CREATE USER 'reporting'@'localhost' IDENTIFIED BY 'a real secret';
-- …or remove the anonymous one, which is what a current server ships without.
DROP USER ''@'localhost';
Where one comes from
Not from anything installed this decade. MySQL stopped shipping anonymous accounts long ago, and
mysql_secure_installation removes them where an older installation left one. A finding here is
almost always an account carried forward through upgrades — which is exactly the account nobody
remembers creating and nobody audits.
That is worth knowing before you remove it: if something genuinely depends on it, that dependency has survived unwritten for years and is worth writing down.
Why high and not critical
Because this one has a password. That is real protection, and it is the only thing separating it from
SEC.AUTH.ROLE_ANONYMOUS_NO_PASSWORD, which is reported at
critical and is a different sentence entirely.
What is not reported
PostgreSQL, by construction rather than by an engine check: pg_authid.rolname is the catalog
key, so a role with an empty name cannot exist there.
An account whose credential state could not be read is undetermined — the anonymity is certain,
but which of the two findings applies is not, and assigning it to the milder one would be a downgrade
taken exactly when the reading was worst.
Related
SEC.AUTH.ROLE_ANONYMOUS_NO_PASSWORD— the same account with nothing behind it either.SEC.AUTH.ROLE_NO_PASSWORD— a named account with no password.