Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions data/txt/sha256sums.txt
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,10 @@ ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/
df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh
1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py
63657c00a046ca0fb28fd069407ab6305bd7b95c42f26a96ed083fd05b152252 extra/vulnserver/vulnserver.py
43214ecb0101bce72eb243c91b90db34693ebfd485d6c111a4ae22591ff7800b extra/vulnserver/vulnserver.py
a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py
9387fb775b694156a71b336a2a9638ef24c577aa38746f391ac040ff05306d95 lib/controller/checks.py
96463b969312bd4fd29452b5fc739f33e5a73f81fdc1ef80ac27debbe9926e42 lib/controller/controller.py
0c6433b289094d37f295238699042a34a6ab950bb3d11f74fe9a83d30bb7f4bd lib/controller/checks.py
ea0fdf6bcda59aae4d093bada965654a0cd940227c2dbdf62b6ded79baa8dfad lib/controller/controller.py
d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py
9c5764c92ce536d1f0f96200359ee5ef1f37f9128769bf990cb77f1d1f8e17b1 lib/core/agent.py
Expand All @@ -181,26 +181,26 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor
5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py
914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py
8b260bff7f24947ece55727277d526c88a91f7cb9ffe059c4b9c190bf85f80e1 lib/core/optiondict.py
056930fba3cf9827f97d280bc38ac785c93108eb84c922f5f39723bb04dcf403 lib/core/optiondict.py
4e7f2ad3d2866093aa195616a0e93de1687406edc0b9038fbfa76bf1c9c174b2 lib/core/option.py
ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py
49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py
03db48f02c3d07a047ddb8fe33a757b6238867352d8ddda2a83e4fec09a98d04 lib/core/readlineng.py
9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py
0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py
888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py
2db950a79f3f8a4bbb0f35731d4e2eef220150961be55d8ba4b1f9565bdd483a lib/core/settings.py
ca14e55b4d49a9b9f4e547180828030e4fcc51176dc9036879dbdae05919dd02 lib/core/settings.py
c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py
a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py
19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py
c1392cda2f202fa3c628f74533c8d9379d1cf7e754ac165e39021bbc2bbc4a22 lib/core/testing.py
e453904a50372216b09146ad9f11cdced2323c10f49c3d866238cc044dcb2cce lib/core/testing.py
95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py
b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py
53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py
2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py
54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py
6060d2d11fab39796b87ace30a872302f365dea3b14d24670915fdb9edc86011 lib/parse/cmdline.py
223badcfd102cdf3313411b63d09b6c59599d58dfc40d27409b1bfa2efc1aa8f lib/parse/cmdline.py
02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py
c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py
5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py
Expand Down Expand Up @@ -240,6 +240,8 @@ a66a4b9df6207dce722c9b71d290ea426723cb4b697b416065dc7dd5db96fe8e lib/techniques
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/error/__init__.py
5bbef46c16e34fd80e3f9f0e9aa255ce2e39be0d0e57479e25890b041c7efc7d lib/techniques/error/use.py
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/__init__.py
44401cad3e39ae9fb899ed5d0e2fdd0879561de05c3117f17f3b0db54f4e3724 lib/techniques/nosql/__init__.py
d62b28bf9f1544e65a1017994402f484166f4d64a1efb724351b15e27b851990 lib/techniques/nosql/inject.py
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py
ceec65f8cb7c3254c4671351c837418c76ac5bc55ccbc40779f67231b54d7085 lib/techniques/union/test.py
c65766f71e285fc85cdf58e7448c4c1d015af2a9dbb44fa3b665a9f13362fbcc lib/techniques/union/use.py
Expand Down Expand Up @@ -597,6 +599,7 @@ c04e8358fb6df45f69f2f26435c971acde280535bf304e84d30cf2681158c6a7 tests/test_has
d539d0ae758b5bb91e314ab82ab4fe03d6fb2f8b377d16aefa6d7d1d77a7d5a9 tests/test_identifiers_output.py
5372270b7ed82b62f273c2e9bd1f7ecd8605371e66cd0ad70663762cb08d42f1 tests/test_inference_engine.py
caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_misc.py
790b78c600b61eb0bdd6e07e14b1db3eb2ddd5fc5d4edb9e975f85ced38558c7 tests/test_nosql.py
57fa9713a3186020be8bcc3f06399e92bf9ce82ec6d3413c76babe19606bb698 tests/test_openapi_drift.py
cde0bea1263ae857561f91ed2bd515e972b716743f017d31b1718a8546c72759 tests/test_pagecontent.py
4bac34af2abddce003756d6776e89b2fda220bb7603ef3761f4f37ee29f9c369 tests/test_payload_marking.py
Expand Down
54 changes: 54 additions & 0 deletions extra/vulnserver/vulnserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,46 @@
LISTEN_ADDRESS = "localhost"
LISTEN_PORT = 8440

# Minimal MongoDB-style collection backing the NoSQL operator-injection endpoint ('/nosql'). The
# 'password' field is the blind-extraction target, constrained by a sibling 'name' equality match.
NOSQL_USERS = {
"luther": "s3cr3t",
"fluffy": "carrot",
"wu": "shanghai",
}

def nosql_match(params):
"""Emulates a MongoDB find() on NOSQL_USERS: reconstructs the operator object for the 'password'
field (from bracket-notation 'password[$ne]=...' or a JSON sub-document) and evaluates it against
the record selected by 'name'. An invalid $regex raises re.error (surfaced as a driver error)."""

record = NOSQL_USERS.get(params.get("name"))

spec = params.get("password")
if isinstance(spec, dict):
op, value = next(iter(spec.items()), ("$eq", None))
else:
op, value = "$eq", spec
for key in params:
match = re.match(r"^password\[(\$\w+)\](?:\[\])?$", key)
if match:
op, value = match.group(1), params[key]
break

if isinstance(value, (tuple, list)):
value = value[-1] if value else None

if record is None:
return False
elif op == "$ne":
return record != value
elif op == "$gt":
return record > (value or "")
elif op == "$regex":
return re.search(value, record) is not None
else: # $eq, $in (single-valued here) and any literal equality
return record == value

_conn = None
_cursor = None
_lock = None
Expand Down Expand Up @@ -285,6 +325,20 @@ def do_REQUEST(self):
self.wfile.write(form.encode(UNICODE_ENCODING))
return

if self.url == "/nosql":
self.send_response(OK)
self.send_header("Content-type", "text/html; charset=%s" % UNICODE_ENCODING)
self.send_header("Connection", "close")
self.end_headers()

try:
output = "<html><body><b>Welcome %s</b></body></html>" % self.params.get("name") if nosql_match(self.params) else "<html><body><b>Invalid credentials</b></body></html>"
except re.error: # invalid $regex -> emulate a MongoDB driver error (drives fingerprinting)
output = "<html><body>MongoServerError: Regular expression is invalid: missing terminating ] for character class</body></html>"

self.wfile.write(output.encode(UNICODE_ENCODING))
return

if self.url == '/':
if not any(_ in self.params for _ in ("id", "query")):
self.send_response(OK)
Expand Down
8 changes: 8 additions & 0 deletions lib/controller/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
from lib.core.settings import MAX_DIFFLIB_SEQUENCE_LENGTH
from lib.core.settings import MAX_STABILITY_DELAY
from lib.core.settings import NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH
from lib.core.settings import NOSQL_ERROR_REGEX
from lib.core.settings import PRECONNECT_INCOMPATIBLE_SERVERS
from lib.core.settings import SINGLE_QUOTE_MARKER
from lib.core.settings import SLEEP_TIME_MARKER
Expand Down Expand Up @@ -1170,6 +1171,13 @@ def _(page):
except (SystemError, RuntimeError) as ex:
logger.debug("Skipping FI heuristic due to regex failure: %s", getSafeExString(ex))

if not conf.nosql and re.search(NOSQL_ERROR_REGEX, page or ""):
infoMsg = "heuristic (NoSQL) test shows that %sparameter '%s' might be vulnerable to NoSQL injection attacks (rerun with switch '--nosql')" % ("%s " % paramType if paramType != parameter else "", parameter)
logger.info(infoMsg)

if conf.beep:
beep()

kb.disableHtmlDecoding = False
kb.heuristicMode = False

Expand Down
5 changes: 5 additions & 0 deletions lib/controller/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,11 @@ def start():

checkWaf()

if conf.nosql:
from lib.techniques.nosql.inject import nosqlScan
nosqlScan()
continue

if conf.nullConnection:
checkNullConnection()

Expand Down
1 change: 1 addition & 0 deletions lib/core/optiondict.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@

"Techniques": {
"technique": "string",
"nosql": "boolean",
"timeSec": "integer",
"uCols": "string",
"uChar": "string",
Expand Down
34 changes: 32 additions & 2 deletions lib/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from thirdparty import six

# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.10.6.159"
VERSION = "1.10.6.160"
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)
Expand Down Expand Up @@ -466,7 +466,8 @@
r"error '[0-9a-f]{8}'((<[^>]+>)|\s)+(?P<result>[^<>]+)",
r"\[[^\n\]]{1,100}(ODBC|JDBC)[^\n\]]+\](\[[^\]]+\])?(?P<result>[^\n]+(in query expression|\(SQL| at /[^ ]+pdo)[^\n<]+)",
r"(?P<result>query error: SELECT[^<>]+)",
r"(?P<result>(?:(?:ORA|PLS)-[0-9]{5}:|SQLCODE[ =:]+-?[0-9]+|SQLSTATE[ =:]+[0-9A-Z]{5}|Dynamic SQL Error|DB2 SQL error:|SAP DBTech JDBC:|SQLiteException:|You have an error in your SQL syntax;|Incorrect syntax near |Unclosed quotation mark after the character string|near \"[^\"]+\": syntax error)[^\n<]*)"
r"(?P<result>(?:(?:ORA|PLS)-[0-9]{5}:|SQLCODE[ =:]+-?[0-9]+|SQLSTATE[ =:]+[0-9A-Z]{5}|Dynamic SQL Error|DB2 SQL error:|SAP DBTech JDBC:|SQLiteException:|You have an error in your SQL syntax;|Incorrect syntax near |Unclosed quotation mark after the character string|near \"[^\"]+\": syntax error)[^\n<]*)",
r'"(?:errmsg|errorMessage|reason|msg)"\s*:\s*"(?P<result>[^"]+)"' # generic JSON error-message field (NoSQL document/REST back-ends)
)

# Regular expression used for parsing charset info from meta html headers
Expand Down Expand Up @@ -847,6 +848,35 @@
# Regular expression used for recognition of file inclusion errors
FI_ERROR_REGEX = r"(?i)[^\n]{0,100}(no such file|failed (to )?open)[^\n]{0,100}"

# Regular expressions (per back-end, anchored to actual error-message structure - not product names) used for heuristic recognition of NoSQL injection
NOSQL_ERRORS = (
("MongoDB", r"Mongo(?:Server|Parse|Network|Runtime|Bulk|WriteConcern)?Error\b|\bBSON(?:Type)?Error\b|\bMongooseError\b|CastError: Cast to|unknown (?:top.level )?operator: ?\$|\$(?:regex|where|expr|in|nin|ne|gt|lt|elemMatch) (?:has to be|is not allowed|must be|not supported|requires)|Regular expression is invalid"),
("CouchDB", r'"error"\s*:\s*"(?:bad_request|query_parse_error|missing_named_query)"|invalid operator: ?\$'),
("Elasticsearch", r'"type"\s*:\s*"[a-z_]*?(?:query_shard|x_content_parse|parsing|search_phase_execution|illegal_argument|too_many_clauses|number_format|script)_exception"|Failed to parse query \['),
("Solr", r"org\.apache\.solr\.[\w.]*(?:SyntaxError|SolrException)"),
("Neo4j", r"Neo\.(?:ClientError|DatabaseError|TransientError|ClientNotification)\.|\bNeo4jError\b|even number of non-escaped quotes|Failed to parse string literal|expected an expression|'(?:UNWIND|OPTIONAL|DETACH|FOREACH|MERGE|LOAD CSV)'"),
("ArangoDB", r"\bArangoError\b|AQL: (?:syntax|parse) error"),
("Cassandra", r"line \d+:\d+ (?:no viable alternative at input|(?:mismatched|extraneous) input '.*?' expecting)|org\.apache\.cassandra|com\.datastax|\bInvalid(?:Request|Query)Exception\b"),
("Redis", r"\bWRONGTYPE\b|ERR Error (?:compiling|running) script|@user_script|\bReplyError\b"),
("Memcached", r"CLIENT_ERROR bad|SERVER_ERROR object too large"),
("InfluxDB", r"error parsing query|unable to parse '[^']*': found"),
("HBase/Phoenix", r"org\.apache\.phoenix|PhoenixParserException|org\.apache\.hadoop\.hbase"),
)
NOSQL_ERROR_REGEX = "(?:%s)" % '|'.join(regex for _, regex in NOSQL_ERRORS)

# Printable-ASCII codepoint bounds bisected (via regexp character-class ranges) during NoSQL blind extraction
NOSQL_CHAR_MIN = 0x20
NOSQL_CHAR_MAX = 0x7e

# Maximum number of document fields enumerated during a NoSQL ($where server-side JavaScript) document dump
NOSQL_MAX_FIELDS = 64

# Maximum number of records walked during a NoSQL blind multi-record (ordered key paging) collection dump
NOSQL_MAX_RECORDS = 100

# Upper bound for the length search during NoSQL blind extraction
NOSQL_MAX_LENGTH = 1024

# Length of prefix and suffix used in non-SQLI heuristic checks
NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH = 6

Expand Down
1 change: 1 addition & 0 deletions lib/core/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def vulnTest():
("-u <url> --flush-session --technique=B --keyset --dump -T users", ("using keyset (seek) pagination", "30 entries", "luther", "nameisnull")), # keyset/seek dump via the SQLite rowid cursor
("-u <url> -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [30]", "nameisnull")),
("-u \"<url>&echo=foobar*\" --flush-session", ("might be vulnerable to cross-site scripting",)),
("-u \"<base>nosql?name=luther&password=x\" -p password --nosql --flush-session", ("is vulnerable to NoSQL injection", "back-end: 'MongoDB'", "NoSQL: GET parameter 'password'", "s3cr3t")), # NoSQL (MongoDB) operator-injection detection + blind regexp extraction
("-u \"<url>&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")),
("-d \"<direct>\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")),
("-d \"<direct>\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=4; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "4,foobar,nameisnull", "'987654321'",)),
Expand Down
3 changes: 3 additions & 0 deletions lib/parse/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,9 @@ def cmdLineParser(argv=None):
techniques.add_argument("--technique", dest="technique",
help="SQL injection techniques to use (default \"%s\")" % defaults.technique)

techniques.add_argument("--nosql", dest="nosql", action="store_true",
help="Test for NoSQL injection (e.g. MongoDB, CouchDB, Neo4j)")

techniques.add_argument("--time-sec", dest="timeSec", type=int,
help="Seconds to delay the DBMS response (default %d)" % defaults.timeSec)

Expand Down
6 changes: 6 additions & 0 deletions lib/techniques/nosql/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env python

"""
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
See the file 'LICENSE' for copying permission
"""
Loading