Skip to content

Commit 622cfa7

Browse files
committed
Adding --openapi-tags
1 parent fc1ae69 commit 622cfa7

6 files changed

Lines changed: 37 additions & 4 deletions

File tree

‎lib/core/option.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,8 +521,13 @@ def _setOpenApiTargets():
521521
logger.info(infoMsg)
522522
content = openFile(conf.openApiFile).read()
523523

524+
tags = [_.strip() for _ in re.split(PARAMETER_SPLITTING_REGEX, conf.openApiTags) if _.strip()] if conf.openApiTags else None
525+
if tags:
526+
infoMsg = "restricting extraction to OpenAPI/Swagger operations tagged: %s" % ", ".join(tags)
527+
logger.info(infoMsg)
528+
524529
try:
525-
targets = openApiTargets(content, origin)
530+
targets = openApiTargets(content, origin, tags)
526531
except ValueError as ex:
527532
errMsg = "unable to parse the OpenAPI/Swagger specification ('%s')" % getSafeExString(ex)
528533
raise SqlmapSyntaxException(errMsg)

‎lib/core/optiondict.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"configFile": "string",
2222
"openApiFile": "string",
2323
"openApiBase": "string",
24+
"openApiTags": "string",
2425
},
2526

2627
"Request": {

‎lib/core/settings.py‎

Lines changed: 1 addition & 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.7.57"
23+
VERSION = "1.10.7.58"
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)

‎lib/parse/cmdline.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ def cmdLineParser(argv=None):
150150
target.add_argument("--openapi-base", dest="openApiBase",
151151
help="Base URL for a host-less OpenAPI/Swagger spec")
152152

153+
target.add_argument("--openapi-tags", dest="openApiTags",
154+
help="Only derive targets from operations with these tag(s)")
155+
153156
# Request options
154157
request = parser.add_argument_group("Request", "These options can be used to specify how to connect to the target URL")
155158

‎lib/parse/openapi.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,15 +206,18 @@ def _baseUrl(spec, origin=None, servers=None):
206206

207207
_METHODS = ("get", "post", "put", "delete", "patch", "options", "head")
208208

209-
def openApiTargets(content, origin=None):
209+
def openApiTargets(content, origin=None, tags=None):
210210
"""
211211
Returns a list of (url, method, data, headers) request tuples derived from an OpenAPI/Swagger
212212
specification. 'headers' is a list of (name, value) tuples (matching conf.httpHeaders). 'origin'
213213
(scheme://host[:port] of the specification's own location) is used only to resolve RELATIVE 'servers'
214214
entries - absolute server URLs are used as declared. Path parameters and header/cookie values carry
215-
the custom injection mark so they become testable injection points.
215+
the custom injection mark so they become testable injection points. 'tags' (list) restricts extraction
216+
to operations declaring at least one of those OpenAPI tags (to scope a scan of a large API).
216217
"""
217218

219+
tagSet = set(tags) if tags else None
220+
218221
spec = _loadSpec(content)
219222
if not isinstance(spec, dict) or not isinstance(spec.get("paths"), dict) or not spec.get("paths"):
220223
errMsg = "no valid 'paths' object found in the provided OpenAPI/Swagger specification"
@@ -236,6 +239,8 @@ def openApiTargets(content, origin=None):
236239
for method, operation in item.items():
237240
if str(method).lower() not in _METHODS or not isinstance(operation, dict): # str(): YAML keys can be non-string (e.g. 404, 'on'->bool)
238241
continue
242+
if tagSet is not None and not (tagSet & set(_ for _ in (operation.get("tags") or []) if isinstance(_, six.string_types))):
243+
continue # '--openapi-tags' filter: operation carries none of the requested tags
239244
try:
240245
# effective base URL with OpenAPI precedence: operation 'servers' > path-item 'servers' > root
241246
opServers = operation.get("servers") or item.get("servers")

‎tests/test_openapi.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,25 @@ def test_header_and_cookie_params_are_injection_marked(self):
134134
self.assertEqual(headers["X-Api"], "k" + MARK)
135135
self.assertEqual(headers["Cookie"], "sess=v" + MARK)
136136

137+
def test_tag_filter_restricts_operations(self):
138+
# '--openapi-tags' keeps only operations declaring at least one requested tag; untagged
139+
# operations are dropped when a filter is active
140+
spec = {"openapi": "3.0.0", "servers": [{"url": "https://api.test"}], "paths": {
141+
"/users/{id}": {"get": {"tags": ["users"], "parameters": [{"name": "id", "in": "path", "schema": {"type": "integer"}}]}},
142+
"/admin": {"post": {"tags": ["admin"], "parameters": [{"name": "q", "in": "query", "schema": {"type": "string"}}]}},
143+
"/ping": {"get": {"parameters": [{"name": "x", "in": "query", "schema": {"type": "string"}}]}}}}
144+
content = json.dumps(spec)
145+
146+
self.assertEqual(len(openApiTargets(content)), 3) # no filter -> everything
147+
self.assertEqual(len(openApiTargets(content, tags=["nope"])), 0) # no match -> nothing (incl. untagged)
148+
149+
users = openApiTargets(content, tags=["users"])
150+
self.assertEqual(len(users), 1)
151+
self.assertIn("/users/", users[0][0])
152+
153+
both = openApiTargets(content, tags=["users", "admin"]) # union of tags
154+
self.assertEqual(sorted(_[1] for _ in both), ["GET", "POST"])
155+
137156
# --- graceful degradation: a broken/poorly-defined spec must never crash the parser ---
138157

139158
def test_malformed_raises_valueerror(self):

0 commit comments

Comments
 (0)