mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2024-11-25 02:53:46 +03:00
commit
aef5d6667f
|
@ -117,6 +117,7 @@ def checkSqlInjection(place, parameter, value):
|
||||||
|
|
||||||
while tests:
|
while tests:
|
||||||
test = tests.pop(0)
|
test = tests.pop(0)
|
||||||
|
threadData.requestCollector.reset()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if kb.endDetection:
|
if kb.endDetection:
|
||||||
|
@ -700,6 +701,7 @@ def checkSqlInjection(place, parameter, value):
|
||||||
injection.data[stype].matchRatio = kb.matchRatio
|
injection.data[stype].matchRatio = kb.matchRatio
|
||||||
injection.data[stype].trueCode = trueCode
|
injection.data[stype].trueCode = trueCode
|
||||||
injection.data[stype].falseCode = falseCode
|
injection.data[stype].falseCode = falseCode
|
||||||
|
injection.data[stype].collectedRequests = threadData.requestCollector.obtain()
|
||||||
|
|
||||||
injection.conf.textOnly = conf.textOnly
|
injection.conf.textOnly = conf.textOnly
|
||||||
injection.conf.titles = conf.titles
|
injection.conf.titles = conf.titles
|
||||||
|
|
|
@ -2601,6 +2601,9 @@ def logHTTPTraffic(requestLogMsg, responseLogMsg):
|
||||||
"""
|
"""
|
||||||
Logs HTTP traffic to the output file
|
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 not conf.trafficFile:
|
if not conf.trafficFile:
|
||||||
return
|
return
|
||||||
|
|
|
@ -149,6 +149,7 @@ from lib.request.pkihandler import HTTPSPKIAuthHandler
|
||||||
from lib.request.rangehandler import HTTPRangeHandler
|
from lib.request.rangehandler import HTTPRangeHandler
|
||||||
from lib.request.redirecthandler import SmartRedirectHandler
|
from lib.request.redirecthandler import SmartRedirectHandler
|
||||||
from lib.request.templates import getPageTemplate
|
from lib.request.templates import getPageTemplate
|
||||||
|
from lib.utils.collect import RequestCollectorFactory
|
||||||
from lib.utils.crawler import crawl
|
from lib.utils.crawler import crawl
|
||||||
from lib.utils.deps import checkDependencies
|
from lib.utils.deps import checkDependencies
|
||||||
from lib.utils.search import search
|
from lib.utils.search import search
|
||||||
|
@ -1844,6 +1845,7 @@ def _setConfAttributes():
|
||||||
conf.scheme = None
|
conf.scheme = None
|
||||||
conf.tests = []
|
conf.tests = []
|
||||||
conf.trafficFP = None
|
conf.trafficFP = None
|
||||||
|
conf.requestCollectorFactory = None
|
||||||
conf.wFileType = None
|
conf.wFileType = None
|
||||||
|
|
||||||
def _setKnowledgeBaseAttributes(flushAll=True):
|
def _setKnowledgeBaseAttributes(flushAll=True):
|
||||||
|
@ -2228,6 +2230,11 @@ def _setTrafficOutputFP():
|
||||||
|
|
||||||
conf.trafficFP = openFile(conf.trafficFile, "w+")
|
conf.trafficFP = openFile(conf.trafficFile, "w+")
|
||||||
|
|
||||||
|
def _setupRequestCollector():
|
||||||
|
conf.requestCollectorFactory = RequestCollectorFactory(collect=conf.collectRequests)
|
||||||
|
threadData = getCurrentThreadData()
|
||||||
|
threadData.requestCollector = conf.requestCollectorFactory.create()
|
||||||
|
|
||||||
def _setDNSServer():
|
def _setDNSServer():
|
||||||
if not conf.dnsDomain:
|
if not conf.dnsDomain:
|
||||||
return
|
return
|
||||||
|
@ -2604,6 +2611,7 @@ def init():
|
||||||
_setTamperingFunctions()
|
_setTamperingFunctions()
|
||||||
_setWafFunctions()
|
_setWafFunctions()
|
||||||
_setTrafficOutputFP()
|
_setTrafficOutputFP()
|
||||||
|
_setupRequestCollector()
|
||||||
_resolveCrossReferences()
|
_resolveCrossReferences()
|
||||||
_checkWebSocket()
|
_checkWebSocket()
|
||||||
|
|
||||||
|
|
|
@ -197,6 +197,7 @@ optDict = {
|
||||||
"binaryFields": "string",
|
"binaryFields": "string",
|
||||||
"charset": "string",
|
"charset": "string",
|
||||||
"checkInternet": "boolean",
|
"checkInternet": "boolean",
|
||||||
|
"collectRequests": "string",
|
||||||
"crawlDepth": "integer",
|
"crawlDepth": "integer",
|
||||||
"crawlExclude": "string",
|
"crawlExclude": "string",
|
||||||
"csvDel": "string",
|
"csvDel": "string",
|
||||||
|
|
|
@ -38,6 +38,8 @@ class _ThreadData(threading.local):
|
||||||
Resets thread data model
|
Resets thread data model
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.requestCollector = None
|
||||||
|
|
||||||
self.disableStdOut = False
|
self.disableStdOut = False
|
||||||
self.hashDBCursor = None
|
self.hashDBCursor = None
|
||||||
self.inTransaction = False
|
self.inTransaction = False
|
||||||
|
|
|
@ -632,6 +632,10 @@ def cmdLineParser(argv=None):
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Never ask for user input, use the default behaviour")
|
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",
|
general.add_option("--binary-fields", dest="binaryFields",
|
||||||
help="Result fields having binary values (e.g. \"digest\")")
|
help="Result fields having binary values (e.g. \"digest\")")
|
||||||
|
|
||||||
|
|
309
lib/utils/collect.py
Normal file
309
lib/utils/collect.py
Normal file
|
@ -0,0 +1,309 @@
|
||||||
|
#!/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)
|
Loading…
Reference in New Issue
Block a user