Skip to main content

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.