Skip to content

Commit 820efa7

Browse files
committed
Adding switch --ssti
1 parent 8ff5d38 commit 820efa7

13 files changed

Lines changed: 1263 additions & 24 deletions

File tree

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ jobs:
109109
110110
- name: Smoke test
111111
run: |
112-
python -m pip install -q lxml
112+
python -m pip install -q lxml jinja2
113113
python sqlmap.py --smoke-test
114114
115115
- name: Vuln test

data/txt/sha256sums.txt

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,10 @@ ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/
160160
df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh
161161
1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh
162162
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py
163-
a4d4ec8aaea6da7b20068209945cf46348bde74b4c90ddf630c3be820d16f73e extra/vulnserver/vulnserver.py
163+
f96ceae5ecb2bfe5eb3b8ae5cf344a93943f13322bc79bb92dbaeafa30f9321f extra/vulnserver/vulnserver.py
164164
a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py
165-
0397a941e27fa23ef375b6bd0a654132b05496d78737253a58524aab7e840789 lib/controller/checks.py
166-
e9fd898a38e4e1bfc975e44b41b344c190e58d845b8602a50a2bf05835ddc7c8 lib/controller/controller.py
165+
9137a8f7368496c84b21944f6b94c28004d3a2a849ac9c8e0b20e294e4c4a93a lib/controller/checks.py
166+
4598de22ed3df63432e9643ba48533a01bec9f0b253c3a11f322ccedaef353f0 lib/controller/controller.py
167167
d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py
168168
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py
169169
9c5764c92ce536d1f0f96200359ee5ef1f37f9128769bf990cb77f1d1f8e17b1 lib/core/agent.py
@@ -181,26 +181,26 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor
181181
5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py
182182
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py
183183
914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py
184-
8b5d4d1f503ef7075820f7eca184c9e55386b7717c5cf93d195fa9e5332d9e34 lib/core/optiondict.py
184+
33ed53b263fa766a808be6797dd812822bb115d3b9db6e3a34763f500f5359e8 lib/core/optiondict.py
185185
e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/option.py
186186
21b2b1745107c211fc7593923a3da7a808d40763c00091c28de5f7c129bcf3bc lib/core/patch.py
187187
49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py
188188
0c36a65b6237732eb001d333f80f0c58c088ff01ae80cf07e4dcc6da2a806364 lib/core/readlineng.py
189189
9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py
190190
0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py
191191
888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py
192-
77a4804887ae9dde7142ede91fcae1bd1d7369132f90d7bf095e8bad6f62e5a4 lib/core/settings.py
192+
7f811ed56c2ce56e2575e732d0853a2064cefa57fa850c51b9e08e00d685ca08 lib/core/settings.py
193193
c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py
194194
a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py
195195
19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py
196-
fbfb3fa79ac0566a985b8cdc3a2e4758bdf4ccf9d94428163bfe6432c72d696b lib/core/testing.py
196+
4056457dd8502ec367ec4633a33856561f562778f862fedc1372531bb2f58671 lib/core/testing.py
197197
95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py
198198
b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py
199199
53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py
200200
2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py
201201
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py
202202
54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py
203-
541b517c9cacba2b62122c1dc2be8f2808afdc32e715983edf29998433b531bb lib/parse/cmdline.py
203+
316cdcb3d8d839dab639ed7eb4935780375d49c93371edbd6224976cbb968c2e lib/parse/cmdline.py
204204
02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py
205205
c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py
206206
5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py
@@ -246,6 +246,8 @@ ffbc7583a563bb9fe5a560ca8363f3e4ec84ecf907b956883ab1f2904f19d529 lib/techniques
246246
cc90c641d74244e45fa0c8c4026315452137e66b6fb5cef681d0eacd4e11eb69 lib/techniques/ldap/inject.py
247247
44401cad3e39ae9fb899ed5d0e2fdd0879561de05c3117f17f3b0db54f4e3724 lib/techniques/nosql/__init__.py
248248
e2cd2b19f82393f9bbc8f374686cd851a4ccc264bb898ea54547ec479a05674c lib/techniques/nosql/inject.py
249+
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/ssti/__init__.py
250+
cb8806c285962593b963464ba870d61f274ee73d9ba878c76fe52795cbe4eced lib/techniques/ssti/inject.py
249251
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py
250252
ceec65f8cb7c3254c4671351c837418c76ac5bc55ccbc40779f67231b54d7085 lib/techniques/union/test.py
251253
c65766f71e285fc85cdf58e7448c4c1d015af2a9dbb44fa3b665a9f13362fbcc lib/techniques/union/use.py
@@ -586,7 +588,7 @@ d16977d057c28888aa41500f79a19789cadef693cb8b7d9a3bca55b983ce2266 tests/test_age
586588
feb763ddcbf4f32822372ca53f8c71c754af7b72510ef06e1e9c77927fc90b10 tests/test_bigarray.py
587589
36bcb68483d824db5d05870fab62f1907221bf256826b734302fbc15a9231c42 tests/test_brute.py
588590
27ad87c0ea377e0657bd6f6a4eaa0e9756aa9d28ec0483bdadeb3f66dcc4660d tests/test_charset.py
589-
c99b77cc5d85334f147a1a6d4b2867af396f70e9f2609f8587344e084910e893 tests/test_checks.py
591+
7596fc69678304923b5c945c0fd9b8ee62a2dfc7fb14ccb6dc7af30893dc8012 tests/test_checks.py
590592
9e678a56e16211c49ab4995b6c658d3f122bfa3b357d9e17ff38f5a489ace6ad tests/test_cloak.py
591593
2ec894f49ca9bd750a23ead16dae176bcbc57d18ec5847fa4a5eeb886d75c1bd tests/test_common_helpers.py
592594
cdacb37cbe5667fded00abe62a822e11c917e9cb5c3f664b7aa1a8d738412ed4 tests/test_common.py
@@ -641,6 +643,7 @@ cec98d72992c0799229a780fa7f0d7f3fb01ec2d708187ce0e4a05c8612f291b tests/test_saf
641643
a1c6cda1e5b483f61e6a4f8ddd0b06a15ddaa3fd2119bfb9dbd9cc970d7a751d tests/test_settings_regex.py
642644
29d0278e3718b0fee422d3f6bb85ca02560138d48cd76f9fe1f35ac19d96071b tests/test_sgmllib.py
643645
d3d991331096e16e5019de3d652e9fff92c09bd9f97c50b1c2c3ceb0ed49b17e tests/test_sqlparse.py
646+
49c72cf40cfa78c573826ca1ab3ad11886e353158a31f15b29c6d71b0e561fcc tests/test_ssti.py
644647
8bcbf1091134dd0a62f6201f8b3645ed87b5ff2f7ba40a87231a29dac412591f tests/test_strings.py
645648
8f1c5f0f337ecd26d35c5551060034e0aa33a62cce5385fc1227fdc485f6383e tests/test_tamper.py
646649
67472bd71c20782cc0f738e2c2e674c29d6985669e14d15b69baef7d0e33de62 tests/test_target_parsing.py

extra/vulnserver/vulnserver.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,31 @@ def do_REQUEST(self):
10191019
self.wfile.write(output.encode(UNICODE_ENCODING))
10201020
return
10211021

1022+
if self.url == "/ssti/search":
1023+
self.send_response(OK)
1024+
self.send_header("Content-type", "text/html; charset=%s" % UNICODE_ENCODING)
1025+
self.send_header("Connection", "close")
1026+
self.end_headers()
1027+
1028+
q = self.params.get("q", "")
1029+
output = "<html><body>"
1030+
1031+
if q:
1032+
try:
1033+
from jinja2 import Template
1034+
# VULNERABLE: unsanitized user input passed to Jinja2 template engine
1035+
template = Template("Hello " + q)
1036+
output += template.render()
1037+
except Exception as ex:
1038+
# Leak template engine error for error-based detection
1039+
output += "<b>%s: %s</b>" % (type(ex).__name__, getUnicode(ex))
1040+
else:
1041+
output += "Hello"
1042+
1043+
output += "</body></html>"
1044+
self.wfile.write(output.encode(UNICODE_ENCODING))
1045+
return
1046+
10221047
if self.url == '/':
10231048
if not any(_ in self.params for _ in ("id", "query")):
10241049
self.send_response(OK)

lib/controller/checks.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
from lib.core.settings import HEURISTIC_CHECK_ALPHABET
8484
from lib.core.settings import INFERENCE_EQUALS_CHAR
8585
from lib.core.settings import LDAP_ERROR_REGEX
86+
from lib.core.settings import SSTI_ERROR_REGEX
8687
from lib.core.settings import XPATH_ERROR_REGEX
8788
from lib.core.settings import IPS_WAF_CHECK_PAYLOAD
8889
from lib.core.settings import IPS_WAF_CHECK_RATIO
@@ -1202,6 +1203,13 @@ def _(page):
12021203
if conf.beep:
12031204
beep()
12041205

1206+
if not conf.ssti and re.search(SSTI_ERROR_REGEX, page or ""):
1207+
infoMsg = "heuristic (SSTI) test shows that %sparameter '%s' might be vulnerable to server-side template injection (rerun with switch '--ssti')" % ("%s " % paramType if paramType != parameter else "", parameter)
1208+
logger.info(infoMsg)
1209+
1210+
if conf.beep:
1211+
beep()
1212+
12051213
kb.disableHtmlDecoding = False
12061214
kb.heuristicMode = False
12071215

lib/controller/controller.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,11 @@ def start():
548548
xpathScan()
549549
continue
550550

551+
if conf.ssti:
552+
from lib.techniques.ssti.inject import sstiScan
553+
sstiScan()
554+
continue
555+
551556
if conf.nullConnection:
552557
checkNullConnection()
553558

lib/core/optiondict.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
"graphql": "boolean",
123123
"ldap": "boolean",
124124
"xpath": "boolean",
125+
"ssti": "boolean",
125126
"timeSec": "integer",
126127
"uCols": "string",
127128
"uChar": "string",
@@ -172,6 +173,8 @@
172173
"lastChar": "integer",
173174
"sqlQuery": "string",
174175
"sqlShell": "boolean",
176+
"sstiQuery": "string",
177+
"sstiShell": "boolean",
175178
"sqlFile": "string",
176179
},
177180

lib/core/settings.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from thirdparty import six
2121

2222
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
23-
VERSION = "1.10.6.189"
23+
VERSION = "1.10.6.190"
2424
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
2525
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
2626
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)
@@ -1015,6 +1015,25 @@
10151015
# Upper bound for the value-length search during XPath blind extraction
10161016
XPATH_MAX_LENGTH = 256
10171017

1018+
# SSTI error signatures per template engine for detection and fingerprinting.
1019+
# Each tuple is (engine_name, regex_fragment).
1020+
SSTI_ERROR_SIGNATURES = (
1021+
("Jinja2", r"jinja2\.exceptions\.\w+|TemplateSyntaxError|UndefinedError|TemplateNotFound|TemplateAssertionError"),
1022+
("Twig", r"Twig[\\_]Error|Twig[\\_]Environment|Unknown (?:filter|function|test|tag)"),
1023+
("Freemarker", r"freemarker\.(?:core|template|extract|cache)\.\w+|ParseException|InvalidReferenceException|TemplateException"),
1024+
("Velocity", r"org\.apache\.velocity\.(?:runtime|exception)\.\w+|ParseErrorException|MethodInvocationException|ResourceNotFoundException"),
1025+
("Spring EL / Thymeleaf", r"org\.springframework\.expression\.\w+|org\.thymeleaf\.\w+|SpelEvaluationException|TemplateProcessingException|ExpressionParsingException"),
1026+
("ERB", r"\(erb\):\d+|NameError.*undefined local variable"),
1027+
("Pug/Jade", r"pug|jade|ParseError"),
1028+
("Handlebars", r"handlebars|Handlebars|Parse error on line"),
1029+
("Generic SSTI", r"template.*?(?:error|syntax|exception)"),
1030+
)
1031+
1032+
SSTI_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in SSTI_ERROR_SIGNATURES)
1033+
1034+
# Upper bound for SSTI value extraction (reserved for future use)
1035+
SSTI_MAX_LENGTH = 256
1036+
10181037
# Length of prefix and suffix used in non-SQLI heuristic checks
10191038
NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH = 6
10201039

lib/core/testing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ def vulnTest():
9292
("-u \"<base>graphql\" --graphql --flush-session --disable-hashing", ("found GraphQL endpoint", "introspection returned", "skipping 2 mutation slot", "GraphQL boolean-based blind", "in-band data exposure", "back-end DBMS: 'SQLite'", "banner: '3.", "GraphQL database tables", "fetched 30 entries from table 'creds'", "db3a16990a0008a3b04707fdef6584a0", "GraphQL scan complete")), # GraphQL: endpoint detection + introspection + mutation-skip + boolean-blind/in-band + back-end fingerprint + batched blind dump of an injection-only table (SQLite-backed)
9393
("-u \"<base>ldap/search?q=x\" --ldap --flush-session --disable-hashing", ("is vulnerable to LDAP injection", "Title: LDAP in-band data exposure", "LDAP: GET parameter 'q' in-band entries", "in-band data exposure", "LDAP scan complete")), # LDAP: error-based detection (unbalanced paren) + boolean oracle + directory attribute extraction via blind substring probing
9494
("-u \"<base>xpath/search?q=x\" --xpath --flush-session --disable-hashing", ("is vulnerable to XPath injection", "Title: XPath boolean-based blind", "XPath: GET parameter 'q' XML tree", "extracted", "XPath scan complete")), # XPath: error-based detection + boolean oracle + blind XML tree-walking via starts-with character extraction
95+
("-u \"<base>ssti/search?q=x\" --ssti --flush-session --disable-hashing", ("is vulnerable to SSTI", "Title: SSTI Jinja2 injection", "back-end template engine: 'Jinja2'", "in-band arithmetic proof confirmed", "SSTI scan complete")), # SSTI: Jinja2 detection via arithmetic control-pair + boolean oracle + distinguishing probe
9596
("-u \"<url>&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")),
9697
("-d \"<direct>\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")),
9798
("-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'",)),

lib/parse/cmdline.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -415,18 +415,6 @@ def cmdLineParser(argv=None):
415415
techniques.add_argument("--technique", dest="technique",
416416
help="SQL injection techniques to use (default \"%s\")" % defaults.technique)
417417

418-
techniques.add_argument("--nosql", dest="nosql", action="store_true",
419-
help="Test for NoSQL injection (e.g. MongoDB, CouchDB, Neo4j)")
420-
421-
techniques.add_argument("--graphql", dest="graphql", action="store_true",
422-
help="Test for GraphQL injection (introspection, field/argument fuzzing, SQL/NoSQL payload families)")
423-
424-
techniques.add_argument("--ldap", dest="ldap", action="store_true",
425-
help="Test for LDAP injection (filter breakout, boolean blind, auth bypass)")
426-
427-
techniques.add_argument("--xpath", dest="xpath", action="store_true",
428-
help="Test for XPath injection (error-based, boolean-blind, blind XML tree-walking)")
429-
430418
techniques.add_argument("--time-sec", dest="timeSec", type=int,
431419
help="Seconds to delay the DBMS response (default %d)" % defaults.timeSec)
432420

@@ -454,6 +442,21 @@ def cmdLineParser(argv=None):
454442
techniques.add_argument("--second-req", dest="secondReq",
455443
help="Load second-order HTTP request from file")
456444

445+
techniques.add_argument("--graphql", dest="graphql", action="store_true",
446+
help="Test for GraphQL injection")
447+
448+
techniques.add_argument("--ldap", dest="ldap", action="store_true",
449+
help="Test for LDAP injection")
450+
451+
techniques.add_argument("--nosql", dest="nosql", action="store_true",
452+
help="Test for NoSQL injection")
453+
454+
techniques.add_argument("--xpath", dest="xpath", action="store_true",
455+
help="Test for XPath injection")
456+
457+
techniques.add_argument("--ssti", dest="ssti", action="store_true",
458+
help="Test for server-side template injection")
459+
457460
# Fingerprint options
458461
fingerprint = parser.add_argument_group("Fingerprint", "These options can be used to perform a back-end database management system version fingerprint")
459462

@@ -568,6 +571,12 @@ def cmdLineParser(argv=None):
568571
enumeration.add_argument("--sql-shell", dest="sqlShell", action="store_true",
569572
help="Prompt for an interactive SQL shell")
570573

574+
enumeration.add_argument("--ssti-query", dest="sstiQuery",
575+
help="SSTI expression to evaluate in-band on the vulnerable parameter")
576+
577+
enumeration.add_argument("--ssti-shell", dest="sstiShell", action="store_true",
578+
help="Prompt for an interactive SSTI expression shell")
579+
571580
enumeration.add_argument("--sql-file", dest="sqlFile",
572581
help="Execute SQL statements from given file(s)")
573582

lib/techniques/ssti/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
5+
See the file 'LICENSE' for copying permission
6+
"""
7+
8+
pass

0 commit comments

Comments
 (0)