禁用udf提权需三重防护:一是配置plugin_dir=/nonexistent/path使create function soname硬性失败;二是回收file权限阻断恶意so写入与敏感文件读取;三是设置secure_file_priv限定文件操作路径并清理存量危险udf函数。

禁用UDF加载:直接让CREATE FUNCTION SONAME失败
MySQL本身不内置system()、sys_exec()这类函数,它们全靠用户通过UDF(User Defined Function)手动安装。所以“禁用系统函数”的本质,是让UDF根本加载不起来。
最可靠的做法是在my.cnf的[mysqld]段强制指定一个不存在的插件目录:
[mysqld] plugin_dir = /nonexistent/path
这样任何CREATE FUNCTION ... SONAME 'xxx.so'都会立刻报错:ERROR 1126 (HY000): Can't open shared library 'xxx.so'。比单纯回收CREATE FUNCTION权限更彻底——因为权限可被绕过,而路径不存在是硬性失败。
- 不要设成
/tmp或/var/lib/mysql/plugin这种真实可写路径 - MySQL 8.0.29+ 可用
--disable-plugin=udf启动参数替代,但配置文件方式兼容性更好 - 顺手删掉默认的示例UDF文件(如
lib/plugin/udf_example.so),避免被误启用
回收FILE权限:断掉UDF落地和文件读取链路
FILE权限不是系统函数,却是UDF提权的关键一环:没有它,攻击者无法用SELECT ... INTO DUMPFILE把恶意so写到MySQL能读的路径,也无法用LOAD_FILE()读取/etc/passwd等敏感文件辅助探测。
执行以下命令彻底回收(注意:这是全局权限,不能按库收缩):
REVOKE FILE ON *.* FROM 'app_user'@'%';
检查是否生效:
使用ydata-profiling(前身为pandas-profiling)生成全面的数据质量报告,包含相关性分析、缺失值模式和基数检测。导出交互式HTML仪表板和JSON摘要。
SELECT User,Host,File_priv FROM mysql.user WHERE File_priv='Y';
-
REVOKE不报错 ≠ 权限已移除,必须用SHOW GRANTS FOR 'user'@'host'确认结果 - 即使只读账号,只要带
FILE,就可能被SQL注入利用——别心存侥幸 - 如果业务真需要导出数据,改用
mysqldump或应用层生成CSV,而非SQL语句
限制secure_file_priv:封死文件操作的物理路径
secure_file_priv不直接影响函数,但它决定了LOAD DATA INFILE和SELECT ... INTO OUTFILE能操作的目录范围。设成/dev/null或空字符串会完全禁用这两个语句;设成具体受限目录(如/var/lib/mysql-files/)则更可控。
在my.cnf中配置:
[mysqld] secure_file_priv = /var/lib/mysql-files/
- 该参数必须在启动时加载,运行时
SET GLOBAL无效(除非有SUPER权限且重启) - 配合
local_infile = OFF,防止客户端从本地上传文件 - 目录需由
mysql用户可读写,且不能是Web目录或系统关键路径
检查并清理已存在的危险UDF函数
配置只是预防,还得清理存量风险。先查当前有没有人已经装了高危UDF:
SELECT name, dl FROM mysql.func WHERE name LIKE '%sys%' OR name LIKE '%exec%' OR name LIKE '%eval%';
如果返回结果,立即卸载:
DROP FUNCTION IF EXISTS sys_exec; DROP FUNCTION IF EXISTS sys_eval;
- 别依赖“没人会去装”,运维脚本、第三方工具、历史遗留都可能引入
-
DROP FUNCTION需要DELETE权限在mysql.func表上,普通账号不应拥有 - 清理后建议重启MySQL,确保内存中无残留句柄
真正卡住UDF提权的不是某条REVOKE命令,而是plugin_dir指向黑洞 + FILE权限回收 + secure_file_priv锁定路径这三者的组合。漏掉任意一环,都可能被绕过。










