ARTICLE DETAIL

资讯详情

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

sqli-labs靶场全通关指南:SQL注入原理、绕过技术与实战经验

sqli-labs靶场全通关指南:SQL注入原理、绕过技术与实战经验 网上讲sqli-labs的教程一抓一大把但大多数要么只贴一句payload让你自己悟要么只讲到Less 10就断了。sqli-labs这套靶场之所以在圈子里长盛不衰就是因为它把SQL注入的常见场景拆成了65关难度是螺旋上升的每过一关都像在解开一个小谜题。我从Less 1一路打到Less 65中间踩过的环境坑、编码坑、工具坑比payload本身多得多。这篇文章我按自己的理解把这套靶场的结构、搭建、通关思路和排查经验完整梳理一遍适合刚接触SQL注入的初学者也适合想把这套靶场拿来做教学或面试题的人。先说明一下SQL注入练习请一定要在授权环境进行本地靶场、CTF平台、公司授权的渗透测试项目都没问题不要拿这套思路去碰与你无关的线上系统。这套靶场的价值恰恰是让你在完全可控的环境里把原理吃透真正遇到生产环境时才能知道怎么防御。1. 项目概述与关卡设计思路1.1 sqli-labs到底是什么sqli-labs是一套基于PHP和MySQL开发的SQL注入靶场一共65个关卡由印度安全研究人员编写后来被全世界安全爱好者反复传播和改造。它的核心思路非常简单每个关卡是一个PHP页面页面里有一段有漏洞的SQL查询代码你需要在URL参数、POST表单、Cookie、Header等位置构造输入让这段SQL执行你想要的操作最后拿到数据库里的敏感信息或者完成逻辑绕过。很多新手喜欢直接把它当成一个“用SQLMap打一遍就完事”的靶场但我个人强烈不建议这么做。这套靶场的设计初衷是让你理解SQL注入的本质也就是应用程序把用户输入直接拼接进SQL语句带来的后果。如果你只是跑一遍工具你会错过大量细节比如闭合方式、字段数判断、盲注时的布尔/延时逻辑。这些细节在真实渗透测试和代码审计里比单纯跑工具值钱得多。1.2 65关的结构设计逻辑这65关并不是随意排的我通关之后回头看发现它其实可以明显分成几个梯队每个梯队解决一类特定的问题。关卡范围核心考点Less 1~4GET型基础注入单引号、数字型、双引号闭合Less 5~10报错注入、布尔盲注、延时注入Less 11~22POST注入、User-Agent/Referer头注入、Cookie注入Less 23~31对注释符、空格、关键字进行绕过的进阶场景Less 32~37宽字节注入重点是GBK编码下的转义绕过Less 38~45堆叠注入多语句执行、Update/Delete注入Less 46~53Order By、Limit等非查询位置的注入利用Less 54~65挑战关卡把前面的技巧混合起来部分关卡限制查询次数这个结构安排很有讲究。前10关是打地基让你搞明白SQL注入最核心的“闭合”和“拼接”问题中间20关是换场景告诉你在不同入口位置该如何调整思路后面10多关是进阶考察你对数据库特性、编码、过滤器绕过这些细节的理解最后那些挑战关就是模拟实战中常见的“有waf、有过滤、有次数限制”的场景。我见过很多人打到Less 30左右就卡住了原因不是不会打而是前面的基础没吃透。SQL注入这东西一环套一环前面草草过关后面就会很痛苦。2. 环境搭建与准备工作2.1 本地环境选型PHP 5.x可能比PHP 7.x更顺手sqli-labs是很多年前写的代码大量使用了mysql_*函数这些函数在PHP 7.0之后就被移除了。如果你直接把它扔到PHP 7或者PHP 8的环境里大概率会看到满屏的Call to undefined function mysql_connect()靶场直接瘫痪。我试过几个方案最终最省心的组合是Windows上用phpStudy把PHP版本切到5.6或者5.5搭配MySQL 5.5/5.7Linux上用XAMPP或者自己编译的LNMP环境PHP同样选5.6。网上也有人改造过兼容PHP 7的版本核心思路是把mysql_*替换成mysqli_*但改过之后部分关卡行为会有细微差别比如Less 38的堆叠注入如果靶场没有用multi_query而是用普通query效果就不一样。如果你有Docker直接拉一个带PHP 5.6和MySQL 5.7的现成镜像也行但要注意容器内的网络端口映射和数据库初始化步骤。我的建议是第一次玩不要过度纠结环境怎么简单怎么来phpStudy大法基本能解决绝大多数问题。2.2 一步步初始化数据库靶场代码下载解压之后第一步不是急着访问页面而是先准备好数据库。sqli-labs目录下通常会带一个sql文件夹里面有db.sql或者sql-lab.sql之类的文件把它导入你的MySQL即可。我以MySQL命令行导入为例先登录数据库然后执行mysql -uroot -p # 输入密码后 source C:/sqli-labs-master/sql/sql-lab.sql;导入成功后会生成security库里面有users、emails、referers、uagents等表。然后打开靶场根目录下的sql-connections文件夹里面有db-creds.inc、sql-connect.php之类的文件把数据库账号密码改成你本机的实际值。$dbuser root; $dbpass your_password; $dbname security;这里有个常见的坑如果你本地MySQL 8密码认证插件默认是caching_sha2_password而靶场代码是很多年前写的可能只认mysql_native_password导致连接失败。解决方案要么是切换MySQL到5.7要么在MySQL 8中手动把这个用户的插件改回mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456;改完重启数据库服务再刷新页面通常就能看到靶场首页了。2.3 搭建中的三个经典坑第一个坑是端口冲突。靶场默认走80端口如果本机已经装了Nginx或者IISApache会启动失败。最简单的处理是改Apache监听端口比如改成8082然后访问http://localhost:8082/sqli-labs-master/。第二个坑是页面空白。很多靶场页面如果数据库连接失败不会给你红字报错而是直接白屏。这不是靶场坏了而是PHP错误输出被关掉了。建议在开发调试阶段把php.ini里的display_errors改成On再把错误级别调高一点。Windows下的phpStudy通常有一个图形化开关Linux下可以在PHP页面里临时加一句error_reporting(E_ALL); ini_set(display_errors, 1);来确认问题。第三个坑是字符集不一致。sqli-labs的宽字节注入严重依赖GBK字符集如果你代码里配置的是utf8即使你看了教程按部就班打一遍宽字节也不会有任何反应。后续要操作Less 32到Less 37时一定要确保代码和数据库连接都使用GBK或相关的编码设置这个细节我后面会详细展开。3. 通关实战从Less 1到Less 65的核心关详解3.1 开胃菜Less 1~4先搞懂“闭合”和“拼接”Less 1是这整套靶场的第一个门槛它的源码核心逻辑大概是这样的$sql SELECT * FROM users WHERE id$id LIMIT 0,1;注意用户的输入$id被两个单引号包了起来。也就是说无论你输入什么最终都会出现在...这个字符串里。所以判断是否存在SQL注入的第一步是打破闭合。最经典的操作是在参数后面加一个单引号http://127.0.0.1/sqli-labs-master/Less-1/?id1页面通常会返回MySQL报错提示You have an error in your SQL syntax这就在告诉你单引号确实影响了SQL语法。接下来就要构造一个能让整个语句正常执行的payload。比如?id1 and 11由于源码里本身有一个尾部单引号我们前面补上一个作为闭合最后再用and 11把语句补完整个SQL就变成了SELECT * FROM users WHERE id1 and 11 LIMIT 0,1这个SQL能正常返回数据。这只是第一步真正的目标是要查库名、查表名、查字段、拖数据。于是需要用到order by来判断字段数?id1 order by 3-- ?id1 order by 4--order by后面的数字如果大于表的实际字段数MySQL会报错小于或等于则表示字段数合法。通过二分法试下来可以发现users表有3列。知道了列数就可以用union select构造联合查询?id1 union select 1,database(),3----在这里是把后面的单引号和LIMIT注释掉注意--后面要跟一个空格在URL里空格可以用编码所以常见的写法是--。我之前看很多人直接记payload不关心为什么用还是结果到了Less 2就懵了。Less 2的源码长这样$sql SELECT * FROM users WHERE id$id LIMIT 0,1;这里的$id没有用任何引号包裹所以它是数字型注入。这时候再套Less 1的payload就会失败正确写法是?id1 union select 1,database(),3--Less 3和Less 4则是双引号搭配单引号、以及双引号包裹的情况本质都是测试闭合方式。我的经验是每到一个新关卡先不要看writeup用单引号、双引号、数字型三种试探方式分别打一下看页面的报错特征这个肌肉记忆一旦建立后面遇到任何奇怪入口你都心里有数。3.2 靠“信封”猜数字Less 5~10的报错注入与盲注Less 1到Less 4页面会直接回显查询结果属于“有回显注入”。从Less 5开始情况变了。Less 5无论你是否注入了数据页面可能只返回You are in...不回显具体内容。这时候有两条路一条是用报错注入把数据带出来一条是盲注逐字符猜解。Less 5的常用payload是双查询报错?id1 and (select 1 from(select count(*),concat((select concat(0x7e,(select database()),0x7e)),floor(rand(0)*2))x from information_schema.tables group by x)a)--这个payload的报错原理是当group by碰上floor(rand(0)*2)产生的重复主键时MySQL会抛出Duplicate entry错误错误信息中就会包含你构造的数据。这种方法比较绕实际测试中更推荐用extractvalue或者updatexml比如?id1 and extractvalue(1,concat(0x7e,(select database()),0x7e))--extractvalue本身是用来解析XML文档的函数当XPath路径参数不合法时MySQL会把传入的值显示在报错信息里。我们用concat把0x7e也就是波浪号~和database()拼在一起故意让XPath语法错误从而让数据库名出现在页面报错里。后面查表名、字段名、数据只需要替换子查询即可比如?id1 and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schemadatabase())))--Less 8是布尔盲注页面连报错都不太给只回显You are in...或者空页面。这时候的逻辑是如果逻辑条件为真页面正常如果为假页面无内容。判断数据库名长度可以这样?id1 and length(database())3--如果页面有内容说明长度大于3继续用二分法缩小范围。确定长度后再逐字符猜解需要用substr和ascii函数组合。比如判断数据库名的第一个字符是否为s?id1 and ascii(substr(database(),1,1))115--这里的115是s的ASCII码。这个操作看起来很笨但它是理解盲注的基础也是很多自动化脚本的基础逻辑。Less 9和Less 10则是延时注入用sleep(4)让页面延迟通过响应时间判断条件真假适合页面完全无差异的场景。盲注的耐心很重要我自己当年一关能折腾两三个小时但练完之后再看SQLMap生成的payload很多细节会豁然开朗。3.3 换入口Less 11~22的POST、Header与Cookie注入从Less 11开始靶场不再让你只改URL参数而是模拟了一个登录表单。用户名和密码通过POST提交后台代码约等于$sqlSELECT username, password FROM users WHERE username$uname and password$passwd LIMIT 0,1;这其实就是SQL注入绕登录的经典场景核心还是单引号闭合。在用户名输入框填入admin --密码随便填一个。整个SQL被注释符提前终止查询条件变成只判断用户名为admin密码校验被跳过了。不过--在表单里可能直接提交有的版本需要把--改成--或者#也可以用admin or 11来绕admin or 11 --Less 13到Less 15是一个递进分别考察单引号加括号的闭合、盲注在POST场景下的应用。Less 16往后开始出现双引号加括号的闭合方式。我想强调一下这些关卡其实非常贴近真实系统的登录框测试很多老系统没有用参数化查询用户名和密码直接拼进SQL一旦你测试出这种绕过方式整个后台权限就形同虚设了。Less 18到Less 20则换了入口把目光放在了User-Agent和Referer这两个HTTP头。靶场会把UA头记录到数据库中但拼接过程没有过滤于是你可以在UA头里直接注入SQL。这类注入在Burp Suite里操作更方便。使用Burp抓包后修改UA头User-Agent: 1 and extractvalue(1,concat(0x7e,(select database())))--服务端就会把错误信息返回或带出数据。为什么要专门搞这种入口因为很多安全设备对URL参数里的特殊字符盯得很紧但HTTP头的过滤相对宽松这也是实战中一个非常经典的绕过思路。Less 21和Less 22则把Cookie作为注入点。把Cookie值改成注入payloadCookie: unameadmin and extractvalue(1,concat(0x7e,(select database())))--Cookie注入在真实系统中经常会遇到尤其是那些把用户信息直接存Cookie并拿来拼SQL的老代码。3.4 过滤器绕过Less 23~31的进阶玩法Less 23开始面试题和CTF里常见的“注释符被过滤”出现了。源码把--、#、/* */等注释符都过滤掉了你想用注释掉后半部分SQL的老套路会失败。这时候的思路是“不注释也能闭合”。比如Less 1中我们可以用?id1 or 11这样不需要注释符后面的LIMIT正好和前面构造的11配对。换句话说你要学会用合法的SQL语法去补全整个表达式而不是只依赖注释符。Less 24是二次注入的经典案例。所谓二次注入是指第一次输入恶意数据时由于转义函数的存在而没有被立即利用但这个恶意数据被存进了数据库等到第二次有人调用这个数据并把它拼进SQL时注入才生效。sqli-labs的这个关卡模拟了一个修改密码功能用户名字段事先被addslashes处理过单引号变成了\于是第一次输入admin时不会直接报错但存进数据库的却是带着非法语义的内容。后面当系统把这个用户名拼进修改密码的SQL时问题就暴露了。这种二次注入在真实系统里非常隐蔽因为我见过很多代码审计人员只盯着一次过滤却忘了数据流转到另一个场景时会重新进入SQL拼接。掌握Less 24之后你会对“数据存储可信度”这个理念有更深的理解。Less 25开始过滤了and和or关键字。很多人的第一反应是“那我用和||代替”确实可以但要注意URL编码问题。%26%26和%7C%7C。Less 25之后的几关会陆续过滤空格和更多关键字解决办法是利用MySQL的特性比如用注释符代替空格/**/用十六进制代替字符串用information_schema的别名等等。这些绕过手法现在听起来很“黑客”但本质上都是对数据库语法和解析器特性的理解。3.5 宝藏关卡Less 32~37的宽字节注入宽字节注入一直劝退很多新手因为它在纯UTF-8环境下根本不生效。它的原理必须配合GBK编码才能解释清楚。我们先看靶场源码$id addslashes($id);addslashes函数会把单引号转义成\在SQL语句里检测到\就认为单引号是字符串内容而不是边界符号。但如果我们构造的输入是%df转义之后会变成%df\从字节层面看是%df%5c%27。这里的关键来了广为人知的是GBK编码中%df%5c可以组成一个汉字字符而MySQL在GBK字符集下解析时可能会把%df%5c合并成一个字符于是后面的就不再被当作被转义的单引号了而是直接变成了闭合字符串边界的单引号。具体payload?id1%df union select 1,database(),3--有些教程会使用%bf%27或者%aa%27原理一样核心是找到一个合法的GBK两字节序列把反斜杠吃掉。宽字节注入在phpStudy默认环境下就能复现但前提是客户端连接字符集需要是GBK相关编码。靶场代码通常在Less-32里面已经把字符集设置好了所以直接访问即可但如果你自己搭建的PHP环境默认为UTF-8那么需要确认代码里是否执行了设置字符集的语句比如mysql_query(SET NAMES gbk);我在实际测试中就遇到过Less 32死活不出数据的情况排查半天发现是环境自带的配置把默认字符集改成了utf8。宽字节注入现在越来越少见了因为多数现代框架会强制UTF-8以及参数化查询但它在理解字符集与SQL解析的边界上依然非常有教育意义。3.6 堆叠与边界场景Less 38~53从Less 38开始靶场进入了堆叠注入的范畴。堆叠注入的核心是支持一次发送多条SQL语句以分号分隔。比如?id1; insert into users(id,username,password) values(100,test,test)--注意堆叠注入不是所有场景都行得通它取决于后端PHP是否使用了支持多语句执行的API例如mysqli_multi_query。如果只是普通的mysqli_query即使你传入了分号数据库也只会执行第一条。这是为什么堆叠注入在真实系统里比union注入少见的原因但一旦支持危害极大。Less 46则很有意思注入点出现在ORDER BY后面。很多人已经习惯在where后面拼条件却忘了order by也是SQL的一部分。如果参数是sort1 desc那完全可以构造为sort1,extractvalue(1,concat(0x7e,(select database())))不过要注意order by后面不能直接用union因为排序子句的语法和前后的where不一样这也是我在Less 46卡了很久的原因。实际上这关最常见的利用方式是报错注入如果页面存在盲注条件也可以用来判断。Less 53之前会不断变化过滤参数但核心都是边界注入里那些“非常规位置”的利用。把这些关卡打明白了你对SQL注入边界条件的理解才算完整。3.7 冲刺阶段Less 54~65挑战关这最后的12关其实是对前面所有技巧的综合测验。有的关卡限制了查询次数比如只能提交5次请求超过后就重置环境有的关卡过滤了大量关键字有的关卡的注入位置在参数名上而不是参数值上。处理方法没有统一公式我的建议是遵循一套详细化的流程第一步先判断闭合方式第二步判断回显位置或者报错条件第三步选择最合适的注入手段避免频繁无效请求触发次数限制。到了这个阶段你需要能够在几分钟内完成一个关卡的判断流程。如果我发现某关SQLMap跑不出来通常是两种原因一是前面提到的查询次数限制让SQLMap的自动化字典吃不消二是一些过滤规则SQLMap内置脚本覆盖不全。所以我还是建议大家手动打一次再去研究工具的自动化绕过两手抓效果最好。4. 常见问题与排查技巧实录4.1 靶场启动失败排查靶场启动失败大概率是Web服务器的锅。Apache端口被占用是最常见的我建议先执行netstat -ano | findstr :80看看80端口被谁占了如果是其他Web服务可以改Apache的监听端口。MySQL起不来也经常出现尤其是之前装过不同版本MySQL的情况下检查一下服务管理器里的MySQL服务状态或者直接用phpStudy的启动状态按钮来看。还有一种情况是下载的源码路径不对导致访问不到首页。你放到www或者htdocs目录之后访问时要注意URL大小写。sqli-labs的文件夹名自带大小写Linux下大小写敏感Windows不敏感但很多教程写的路径是/sqli-labs-master/如果你文件夹叫SQLi-Labs-master在Linux下就可能404。4.2 字符集与编码陷阱关于宽字节注入不生效的问题我再说两点。第一一定要看靶场代码里的字符集设置。有些版本在sql-connections/sql-connect.php里写的连接字符集可能被注释掉了要手动打开。第二在URL输入%df时不要在浏览器地址栏里直接复制粘贴后回车因为浏览器会进行二次URL解码。我用Burp Suite的Repeater模块来做这类测试最稳参数值直接写1%df union select 1,database(),3--Burp会原样把百分号传给后端。编码问题还体现在中文字符的注入上如果你要盲猜某个库里中文字段名建议先把数据转为十六进制避免传输过程乱码。我们可以用hex()和unhex()完成转换。4.3 工具使用心得与测试流程固化很多读者一到靶场就习惯性打开SQLMap跑一下我理解这种效率需求但强烈建议至少手动打完前15关。SQLMap跑出来的payload不一定可读盲目复制到报告里也没说服力。如果执意要用SQLMap我分享一个比较稳健的命令格式sqlmap -u http://127.0.0.1/sqli-labs-master/Less-1/?id1 --batch --level3 --risk2 --dbs跑完之后如果要拖数据再用-D security --tables、-T users --columns、-T users --dump这些参数逐层深入。盲注关卡建议加上--dbmsmysql免得SQLMap去跑一堆无关的探测。这里我也想说工具是放大你能力的杠杆不是替代你思考的拐杖。我自己在实际面试和带人的时候更看重对方能不能说出“这是一个字符型注入闭合方式是单引号字段数有3列”这种清晰的判断过程。4.4 常用故障速查表现象可能原因解决办法访问靶场首页404文件路径大小写或目录层级不对检查URL和实际目录是否一致页面显示call() undefinedPHP版本过高mysql_函数移除切换PHP到5.6或使用兼容版页面白屏无任何输出数据库连不上或display_errors关闭修改db-creds.inc并打开错误输出报错信息里有密码错误db-creds.inc账号密码不对改成和本地MySQL一致宽字节注入无反应字符集不是GBK检查代码里SET NAMES gbk注入后页面只显示You are in没有回显位置改用报错注入或盲注order by判断字段数无效可能是查询被注释过滤尝试用group by代替5. 我的实战心得与学习路线建议5.1 怎么才算真的通关很多人把65关的payload复制进浏览器看到成功就觉得自己通关了。我对此持保留意见。我理解的“通关”是给你一个从未见过的关卡你凭自己的测试流程先确定注入类型再确定注入点位置再选择合适的利用方式最终拿到数据并且在过程中能解释每一步为什么这么做。这才是可迁移的能力。我自己打第二遍sqli-labs的时候给自己定的规则是不用工具每关只允许5次请求。这迫使我在每次请求前把payload想清楚磨炼判断力。Less 54到Less 65的挑战关本身就有次数限制这其实就是官方在提示你自动化脚本不是万能解药你需要学会低信息下决策。5.2 一种很实用的通关之后巩固方法通关之后别急着把靶场删掉我建议做三件事。第一件事每关都打开源码对照里面写死的SQL语句重新推导一次为什么这样注入会生效这比什么都长记性。第二件事把每个关卡对应的PHP代码归类记录过滤函数、闭合方式、数据库操作函数建一份自己的对照表后期复习效率极高。第三件事尝试把某一关改成“假设我完全修复它”也就是给代码加上参数化查询看看修改后原本的payload是否全部失效。这一步做好了你对SQL注入防御的理解会拔高一大截。5.3 从靶场到现实世界的能力迁移sqli-labs里的很多场景看起来是专用的但底层能力完全可以迁移。比如Less 11的表单注入对应的就是现实系统中的登录接口Less 18的UA注入对应的就是日志采集系统对Header的日志记录Less 24的二次注入对应的就是用户注册、密码修改这种“先存后取”的业务流程。你在靶场里练出的判断顺序放到真实的代码审计中同样适用找到一个从用户输入到SQL语句的数据流然后问自己它在拼接时有没有被安全处理。我自己带新人时最推荐的是“靶场源码审计自写漏洞环境”三合一路线。先把sqli-labs打透然后自己写几个有漏洞的简单页面用MySQL记录日志观察实际执行的SQL语句。比如在Less 1页面里新增一个var_dump($sql)就能看到你输入的payload进入SQL之后的完整句子。见过“真实拼接结果”之后很多抽象概念一下子就能落地了。最后再分享一个小习惯每次通关后我都会在笔记里自己手写一遍注入流程包括用到的关键函数、字段数、闭合方式、数据库版本差异。写多了之后你会发现自己在面对未知系统时不再像一开始那样凭感觉乱试而是会在脑子里自动浮现出一条测试路径。遇到完全陌生的地方不用慌回想一下sqli-labs里那一关是怎么处理的大概率能给你参考。这套靶场值得反复打每次都能看到新东西。
返回列表