ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SQL注入进阶:从二阶注入到UDF提权的完整链路解析

SQL注入进阶:从二阶注入到UDF提权的完整链路解析 做安全这一行既看过不少只把“SQL注入”停留在or 11层面的朋友也看过把一条数据库告警查到服务器权限的较真选手。真正把“SQL注入”玩明白的人几乎都会把三件事放在一起琢磨二阶注入、堆叠查询、UDF提权。它们的攻击思路天然是一条链路——先用常规或非常规手法拿到入口再用堆叠查询扩大战果最后通过UDF把权限从数据库拔高到操作系统三种形态环环相扣难度一个比一个高危害也一级级放大。这篇内容适合正在学习渗透测试的初学者、需要写安全检测规则的研发同学也适合做代码审计或SDLC的负责人。我会把“这不是怎么绕过WAF的教程”这句话先说清楚重点放在原理、判定思路、复现路径和修复方案上。因为大家迟早会发现没有原理支撑的payload换个场景就失灵没有运维层配合的修复代码写得再严也拦不住提权。1. 为什么说“二阶注入、堆叠查询、UDF提权”是一条完整链路1.1 从一次OA系统告警讲起前阵子你如果刷过安全资讯大概率会看到某电子文档安全管理系统的一个接口存在SQL注入漏洞的消息接口名就叫cdgauthorisetempleteservice1之类。很多人扫一眼就过去了我的第一反应却是这类系统一旦被SQL注入撕开一个口子攻击者要的是后续动作而不是简单读写表。这类管理系统的数据库权限配置往往偏高早期的部署还可能直接沿用低版本的MySQLsecure_file_priv都没被限制。也就是说一个接口的注入点真正连起来的可能是注入点获取管理员口令 → 后台文件上传或直接执行堆叠查询写文件 → UDF提权拿到主机shell。这就像你看到一扇窗户没锁专业的人不是只探头进去拿东西而是会去想怎么打开大门让搬运车进来。二级标题里的三个关键词本质上是同一条入侵路径的不同节点。1.2 把需求翻译成技术语言如果有人问你“学高级SQL注入到底学什么”我一般给一个最精简的需求拆解输入点能不能被当成SQL代码解析如果当前SQL语句的后缀是固定的我能不能用堆叠查询开启第二条语句当前数据库账号有没有文件读写权限数据库安装目录、插件目录是否可写能不能通过UDF把函数变成命令执行接口这四个问题正好对应了“注入能力 → 执行能力 → 写能力 → 系统权限”的逐级放大。很多人只关心第一个问题觉得能注出来数据就算完事。实际上安全测试里最值钱的是证明“这个漏洞能对系统造成什么真实影响”UDF提权就是SQL注入危害从“数据库”跃迁到“服务器”的关键证据。1.3 影响面不只是“脱库”SQL注入的常规危害大家都懂登录绕过、敏感数据泄露、后台操作等。但一旦进入高级阶段影响面会扩大到这三层应用层任意数据被读写业务逻辑被绕过账号体系被控制。数据库层数据库配置被篡改文件系统被读写存储过程被调用。服务器层如果最终走到sys_exec这类UDF函数数据库用户就可以直接执行操作系统命令反弹shell、植入后门、读取服务器内存中的秘密都可能发生。这也是为什么我做代码审计时只要看到这里有个SQL注入不管是POST参数还是User-Agent头都会下意识再判断一下数据库账号权限而不能只停留在“修掉这个注入点”。因为你根本不知道攻击者背后还有多少招数等着你。2. 三种攻击形态的原理拆解以及常见的理解误区2.1 SQL注入登录绕过从一个if条件开始拆先复习一个最简单的场景也是搜索引擎里最常看到的热词“万能密码绕过登录”和“CTF SQL注入绕过登录”。它们本身不算高级但理解透了能帮我们把后面两个概念彻底想明白。假设网站的登录查询是SELECT * FROM users WHERE username $u AND password $p普通用户输入admin和123456拼出来就是SELECT * FROM users WHERE username admin AND password 123456可如果$p被输入成 OR 11拼出来就是SELECT * FROM users WHERE username admin AND password OR 11最终整个条件变成(usernameadmin AND password) OR (11)恒真绕过成功。到这里大部分人都会说“哦懂了闭合引号然后注释掉后面”。但对做防御的人来说关键是理解这个地方的“语法边界”被打破了用户输入本来是“数据”却被拼接成了“代码”。后面我们看到的堆叠查询、二阶注入本质都是同一种错误在不同场景下的变体。所以别再只问“为什么加了addslashes还会注入”因为转义永远只解决了一部分字符串边界问题解决不了像intval之后拼接数字、二次读取导致数据进入新SQL上下文这类问题。2.2 二阶注入危害被数据库“存档”了这是很多人理解得最模糊的一个点。我常这样跟朋友打比方一阶注入就像当场吵架输入什么立刻爆发二阶注入却像是把你的恶意写进了档案系统当时没察觉等哪天档案被调出来用的时候才炸。经典例子如下用户注册时在用户名里填写admin --。注册SQL也许用的是预编译或者过滤参数整个字符串被原样存进数据库没报错也没影响注册。第二天管理员在后台通过用户名来修改资料拼接了带用户名的SQL比如UPDATE users SET emailxxx WHERE usernameadmin -- 。这里--把后面的条件注释掉导致整张表所有人的资料都被修改了。很多人第一次听到会愣一下“明明第一步是安全的啊。”对第一步如果单独看可能没问题问题在于你把“不可信输入”原样存进了库而且没记录“这个字段是外部输入”的元信息。下次有些聚合并操作或检索流程直接把数据库里的字符串当成可拼接SQL的一部分就触发了。防御心态如果没有建立起来最典型的错误是只在数据库入口做转义认为只要入库时“干净了”就万事大吉。实际上二次注入需要系统在所有用到该字段的SQL中都保持参数化同时在输出和拼接不同语言环境时做对应编码。只要你把数据从MySQL里读出来再到PHP/Java/其他语言里拼SQL这条链路上任何一个连接点没有边界防护都可能被利用。2.3 堆叠查询一个数据库连接执行N条语句常规注入下很多人靠UNION SELECT来拼接列或读取数据但它有一个硬限制要求原SQL和目标查询的结果列数一致而且能力有限。堆叠查询不同它不需要“列数对齐”只需要数据库连接支持同时执行多条语句并且分号后允许注入方追加完整SQL。还是同一个例子SELECT * FROM users WHERE id 1如果代码允许在同一个连接上一次执行多条语句且输入点处理不当就会变成SELECT * FROM users WHERE id 1; DROP TABLE secret;这意味着攻击者不局限于读取当前查询的内容而是可以执行任意增删改查。它比常规注入危险得多因为它把“注入”从“操纵一条查询”升级成“在数据库里自由执行”。不过我这里要说一下实际限制。很多语言和数据库驱动默认不允许执行堆叠查询PDO MySQL默认的PDO::MYSQL_ATTR_MULTI_STATEMENTS通常是关闭的。MySQL的服务器端虽然支持一条协议发多条语句但驱动不开也白搭。有些老版本MySQL或某些管理系统使用的连接层比较宽松才会真正暴露风险。堆叠查询的常见用途不是删库——那样太没技术含量而且很容易被发现。它真正让人头疼的是配合写文件、调用存储过程和后续的权限提升动作。比如你注入点后面没法接INTO OUTFILE因为前面的SELECT结构已经锁死了但堆叠查询可以另起一条写文件的SQL瞬间打开新局面。2.4 UDF提权数据库账号到系统shell的临界点UDF全称是User Defined Function即用户自定义函数。MySQL允许用户把一段动态库注册成函数比如sys_exec()可以直接执行系统命令。很多场景下攻击者在“数据库内”已经拿到了足够高的权限比如可以读写MySQL的plugin目录但还缺一个“操作系统命令执行”的能力于是上传一个编译好的UDF库文件再用SQL创建函数最终通过SQL调用达到命令执行的提权效果。提到UDF提权有三个前提条件缺一不可数据库以较高权限运行最常见是root或mysql用户并且Plugin目录可写。当前数据库账号具备FILE权限也就是能使用SELECT ... INTO OUTFILE写文件。secure_file_priv没有限制为NULL或者允许写入插件目录。满足这些条件后大概流程就是把写好的UDF动态库通过SQL文本或十六进制方式写入MySQL的插件目录然后注册自定义函数。到了这一步数据库就不再是个“存储系统”了它变成了一台能执行系统命令的小型跳板。对防守方来说敏感目录的可写权限和secure_file_priv设置几乎是打包在一起检查的。这里需要特别强调不要指望攻击者会按部就班。我在项目里见过的情况是明明数据库账号不是root但堆叠查询配合已有的存储过程、计划任务、通用日志路径修改等手法照样能实现命令执行或写webshell。所以UDF提权不是终点它只是“数据库权限 → 系统权限”的一种代表路径关键是思维上有没有建立“数据库账号也是一种系统账号”的意识。我在实际渗透测试和应急响应里发现很多研发对“数据库权限过高”没有概念认为“库在服务器里跑着phpMyAdmin开着也正常”。可真正发生安全事件时日志里往往会出现一堆sys_exec调用记录事后看非常明显但因为没人提前去关心系统已经沦陷很久了。3. 三种攻击形态的技术对比与判定清单3.1 先用一张表看懂三者差异维度一阶注入二阶注入堆叠查询UDF提权触发点输入参数直接被拼到SQL数据库里已有的数据被二次拼接进SQL同一连接执行多条语句数据库插件目录写入UDF库前置条件未参数化查询数据存储后再次被使用驱动开启多语句支持FILE权限、Plugin目录可写核心危害绕过认证、拖库绕过入口防护后置触发执行任意增删改获得系统命令执行能力排查重点所有外部输入点全链路追踪对数据库字段的使用连接串和框架配置权限、路径、监控告警这张表我一直当成速查卡用。很多时候一个漏洞被通报出来时企业内部不同团队会因为术语不一致吵半天开发说“这是二次注入”安全说“这是越权”运维说“数据库被入侵了”。其实他们都在描述同一条链上的不同位置把表摆出来责任边界就清楚了。3.2 识别一个输入点是否可能触发二阶注入我最常用的判断方法很笨但有效跟着数据走。从入口参数出发看它会落到哪张表之后会不会有其他SQL再把该字段读出来拼接使用。技术执行时可以这样拆列出所有“接收用户输入并入库”的字段。搜代码里所有WHERE条件、ORDER BY、GROUP BY等位置是否使用过这些字段。重点看后台列表页、搜索页、管理页这些页面特别容易直接把数据库字符串拼进SQL。还要看管理后台是不是对“当前登录用户的昵称”之类的字段敏感如果管理操作会反查用户信息用户名如果经过二次拼接就可能成为触发点。另外要叮嘱一句很多系统喜欢在前端拼SQL条件比如导出Excel时把筛选字段传给后端。这种逻辑最适合做防御措施宁可改造为白名单加预编译也不要把筛选字段名直接映射到数据库列名。3.3 判定当前环境是否具备UDF提权条件如果你在做合法授权范围内的测试或者做企业内部加固遇到一个MySQL数据库可以按下面顺序检查当前用户SELECT user();是否具备FILE权限SELECT * FROM mysql.user WHERE Grant_privY;老版本可以直接看File_priv。插件目录SHOW VARIABLES LIKE plugin%;文件安全限制SHOW VARIABLES LIKE secure_file_priv;数据库版本不同版本写UDF库的路径、字段名差异很大尤其是MySQL 8.0之后部分安全机制变化明显。有两条经验值得分享第一判断“能不能提权”比“要不要复现提权”更重要。很多复盘场景只要证明权限配置允许UDF写入实际上就该直接启动应急预案了没必要真把命令执行环境搭出来给生产库添乱。第二我见过不少系统里plugin_dir真的可写但数据库是以低权限服务账号运行的这时即使UDF上传成功命令执行权限也有限反而会走计划任务等手段。要结合环境综合看待不要把UDF当成唯一答案。4. 靶场环境下的一次完整动手复盘4.1 登录绕过和堆叠查询的实操对比这里我不建议直接拿公网真实系统练手更推荐用Pikachu这类开放在本地的漏洞靶场。自己搭一个docker或者在虚拟机里跑起来完全合法且重复性高。我习惯的复现路径是先测试最基础的SQL注入是否闭合引号。单引号报错、双引号不报错这类现象能帮你快速确定是哪种类型。用order by判断列数再用union select查看回显位置。这一步的核心是“理解原查询的字段结构”。测试分号后面能不能继续执行语句。比如在参数值后加上;select sleep(3)观察响应时间有没有异常。如果延时不明显很有可能连接层禁止多语句。如果多语句可行再考虑具体的影响比如修改管理员密码、往某张表里写日志、调用存储过程等。以Pikachu的SQL注入关卡为例它的登录框直接体现了经典问题。输入admin or 11和一些简单绕过写法后返回正常登录立刻就能定位出SQL拼接问题。很多CTF题里也出现过类似套路但题目往往会加过滤——把空格、or、--都替换为空这时就要考虑用注释符/**/、十六进制编码、大小写变形来绕过。我建议在训练时把每种过滤都记录在案因为它们能帮助你理解WAF的配置思路真正工作时才能既会打也会防。4.2 二次注入和UDF提权在靶场里的连接打开Pikachu后除了常规的SQL注入闯关更值得注意的是“数据库层攻击”相关的模块。你可以把表结构简单改一下模拟一个带用户名回查的后台功能注册页面创建一个用户用户名故意写test or 11。用后台另一个正常功能点查询该用户。如果页面里没有做参数化查询逻辑就会把用户名原样拼进去触发全表查到的问题。很多人做这一步容易犯迷糊总想着“后台就不能用预编译吗”其实靶场真正的价值不是展示业务做得多糙而是让你体会一个数据一旦被存储为字符串后续所有消费它的地方都可能是攻击面。UDF提权不建议在Pikachu内部直接完成因为它更偏数据库运维层。我通常是在一台全新的MariaDB/MySQL容器里做实验。实验前先确认版本和插件目录然后使用管理员账号上传UDF库文件并注册自定义函数。整个动作其实很短但因为步骤少恰恰是很多人最想跳过细节的地方。不同系统的UDF库格式不能混用Linux下要用.soWindows下要用.dll版本还需要跟MySQL主版本匹配否则函数一调用就是崩溃。再强调一次UDF实验一定放在隔离的测试容器里完成。不要试图在自己的工作库或某个客户的测试库里顺手验证因为UDF库的残留很麻烦一旦函数注册成功删除时要先删函数再删文件顺序反了数据库会直接报错。容器实验的最大好处是可以随时重建把战场打扫得非常干净。4.3 每一层攻击对应的防御动作打靶不能只打不修。我在复现完一个点后会强制自己和团队成员把“防御动作”也写在同一份笔记里登录绕过把SQL改成参数化查询参数绑定后无论输入什么字符都只会被当作字符串字面量不会进入SQL语法层。堆叠查询在应用层关闭驱动多语句支持。PDO连接串明确不开启MULTI_STATEMENTSJava的JDBC连接去掉allowMultiQueriestrue。二阶注入不仅要参数化还要对新进入数据库的所有字段都保持同样标准对所有“从库里取出后再次拼接”的逻辑做审计。不要相信数据库里存的内容是绝对安全的。UDF提权数据库账号不能随便给FILE权限secure_file_privNULL尽量打开数据库不能以root系统权限运行插件目录做写保护定时检测是否有异常.so/.dll文件被新增。我在实际加固里还发现一个关键点很多人修完一个注入点后不做回归测试结果攻击者换个参数又打进来了。正确做法是拿同一份测试用例跑全量回归把上一个漏洞的所有已知payload集合覆盖一遍确保修复没有留下变体。5. 常见问题排查与修复落地5.1 WAF“高级过滤”为何经常失效网上很多文章叫“SQL注入高级过滤”看多了之后你会得到一个结论黑名单过滤永远滞后。因为过滤规则本质是“猜攻击者会怎么绕”而攻击者只要多一种编码方式就多一次机会。真正有效的思路是“分离数据与代码输入”也就是把所有SQL分为两类结构性SQL预编译动态表名/排序字段等实在无法预编译的那部分用白名单校验。表名不在允许范围内就直接拒绝比正则过滤可靠得多。很多公司总想把过滤放在WAF或代码最外层结果WAF看一眼参数、代码再过滤一遍看似双保险实际上只要两边规则不一致攻击者就有很大的绕行空间。真正要做的是进行参数化查询WAF只当辅助让业务代码从一开始就不具备被SQL注入的基础。5.2 如何判断一个接口是否已经被人尝试过UDF提权在做应急响应时我一般会看下面几个痕迹MySQL插件目录下有没有新增的.so或.dll文件注意按文件的创建时间排序找出新文件。MySQL系统库mysql.func表有没有异常函数记录常见异常函数有sys_exec、sys_eval或自定义名称。数据库错误日志里有没有加载动态库失败的信息。系统目录如/tmp或软件安装目录有没有可疑的共享库文件残留。服务器上有没有异常的系统计划任务、新增的启动项或监听端口。只要出现前两项基本可以判定攻击者已经尝试或完成了UDF提权接下来要做的就不是“修一个SQL注入点”的事而是按“服务器已被控制”的剧本处理。把机器断网、保留现场、通知安全团队这比继续做代码级修复优先级高得多。5.3 一套可以抄作业的MySQL加固建议先给一段直接可执行的检查SQL示例用来快速核对基础状态SHOW VARIABLES LIKE secure_file_priv; SHOW VARIABLES LIKE plugin_dir; SELECT user, host, File_priv FROM mysql.user; SELECT * FROM mysql.func;如果secure_file_priv的值是空的或某个具体的目录说明数据库允许文件导出建议设为NULL禁止任何SQL层面的文件读写。如果你必须用文件导出功能把它指向一个业务专用目录并确保数据库账号无法写入任何代码执行相关目录。插件目录的权限也建议从系统和数据库两层同时限制系统层去掉写权限数据库层也别给它开放动态库上传路径。UDF提权的前提是“能往插件目录写文件”只要这一步断掉整条链路就断在最关键的地方。应用层代码层面我会给出一个老生常谈但最管用的例子。用PHP的PDO写登录查询时参数化应该长这样$stmt $pdo-prepare(SELECT * FROM users WHERE username ? AND password ?); $stmt-execute([$username, $password]);Java的PreparedStatement同理。凡是写SQL的代码库里出现字符串拼接不管拼接内容来自客户端还是数据库字段都应该触发一次Code Review。尤其是那些把用户昵称、搜索关键词、排序字段直接拼进去的代码往往是SQL注入高发区。5.4 排查清单速查表检查角度排查思路高可用结论应用代码是否存在拼接SQL的接口参数化覆盖全部持久层框架配置是否开启多语句支持默认关闭特殊场景必须单独论证数据库配置secure_file_priv和local_infile设置前者设NULL后者关闭数据库账号业务账号是否存在DBA或FILE权限按最小权限拆分插件目录是否可被数据库进程写文件系统层移除写权限存储字段有没有把外部输入直接当SQL片段输出/拼接时必须重新校验运维日志是否有异常UDF加载或故障记录联动告警并定期审查排查顺序很重要我从实战中得到的建议是“先看数据库账号权限再看应用层”。因为如果账号权限太高即使应用层堵住了这个接口别的接口可能还会漏只有把所有业务账号与数据库权限缩到最小SQL注入后果才被限制在可控范围内。6. 个人实操复盘与几个值得记住的坑先从踩得最重的坑说起。我刚从前端切到安全时想当然地把“防SQL注入”等同于“每个地方都拼一个过滤函数”。后来一次代码审计中我发现一个系统确实对输入参数做了很多转义只要外部传参都会过滤可是当数据被写进数据库后后台另一处功能用该数据做排序和查询条件时没有做任何过滤。那次被确认存在二次注入后我才真正体会到安全不是入口一个函数能解决的它要贯穿数据的全生命周期。另一个坑发生在做堆叠查询测试时。当时我明明在MySQL命令行可以一次执行多条语句但通过业务接口测试时发现分号后的内容总是无效排查了半天才发现是中间层数据库连接配置里关闭了多语句支持。这个经历告诉我防御方只要做对“驱动配置”这一件事就能挡掉一大半堆叠注入攻击并不需要特别复杂的算法。关于UDF提权我最想说的一点是“别小看环境一致性”。你在测试环境复现出UDF提权不代表生产环境也能复现因为数据库版本、插件路径、操作系统位数、加载库的格式都可能不同。但这不等于生产环境就安全。我曾经在一个老系统里发现业务账号能读mysql库的user表还能向插件目录写文件虽然最终因为系统版本限制没能加载UDF但已经足够证明这台数据库存在横向移动风险。所以做安全评估时不要只以“能不能打通”作为唯一指标“具备几个高危前置条件”同样值得写进报告。如果你也在研究SQL注入我建议把精力从收集花哨payload转向“搭一套自己的训练环境”从登录绕过的参数化改造开始再到二次注入的数据流追踪、堆叠查询的驱动配置最后用提权实验亲手感受“数据库权限 → 系统权限”的临界点。只有自己真正完整走过一轮再回头看网上那些漏洞情报或热搜关键词才会明白每条漏洞公告背后到底在说什么。
返回列表