This commit is contained in:
Miroslav Stampar
2017-07-03 16:55:24 +02:00
parent aef5d6667f
commit 1678b606a2
10 changed files with 230 additions and 345 deletions

View File

@@ -117,7 +117,6 @@ def checkSqlInjection(place, parameter, value):
while tests:
test = tests.pop(0)
threadData.requestCollector.reset()
try:
if kb.endDetection:
@@ -701,7 +700,6 @@ def checkSqlInjection(place, parameter, value):
injection.data[stype].matchRatio = kb.matchRatio
injection.data[stype].trueCode = trueCode
injection.data[stype].falseCode = falseCode
injection.data[stype].collectedRequests = threadData.requestCollector.obtain()
injection.conf.textOnly = conf.textOnly
injection.conf.titles = conf.titles

View File

@@ -2601,17 +2601,15 @@ def logHTTPTraffic(requestLogMsg, responseLogMsg):
"""
Logs HTTP traffic to the output file
"""
threadData = getCurrentThreadData()
assert threadData.requestCollector is not None, "Request collector should be initialized by now"
threadData.requestCollector.collectRequest(requestLogMsg, responseLogMsg)
if conf.harFile:
conf.httpCollector.collectRequest(requestLogMsg, responseLogMsg)
if not conf.trafficFile:
return
with kb.locks.log:
dataToTrafficFile("%s%s" % (requestLogMsg, os.linesep))
dataToTrafficFile("%s%s" % (responseLogMsg, os.linesep))
dataToTrafficFile("%s%s%s%s" % (os.linesep, 76 * '#', os.linesep, os.linesep))
with kb.locks.log:
dataToTrafficFile("%s%s" % (requestLogMsg, os.linesep))
dataToTrafficFile("%s%s" % (responseLogMsg, os.linesep))
dataToTrafficFile("%s%s%s%s" % (os.linesep, 76 * '#', os.linesep, os.linesep))
def getPageTemplate(payload, place): # Cross-linked function
raise NotImplementedError

View File

@@ -149,7 +149,7 @@ from lib.request.pkihandler import HTTPSPKIAuthHandler
from lib.request.rangehandler import HTTPRangeHandler
from lib.request.redirecthandler import SmartRedirectHandler
from lib.request.templates import getPageTemplate
from lib.utils.collect import RequestCollectorFactory
from lib.utils.har import HTTPCollectorFactory
from lib.utils.crawler import crawl
from lib.utils.deps import checkDependencies
from lib.utils.search import search
@@ -1830,6 +1830,7 @@ def _setConfAttributes():
conf.dumpPath = None
conf.hashDB = None
conf.hashDBFile = None
conf.httpCollector = None
conf.httpHeaders = []
conf.hostname = None
conf.ipv6 = False
@@ -1845,7 +1846,7 @@ def _setConfAttributes():
conf.scheme = None
conf.tests = []
conf.trafficFP = None
conf.requestCollectorFactory = None
conf.HARCollectorFactory = None
conf.wFileType = None
def _setKnowledgeBaseAttributes(flushAll=True):
@@ -2230,10 +2231,11 @@ def _setTrafficOutputFP():
conf.trafficFP = openFile(conf.trafficFile, "w+")
def _setupRequestCollector():
conf.requestCollectorFactory = RequestCollectorFactory(collect=conf.collectRequests)
threadData = getCurrentThreadData()
threadData.requestCollector = conf.requestCollectorFactory.create()
def _setupHTTPCollector():
if not conf.harFile:
return
conf.httpCollector = HTTPCollectorFactory(conf.harFile).create()
def _setDNSServer():
if not conf.dnsDomain:
@@ -2611,7 +2613,7 @@ def init():
_setTamperingFunctions()
_setWafFunctions()
_setTrafficOutputFP()
_setupRequestCollector()
_setupHTTPCollector()
_resolveCrossReferences()
_checkWebSocket()

View File

@@ -197,7 +197,6 @@ optDict = {
"binaryFields": "string",
"charset": "string",
"checkInternet": "boolean",
"collectRequests": "string",
"crawlDepth": "integer",
"crawlExclude": "string",
"csvDel": "string",
@@ -206,6 +205,7 @@ optDict = {
"flushSession": "boolean",
"forms": "boolean",
"freshQueries": "boolean",
"harFile": "string",
"hexConvert": "boolean",
"outputDir": "string",
"parseErrors": "boolean",

View File

@@ -19,7 +19,7 @@ from lib.core.enums import DBMS_DIRECTORY_NAME
from lib.core.enums import OS
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.1.7.2"
VERSION = "1.1.7.3"
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)

View File

@@ -618,9 +618,6 @@ def cmdLineParser(argv=None):
general = OptionGroup(parser, "General", "These options can be used "
"to set some general working parameters")
#general.add_option("-x", dest="xmlFile",
# help="Dump the data into an XML file")
general.add_option("-s", dest="sessionFile",
help="Load session from a stored (.sqlite) file")
@@ -632,10 +629,6 @@ def cmdLineParser(argv=None):
action="store_true",
help="Never ask for user input, use the default behaviour")
general.add_option("--collect-requests", dest="collectRequests",
action="store_true",
help="Collect requests in HAR format")
general.add_option("--binary-fields", dest="binaryFields",
help="Result fields having binary values (e.g. \"digest\")")
@@ -661,8 +654,7 @@ def cmdLineParser(argv=None):
general.add_option("--eta", dest="eta",
action="store_true",
help="Display for each output the "
"estimated time of arrival")
help="Display for each output the estimated time of arrival")
general.add_option("--flush-session", dest="flushSession",
action="store_true",
@@ -676,6 +668,9 @@ def cmdLineParser(argv=None):
action="store_true",
help="Ignore query results stored in session file")
general.add_option("--har", dest="harFile",
help="Log all HTTP traffic into a HAR file")
general.add_option("--hex", dest="hexConvert",
action="store_true",
help="Use DBMS hex function(s) for data retrieval")

View File

@@ -1,309 +0,0 @@
#!/usr/bin/env python
"""
Copyright (c) 2006-2017 sqlmap developers (http://sqlmap.org/)
See the file 'doc/COPYING' for copying permission
"""
from BaseHTTPServer import BaseHTTPRequestHandler
from httplib import HTTPResponse
from StringIO import StringIO
import base64
import re
from lib.core.data import logger
from lib.core.settings import VERSION
class RequestCollectorFactory:
def __init__(self, collect=False):
self.collect = collect
def create(self):
collector = RequestCollector()
if not self.collect:
collector.collectRequest = self._noop
else:
logger.info("Request collection is enabled.")
return collector
@staticmethod
def _noop(*args, **kwargs):
pass
class RequestCollector:
def __init__(self):
self.reset()
def collectRequest(self, requestMessage, responseMessage):
self.messages.append(RawPair(requestMessage, responseMessage))
def reset(self):
self.messages = []
def obtain(self):
if self.messages:
return {"log": {
"version": "1.2",
"creator": {"name": "SQLMap", "version": VERSION},
"entries": [pair.toEntry().toDict() for pair in self.messages],
}}
class RawPair:
def __init__(self, request, response):
self.request = request
self.response = response
def toEntry(self):
return Entry(request=Request.parse(self.request),
response=Response.parse(self.response))
class Entry:
def __init__(self, request, response):
self.request = request
self.response = response
def toDict(self):
return {
"request": self.request.toDict(),
"response": self.response.toDict(),
}
class Request:
def __init__(self, method, path, httpVersion, headers, postBody=None, raw=None, comment=None):
self.method = method
self.path = path
self.httpVersion = httpVersion
self.headers = headers or {}
self.postBody = postBody
self.comment = comment
self.raw = raw
@classmethod
def parse(cls, raw):
request = HTTPRequest(raw)
return cls(method=request.command,
path=request.path,
httpVersion=request.request_version,
headers=request.headers,
postBody=request.rfile.read(),
comment=request.comment,
raw=raw)
@property
def url(self):
host = self.headers.get('Host', 'unknown')
return "http://%s%s" % (host, self.path)
def toDict(self):
out = {
"httpVersion": self.httpVersion,
"method": self.method,
"url": self.url,
"headers": [dict(name=key, value=value) for key, value in self.headers.items()],
"comment": self.comment,
}
if self.postBody:
contentType = self.headers.get('Content-Type')
out["postData"] = {
"mimeType": contentType,
"text": self.postBody,
}
return out
class Response:
extract_status = re.compile(r'\((\d{3}) (.*)\)')
def __init__(self, httpVersion, status, statusText, headers, content, raw=None, comment=None):
self.raw = raw
self.httpVersion = httpVersion
self.status = status
self.statusText = statusText
self.headers = headers
self.content = content
self.comment = comment
@classmethod
def parse(cls, raw):
altered = raw
comment = None
if altered.startswith("HTTP response ["):
io = StringIO(raw)
first_line = io.readline()
parts = cls.extract_status.search(first_line)
status_line = "HTTP/1.0 %s %s" % (parts.group(1), parts.group(2))
remain = io.read()
altered = status_line + "\n" + remain
comment = first_line
response = HTTPResponse(FakeSocket(altered))
response.begin()
return cls(httpVersion="HTTP/1.1" if response.version == 11 else "HTTP/1.0",
status=response.status,
statusText=response.reason,
headers=response.msg,
content=response.read(-1),
comment=comment,
raw=raw)
def toDict(self):
content = {
"mimeType": self.headers.get('Content-Type'),
"text": self.content,
}
binary = set(['\0', '\1'])
if any(c in binary for c in self.content):
content["encoding"] = "base64"
content["text"] = base64.b64encode(self.content)
return {
"httpVersion": self.httpVersion,
"status": self.status,
"statusText": self.statusText,
"headers": [dict(name=key, value=value) for key, value in self.headers.items()],
"content": content,
"comment": self.comment,
}
class FakeSocket:
# Original source:
# https://stackoverflow.com/questions/24728088/python-parse-http-response-string
def __init__(self, response_text):
self._file = StringIO(response_text)
def makefile(self, *args, **kwargs):
return self._file
class HTTPRequest(BaseHTTPRequestHandler):
# Original source:
# https://stackoverflow.com/questions/4685217/parse-raw-http-headers
def __init__(self, request_text):
self.comment = None
self.rfile = StringIO(request_text)
self.raw_requestline = self.rfile.readline()
if self.raw_requestline.startswith("HTTP request ["):
self.comment = self.raw_requestline
self.raw_requestline = self.rfile.readline()
self.error_code = self.error_message = None
self.parse_request()
def send_error(self, code, message):
self.error_code = code
self.error_message = message
if __name__ == '__main__':
import unittest
class RequestParseTest(unittest.TestCase):
def test_basic_request(self):
req = Request.parse("GET /test HTTP/1.0\r\n"
"Host: test\r\n"
"Connection: close")
self.assertEqual("GET", req.method)
self.assertEqual("/test", req.path)
self.assertEqual("close", req.headers['Connection'])
self.assertEqual("test", req.headers['Host'])
self.assertEqual("HTTP/1.0", req.httpVersion)
def test_with_request_as_logged_by_sqlmap(self):
raw = "HTTP request [#75]:\nPOST /create.php HTTP/1.1\nHost: 127.0.0.1\nAccept-encoding: gzip,deflate\nCache-control: no-cache\nContent-type: application/x-www-form-urlencoded; charset=utf-8\nAccept: */*\nUser-agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.215 Safari/534.10\nCookie: PHPSESSID=65c4a9cfbbe91f2d975d50ce5e8d1026\nContent-length: 138\nConnection: close\n\nname=test%27%29%3BSELECT%20LIKE%28%27ABCDEFG%27%2CUPPER%28HEX%28RANDOMBLOB%280.0.10000%2F2%29%29%29%29--&csrfmiddlewaretoken=594d26cfa3fad\n" # noqa
req = Request.parse(raw)
self.assertEqual("POST", req.method)
self.assertEqual("138", req.headers["Content-Length"])
self.assertIn("csrfmiddlewaretoken", req.postBody)
self.assertEqual("HTTP request [#75]:\n", req.comment)
class RequestRenderTest(unittest.TestCase):
def test_render_get_request(self):
req = Request(method="GET",
path="/test.php",
headers={"Host": "example.com", "Content-Length": "0"},
httpVersion="HTTP/1.1",
comment="Hello World")
out = req.toDict()
self.assertEqual("GET", out["method"])
self.assertEqual("http://example.com/test.php", out["url"])
self.assertIn({"name": "Host", "value": "example.com"}, out["headers"])
self.assertEqual("Hello World", out["comment"])
self.assertEqual("HTTP/1.1", out["httpVersion"])
def test_render_with_post_body(self):
req = Request(method="POST",
path="/test.php",
headers={"Host": "example.com",
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8"},
httpVersion="HTTP/1.1",
postBody="name=test&csrfmiddlewaretoken=594d26cfa3fad\n")
out = req.toDict()
self.assertEqual(out["postData"], {
"mimeType": "application/x-www-form-urlencoded; charset=utf-8",
"text": "name=test&csrfmiddlewaretoken=594d26cfa3fad\n",
})
class ResponseParseTest(unittest.TestCase):
def test_parse_standard_http_response(self):
raw = "HTTP/1.1 404 Not Found\nContent-length: 518\nX-powered-by: PHP/5.6.30\nContent-encoding: gzip\nExpires: Thu, 19 Nov 1981 08:52:00 GMT\nVary: Accept-Encoding\nUri: http://127.0.0.1/\nServer: Apache/2.4.10 (Debian)\nConnection: close\nPragma: no-cache\nCache-control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\nDate: Fri, 23 Jun 2017 16:18:17 GMT\nContent-type: text/html; charset=UTF-8\n\n<!doctype html>\n<html>Test</html>\n" # noqa
resp = Response.parse(raw)
self.assertEqual(resp.status, 404)
self.assertEqual(resp.statusText, "Not Found")
def test_parse_response_as_logged_by_sqlmap(self):
raw = "HTTP response [#74] (200 OK):\nContent-length: 518\nX-powered-by: PHP/5.6.30\nContent-encoding: gzip\nExpires: Thu, 19 Nov 1981 08:52:00 GMT\nVary: Accept-Encoding\nUri: http://127.0.0.1/\nServer: Apache/2.4.10 (Debian)\nConnection: close\nPragma: no-cache\nCache-control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\nDate: Fri, 23 Jun 2017 16:18:17 GMT\nContent-type: text/html; charset=UTF-8\n\n<!doctype html>\n<html>Test</html>\n" # noqa
resp = Response.parse(raw)
self.assertEqual(resp.status, 200)
self.assertEqual(resp.statusText, "OK")
self.assertEqual(resp.headers["Content-Length"], "518")
self.assertIn("Test", resp.content)
self.assertEqual("HTTP response [#74] (200 OK):\n", resp.comment)
class ResponseRenderTest(unittest.TestCase):
def test_simple_page_encoding(self):
resp = Response(status=200, statusText="OK",
httpVersion="HTTP/1.1",
headers={"Content-Type": "text/html"},
content="<html>\n<body>Hello</body>\n</html>")
out = resp.toDict()
self.assertEqual(200, out["status"])
self.assertEqual("OK", out["statusText"])
self.assertIn({"name": "Content-Type", "value": "text/html"}, out["headers"])
self.assertEqual(out["content"], {
"mimeType": "text/html",
"text": "<html>\n<body>Hello</body>\n</html>",
})
def test_simple_body_contains_binary_data(self):
resp = Response(status=200, statusText="OK",
httpVersion="HTTP/1.1",
headers={"Content-Type": "application/octet-stream"},
content="test\0abc")
out = resp.toDict()
self.assertEqual(out["content"], {
"encoding": "base64",
"mimeType": "application/octet-stream",
"text": "dGVzdABhYmM=",
})
unittest.main(buffer=False)

194
lib/utils/har.py Normal file
View File

@@ -0,0 +1,194 @@
#!/usr/bin/env python
"""
Copyright (c) 2006-2017 sqlmap developers (http://sqlmap.org/)
See the file 'doc/COPYING' for copying permission
"""
import base64
import BaseHTTPServer
import httplib
import re
import StringIO
from lib.core.data import logger
from lib.core.settings import VERSION
class HTTPCollectorFactory:
def __init__(self, harFile=False):
self.harFile = harFile
def create(self):
collector = HTTPCollector()
return collector
class HTTPCollector:
def __init__(self):
self.messages = []
def collectRequest(self, requestMessage, responseMessage):
self.messages.append(RawPair(requestMessage, responseMessage))
def obtain(self):
return {"log": {
"version": "1.2",
"creator": {"name": "sqlmap", "version": VERSION},
"entries": [pair.toEntry().toDict() for pair in self.messages],
}}
class RawPair:
def __init__(self, request, response):
self.request = request
self.response = response
def toEntry(self):
return Entry(request=Request.parse(self.request),
response=Response.parse(self.response))
class Entry:
def __init__(self, request, response):
self.request = request
self.response = response
def toDict(self):
return {
"request": self.request.toDict(),
"response": self.response.toDict(),
}
class Request:
def __init__(self, method, path, httpVersion, headers, postBody=None, raw=None, comment=None):
self.method = method
self.path = path
self.httpVersion = httpVersion
self.headers = headers or {}
self.postBody = postBody
self.comment = comment
self.raw = raw
@classmethod
def parse(cls, raw):
request = HTTPRequest(raw)
return cls(method=request.command,
path=request.path,
httpVersion=request.request_version,
headers=request.headers,
postBody=request.rfile.read(),
comment=request.comment,
raw=raw)
@property
def url(self):
host = self.headers.get("Host", "unknown")
return "http://%s%s" % (host, self.path)
def toDict(self):
out = {
"httpVersion": self.httpVersion,
"method": self.method,
"url": self.url,
"headers": [dict(name=key.capitalize(), value=value) for key, value in self.headers.items()],
"comment": self.comment,
}
if self.postBody:
contentType = self.headers.get("Content-Type")
out["postData"] = {
"mimeType": contentType,
"text": self.postBody.rstrip("\r\n"),
}
return out
class Response:
extract_status = re.compile(r'\((\d{3}) (.*)\)')
def __init__(self, httpVersion, status, statusText, headers, content, raw=None, comment=None):
self.raw = raw
self.httpVersion = httpVersion
self.status = status
self.statusText = statusText
self.headers = headers
self.content = content
self.comment = comment
@classmethod
def parse(cls, raw):
altered = raw
comment = None
if altered.startswith("HTTP response ["):
io = StringIO.StringIO(raw)
first_line = io.readline()
parts = cls.extract_status.search(first_line)
status_line = "HTTP/1.0 %s %s" % (parts.group(1), parts.group(2))
remain = io.read()
altered = status_line + "\n" + remain
comment = first_line
response = httplib.HTTPResponse(FakeSocket(altered))
response.begin()
try:
content = response.read(-1)
except httplib.IncompleteRead:
content = raw[raw.find("\n\n") + 2:].rstrip("\r\n")
return cls(httpVersion="HTTP/1.1" if response.version == 11 else "HTTP/1.0",
status=response.status,
statusText=response.reason,
headers=response.msg,
content=content,
comment=comment,
raw=raw)
def toDict(self):
content = {
"mimeType": self.headers.get("Content-Type"),
"text": self.content,
}
binary = set(['\0', '\1'])
if any(c in binary for c in self.content):
content["encoding"] = "base64"
content["text"] = base64.b64encode(self.content)
return {
"httpVersion": self.httpVersion,
"status": self.status,
"statusText": self.statusText,
"headers": [dict(name=key.capitalize(), value=value) for key, value in self.headers.items() if key.lower() != "uri"],
"content": content,
"comment": self.comment,
}
class FakeSocket:
# Original source:
# https://stackoverflow.com/questions/24728088/python-parse-http-response-string
def __init__(self, response_text):
self._file = StringIO.StringIO(response_text)
def makefile(self, *args, **kwargs):
return self._file
class HTTPRequest(BaseHTTPServer.BaseHTTPRequestHandler):
# Original source:
# https://stackoverflow.com/questions/4685217/parse-raw-http-headers
def __init__(self, request_text):
self.comment = None
self.rfile = StringIO.StringIO(request_text)
self.raw_requestline = self.rfile.readline()
if self.raw_requestline.startswith("HTTP request ["):
self.comment = self.raw_requestline
self.raw_requestline = self.rfile.readline()
self.error_code = self.error_message = None
self.parse_request()
def send_error(self, code, message):
self.error_code = code
self.error_message = message