根源是搜索参数未过滤直接拼入SQL导致布尔盲注;必须用预编译绑定参数,禁用字符串拼接,配合白名单清洗与响应一致性校验。

搜索参数没过滤,直接拼进 SQL 是根源
CMS 搜索功能出布尔盲注,99% 是因为 $_GET['q'] 或类似参数被原样拼进 WHERE title LIKE '%...%' 这类语句。比如:SELECT * FROM article WHERE title LIKE '%'.$_GET['q'].'%' —— 攻击者传 q=test' AND 1=1-- - 就能控制真假分支,页面有/无结果就是布尔信号。
别指望加 addslashes() 或 str_replace("'", "''") 能挡住:MySQL 宽字节、PostgreSQL 编码不一致时,这些转义全失效;更别说布尔盲注根本不需要触发错误,只靠逻辑分支就能跑数据。
- 确认漏洞点:用
q=test' AND 1=1-- -和q=test' AND 1=2-- -对比返回内容是否不同(非 HTTP 状态码,而是页面结构、JS 变量、空 div 是否出现) - 检查所有搜索入口:包括前台搜索框、后台管理页的模糊查询、API 接口的
keyword参数 - 注意多层调用:有些 CMS 把搜索逻辑封装在函数里(如
search_articles($q)),得顺着调用链找到最终拼 SQL 的那一行
必须用预编译,不能只改变量名或加 trim()
修复不是把 $q 换成 $safe_q,也不是加个 trim() 或 htmlspecialchars()——这些对布尔盲注完全无效,因为攻击 payload 不含 HTML 标签,只靠 SQL 逻辑生效。
真正起效的是让数据库驱动接管变量绑定。以 PHP + MySQLi 为例,必须写成:
$stmt = $mysqli->prepare("SELECT * FROM article WHERE title LIKE ?");
$search_term = '%' . $_GET['q'] . '%';
$stmt->bind_param('s', $search_term);
$stmt->execute();
关键点:
-
?占位符不能出现在引号里,也不能拼在字符串中间(如"'%?%'"是错的) - LIKE 模糊匹配的
%必须在 PHP 层拼好再传入,不能写进 SQL 字符串 - 如果 CMS 用 PDO,必须用
bindValue()或bindParam(),不能用query()+ 字符串拼接 - 旧版 CMS 常见陷阱:ORM 的
whereRaw()或DB::raw()仍会拼接,等同于裸 SQL
字符型闭合和注释符绕过会让修复失效
即使用了预编译,若前端传参本身破坏了 SQL 结构,比如用户输入 test' -- -,而代码又没做基础校验,可能导致 prepare 失败或降级到拼接模式。尤其当 CMS 允许搜索带单引号的正常词(如 “O’Reilly”)时,问题更隐蔽。
稳妥做法是双保险:
- 预编译是底线,绝不妥协
- 对搜索词做最小必要清洗:允许字母、数字、中文、常见标点(如破折号、括号),但拒绝 SQL 关键字(
AND、OR、SLEEP)、注释符(--、/*)、函数名(substr、ascii)——用白名单正则,别用黑名单 - 若业务真要支持单引号,统一转义为两个单引号(
'→''),且仅在预编译前做,不是代替预编译 - 测试绕过:传
q=test%27%20AND%201=1--%20-(URL 编码后),看是否还能触发布尔差异
修复后必须关掉错误回显并验证响应一致性
布尔盲注依赖页面“有/无变化”,所以修复后要确保两种 payload 返回完全一致的 HTML 结构、HTTP 状态码、响应体长度——哪怕只是多一个空格,都可能被自动化工具识别为布尔信号。
容易被忽略的细节:
- PHP 的
display_errors=Off和error_reporting(0)必须开启,否则prepare()失败时会暴露 SQL 片段 - 某些 CMS 在搜索无结果时返回空数组,有结果时返回 JSON,这种差异本身就是布尔信道,得统一返回格式(如始终包一层
{"data":[], "total":0}) - 前端 JS 可能根据后端返回字段(如
has_result:true)动态渲染,这个字段也得固定,不能随输入变化 - 用 curl 测两次:
curl -s "url?q=test" | wc -c和curl -s "url?q=test%27%20AND%201=1--%20-" | wc -c,长度必须完全相等


















