Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
747 changes: 0 additions & 747 deletions data/txt/sha256sums.txt

This file was deleted.

2 changes: 0 additions & 2 deletions doc/THIRD-PARTY.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

* The `Chardet` library located under `thirdparty/chardet/`.
Copyright (C) 2008, Mark Pilgrim.
* The `MultipartPost` library located under `thirdparty/multipart/`.
Copyright (C) 2006, Will Holcomb.
* The `icmpsh` tool located under `extra/icmpsh/`.
Copyright (C) 2010, Nico Leidecker, Bernardo Damele.

Expand Down
5 changes: 0 additions & 5 deletions extra/shutils/precommit-hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ chmod +x .git/hooks/pre-commit

PROJECT="../../"
SETTINGS="../../lib/core/settings.py"
DIGEST="../../data/txt/sha256sums.txt"

declare -x SCRIPTPATH="${0}"

PROJECT_FULLPATH=${SCRIPTPATH%/*}/$PROJECT
SETTINGS_FULLPATH=${SCRIPTPATH%/*}/$SETTINGS
DIGEST_FULLPATH=${SCRIPTPATH%/*}/$DIGEST

git diff $SETTINGS_FULLPATH | grep "VERSION =" > /dev/null && exit 0

Expand All @@ -37,6 +35,3 @@ then
fi
git add "$SETTINGS_FULLPATH"
fi

cd $PROJECT_FULLPATH && git ls-files | sort | uniq | grep -Pv '^\.|sha256' | xargs sha256sum > $DIGEST_FULLPATH && cd -
git add "$DIGEST_FULLPATH"
44 changes: 24 additions & 20 deletions lib/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1586,7 +1586,6 @@ def setPaths(rootPath):
paths.COMMON_COLUMNS = os.path.join(paths.SQLMAP_TXT_PATH, "common-columns.txt")
paths.COMMON_FILES = os.path.join(paths.SQLMAP_TXT_PATH, "common-files.txt")
paths.COMMON_TABLES = os.path.join(paths.SQLMAP_TXT_PATH, "common-tables.txt")
paths.DIGEST_FILE = os.path.join(paths.SQLMAP_TXT_PATH, "sha256sums.txt")
paths.SQL_KEYWORDS = os.path.join(paths.SQLMAP_TXT_PATH, "keywords.txt")
paths.SMALL_DICT = os.path.join(paths.SQLMAP_TXT_PATH, "smalldict.txt")
paths.USER_AGENTS = os.path.join(paths.SQLMAP_TXT_PATH, "user-agents.txt")
Expand Down Expand Up @@ -5840,30 +5839,35 @@ def chunkSplitPostData(data):

return "".join(retVal)

def checkSums():
def isGitRepository():
"""
Validate the content of the digest file (i.e. sha256sums.txt)
>>> checkSums()
Whether the running source tree is a git working copy (i.e. a clone / dev checkout, as opposed to a
pip/tarball install)
"""

return os.path.isdir(os.path.join(paths.SQLMAP_ROOT_PATH, ".git"))

def codeIsModified():
"""
Best-effort check whether a git working copy has local modifications, used to avoid auto-reporting
crashes that stem from a user's OWN changes. Only meaningful for git checkouts (dev/clone); a
pip/tarball install is taken as shipped (returns False). A transient git error also yields False,
so a missing git binary never silences a legitimate report.

>>> codeIsModified() in (True, False)
True
"""

retVal = True
retVal = False

if paths.get("DIGEST_FILE"):
for entry in getFileItems(paths.DIGEST_FILE):
match = re.search(r"([0-9a-f]+)\s+([^\s]+)", entry)
if match:
expected, filename = match.groups()
filepath = os.path.join(paths.SQLMAP_ROOT_PATH, filename).replace('/', os.path.sep)
if not checkFile(filepath, False):
continue
with open(filepath, "rb") as f:
content = f.read()
if b'\0' not in content:
content = content.replace(b"\r\n", b"\n")
if not hashlib.sha256(content).hexdigest() == expected:
retVal &= False
break
if isGitRepository():
try:
process = subprocess.Popen("git diff-index --quiet HEAD --", shell=True, cwd=paths.SQLMAP_ROOT_PATH, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
process.communicate()
if process.returncode == 1: # 0 == clean, 1 == modified (anything else == git error)
retVal = True
except Exception:
pass

return retVal

Expand Down
4 changes: 2 additions & 2 deletions lib/core/option.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@
from lib.utils.purge import purge
from lib.utils.search import search
from thirdparty import six
from thirdparty.multipart import multipartpost
from lib.request.multiparthandler import MultipartPostHandler
from thirdparty.six.moves import collections_abc as _collections
from thirdparty.six.moves import http_client as _http_client
from thirdparty.six.moves import http_cookiejar as _http_cookiejar
Expand All @@ -173,7 +173,7 @@
proxyHandler = _urllib.request.ProxyHandler()
redirectHandler = SmartRedirectHandler()
rangeHandler = HTTPRangeHandler()
multipartPostHandler = multipartpost.MultipartPostHandler()
multipartPostHandler = MultipartPostHandler()

# Reference: https://mail.python.org/pipermail/python-list/2009-November/558615.html
try:
Expand Down
2 changes: 1 addition & 1 deletion lib/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from thirdparty import six

# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.10.7.31"
VERSION = "1.10.7.34"
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)
Expand Down
46 changes: 24 additions & 22 deletions lib/request/connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,31 +524,33 @@ def getPage(**kwargs):

if webSocket:
ws = websocket.WebSocket()
ws.settimeout(WEBSOCKET_INITIAL_TIMEOUT if kb.webSocketRecvCount is None else timeout)
wsHeaders = tuple("%s: %s" % (getUnicode(key), getUnicode(value)) for key, value in headers.items() if getUnicode(key).upper() != HTTP_HEADER.HOST.upper())
ws.connect(url, header=wsHeaders, cookie=cookie) # WebSocket will add Host field of headers automatically
ws.send(urldecode(post or ""))

_page = []

if kb.webSocketRecvCount is None:
while True:
try:
_page.append(ws.recv())
if sum(len(_) for _ in _page) > MAX_CONNECTION_TOTAL_SIZE:
warnMsg = "too large websocket response detected. Automatically trimming it"
singleTimeWarnMessage(warnMsg)
try:
ws.settimeout(WEBSOCKET_INITIAL_TIMEOUT if kb.webSocketRecvCount is None else timeout)
wsHeaders = tuple("%s: %s" % (getUnicode(key), getUnicode(value)) for key, value in headers.items() if getUnicode(key).upper() != HTTP_HEADER.HOST.upper())
ws.connect(url, header=wsHeaders, cookie=cookie) # WebSocket will add Host field of headers automatically
ws.send(urldecode(post or ""))

_page = []

if kb.webSocketRecvCount is None:
while True:
try:
_page.append(ws.recv())
if sum(len(_) for _ in _page) > MAX_CONNECTION_TOTAL_SIZE:
warnMsg = "too large websocket response detected. Automatically trimming it"
singleTimeWarnMessage(warnMsg)
break
except websocket.WebSocketTimeoutException:
kb.webSocketRecvCount = len(_page)
break
except websocket.WebSocketTimeoutException:
kb.webSocketRecvCount = len(_page)
break
else:
for i in xrange(max(1, kb.webSocketRecvCount)):
_page.append(ws.recv())
else:
for i in xrange(max(1, kb.webSocketRecvCount)):
_page.append(ws.recv())

page = "\n".join(_page)
page = "\n".join(_page)
finally:
ws.close()

ws.close()
code = ws.status
status = _http_client.responses.get(code, "")

Expand Down
16 changes: 11 additions & 5 deletions lib/request/inject.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,10 @@ def _threadedInferenceValues(exprBuilder, indices, context=None, charsetType=Non

indices = list(indices)

savedTechnique = getTechnique()
# Snapshot the raw per-thread technique (may be None) so the finally can restore it verbatim -
# getTechnique() would fall back to kb.technique, and a None result there used to skip the restore
# entirely, leaking the BOOLEAN/TIME we set below onto the calling thread for every later caller.
savedTechnique = getCurrentThreadData().technique

if isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN):
setTechnique(PAYLOAD.TECHNIQUE.BOOLEAN)
Expand All @@ -433,8 +436,12 @@ def _threadedInferenceValues(exprBuilder, indices, context=None, charsetType=Non
else:
return None

initTechnique(getTechnique())
payload = agent.payload(newValue=agent.suffixQuery(agent.prefixQuery(getTechniqueData().vector)))
try:
initTechnique(getTechnique())
payload = agent.payload(newValue=agent.suffixQuery(agent.prefixQuery(getTechniqueData().vector)))
except:
setTechnique(savedTechnique) # these run before the runThreads try/finally below, so restore here too
raise

results = [None] * len(indices)
cursor = iter(xrange(len(indices)))
Expand Down Expand Up @@ -501,8 +508,7 @@ def inferenceThread():
kb.partRun = savedPartRun
mainThreadData.disableStdOut = savedStdOut
mainThreadData.shared = savedShared
if savedTechnique is not None:
setTechnique(savedTechnique)
setTechnique(savedTechnique)

# Robustness: any slot a worker could not retrieve (None, i.e. a transient per-cell failure) is
# re-extracted serially via the classic getValue() path - full error handling, and a persistent error
Expand Down
89 changes: 89 additions & 0 deletions lib/request/multiparthandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env python

"""
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
See the file 'LICENSE' for copying permission
"""

import io
import mimetypes
import re

from lib.core.compat import choose_boundary
from lib.core.convert import getBytes
from lib.core.exception import SqlmapDataException
from thirdparty.six.moves import urllib as _urllib

# Controls how sequences are encoded: if True, an element may be given multiple values by assigning a sequence
DOSEQ = True

class MultipartPostHandler(_urllib.request.BaseHandler):
"""
urllib handler that transparently encodes a dict request body as multipart/form-data when it carries
file-like values (and as a plain urlencoded body otherwise). Native replacement for the historically
vendored 'thirdparty/multipart/multipartpost.py' (Will Holcomb, 2006); the logic already relied on
sqlmap's own helpers, so it now lives here as a first-class request handler.
"""

handler_order = _urllib.request.HTTPHandler.handler_order - 10 # must run before the HTTP handler

def http_request(self, request):
data = request.data

if isinstance(data, dict):
files, variables = [], []

try:
for key, value in data.items():
if hasattr(value, "fileno") or hasattr(value, "file") or isinstance(value, io.IOBase):
files.append((key, value))
else:
variables.append((key, value))
except TypeError:
raise SqlmapDataException("not a valid non-string sequence or mapping object")

if not files:
data = _urllib.parse.urlencode(variables, DOSEQ)
else:
boundary, data = self.multipartEncode(variables, files)
request.add_unredirected_header("Content-Type", "multipart/form-data; boundary=%s" % boundary)

request.data = data

# normalize bare LF to CRLF inside a multipart body (Reference: https://github.com/sqlmapproject/sqlmap/issues/4235)
if request.data:
for match in re.finditer(b"(?i)\\s*-{20,}\\w+(\\s+Content-Disposition[^\\n]+\\s+|\\-\\-\\s*)", request.data):
part = match.group(0)
if b'\r' not in part:
request.data = request.data.replace(part, part.replace(b'\n', b"\r\n"))

return request

def multipartEncode(self, variables, files, boundary=None):
boundary = boundary or choose_boundary()
buffer_ = b""

for key, value in variables:
if key is not None and value is not None:
buffer_ += b"--%s\r\n" % getBytes(boundary)
buffer_ += b"Content-Disposition: form-data; name=\"%s\"" % getBytes(key)
buffer_ += b"\r\n\r\n" + getBytes(value) + b"\r\n"

for key, fd in files:
filename = fd.name.split('/')[-1] if '/' in fd.name else fd.name.split('\\')[-1]
try:
contentType = mimetypes.guess_type(filename)[0] or b"application/octet-stream"
except Exception:
# Reference: http://bugs.python.org/issue9291
contentType = b"application/octet-stream"
buffer_ += b"--%s\r\n" % getBytes(boundary)
buffer_ += b"Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n" % (getBytes(key), getBytes(filename))
buffer_ += b"Content-Type: %s\r\n" % getBytes(contentType)
fd.seek(0)
buffer_ += b"\r\n%s\r\n" % fd.read()

buffer_ += b"--%s--\r\n\r\n" % getBytes(boundary)

return boundary, getBytes(buffer_)

https_request = http_request
4 changes: 2 additions & 2 deletions sqlmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@

from lib.core.common import banner
from lib.core.common import checkPipedInput
from lib.core.common import checkSums
from lib.core.common import codeIsModified
from lib.core.common import createGithubIssue
from lib.core.common import dataToStdout
from lib.core.common import extractRegexResult
Expand Down Expand Up @@ -282,7 +282,7 @@ def main():
print()
errMsg = unhandledExceptionMessage()
excMsg = traceback.format_exc()
valid = checkSums()
valid = not codeIsModified()

os._exitcode = 255

Expand Down
6 changes: 4 additions & 2 deletions tests/test_databases_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class _BaseEnumTest(unittest.TestCase):

# conf keys every test may read/write
_CONF_KEYS = ("direct", "technique", "db", "tbl", "col", "exclude",
"getComments", "excludeSysDbs", "search", "freshQueries")
"getComments", "excludeSysDbs", "search", "freshQueries", "threads")

def setUp(self):
self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS}
Expand Down Expand Up @@ -117,6 +117,7 @@ def _enable_inference(self):
"""Take the blind inference branch: conf.direct off, a BOOLEAN technique present."""
conf.direct = False
conf.technique = None
conf.threads = 1 # exercise the serial getValue() path these tests mock (>1 takes the value-parallel branch)
kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}}


Expand Down Expand Up @@ -533,7 +534,7 @@ def gv(query, *a, **k):

class _DbBase(unittest.TestCase):
_CONF_KEYS = ("direct", "technique", "db", "tbl", "col", "exclude",
"getComments", "excludeSysDbs", "search", "freshQueries")
"getComments", "excludeSysDbs", "search", "freshQueries", "threads")

def setUp(self):
self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS}
Expand Down Expand Up @@ -588,6 +589,7 @@ def _fresh(self):
def _inference(self):
conf.direct = False
conf.technique = None
conf.threads = 1 # exercise the serial getValue() path these tests mock (>1 takes the value-parallel branch)
kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}}


Expand Down
Empty file removed thirdparty/multipart/__init__.py
Empty file.
Loading