udf提权需先通过sql注入获得低权限,再创建并调用自定义函数执行系统命令;修复关键在于禁用create function权限、收紧secure_file_priv、彻底阻断sql注入入口。

UDF 提权本身不是 SQL 注入的直接结果,而是攻击者在已获得数据库低权限(比如普通用户执行查询)后,利用注入点上传或创建自定义函数(CREATE FUNCTION),再调用该函数执行系统命令(如 sys_eval、sys_exec)完成提权。修复重点不在“删除存储过程”,而在切断 UDF 的加载路径 + 收紧执行权限 + 阻断注入入口。
为什么删 mysql.func 表或系统存储过程没用
攻击者通过 SQL 注入写入的 UDF 不依赖内置存储过程,而是直接注册到 mysql.func 系统表,并以共享库(.so / .dll)形式存在磁盘上。删掉 sys_eval 这类函数名只是表面清理,只要攻击者还能执行 CREATE FUNCTION 和 SELECT,就能重新注册任意 UDF。
必须禁用 CREATE FUNCTION 权限
这是最关键的一步。即使应用只读,也不应给应用账号 CREATE ROUTINE 或 EXECUTE 权限 —— 它们是 UDF 注册和调用的前提。
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'appuser'@'%';- 绝对不要加
GRANT CREATE ROUTINE, EXECUTE ON *.* TO ... - 检查现有账号:
SHOW GRANTS FOR 'appuser'@'%';,发现多余权限立即REVOKE
关闭 secure_file_priv 并移除 UDF 库文件
UDF 必须从服务器本地文件系统加载,而 secure_file_priv 控制可读目录。若其值为 NULL 或空字符串,攻击者可能通过 SELECT ... INTO DUMPFILE 写入恶意 .so;若为具体路径(如 /var/lib/mysql-files/),则需清空该目录并确保不可写。
- 查当前设置:
SELECT @@secure_file_priv; - 若返回
NULL或空,必须在my.cnf中显式设为有效路径(如/tmp/)或''(MySQL 8.0+ 不允许空,设为/dev/null类无效路径不生效,推荐设为仅 root 可写的空目录) - 确认后重启 MySQL,并手动删除疑似 UDF 文件:
find /var/lib/mysql* -name "*.so" -o -name "lib_mysqludf_*"
应用层必须堵死注入入口,否则一切加固都白搭
UDF 提权是二次攻击,源头永远是未参数化的 SQL。不能指望靠删函数或改配置“兜底”。
- 所有查询必须用参数化:PHP 用
PDO::prepare()+bindValue(),Java 用PreparedStatement,Python 用cursor.execute("SELECT ... WHERE id = %s", [user_id]) - 禁用拼接 SQL 的 ORM 方法:比如 SQLAlchemy 的
text("...")若带.format()或f-string就等同于裸拼 - 禁止使用
mysqli_query($conn, "SELECT * FROM users WHERE id = " . $_GET['id'])这类写法
真正危险的不是某个残留的 sys_eval 函数,而是开发人员仍习惯把用户输入当字符串拼进 SQL —— 只要这个习惯没改,攻击者下次会换别的 UDF 名、换别的共享库路径、甚至用 LOAD DATA INFILE 配合 secure_file_priv 绕过,手段只会更隐蔽。











