sqlmap/lib/core/swagger.py

212 lines
7.4 KiB
Python
Raw Normal View History

#!/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
2021-09-17 11:44:18 +03:00
from lib.core.exception import SqlmapSkipTargetException
2021-11-03 05:03:32 +03:00
from typing import Dict
2021-09-17 08:49:06 +03:00
class Operation:
2021-09-17 11:44:18 +03:00
def __init__(self, name, method, props):
self.name = name
self.method = method
self.props = props
2021-09-17 08:49:06 +03:00
def tags(self):
2021-09-17 11:44:18 +03:00
return self.props["tags"]
2021-09-17 08:49:06 +03:00
def parameters(self):
2021-09-17 11:44:18 +03:00
return self.props["parameters"]
2021-09-17 08:49:06 +03:00
def parametersForTypes(self, types):
return list(filter(lambda p: (p["in"] in types), self.parameters()))
def bodyRef(self):
2021-09-17 11:44:18 +03:00
if "requestBody" in self.props:
return self.props["requestBody"]["content"]["application/json"]["schema"]["$ref"]
2021-09-17 08:49:06 +03:00
return None
2021-09-17 08:49:06 +03:00
# header injection is not currently supported
def injectable(self, body):
2021-11-02 08:54:38 +03:00
return len(self.parametersForTypes(["query", "path", "header"])) > 0 or body
2021-09-17 08:49:06 +03:00
def queryString(self):
queryParameters = self.parametersForTypes(["query"])
if len(queryParameters) < 1:
return None
queryString = ""
for qp in queryParameters:
2021-09-17 11:44:18 +03:00
if "example" not in qp:
raise SqlmapSkipTargetException("missing example for parameter '%s'" %qp["name"])
2021-09-17 08:49:06 +03:00
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:
2021-09-17 11:44:18 +03:00
if "example" not in p:
raise SqlmapSkipTargetException("missing example for parameter '%s'" %p["name"])
2021-09-17 08:49:06 +03:00
parameterPath = parameterPath.replace("{%s}" %p["name"], "%s*" %p["example"])
return parameterPath
2021-11-02 08:54:38 +03:00
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
2021-11-03 05:03:32 +03:00
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
2021-11-03 05:03:32 +03:00
def _example(swagger, objOrRefPath):
2021-09-17 08:49:06 +03:00
example = {}
2021-11-03 05:03:32 +03:00
obj = _obj(swagger, objOrRefPath)
if "type" in obj and obj["type"] == "object" and "properties" in obj:
properties = obj["properties"]
for prop in properties:
2021-11-03 05:03:32 +03:00
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]:
2021-09-17 08:49:06 +03:00
example[prop] = _example(swagger, properties[prop]["$ref"])
elif properties[prop]["type"] == "array" and "$ref" in properties[prop]["items"]:
2021-09-17 08:49:06 +03:00
example[prop] = [ _example(swagger, properties[prop]["items"]["$ref"]) ]
2021-11-03 05:03:32 +03:00
elif "example" in properties[prop]:
value = properties[prop]["example"]
example[prop] = value
2021-09-17 11:44:18 +03:00
else:
raise SqlmapSkipTargetException("missing example for parameter '%s'" %prop)
2021-11-03 05:03:32 +03:00
elif "example" in obj:
return obj["example"]
else:
raise SqlmapSkipTargetException("missing example for object '%s'" %obj)
2021-09-17 11:44:18 +03:00
2021-09-17 08:49:06 +03:00
return example
def parse(content, tags):
"""
2021-11-12 05:44:35 +03:00
Parses Swagger 2.x and OpenAPI 3.x.x JSON documents
2021-09-17 11:48:56 +03:00
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)
2021-11-12 05:44:35 +03:00
openapiv3 = False
swaggerv2 = False
# extra validations
2021-11-12 05:44:35 +03:00
if "openapi" in swagger and swagger["openapi"].startswith("3."):
openapiv3 = True
if "swagger" in swagger and swagger["swagger"].startswith("2."):
swaggerv2 = True
2021-11-12 05:44:35 +03:00
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
2021-11-12 05:44:35 +03:00
"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)
2021-11-12 05:44:35 +03:00
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"]:
2021-09-17 11:44:18 +03:00
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
2021-09-17 08:49:06 +03:00
if tags is not None and not any(tag in op.tags() for tag in tags):
continue
2021-09-17 11:44:18 +03:00
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)
2021-11-02 08:54:38 +03:00
headers = op.headers()
2021-09-17 11:44:18 +03:00
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))
2021-11-02 08:54:38 +03:00
yield (url, method, data, cookie, tuple(headers))
2021-09-17 11:44:18 +03:00
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)