Database audit fix

How to Audit and Limit MySQL Admin User Privileges

SUPER lets a MySQL account change global variables, kill any session and bypass read_only, so an application login holding it is a full administrator. List the holders in mysql.user, revoke SUPER, and grant only the DML the app runs.

Hiren KalariyaLast reviewed: Sep 25, 2026Check mysql-admin-accounts
Medium
severity
Yes
needs root or sudo
3
results it can return
4 of 6
checks in this audit

Every threshold on this page is transcribed from the database-security-least-privilege audit script that CtrlOps runs, and a build check fails if the two ever disagree. See all 6 Least-Privilege Permissions checks, or how the audit runs.

Medium severityNeeds sudo

What this check reads

The check queries SELECT user,host FROM mysql.user WHERE Super_priv='Y', excluding the same fixed system list as the FILE check: root, mysql.session, mysql.sys, mysql.infoschema, mariadb.sys, mysql, debian-sys-maint and percona.telemetry. It reads that one column and nothing else, so the MySQL 8 dynamic privileges held in mysql.global_grants never reach the result. One surviving row is the threshold: any account left after the exclusions is a WARN listing each user@host pair, and an empty result passes.

When it applies

Runs only when MySQL/MariaDB is detected and reachable as root over the socket (MYOK=1); otherwise SKIP. It tests exactly one column - Super_priv in mysql.user - excluding the same fixed system account list as the FILE check. MySQL 8 dynamic privileges (SYSTEM_VARIABLES_ADMIN, CONNECTION_ADMIN, SET_USER_ID, BACKUP_ADMIN, …) live in mysql.global_grants and are not examined, so an account with near-superuser dynamic privileges but no SUPER passes this check.

What each result means

Result thresholds this check applies
ResultWhenWhat it means
WARNNon-system accounts hold SUPERAn account outside the expected system set holds administrative rights over the whole server, not just its own schema.
PASSNoneNo unexpected account holds SUPER, though dynamic privileges of comparable power are outside what this check sees.
SKIPNot accessibleNothing was verified. MySQL could not be queried as root, so no account has been cleared of administrative rights.

Why it matters

SUPER (deprecated in MySQL 8.0 in favour of dynamic privileges, still present) allows changing global variables, killing any session, bypassing read_only, setting definers, and more. Application accounts need only DML on their schema. OWASP: no admin rights for application accounts; CIS MySQL "Ensure 'SUPER' is not granted to non-administrative users".

Why it fails, and when it is wrong

  • GRANT ALL ON *.* to an app user, or a migration tool run with an admin account that was later reused by the app.
  • Replication, monitoring (pmm, zabbix) and backup accounts sometimes hold SUPER when they only need REPLICATION CLIENT, PROCESS, BACKUP_ADMIN etc.
  • MySQL 8 dynamic privileges (SYSTEM_VARIABLES_ADMIN, CONNECTION_ADMIN, SET_USER_ID) are not checked; an account with those but without SUPER passes.

How to fix it

REVOKE SUPER ON *.* FROM 'app'@'10.0.0.5';
GRANT SELECT,INSERT,UPDATE,DELETE ON appdb.* TO 'app'@'10.0.0.5';
-- inspect dynamic privileges too:
SELECT * FROM mysql.global_grants WHERE user NOT IN ('root','mysql.session','mysql.sys','mysql.infoschema');

Verify the fix

sudo mysql -N -e "SELECT CONCAT(user,'@',host) FROM mysql.user WHERE Super_priv='Y'"
# expected: only system accounts

# The blind spot - dynamic privileges the check never looks at:
sudo mysql -e "SELECT user, host, priv FROM mysql.global_grants
  WHERE user NOT IN ('root','mysql.session','mysql.sys','mysql.infoschema')"
# expected: nothing broad (SYSTEM_VARIABLES_ADMIN, SET_USER_ID, CONNECTION_ADMIN)

sudo mysql -e "SHOW GRANTS FOR 'app'@'10.0.0.5'"
# expected: only DML on the application schema

Debugging

MYOK=0; see the Authentication debugging notes.

These often hold SUPER when they need far less: REPLICATION CLIENT, REPLICATION SLAVE, PROCESS, or BACKUP_ADMIN. Grant the specific privilege and revoke SUPER; the finding then clears legitimately.

The known blind spot. Run the mysql.global_grants query above; dynamic privileges such as SYSTEM_VARIABLES_ADMIN are close to SUPER in power and this check cannot see them.

SUPER is deprecated there; the server may tell you to use dynamic privileges instead. Revoke it anyway if the column says Y, then grant the narrow dynamic privilege the account actually needs.

It was relying on something specific: bypassing read_only, setting a definer, or changing a global variable at runtime. Find out which from the error, then grant just that dynamic privilege rather than restoring SUPER.

Sources

How the script reads this

Next

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

All 6 Least-Privilege Permissions 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.

Windows

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