1. 从一道经典CTF题看SQL注入的攻防本质最近在整理一些老的CTF题目发现[gxyctf2019]babysqli这道题虽然名字叫“baby”但里面涉及到的绕过技巧和思考过程恰恰是理解SQL注入攻防核心的绝佳材料。很多刚入门安全的朋友一提到SQL注入可能立刻想到的是‘ or 11 --这种“万能密码”但在实际环境尤其是CTF比赛中防御措施往往会让这种简单攻击失效。这道题就模拟了这样一种场景开发者已经做了一些基础的过滤但过滤得并不彻底留下了我们可以利用的“缝隙”。今天我就带大家完整复盘这道题的解题思路不仅告诉你答案更重要的是拆解每一步背后的原理和思考逻辑让你真正掌握这种“缝隙”中寻找机会的能力。这道题的目标很明确通过一个存在漏洞的登录框获取数据库中的敏感信息也就是flag。题目名字叫“babysqli”暗示它可能有一些基础的过滤但并非无懈可击。我们解题的过程本质上就是一场与开发者过滤逻辑的博弈。你需要像侦探一样从有限的交互中推测出后端代码可能的样子然后设计出能够绕过其防御的“Payload”。接下来我们就一步步拆解这个博弈过程。2. 初探与信息收集理解战场环境任何实战攻击的第一步都是信息收集CTF也不例外。面对一个登录框我们首先要做的就是试探它的行为边界和过滤规则。2.1 基础试探与错误回显分析我首先尝试了最经典的测试Payload在用户名或密码字段输入一个单引号‘。输入admin‘并提交后页面返回了一个SQL语法错误。这个错误信息本身就是第一个重要情报。它直接告诉我们两个关键点第一后端确实直接将用户输入拼接到了SQL语句中存在注入漏洞第二网站开启了错误回显这意味着我们可以通过错误信息来获取数据库的详细内容这通常被称为“报错注入”。注意在生产环境中开启详细的数据库错误回显是极度危险的做法因为它会为攻击者提供大量信息。CTF题目中设置错误回显是为了降低难度引导我们使用报错注入技术。得到错误回显后我尝试了‘ or 11 --这个经典Payload期望能直接绕过登录。但这次登录失败了页面没有返回错误而是像处理了正常错误密码一样。这立刻引起了我的警觉。这说明开发者很可能对or、and、--注释符等关键词进行了过滤或处理。我的初步假设是后端可能使用str_replace、preg_replace之类的函数将这些关键词替换为空字符串或进行了其他处理。2.2 关键词过滤的验证与绕过思路初现为了验证过滤规则我设计了一系列测试admin‘ or ‘1‘‘1如果or被过滤这个语句会变成admin‘ ‘1‘‘1语法错误。admin‘ and ‘1‘‘1测试and是否被过滤。admin‘ --测试注释符是否被过滤。测试发现使用or和--时行为异常而使用and时有时能引发语法错误。这暗示过滤可能不是简单的删除或者删除后产生了新的组合。一个更可靠的测试方法是使用双写绕过。我尝试了admin‘ oorr ‘1‘‘1。如果过滤函数只执行一次将or替换为空那么oorr中间的or被删除后剩下的or又会拼接起来从而绕过过滤。但在这个题目里双写绕过并未成功。这时我转换思路。既然常见的逻辑运算符和注释符可能被干扰那么有没有不依赖它们的方法呢答案是肯定的。这就是“联合查询注入”Union Injection登场的时候。联合查询的核心是使用UNION或UNION ALL操作符将我们精心构造的查询结果拼接到原始查询的结果集后面。只要字段数匹配我们就能让数据库返回我们想要的数据而不是执行原本的登录逻辑。3. 核心攻击链构建字段数探测与联合查询确定了使用联合查询的思路后我们需要解决两个技术问题第一原始查询语句到底查询了几个字段第二我们如何确定哪些字段的回显位置是可见的3.1 使用ORDER BY精确探测字段数ORDER BY子句用于根据指定列索引对结果集排序。如果ORDER BY 5表示按第5列排序而查询结果只有3列数据库就会报错。我们可以利用这个特性来精确探测字段数。我从ORDER BY 1开始尝试逐渐增加数字admin‘ order by 1 --正常假设过滤存在我们先按此逻辑思考admin‘ order by 2 --正常admin‘ order by 3 --正常admin‘ order by 4 --报错这个过程说明原始的SELECT语句查询了3个字段。这是一个至关重要的信息因为我们的UNION SELECT语句也必须跟上相同数量的字段否则会因字段数不匹配而语法错误。实操心得在实际测试中如果order by也被过滤可以尝试用group by替代原理类似。也可以使用union select null,null,null...不断递增null的个数直到页面返回正常不报错来确定字段数。null兼容所有数据类型是最安全的占位符。3.2 构造联合查询并定位回显点知道字段数是3后我就可以构造联合查询了。首先我需要让原始查询的前半部分结果为空这样页面上显示的就全是我们UNION后面的结果。通常可以构造一个必然为假的条件例如‘ and 12 union select 1,2,3 --。但这里and和--可能被过滤所以需要调整。我尝试了admin‘ union select 1,2,3‘。这里我故意在第三个字段后加了一个单引号目的是闭合原SQL语句中可能存在的后续引号并让后面的内容成为注释或多余部分可能引发错误但有时也能执行。经过多次测试和观察页面回显我发现输入admin‘ union select 1,2,3时页面发生了变化原本登录错误的地方显示了数字2和3。这是一个里程碑式的发现它意味着我的UNION SELECT语句成功执行了。页面模板会将其查询结果的第2和第3列的内容显示出来。第1列的内容可能用于其他逻辑如用户ID判断而未直接显示。数字2和3的位置就是我们可以用来输出数据库信息如表名、列名、数据的“回显点”。4. 信息提取从数据库结构到最终Flag有了可用的回显点接下来的过程就是标准的SQL注入信息提取流程但每一步都需要考虑题目可能存在的过滤。4.1 获取数据库名与表名在MySQL中database()函数返回当前数据库名group_concat(table_name)可以从information_schema.tables中聚合所有表名。我构造了如下Payload将数据库名放在回显点2表名放在回显点3admin‘ union select 1, database(), group_concat(table_name) from information_schema.tables where table_schemadatabase()‘执行后在页面的回显位置我看到了位置2数据库名babysqli位置3表名news, users, flag目标非常清晰了数据库里有一个名为flag的表这极大概率就是我们的目标。4.2 获取目标表的结构列名下一步是查看flag表有哪些列。这需要查询information_schema.columns。Payload如下admin‘ union select 1,2, group_concat(column_name) from information_schema.columns where table_schemadatabase() and table_name‘flag‘这里有一个关键点‘flag‘这个字符串常量。如果题目过滤了单引号这个Payload就会失效。幸运的是这道题没有过滤单引号。如果遇到过滤我们可以使用十六进制编码绕过比如‘flag‘的十六进制是0x666c6167那么Payload可以写成...table_name0x666c6167。执行后在回显点3看到了列名flag。果然这个表只有一列列名就是flag。4.3 最终提取Flag数据最后一步直接从flag表查询flag列的数据。Payloadadmin‘ union select 1,2, flag from flag‘执行后在页面的回显点3成功获取到了最终的Flag字符串格式通常类似flag{xxxx-xxxx-xxxx}。5. 深度复盘过滤逻辑推测与高级绕过探讨解题之后我们回过头来尝试推测一下题目后端的过滤逻辑。根据我们测试时or 11失败而union select成功的情况一个合理的推测是开发者可能只对or、and、--、#等登录绕过常用的关键词进行了过滤例如替换为空但对union、select、from、where等信息查询常用的关键词却疏于防范。这是一种典型的“不完全防护”以为防住了万能密码就万事大吉却留下了更危险的信息泄露漏洞。如果这是一道更难的题目可能会设置以下障碍我们也有相应的绕过思路过滤union和select可以使用大小写混淆UnIoN SeLeCt或者使用双写ununionion seselectlect如果过滤函数只执行一次。更高级的可以用||连接符或配合子查询。过滤空格可以使用注释符/**/代替空格如union/**/select/**/1,2,3。Tab符%09、换行符%0a有时也能奏效。过滤单引号对于字符串可以使用十六进制编码如‘flag‘变为0x666c6167。对于数字则无需引号。完全关闭错误回显这时报错注入就失效了。我们需要转向“盲注”Blind SQLi。通过构造逻辑判断根据页面返回内容的不同真/假、时间延迟来逐位推测数据。例如admin‘ and if(ascii(substr(database(),1,1))100, sleep(2), 1) --如果页面响应延迟2秒说明数据库名第一个字符的ASCII码大于100。6. 防御视角开发者该如何避免此类问题作为开发者从这道题能学到什么仅仅过滤几个关键词是远远不够的。根本的解决方案是使用参数化查询预编译语句这是最有效、最根本的防御手段。让SQL语句的“骨架”和“数据”完全分离用户输入永远只被当作数据处理无法改变语句结构。在PHP中可以使用PDO或MySQLi的预处理功能。最小权限原则给Web应用数据库账户分配最小的、必要的权限。比如只授予查询权限不授予删除、修改表结构的权限。关闭错误回显在生产环境中务必关闭数据库详细错误信息的前端展示使用统一的、模糊的错误页面。Web应用防火墙WAF部署WAF可以帮助过滤和拦截常见的攻击Payload但不应作为唯一防线。定期安全审计与代码扫描对代码进行人工审计或使用自动化工具扫描及时发现潜在的注入点。[gxyctf2019]babysqli这道题就像它的名字一样是一个引导初学者深入理解SQL注入的“婴儿步”。它从简单的报错注入入手引导你思考过滤与绕过实践联合查询的完整流程。真正掌握它不在于记住最终的Payload而在于理解每一步试探背后的原因以及当某条路被堵死时如何灵活地寻找另一条通路。这种在限制条件下寻找解决方案的思维才是网络安全研究和CTF竞赛中最宝贵的收获。下次当你再遇到一个登录框希望你能像解这道题一样系统地观察、假设、测试、验证而不仅仅是机械地输入几个所谓的“万能”Payload。