This is less a front-door break-in than abusing the master key after you already got into the building
The finding bundles old MySQL user-defined-function issues that let a database user coerce the server into loading local libraries. The concrete bugs split into two buckets: patched 2005-era flaws in 4.0 < 4.0.25, 4.1 < 4.1.13, and 5.0 < 5.0.7/5.0.8 for the CREATE FUNCTION overflow/path-handling bugs, plus the broader Windows UDF abuse described in AppSecInc MYSQL05-V0003, which required INSERT on mysql.func and had no vendor patch because it was fundamentally about how UDF loading worked.
Tenable's HIGH label is too generous for modern enterprise prioritization. The decisive friction is attacker position: this is not unauthenticated remote exploitation, it is authenticated database abuse requiring a high-trust privilege on the MySQL system tables, and the most explicit hang/crash path is Windows-specific on software branches from 2005.
4 steps from start to impact.
Reach MySQL with valid credentials using mysql client
- TCP reachability to MySQL, typically
3306/tcp - A valid MySQL username and password
- Most enterprise MySQL is not Internet-exposed
- Credential theft or reuse is required before the CVE matters
- Database firewalls, SGs, and app-tier segmentation often block direct client access
Obtain UDF creation capability via mysql.func / CREATE FUNCTION
INSERT on mysql.func. That is a strong privilege because it lets the caller register server-side native code hooks, not just run ordinary SQL.- Privileges to create UDFs, historically
INSERTonmysql.func - A target running MySQL with UDF support enabled
- This privilege is rarely granted to ordinary application accounts
- If an attacker already has DBA-like rights, they usually have easier ways to damage or own the database
mysql.tables_priv and SHOW GRANTS; unauthenticated scanners usually miss this prerequisite entirely.Abuse Windows library loading with crafted CREATE FUNCTION
CREATE FUNCTION ... SONAME, the attacker points MySQL at a bad or unintended DLL. On Windows this can hang the server through LoadLibraryEx() error handling, or load libraries exposing *_init / *_deinit symbols that MySQL mistakes for compatible UDF entry points.- Target is Windows for the clearest
LoadLibraryExhang path - The server process can resolve the referenced DLL path/name
- Non-Windows systems are much less relevant for the specific CVE-2005-2572 path
- Modern deployments often run Linux, not Windows, for MySQL
- The attacker still depends on local library availability and calling conventions
CREATE FUNCTION are the best signal; network IDS generally lacks deep MySQL statement visibility unless explicitly configured.Trigger crash, hang, or possible code execution in mysqld
mysqld, hang the service, or in older cases reach memory corruption. Impact runs in the context of the database service account, so blast radius is the DB host and its data, not the entire estate by default.- Successful function registration
- Service account privileges sufficient for meaningful host impact
- A crash is easier than reliable code execution
- Modern service hardening and lower-privilege service accounts reduce host-level payoff
- The effect is usually single-host, not wormable
The supporting signals.
| In-the-wild status | No current exploitation pressure found: no CISA KEV listing located, and I found no GreyNoise-style current campaign evidence tied to CVE-2005-2572. |
|---|---|
| Public exploit / PoC | Advisory-grade PoC only: Team SHATTER / Reid Borsuk published detailed exploitation mechanics in MYSQL05-V0001, V0002, and V0003; I did not find a mainstream Metasploit-era turnkey exploit for this exact CVE. |
| EPSS | 1.363% EPSS from Tenable's CVE page, which is low absolute probability despite a non-trivial percentile among old CVEs. |
| KEV status | Not listed in CISA KEV based on the catalog page checked; no evidence this is a routinely exploited modern priority item. |
| CVSS vector | CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H on Tenable, but that vector overstates reality because PR:L here really means *authenticated DB user with UDF-capable privilege*, not a garden-variety low-priv account. |
| Affected versions | Narrow practical exposure: patched CREATE FUNCTION issues were fixed in 4.0.25, 4.1.13, and 5.0.7-beta/5.0.8; the no-patch Windows UDF abuse in MYSQL05-V0003 was described as affecting ALL, but still required INSERT on mysql.func and was mainly a Windows operational risk. |
| Fixed versions / backports | Mixed: MYSQL05-V0001 and V0002 were patched by MySQL in 4.0.25, 4.1.13, and 5.0.7-beta; Ubuntu backported the CREATE FUNCTION overflow fix in USN-180-1. MYSQL05-V0003 states no fix available and recommends privilege restriction instead. |
| Exposure data | Internet scanability exists, exploitability does not follow: Censys and Shodan both track exposed MySQL services on 3306/tcp, but scan data cannot tell you whether a reachable account also has mysql.func insertion rights—the key gating factor here. |
| Disclosure timeline | Research disclosed 2005-08-08 in AppSecInc advisories; NVD published CVE-2005-2572 on 2005-08-16. |
| Researcher | Reid Borsuk of Application Security, Inc. / Team SHATTER. |
noisgate verdict.
The single biggest severity reducer is that exploitation requires authenticated MySQL access plus UDF-capable privilege on mysql.func, which is already a high-trust, post-compromise condition. On top of that, the clearest exploit path is tied to Windows and 2005-era MySQL branches, so the reachable population in a modern enterprise is tiny.
Why this verdict
- Start from vendor 8.8, then cut hard for attacker position: the attacker needs valid MySQL credentials and the ability to create UDFs, historically
INSERTonmysql.func; that's far closer to DBA-equivalent access than to commodity remote exploitation. - Cut again for population: the hang/crash path in
CVE-2005-2572is Windows-specific, and the explicitly patched branches are ancient (4.0/4.1/5.0from 2005). For most enterprises this is legacy-software residue, not broad fleet exposure. - Cut for threat pressure: no KEV listing, low EPSS in absolute terms, and only advisory-level public exploit material. There is no evidence this is a live, mass-targeted patch-everything event.
Why not higher?
It is not unauthenticated remote, not wormable, and not broadly reachable from the Internet in the way a web RCE is. The exploit chain assumes the attacker is already inside the database trust boundary with a privilege most application accounts should never have.
Why not lower?
If you truly still run legacy Windows MySQL and have sloppy grants on mysql.func, the impact is real: service crash, service hang, and potentially code execution in the database service context. So this is not pure scanner trivia; it is just low-priority for patch scheduling because the exposure preconditions are so restrictive.
What to do — in priority order.
- Revoke UDF creation rights — Remove
INSERTonmysql.funcand any equivalent UDF-install capability from non-admin principals. For a LOW verdict there is no SLA (treat as backlog hygiene), but this is the fastest way to collapse exploitability without waiting on software change windows. - Block direct MySQL reachability — Restrict
3306/tcpto application tiers and DBA jump hosts so stolen end-user or lateral-movement creds cannot even reach the service. For a LOW verdict there is no SLA (treat as backlog hygiene), but do it whenever you are already touching database firewall policy. - Alert on
CREATE FUNCTIONandmysql.funcchanges — Turn on database auditing or SIEM parsing forCREATE FUNCTION,DROP FUNCTION, and writes tomysql.func. For a LOW verdict there is no SLA (treat as backlog hygiene), but this gives you durable detection if the plugin remains noisy across legacy appliances. - Run
mysqldwith least OS privilege — If you still have Windows MySQL, make sure the service account is not local admin and cannot write broadly across the host. For a LOW verdict there is no SLA (treat as backlog hygiene), but this limits host impact if someone does reach UDF abuse.
- WAF rules do not help because this is MySQL protocol abuse, not HTTP input.
- MFA on the application does not protect direct database accounts already sitting behind the app tier.
- Just upgrading the OS DLLs does not solve the underlying
mysql.funcprivilege exposure or the no-patch UDF abuse pattern described inMYSQL05-V0003.
Crowdsourced verification payload.
Run this from a DBA workstation or the target DB host where the mysql CLI is installed. Invoke it with an administrative login that can read mysql.tables_priv, for example: python3 check_mysql_udf_17698.py --mysql /usr/bin/mysql --login-path=localdba or python check_mysql_udf_17698.py --mysql "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql.exe" -h db01 -u root -p. No OS admin rights are required, but the MySQL account needs metadata visibility on system tables.
#!/usr/bin/env python3
# check_mysql_udf_17698.py
# Exit codes:
# 0 = PATCHED
# 1 = VULNERABLE
# 2 = UNKNOWN
import argparse
import subprocess
import sys
import re
SAFE_ADMIN_USERS = {
'root@localhost',
'mysql.session@localhost',
'mysql.sys@localhost',
'mysql.infoschema@localhost',
}
def run_mysql(mysql_bin, conn_args, sql):
cmd = [mysql_bin, '--batch', '--raw', '--skip-column-names'] + conn_args + ['-e', sql]
proc = subprocess.run(cmd, capture_output=True, text=True)
if proc.returncode != 0:
raise RuntimeError(proc.stderr.strip() or 'mysql command failed')
return proc.stdout.strip()
def parse_version(v):
m = re.match(r'^(\d+)\.(\d+)\.(\d+)', v)
if not m:
return None
return tuple(int(x) for x in m.groups())
def version_lt(a, b):
return a < b
def version_in_legacy_vuln_range(vt):
# Legacy fixed versions from MySQL advisories/bugs for related UDF issues.
if vt is None:
return False
major, minor, patch = vt
if (major, minor) == (4, 0) and version_lt(vt, (4, 0, 25)):
return True
if (major, minor) == (4, 1) and version_lt(vt, (4, 1, 13)):
return True
if (major, minor) == (5, 0) and version_lt(vt, (5, 0, 8)):
return True
return False
def main():
ap = argparse.ArgumentParser(description='Assess Tenable 17698 / MySQL UDF risk')
ap.add_argument('--mysql', required=True, help='Path to mysql CLI')
ap.add_argument('-h', '--host', dest='host', help='MySQL host')
ap.add_argument('-P', '--port', dest='port', help='MySQL port')
ap.add_argument('-u', '--user', dest='user', help='MySQL user')
ap.add_argument('-p', '--password', dest='password', help='MySQL password')
ap.add_argument('--login-path', dest='login_path', help='MySQL login-path to use')
args = ap.parse_args()
conn_args = []
if args.host:
conn_args += ['-h', args.host]
if args.port:
conn_args += ['-P', str(args.port)]
if args.user:
conn_args += ['-u', args.user]
if args.password is not None:
conn_args += [f'-p{args.password}']
if args.login_path:
conn_args += [f'--login-path={args.login_path}']
try:
info = run_mysql(
args.mysql,
conn_args,
"SELECT VERSION(), @@version_compile_os, @@version_comment;"
)
parts = info.split('\t')
if len(parts) < 3:
print('UNKNOWN - could not parse VERSION()/OS data')
sys.exit(2)
version_str, compile_os, version_comment = parts[0], parts[1], parts[2]
version_tuple = parse_version(version_str)
os_text = f'{compile_os} {version_comment}'.lower()
is_windows = 'win' in os_text
grants = run_mysql(
args.mysql,
conn_args,
"SELECT CONCAT(User,'@',Host), Table_priv FROM mysql.tables_priv WHERE Db='mysql' AND Table_name='func';"
)
risky_principals = []
if grants:
for line in grants.splitlines():
cols = line.split('\t')
if len(cols) != 2:
continue
principal, privs = cols[0].strip(), cols[1].strip().upper()
if 'INSERT' in privs and principal not in SAFE_ADMIN_USERS:
risky_principals.append(principal)
# Decision logic:
# - Non-Windows: treat the CVE-2005-2572 path as not applicable.
# - Windows + risky mysql.func grants: vulnerable from a hardening standpoint.
# - Windows + ancient legacy branches: vulnerable due to the old UDF bug family.
if not is_windows:
print(f'PATCHED - server is not Windows-like ({compile_os} / {version_comment}); CVE-2005-2572 main path is not applicable')
sys.exit(0)
if risky_principals:
print('VULNERABLE - Windows MySQL with non-admin INSERT privilege on mysql.func: ' + ', '.join(risky_principals))
sys.exit(1)
if version_in_legacy_vuln_range(version_tuple):
print(f'VULNERABLE - Windows MySQL version {version_str} falls in legacy UDF vulnerable ranges')
sys.exit(1)
print(f'PATCHED - Windows MySQL version {version_str} is not in legacy fixed ranges and no risky mysql.func INSERT grants were found')
sys.exit(0)
except Exception as e:
print(f'UNKNOWN - {e}')
sys.exit(2)
if __name__ == '__main__':
main()
If you remember one thing.
mysql.func insertion rights and schedule upgrade/retirement in normal maintenance rather than burning emergency patch capacity.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.