mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2025-07-25 15:39:48 +03:00
Merge 051eb12a8d
into 6d472dc2b0
This commit is contained in:
commit
69147945f9
|
@ -184,6 +184,7 @@ from lib.core.settings import VERSION_COMPARISON_CORRECTION
|
|||
from lib.core.settings import VERSION_STRING
|
||||
from lib.core.settings import ZIP_HEADER
|
||||
from lib.core.settings import WEBSCARAB_SPLITTER
|
||||
from lib.core.swagger import parse as _parseSwagger
|
||||
from lib.core.threads import getCurrentThreadData
|
||||
from lib.utils.safe2bin import safecharencode
|
||||
from lib.utils.sqlalchemy import _sqlalchemy
|
||||
|
@ -5461,6 +5462,10 @@ def parseRequestFile(reqFile, checkParams=True):
|
|||
for target in _parseWebScarabLog(content):
|
||||
yield target
|
||||
|
||||
if conf.swaggerFile:
|
||||
for target in _parseSwagger(content, conf.swaggerTags):
|
||||
yield target
|
||||
|
||||
def getSafeExString(ex, encoding=None):
|
||||
"""
|
||||
Safe way how to get the proper exception represtation as a string
|
||||
|
|
|
@ -480,6 +480,31 @@ def _setBulkMultipleTargets():
|
|||
warnMsg = "no usable links found (with GET parameters)"
|
||||
logger.warning(warnMsg)
|
||||
|
||||
def _setSwaggerMultipleTargets():
|
||||
if not conf.swaggerFile:
|
||||
return
|
||||
|
||||
infoMsg = "parsing multiple targets from swagger '%s'" % conf.swaggerFile
|
||||
logger.info(infoMsg)
|
||||
|
||||
if not os.path.exists(conf.swaggerFile):
|
||||
errMsg = "the specified list of targets does not exist"
|
||||
raise SqlmapFilePathException(errMsg)
|
||||
|
||||
if checkFile(conf.swaggerFile, False):
|
||||
debugMsg = "swagger file '%s' checks out" % conf.swaggerFile
|
||||
logger.debug(debugMsg)
|
||||
|
||||
for target in parseRequestFile(conf.swaggerFile):
|
||||
kb.targets.add(target)
|
||||
|
||||
else:
|
||||
errMsg = "the specified list of targets is not a file "
|
||||
errMsg += "nor a directory"
|
||||
raise SqlmapFilePathException(errMsg)
|
||||
|
||||
|
||||
|
||||
def _findPageForms():
|
||||
if not conf.forms or conf.crawlDepth:
|
||||
return
|
||||
|
@ -1780,7 +1805,7 @@ def _cleanupOptions():
|
|||
if conf.tmpPath:
|
||||
conf.tmpPath = ntToPosixSlashes(normalizePath(conf.tmpPath))
|
||||
|
||||
if any((conf.googleDork, conf.logFile, conf.bulkFile, conf.forms, conf.crawlDepth, conf.stdinPipe)):
|
||||
if any((conf.googleDork, conf.logFile, conf.bulkFile, conf.forms, conf.crawlDepth, conf.stdinPipe, conf.swaggerFile)):
|
||||
conf.multipleTargets = True
|
||||
|
||||
if conf.optimize:
|
||||
|
@ -1936,6 +1961,9 @@ def _cleanupOptions():
|
|||
if conf.dummy:
|
||||
conf.batch = True
|
||||
|
||||
if conf.swaggerTags:
|
||||
conf.swaggerTags = [_.strip() for _ in re.split(PARAMETER_SPLITTING_REGEX, conf.swaggerTags)]
|
||||
|
||||
threadData = getCurrentThreadData()
|
||||
threadData.reset()
|
||||
|
||||
|
@ -2720,7 +2748,7 @@ def _basicOptionValidation():
|
|||
errMsg = "maximum number of used threads is %d avoiding potential connection issues" % MAX_NUMBER_OF_THREADS
|
||||
raise SqlmapSyntaxException(errMsg)
|
||||
|
||||
if conf.forms and not any((conf.url, conf.googleDork, conf.bulkFile)):
|
||||
if conf.forms and not any((conf.url, conf.googleDork, conf.bulkFile, conf.swaggerFile)):
|
||||
errMsg = "switch '--forms' requires usage of option '-u' ('--url'), '-g' or '-m'"
|
||||
raise SqlmapSyntaxException(errMsg)
|
||||
|
||||
|
@ -2834,7 +2862,7 @@ def _basicOptionValidation():
|
|||
errMsg = "value for option '--union-char' must be an alpha-numeric value (e.g. 1)"
|
||||
raise SqlmapSyntaxException(errMsg)
|
||||
|
||||
if conf.hashFile and any((conf.direct, conf.url, conf.logFile, conf.bulkFile, conf.googleDork, conf.configFile, conf.requestFile, conf.updateAll, conf.smokeTest, conf.wizard, conf.dependencies, conf.purge, conf.listTampers)):
|
||||
if conf.hashFile and any((conf.direct, conf.url, conf.logFile, conf.bulkFile, conf.swaggerFile, conf.googleDork, conf.configFile, conf.requestFile, conf.updateAll, conf.smokeTest, conf.wizard, conf.dependencies, conf.purge, conf.listTampers)):
|
||||
errMsg = "option '--crack' should be used as a standalone"
|
||||
raise SqlmapSyntaxException(errMsg)
|
||||
|
||||
|
@ -2905,7 +2933,7 @@ def init():
|
|||
|
||||
parseTargetDirect()
|
||||
|
||||
if any((conf.url, conf.logFile, conf.bulkFile, conf.requestFile, conf.googleDork, conf.stdinPipe)):
|
||||
if any((conf.url, conf.logFile, conf.bulkFile, conf.swaggerFile, conf.requestFile, conf.googleDork, conf.stdinPipe)):
|
||||
_setHostname()
|
||||
_setHTTPTimeout()
|
||||
_setHTTPExtraHeaders()
|
||||
|
@ -2921,6 +2949,7 @@ def init():
|
|||
_doSearch()
|
||||
_setStdinPipeTargets()
|
||||
_setBulkMultipleTargets()
|
||||
_setSwaggerMultipleTargets()
|
||||
_checkTor()
|
||||
_setCrawler()
|
||||
_findPageForms()
|
||||
|
|
|
@ -18,6 +18,8 @@ optDict = {
|
|||
"requestFile": "string",
|
||||
"sessionFile": "string",
|
||||
"googleDork": "string",
|
||||
"swaggerFile": "string",
|
||||
"swaggerTags": "string",
|
||||
"configFile": "string",
|
||||
},
|
||||
|
||||
|
|
218
lib/core/swagger.py
Normal file
218
lib/core/swagger.py
Normal file
|
@ -0,0 +1,218 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Copyright (c) 2006-2021 sqlmap developers (https://sqlmap.org/)
|
||||
See the file 'LICENSE' for copying permission
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from lib.core.data import logger
|
||||
from lib.core.exception import SqlmapSyntaxException
|
||||
from lib.core.exception import SqlmapSkipTargetException
|
||||
from typing import Dict
|
||||
|
||||
class Operation:
|
||||
|
||||
def __init__(self, name, method, props):
|
||||
self.name = name
|
||||
self.method = method
|
||||
self.props = props
|
||||
|
||||
def tags(self):
|
||||
return self.props["tags"]
|
||||
|
||||
def parameters(self):
|
||||
return self.props["parameters"]
|
||||
|
||||
def parametersForTypes(self, types):
|
||||
return list(filter(lambda p: (p["in"] in types), self.parameters()))
|
||||
|
||||
def bodyRef(self):
|
||||
# OpenAPI v3
|
||||
if "requestBody" in self.props:
|
||||
return self.props["requestBody"]["content"]["application/json"]["schema"]["$ref"]
|
||||
# swagger v2
|
||||
elif "parameters" in self.props:
|
||||
inParameters = self.parametersForTypes(["body"])
|
||||
if not isinstance(inParameters, list) or len(inParameters) < 1:
|
||||
return None
|
||||
return inParameters[0]["schema"]["$ref"]
|
||||
return None
|
||||
|
||||
# header injection is not currently supported
|
||||
def injectable(self, body):
|
||||
return len(self.parametersForTypes(["query", "path", "header"])) > 0 or body
|
||||
|
||||
def queryString(self):
|
||||
queryParameters = self.parametersForTypes(["query"])
|
||||
if len(queryParameters) < 1:
|
||||
return None
|
||||
queryString = ""
|
||||
for qp in queryParameters:
|
||||
if "example" not in qp:
|
||||
raise SqlmapSkipTargetException("missing example for parameter '%s'" %qp["name"])
|
||||
queryString += "&%s=%s" %(qp["name"], qp["example"])
|
||||
|
||||
return queryString.replace('&', '', 1)
|
||||
|
||||
def path(self, path):
|
||||
pathParameters = self.parametersForTypes(["path"])
|
||||
if len(pathParameters) < 1:
|
||||
return path
|
||||
parameterPath = path
|
||||
for p in pathParameters:
|
||||
if "example" not in p:
|
||||
raise SqlmapSkipTargetException("missing example for parameter '%s'" %p["name"])
|
||||
parameterPath = parameterPath.replace("{%s}" %p["name"], "%s*" %p["example"])
|
||||
return parameterPath
|
||||
|
||||
def headers(self):
|
||||
hdrs = []
|
||||
headerParameters = self.parametersForTypes(["header"])
|
||||
if len(headerParameters) < 1:
|
||||
return hdrs
|
||||
for hp in headerParameters:
|
||||
if "example" not in hp:
|
||||
raise SqlmapSkipTargetException("missing example for header '%s'" %hp["name"])
|
||||
hdrs.append((hp["name"], "%s*" %hp["example"]))
|
||||
return hdrs
|
||||
|
||||
def _obj(swagger, objOrRefPath):
|
||||
if isinstance(objOrRefPath, Dict):
|
||||
return objOrRefPath
|
||||
paths = objOrRefPath.replace("#/", "", 1).split('/')
|
||||
r = swagger
|
||||
for p in paths:
|
||||
r = r[p]
|
||||
return r
|
||||
|
||||
def _example(swagger, objOrRefPath):
|
||||
example = {}
|
||||
obj = _obj(swagger, objOrRefPath)
|
||||
|
||||
if "type" in obj and obj["type"] == "object" and "properties" in obj:
|
||||
properties = obj["properties"]
|
||||
for prop in properties:
|
||||
if properties[prop]["type"] == "object":
|
||||
example[prop] = {}
|
||||
for objectProp in properties[prop]["properties"]:
|
||||
example[prop][objectProp] = _example(swagger, properties[prop]["properties"][objectProp])
|
||||
elif "$ref" in properties[prop]:
|
||||
example[prop] = _example(swagger, properties[prop]["$ref"])
|
||||
elif properties[prop]["type"] == "array" and "$ref" in properties[prop]["items"]:
|
||||
example[prop] = [ _example(swagger, properties[prop]["items"]["$ref"]) ]
|
||||
elif "example" in properties[prop]:
|
||||
value = properties[prop]["example"]
|
||||
example[prop] = value
|
||||
else:
|
||||
raise SqlmapSkipTargetException("missing example for parameter '%s'" %prop)
|
||||
elif "example" in obj:
|
||||
return obj["example"]
|
||||
else:
|
||||
raise SqlmapSkipTargetException("missing example for object '%s'" %obj)
|
||||
|
||||
|
||||
return example
|
||||
|
||||
def parse(content, tags):
|
||||
"""
|
||||
Parses Swagger 2.x and OpenAPI 3.x.x JSON documents
|
||||
|
||||
Target injectable parameter values are generated from the "example" properties.
|
||||
Only property-level "example" is supported. The "examples" property is not supported.
|
||||
"""
|
||||
|
||||
try:
|
||||
swagger = json.loads(content)
|
||||
|
||||
openapiv3 = False
|
||||
swaggerv2 = False
|
||||
|
||||
# extra validations
|
||||
if "openapi" in swagger and swagger["openapi"].startswith("3."):
|
||||
openapiv3 = True
|
||||
|
||||
if "swagger" in swagger and swagger["swagger"].startswith("2."):
|
||||
swaggerv2 = True
|
||||
|
||||
if not (openapiv3 or swaggerv2):
|
||||
errMsg = "swagger must be either Swagger 2.x or OpenAPI 3.x.x!"
|
||||
raise SqlmapSyntaxException(errMsg)
|
||||
|
||||
if (openapiv3 and
|
||||
("servers" not in swagger or
|
||||
not isinstance(swagger["servers"], list) or
|
||||
len(swagger["servers"]) < 1 or
|
||||
"url" not in swagger["servers"][0])):
|
||||
errMsg = "swagger server is missing!"
|
||||
raise SqlmapSyntaxException(errMsg)
|
||||
|
||||
if swaggerv2 and "host" not in swagger:
|
||||
errMsg = "swagger server is missing!"
|
||||
raise SqlmapSyntaxException(errMsg)
|
||||
|
||||
if openapiv3:
|
||||
# only one server supported
|
||||
server = swagger["servers"][0]["url"]
|
||||
|
||||
logger.info("swagger OpenAPI version '%s', server '%s'" %(swagger["openapi"], server))
|
||||
elif swaggerv2:
|
||||
logger.info("swagger version '%s'" %swagger["swagger"])
|
||||
|
||||
basePath = ""
|
||||
if "basePath" in swagger:
|
||||
basePath = swagger["basePath"]
|
||||
|
||||
scheme = "https"
|
||||
if ("schemes" in swagger and
|
||||
isinstance(swagger["schemes"], list) and
|
||||
len(swagger["schemes"]) > 0):
|
||||
scheme = swagger["schemes"][0]
|
||||
|
||||
server = "%s://%s%s" % (scheme, swagger["host"], basePath)
|
||||
|
||||
logger.info("swagger version '%s', server '%s'" %(swagger["swagger"], server))
|
||||
|
||||
|
||||
for path in swagger["paths"]:
|
||||
for method in swagger["paths"][path]:
|
||||
op = Operation(path, method, swagger["paths"][path][method])
|
||||
method = method.upper()
|
||||
|
||||
# skip any operations without one of our tags
|
||||
if tags is not None and not any(tag in op.tags() for tag in tags):
|
||||
continue
|
||||
|
||||
try:
|
||||
body = {}
|
||||
bodyRef = op.bodyRef()
|
||||
if bodyRef:
|
||||
body = _example(swagger, bodyRef)
|
||||
|
||||
if op.injectable(body):
|
||||
url = None
|
||||
data = None
|
||||
cookie = None
|
||||
|
||||
parameterPath = op.path(path)
|
||||
headers = op.headers()
|
||||
qs = op.queryString()
|
||||
url = "%s%s" % (server, parameterPath)
|
||||
if body:
|
||||
data = json.dumps(body)
|
||||
|
||||
if qs is not None:
|
||||
url += "?" + qs
|
||||
|
||||
logger.debug("including url '%s', method '%s', data '%s', cookie '%s'" %(url, method, data, cookie))
|
||||
yield (url, method, data, cookie, tuple(headers))
|
||||
else:
|
||||
logger.info("excluding path '%s', method '%s' as there are no parameters to inject" %(path, method))
|
||||
|
||||
except SqlmapSkipTargetException as e:
|
||||
logger.warn("excluding path '%s', method '%s': %s" %(path, method, e))
|
||||
|
||||
except json.decoder.JSONDecodeError:
|
||||
errMsg = "swagger file is not valid JSON"
|
||||
raise SqlmapSyntaxException(errMsg)
|
|
@ -141,6 +141,12 @@ def cmdLineParser(argv=None):
|
|||
target.add_argument("-g", dest="googleDork",
|
||||
help="Process Google dork results as target URLs")
|
||||
|
||||
target.add_argument("--swaggerFile", dest="swaggerFile",
|
||||
help="Parse target(s) from a Swagger OpenAPI 3.x.x JSON file ")
|
||||
|
||||
target.add_argument("--swaggerTags", dest="swaggerTags",
|
||||
help="Only process swagger operations that include one of these tags")
|
||||
|
||||
target.add_argument("-c", dest="configFile",
|
||||
help="Load options from a configuration INI file")
|
||||
|
||||
|
|
|
@ -79,14 +79,14 @@ def configFileParser(configFile):
|
|||
|
||||
mandatory = False
|
||||
|
||||
for option in ("direct", "url", "logFile", "bulkFile", "googleDork", "requestFile", "wizard"):
|
||||
for option in ("direct", "url", "logFile", "bulkFile", "googleDork", "requestFile", "wizard", "swaggerFile"):
|
||||
if config.has_option("Target", option) and config.get("Target", option) or cmdLineOptions.get(option):
|
||||
mandatory = True
|
||||
break
|
||||
|
||||
if not mandatory:
|
||||
errMsg = "missing a mandatory option in the configuration file "
|
||||
errMsg += "(direct, url, logFile, bulkFile, googleDork, requestFile or wizard)"
|
||||
errMsg += "(direct, url, logFile, bulkFile, googleDork, requestFile, wizard or swaggerFile)"
|
||||
raise SqlmapMissingMandatoryOptionException(errMsg)
|
||||
|
||||
for family, optionData in optDict.items():
|
||||
|
|
|
@ -32,6 +32,12 @@ requestFile =
|
|||
# Example: +ext:php +inurl:"&id=" +intext:"powered by "
|
||||
googleDork =
|
||||
|
||||
# Parse target(s) for a Swagger OpenAPI 3.x.x JSON file
|
||||
swaggerFile =
|
||||
|
||||
# Only process swagger operations that have one of these tags (e.g. tagA,tagB)
|
||||
swaggerTags =
|
||||
|
||||
|
||||
# These options can be used to specify how to connect to the target URL.
|
||||
[Request]
|
||||
|
|
Loading…
Reference in New Issue
Block a user