尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

从SQL注入到系统权限:读数据、写文件、执行命令全解析

发布时间:2026/9/25 13:25:40

资讯中心
01
ARTICLE

从SQL注入到系统权限:读数据、写文件、执行命令全解析

从SQL注入到系统权限:读数据、写文件、执行命令全解析
1. 从注入点到系统权限SQL注入利用的三个阶段很多人学SQL注入停留在 or 11--这种万能密码绕过或者union select拖个数据库就完事了。但实际上一个注入点能做到的事情远不止把数据拿出来这么简单——只要权限够、条件允许你完全可以从数据库一路打到操作系统层面。这篇文章我就把SQL注入的三种利用方式完整串一遍读数据、写文件、执行命令。先说清楚一个前提SQL注入的本质是攻击者控制了拼接到SQL语句里的数据部分让数据库把它当成代码来执行。当这个控制能力足够强时数据库就成了攻击者在目标内网里的一台跳板机。读数据是最基础的写文件是把数据变成武器执行命令则是武器真正开火。我见过不少新手拿着sqlmap跑出--dump觉得注入练完了。其实不是的。真正的实战链路应该是注入点 → 判断类型 → 读数据 → 尝试写文件 → 拿到命令执行能力。每一步都有前置条件每一个前置条件都是可以提前探测的。这篇文章我会结合自己在靶场和其他授权环境里的实操经验把每一步的原理、操作、坑都讲清楚。文章会涉及MySQL为主穿插PostgreSQL、SQL Server等常见数据库的差异。适合正在学SQL注入的入门者也适合已经会跑sqlmap但想深入理解底层原理的人。2. 读数据注入点的第一价值就是把库里的东西拿出来2.1 先搞清楚你想读什么SQL注入能读的数据简单说就是数据库里存的所有东西——业务数据、用户账号密码、会话Token、支付信息、后台管理员账号甚至其他数据库的凭据如果配置了DBLink。但真正专业的做法不是上来就select * from users而是先搞清楚目标数据库类型是什么当前用户是什么权限有哪些库、哪些表、哪些字段是有价值的这就是information_schema存在的原因。在MySQL里information_schema是一套只读的系统视图存着所有数据库的元数据。攻击者拿到注入点之后的第一步通常是读information_schema.tables和information_schema.columns。以MySQL为例判断数据库类型的特征很直观version返回类似8.0.32的版本号concat()里的分隔符拼接出来会有明显的MySQL标志。而如果是PostgreSQLversion()函数返回一串很长的PostgreSQL版本信息。判断类型为什么要放在第一步因为后面的读文件、执行命令手法在不同数据库里差异极大判断错了全白干。2.2 联合查询与报错注入的取舍逻辑读数据的常见注入手法就四种联合查询注入、报错注入、布尔盲注、时间盲注。各有适用场景选择逻辑很简单能并查出结果就别用盲注能报错回显就别用时间盲注。联合查询注入Union Select适合页面有回显位置的注入点。比如一个新闻详情页URL里的id1对应了页面上的标题和正文这时候你就可以通过union select控制输出的内容。核心技巧是探字段数order by 3能正常、order by 4报错说明是3个字段。然后找到回显位置把数据放到显示出来的那个字段位子上。看似简单但很多人第一次写union select就翻车了。常见问题是union两侧的列数不一致、忘了前一条select的查询结果会被当作第一行、数据类型对不上比如字段A在页面上是数字你回显一个字符串它可能直接不显示。我的习惯是先把联合查询的payload写成id-1 union select 1,2,3用一个不存在的id值把原始查询结果清空这样页面上就只剩我控制的内容回显位置一目了然。报错注入的适用场景是页面没有直接的数据回显但数据库报错信息会原样输出。MySQL里最常用的两个函数是updatexml()和extractvalue()它们的报错信息会包含我们传入的表达式。比如id1 and updatexml(1,concat(0x7e,(select user()),0x7e),1)因为XPATH语法错误MySQL会把concat()里的内容拼进报错信息返回。注意0x7e是波浪号~用来在报错信息里做分隔标记防止报错信息被截断看不清。一个关键细节报错注入的payload里子查询的结果长度有限制——updatexml和extractvalue的报错内容最多显示32个字符。所以遇到超长内容比如一个36位的UUID或者一长串加密hash你直接查就会看到内容被截了一截。这时候要用reverse()、substr()等函数分段读取我在实战里经常一个字段要分好几段拼接才能读全。2.3 数据读不出来的两个大坑坑一盲注场景下数据不是读出来的是猜出来的。当页面既不回显数据也不回显报错信息只有登录成功/失败或页面正常/空白这种二元状态你就只能走布尔盲注构造一个条件让页面状态因条件真假而变化然后逐字符拆解数据。sqlmap的--techniqueB就是干这个的但手工盲注的能力也得练——至少你要知道ascii(substr((select database()),1,1))65这种判断逻辑是怎么一步步跑出来的。时间盲注同理只是把判断条件变成了sleep(5)是否生效。坑二数据量超出单次查询限制。联合查询一次只能回显一行报错注入一次只能拿32个字符盲注一次只能确认一个字符。所以读大量数据时必须配合limit、group_concat()、substr()这些函数拆分。尤其是group_concat()它默认的长度上限是group_concat_max_len默认1024字节遇到一个表几百条记录时超出的部分不会报错只会静默截断。我当时调试半天才发现是自己用的姿势不对后来都写成union select group_concat(username,0x3a,password separator 0x3c62723e) from users并配合session变量临时改大group_concat_max_len来确保数据读全。2.4 不同数据库的读法差异PostgreSQL的元数据不在information_schema里但很多关键信息在pg_catalog里。常用的是-- 查询所有数据库 select datname from pg_database; -- 查询当前用户 select current_user; -- 查询所有表 select tablename from pg_tables where schemanamepublic;SQL Server则是用sys.databases、sys.tables、sys.columns这套系统视图db_name()获取当前库名user_name()获取当前用户。Oracle要用all_tables、all_tab_columns这些数据字典。读数据的底层逻辑都一样——先元数据、再数据本身、最后按需拆分读取换数据库只是换一套系统表的名称。3. 写文件从能读到能落盘的跨越3.1 secure_file_privMySQL写文件的生死线secure_file_priv是MySQL里控制LOAD_FILE()和INTO OUTFILE行为的安全参数。它的三种取值直接决定了你能不能写文件取值含义NULL禁止文件读写操作空字符串不限制任意路径可读写指定目录路径只能在指定目录下读写MySQL 5.7及以上版本默认是NULL不能读写文件MySQL 5.5及更早版本默认是空字符串不限制。这也是为什么很多老教程里拿到注入点直接写shell的玩法在现在的靶场里经常失效。遇到不能写的情况优先排查当前用户有没有FILE权限、secure_file_priv是不是NULL、能不能通过配置文件修改如果DB是独立部署的写不了/tmp但能写web目录直接改配置文件重启就行但通常你没有这个权限。判断这两个条件是否满足的SQL-- 查当前用户权限 select user,file_priv from mysql.user where usercurrent_user(); -- 查secure_file_priv当前值 show variables like secure_file_priv;注意一点secure_file_priv是全局变量读它需要足够权限。如果注入点权限较低直接show variables可能被拒。这时候可以尝试用报错注入的方式去套变量值比如extractvalue(1,concat(0x7e,(select secure_file_priv),0x7e))运气好就能读到。3.2 INTO OUTFILE和INTO DUMPFILE一字之差用法完全不同INTO OUTFILE把查询结果原样写入文件多行数据会以文本形式保存。INTO DUMPFILE写入的是单行二进制内容不做行尾转换适合写需要保持精度的内容比如导出的二进制文件图片、压缩包、已经被编码成hex的webshell。写shell的标准payload是select ?php eval($_POST[x]);? into outfile /var/www/html/shell.php但很多情况下直接写会失败原因通常是目标web目录有权限限制、secure_file_priv限制、或者php文件被WAF拦截。这时候有一个改良思路是先把内容hex编码再用0x写入select 0x3C3F70687020406576616C28245F504F53545B2278225D293B3F3E into outfile /var/www/html/shell2.php数据库在执行时会自动把0x...十六进制串解码成二进制内容落盘。这个技巧还有一个额外好处可以绕过低版本MySQL对引号内容的特殊处理让payload本身不包含单双引号在多个环节上降低被检测的概率。注意仅用于授权的靶场和环境在自己负责的系统中做测试时要提前做好日志清理与备份。另一个经常被忽略的细节是文件写入路径里目录必须存在。如果你写/var/www/html/test/shell.php但test目录不存在MySQL不会自动帮你创建目录直接报错。所以写文件之前先确认目录存在或者在SQL里用datadir、basedir等变量辅助拼路径看看站点根目录到底在哪。3.3 写日志文件不依赖OUTFILE的备选方案当INTO OUTFILE被secure_file_priv按死、无法写入web目录时还有一个路径是通过MySQL日志文件来写内容。原理是MySQL的general_log会把所有执行的SQL记录到日志文件里。如果你能修改全局变量general_log和general_log_file就可以把日志文件指向一个web目录下的php文件然后执行一条包含webshell代码的查询语句日志文件里就会记录这一行webshell代码。操作命令set global general_log on; set global general_log_file /var/www/html/cmd.php; -- 执行包含webshell的查询写成注释里最保险防止被日志截断 select ?php eval($_POST[x]);?然后把日志文件路径指回原位置、关闭general_log请求cmd.php一个日志变成的WebShell就上线了。这个技巧在CTF和真实授权测试中经常作为OUTFILE失败后的Plan B。前提是当前用户具备SUPER权限来修改全局变量。3.4 写文件到底能不能成功先看这三个条件综合下来SQL注入写文件成功的必要条件一个都不能少当前用户有FILE权限或者能通过提权拿到FILE权限secure_file_priv没有限制到NULL允许向目标目录写文件目标路径可写web目录有写权限且目录存在一旦这三个条件同时满足写文件基本就是一次SQL语句的事。我在实际测试里见过最成功的一次场景是站点的上传目录权限没做收口secure_file_priv是空字符串直接把一句话写进上传目录然后通过GET参数拼接php代码执行整个过程没有触发任何安全告警——因为流量从外表看只是普通的上传目录访问日志。4. 执行命令从WebShell到系统权限的红利最大化4.1 MySQL UDF老技术但原理值得吃透拿到WebShell之后紧接着想做的就是命令执行。老版本的MySQL有一个经典玩法——UDFUser Defined Function通过自定义函数调用系统命令。原理很简单MySQL允许用户自定义函数函数用C/C编译成.so文件放在系统目录里然后用SQL调用它。整个利用过程大概是拿到WebShell后确认MySQL插件目录select plugin_dir;把编译好的udf.so文件写入插件目录或者通过SQL把UDF的十六进制内容直接dumpfile进插件目录创建自定义函数create function sys_exec returns string soname udf.so;执行系统命令select sys_exec(id);这个玩法在MySQL 5.1及以前非常流行因为插件目录权限要求低、UDF文件可以直接用SQL写入。但现在主流MySQL版本默认不加载plugin_dir之外的文件UDF提权已经很难走通了。而且安全产品对create function这类语句的监控已经非常成熟。所以在今天的实战中UDF更多是用来理解数据库如何变成命令执行入口的教学案例而不是首选武器。4.2 更现实的命令执行路径今天我们拿到SQL注入点后更现实的命令执行链路其实是读数据搞到管理员账号和后台地址登录后台找到后台本身就能执行系统命令的功能比如计划任务、模板编辑、命令模块如果在后台找不到直接入口用注入点的写文件能力落一个WebShellWebShell连接菜刀/冰蝎/蚁剑利用面板自带的虚拟终端执行命令这条链路每一步都有充分的前置条件、每一步都能被审计但绝大多数情况下这就是实战中进展最快的那条路。SQL注入直接执行命令并不是一个常态常态是组合拳。如果一定要找一个数据库本身就能执行命令的现代场景PostgreSQL有一个特性很值得关注COPY ... FROM PROGRAM。PostgreSQL运行了pg_execute_server_program权限的用户可以直接通过它调用系统命令copy (select ?php eval($_POST[x]);?) to program echo ?php eval($_POST[\x\]);? /var/www/html/cmd.php;这是一个真正一条SQL命令执行系统命令的案例。在SQL Server里对应的是xp_cmdshell扩展存储过程Oracle则是通过Java存储过程或外部程序。但这些真·命令执行功能在现代数据库里默认都是关闭的MySQL甚至直接在设计上就没有这种等价物。所以这个问题要辩证看数据库可以执行命令吗很多时候可以但需要特定数据库、特定权限、且通常要做一些配置上的冒险。4.3 为对抗检测做的低痕迹优化不管走哪条路凡是涉及到写文件、改配置、执行命令都会留下痕迹。从红队视角看有几个细节点要注意写WebShell的文件名避免使用shell.php、cmd.php这类特征明显的名字可以用图片名、皮肤文件、上传临时文件名等伪装。连接WebShell时使用加密马如冰蝎、哥斯拉避免明文POST特征。修改general_log文件后要记得恢复原值避免服务异常导致被排查。MySQL和PHP版本差异可能导致WebShell解析失败运维环境里官方PHP-FPM与Apache的解析条件不完全一致写马之前可以先探测一下解析环境。这些点不是帮你违法而是帮你在授权测试里更真实地评估风险暴露面。防御方也会看这些特征来排查入侵攻防本来就是互相学习的过程。5. 从攻击链路反推防御重点这是一篇给蓝队也看的文章5.1 注入点是怎么被炼出来的如果想做好防御就要先理解攻击者的三步走探测注入点、确认利用条件、锁死利用链。探测注入点的常见动作是加单引号、加and 11和and 12、加sleep()等让响应出现时间差。这些流量特征在WAF层面大部分都能识别所以攻击者会做各种编码绕过URL编码、二次编码、大小写混淆、注释符拼接、等价函数替换substr换mid、利用%0a或%23截断等等。关键是所有绕过都发生在SQL层而根治方法是在代码层用参数化查询。我在无数项目里反复说同一句话只要你的SQL语句是从用户输入拼接出来的WAF再强也是事后补救换成预处理语句PreparedStatement之后注入在语法解析阶段就不可能成立。5.2 数据库侧的几个硬配置如果代码一时改不了数据库侧这几个配置能直接砍掉文章里大部分攻击路径secure_file_priv设为NULL或严格目录白名单直接封死写文件路径业务账号权限最小化禁FILE、禁SUPER、禁GRANT用哪张表就给哪张表的SELECT/INSERT/UPDATE/DELETEgeneral_log默认关闭日志文件路径不能落在web可访问目录数据库不要用root/system账号跑连接串里用独立低权限账号部署数据库审计把create function、into outfile、set global这类高危语句单独告警这些点做下来SQL注入即使存在攻击者的利用路径也会被中断在读数据阶段——绝大多数业务泄露事件实际上到拖库这一步就足以造成毁灭性损失了别等攻击者写到WebShell才后悔。5.3 我被问过最多的几个防御误区和一次真实的应急响应经验有个经典的误区是我们用了ORM所以肯定没有SQL注入。很多人不知道ORM也有原生查询接口更不知道order by、in、like这些动态拼接是ORM拦不住的。我在一次应急响应里看过整套Spring Boot MyBatis的系统开发只把${}用在了排序字段上就这么一个口子整个后台的用户表都被拖干净了。那次应急响应的排查链路让我印象很深发现数据库binlog里有连续的大批量select * from记录追到访问日志发现请求参数里带着order by (case when ...的布尔盲注特征。修复方案其实很简单——把动态排序字段改成白名单校验不在白名单里就直接返回默认排序。但就是这种小地方最容易出大事故。还有团队问我数据库账号已经做了最小权限注入还能拖库吗答案是可以——很多业务账号天然就有SELECT全库的权限最小权限≠没有权限。真正要做的是给业务账号限定它真正需要的那几张表连information_schema的读取权限都做收敛。这个动作很多DBA觉得没必要但一旦被拖库后悔药是没有的。6. 写在最后别把SQL注入当过时话题我见过太多新手觉得SQL注入太老了现在哪还有这种漏洞。但2023年底到2024年仍然有大量真实漏洞通报是SQL注入向的关键不是注入这个漏洞形式消失而是利用路径变窄了默认安全配置收紧了、数据库版本升级了、WAF变聪明了。但这不意味着利用手法没用了——恰恰相反读数据、写文件、执行命令这条利用链在任何一次数据库相关的渗透测试里都是主链路只是每一步的门槛都变高了。练的话我建议在本地搭一套DVWA或SQLi Labs靶场先手工把联合查询、报错注入、盲注都跑一遍再上sqlmap对比结果。然后试着把secure_file_priv改成空字符串做一次真实的文件写入和日志写入实验——注意务必在授权、隔离的靶机环境中操作这一步能帮你建立写入失败的原因排查的体感。最后再研究UDF、COPY FROM PROGRAM这些原理解析配合隔离环境里的docker做破坏性研究就足够了。最后再分享一个个人体会很多人写文件失败就立刻怀疑是被安全设备拦了但实际操作里超过一半的情况只是目录不存在、权限不够、或者secure_file_priv没查。先把数据库侧的前置条件完整排查一遍再考虑对抗层面的东西效率会高很多。SQL注入的学习没有捷径就是一次一次把payload从能跑变成跑得优雅再从跑得优雅变成跑得隐蔽。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。