SQL注入布尔盲注原理:从 and 1=1 与 and 1=2 探针到实战测试
1. 从两个经典测试看SQL注入的本质在Web安全测试尤其是渗透测试的初级阶段我们经常会在URL里看到类似?id1 and 11和?id1 and 12这样的构造。对于刚入门的朋友来说这就像是两个神秘的“咒语”知道它们能探测漏洞但未必完全清楚背后的原理和差异。今天我就结合自己这些年挖洞和做代码审计的经验来彻底拆解这两个经典Payload的功能、适用场景以及背后的逻辑。这不仅是理解SQL注入的起点更是区分数字型注入和字符型注入的关键理解了它你就能看懂很多自动化扫描器的报告甚至手动构造更精准的测试语句。简单来说and 11和and 12是一对用于布尔盲注初步探测的“黄金搭档”。它们本身不直接获取数据而是像一个探针通过观察页面返回的差异正常显示 vs. 错误或空白来判断我们注入的SQL片段是否被数据库成功执行。11是一个永真条件12是一个永假条件。如果应用程序对用户输入处理不当将我们输入的and 12拼接到SQL语句中并执行就会改变原查询的逻辑从而导致页面表现异常。2. 核心原理SQL语句的拼接与逻辑控制要理解这两个Payload我们必须先看看它们是如何被“塞进”原本的SQL语句里的。这直接关系到注入的类型。2.1 数字型注入场景下的逻辑推演假设后端处理id参数的PHP代码是这样的一种常见的不安全写法$id $_GET[id]; $sql SELECT * FROM articles WHERE id $id;当我们访问?id1时执行的SQL是SELECT * FROM articles WHERE id 1现在我们传入?id1 and 11。后端代码不做任何过滤直接拼接语句变成了SELECT * FROM articles WHERE id 1 and 1111永远为TRUE。WHERE id 1 AND TRUE等价于WHERE id 1。所以页面应该正常显示id1的文章内容。接着我们传入?id1 and 12。语句变成SELECT * FROM articles WHERE id 1 and 1212永远为FALSE。WHERE id 1 AND FALSE整个条件结果为FALSE。这条查询将不会返回任何结果。此时我们观察到的现象将是访问?id1正常显示文章。访问?id1 and 11同样正常显示文章因为条件永真未改变原有效查询结果。访问?id1 and 12页面显示异常可能是一片空白、显示“未找到内容”或与?id1时完全不同。如果同时满足以上三点尤其是11正常而12异常那么这里就极有可能存在数字型SQL注入漏洞。因为我们的逻辑运算符and和条件表达式12被成功地作为SQL语法的一部分执行了。2.2 字符型注入场景下的“坑”与突破字符型注入更常见。假设后端代码是这样的$id $_GET[id]; $sql SELECT * FROM users WHERE username $id;当我们访问?idadmin时SQL是SELECT * FROM users WHERE username admin如果我们直接尝试?idadmin and 11拼接后的语句将是SELECT * FROM users WHERE username admin and 11数据库会去寻找一个用户名为字面字符串“admin and 11”的用户这显然不是我们想要的。我们的Payload被当作字符串的一部分而不是SQL代码。这时我们需要先闭合前面的单引号并注释掉后面的单引号。所以正确的测试Payload应该是?idadmin and 11?idadmin and 12或者更常用的用注释符--或#?idadmin and 11 --?idadmin and 12 --以?idadmin and 11 --为例拼接后的SQL为SELECT * FROM users WHERE username admin and 11 -- --后面的所有内容包括原本应该闭合的那个单引号都被注释掉了。语句等价于WHERE usernameadmin AND TRUE正常查询。 同理?idadmin and 12 --会导致条件永假查询无结果。注意注释符的使用因数据库而异。MySQL中常用--注意后面有个空格或#Oracle用--SQL Server用--。在URL中#通常需要编码为%23因为#在URL中是锚点标识。2.3 为什么说这是“布尔盲注”的探针布尔盲注发生在应用程序不会将数据库错误信息回显到页面但会根据查询语句返回“真”或“假”呈现不同的页面内容比如查询到结果显示详情查不到结果显示“空”或跳转。and 11和and 12完美地利用了这一点and 11(永真)如果注入成功它不应该改变原有正确查询的结果。页面状态应与正常请求一致。and 12(永假)如果注入成功它会使整个WHERE条件失败导致查询结果为空。页面状态应发生明显变化如内容消失。通过对比这两个Payload触发的页面响应差异我们就能初步断定是否存在SQL注入点以及这个注入点是否支持布尔逻辑判断。这是手动注入测试至关重要的第一步。3. 深入实操不同场景下的测试与判断在实际测试中情况可能比理论更复杂。下面我们展开几个典型场景。3.1 基础测试流程与观察要点寻找候选点寻找带有参数如id,name,cat的URL例如/news.php?id1、/search?keywordapple。发送基准请求记录?id1的正常页面响应内容。保存HTML或记住关键特征如标题、某段特定文字、页面布局。发送永真测试请求?id1 and 11数字型或?id1 and 11字符型。关键观察页面是否与基准请求完全一致注意是“完全一致”包括所有动态内容、元素位置而不仅仅是能正常打开。有些网站会对异常参数返回200状态码但内容不同。发送永假测试请求?id1 and 12或?id1 and 12。关键观察页面是否出现明显变化例如文章内容区域空白。显示“查询无结果”、“Not Found”。页面结构虽在但核心数据缺失。甚至直接跳转到首页或错误页。交叉验证有时需要尝试or 11和or 12。例如?id1 or 11可能会返回大量数据甚至整个表的内容而?id1 or 12则可能只返回id1的数据。这也能帮助判断注入类型和上下文。实操心得不要只看浏览器渲染的页面一定要用Burp Suite、Fiddler这类工具拦截和对比HTTP响应。直接对比**响应体Response Body**的长度和内容差异最可靠。页面一个微小的布局变动或隐藏字段值的变化都可能是关键的判断依据。3.2 应对编码与过滤的绕过技巧现代应用多少会有些防御措施直接测试可能失败。URL编码空格在URL中可能被处理或过滤。可以尝试用或%20代替空格?id1and11或?id1%20and%2011。and、or也可能被过滤可尝试双写anandd、大小写混写AnD或用符号MySQL中代替and。内联注释绕过MySQL的特性/*! ... */中的代码会被执行。可以尝试?id1 /*!and*/ 11。这对于绕过简单的空格过滤或关键字过滤有时有效。参数类型陷阱即使参数看起来是数字如id1后端也可能用字符串类型接收Java的StringPython的str。所以字符型注入的测试优先级通常应高于数字型。我习惯先测试字符型加单引号如果不成功再测试数字型。布尔状态不明显有时and 12页面变化很细微。可以尝试更强烈的对比比如?id1 and (select 1)1vs?id1 and (select 1)2引入一个必然导致查询结果集变化的子查询如?id1 and exists(select 1 from users)vs?id1 and exists(select 1 from a_table_that_not_exists)。3.3 从探测到利用信息获取的思路延伸确认注入点后and 11和and 12的使命就完成了。接下来我们会利用这个布尔逻辑差异像“猜”一样获取数据。例如我们想猜解当前数据库用户名的第一个字母是什么。可以构造一系列布尔判断?id1 and ascii(substring(user(),1,1)) 100 --?id1 and ascii(substring(user(),1,1)) 100 --通过观察页面返回是“真状态”还是“假状态”结合二分查找法就能逐位推断出完整的用户名、数据库名、表名、字段名乃至表中的数据。这个过程虽然繁琐但却是布尔盲注的核心。自动化工具如SQLmap本质上就是自动化、优化了这个“猜”的过程。4. 常见问题与排查技巧实录在实际测试中你会遇到各种“奇怪”的现象。这里记录几个典型问题和排查思路。4.1 为什么and 11页面报错或空白这通常不是注入成功的标志反而可能说明Payload破坏了SQL语法。可能性1参数是字符型但你用了数字型Payload。例如原语句是WHERE name$input你传入and 11导致语句变成WHERE nameand 11语法虽然可能不报错它只是一个字符串但查询不到数据页面空白。解决方案尝试加上单引号闭合和注释 and 11或 and 11 --。可能性2存在简单的过滤。例如程序检测到and、or、空格等关键字将其替换为空或直接拦截。解决方案尝试绕过技巧如用代替空格用代替and或用内联注释。可能性3存在WAFWeb应用防火墙。云WAF或硬件WAF会识别常见攻击模式并阻断请求。解决方案尝试使用更冷僻的语法如?id1 DIV 1MySQL中DIV是整数除法1 DIV 1结果为1可作为真值或使用分段传输、协议层绕过等技术这属于更高级的范畴。4.2 为什么and 11和and 12返回的页面看起来一样这是最让人困惑的情况之一。可能性1注入点位于不影响查询结果的位置。例如注入点在ORDER BY或LIMIT子句中and 11可能不改变排序和限制结果。需要尝试?id1 asc和?id1 desc来测试ORDER BY注入。可能性2应用程序有统一的错误处理机制。无论SQL是否出错都返回一个状态码为200的友好错误页面如“系统繁忙”。解决方案仔细对比两个响应的HTML源码寻找细微差别比如一个隐藏的error字段或者响应时间的差异时间盲注。可以尝试?id1 and sleep(5)--如果页面延迟5秒返回则说明存在时间盲注。可能性3逻辑判断有误。可能原查询本身就有复杂的逻辑或者使用了UNION使得永假条件依然返回了部分数据。解决方案使用更“干净”的测试例如将Payload放在UNION SELECT之后或者尝试?id1 and (select 0) --永假与?id1 and (select 1) --永真。4.3 在CTF靶场和真实漏洞扫描中的体现CTFHub / DVWA / Pikachu 靶场这些靶场通常设计了明显的布尔状态差异。and 11和and 12是解题的“敲门砖”。通过它们确认注入点后就可以按部就班地进行数据库名、表名、字段名的猜解。靶场环境干净干扰少是练习原理的绝佳场所。奇安信 / AWVS 等安全扫描器报告当扫描器报告“SQL注入漏洞”时它很可能已经完成了类似and 11/and 12的布尔逻辑测试并观察到了响应差异。报告里可能会给出触发漏洞的Payload示例。作为开发者或安全人员看到这样的报告第一步就是去验证按照报告提示的参数和Payload手动重放请求亲自观察页面差异确认漏洞是否真实存在。这比盲目相信扫描结果更重要。MyBatis${}导致的注入这是Java开发中一个经典问题。在MyBatis的XML映射文件中如果使用#{id}是预编译安全的而使用${id}则会直接进行字符串拼接。如果id参数来自用户输入且未过滤那么攻击者传入1 and 11最终生成的SQL语句就是WHERE id 1 and 11漏洞由此产生。代码审计时全局搜索${是发现此类漏洞的高效方法。4.4 浏览器与服务器的交互细节GET请求长度限制虽然HTTP协议本身对URL长度没有限制但浏览器和服务器有实际约束通常几KB到几十KB。对于复杂的盲注Payload长度一般足够。如果遇到超长Payload被截断可以考虑改用POST请求进行注入测试。中文字符编码在GET请求中中文字符会进行URL编码如%E6%B5%8B%E8%AF%95。在测试包含中文参数的注入点时需要确保Payload在编码后依然保持正确的SQL语法。有时服务器的解码顺序和字符集设置如GBK可能引发宽字节注入等更深层次的漏洞这超出了基础布尔测试的范围但值得警惕。理解?id1 and 11和?id1 and 12不仅仅是记住了两个字符串。它是你理解SQL注入如何通过篡改程序逻辑来探测漏洞的思维起点。从这两个简单的等式出发你可以延伸到联合查询注入、报错注入、堆叠注入乃至各种匪夷所思的绕过技巧。手动测试的过程虽然缓慢但它能培养你对HTTP请求、响应以及程序逻辑边界的深刻直觉这是任何自动化工具都无法替代的。下次当你再看到URL中的这些参数时希望你能清晰地看到背后那条正在被拼接和执行的SQL语句以及那条介于正常与异常之间的、可供利用的模糊地带。