This commit is contained in:
Miroslav Stampar 2024-06-17 19:03:39 +02:00
parent cf91046766
commit 6ae0d0f54e
3 changed files with 414 additions and 16 deletions

View File

@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi
4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py
bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py
eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py
daeccc20761331d7a9e23756e583aae7da29aa8a22442e213d94a042362be087 lib/core/settings.py
e61388bf2a8ce5df511d28fb09749a937cbef4bd8878c2c7bc85f244e15e25ec lib/core/settings.py
2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py
e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py
54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py
@ -551,7 +551,7 @@ bd0fd06e24c3e05aecaccf5ba4c17d181e6cd35eee82c0efd6df5414fb0cb6f6 tamper/xforwar
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/ansistrm/__init__.py
e8f0ea4d982ef93c8c59c7165a1f39ccccddcb24b9fec1c2d2aa5bdb2373fdd5 thirdparty/beautifulsoup/beautifulsoup.py
7d62c59f787f987cbce0de5375f604da8de0ba01742842fb2b3d12fcb92fcb63 thirdparty/beautifulsoup/__init__.py
1b0f89e4713cc8cec4e4d824368a4eb9d3bdce7ddfc712326caac4feda1d7f69 thirdparty/bottle/bottle.py
0915f7e3d0025f81a2883cd958813470a4be661744d7fffa46848b45506b951a thirdparty/bottle/bottle.py
9f56e761d79bfdb34304a012586cb04d16b435ef6130091a97702e559260a2f2 thirdparty/bottle/__init__.py
0ffccae46cb3a15b117acd0790b2738a5b45417d1b2822ceac57bdff10ef3bff thirdparty/chardet/big5freq.py
901c476dd7ad0693deef1ae56fe7bdf748a8b7ae20fde1922dddf6941eff8773 thirdparty/chardet/big5prober.py

View File

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

@ -69,7 +69,7 @@ if __name__ == '__main__':
# Imports and Python 2/3 unification ##########################################
###############################################################################
import base64, calendar, cgi, email.utils, functools, hmac, itertools,\
import base64, calendar, email.utils, functools, hmac, itertools,\
mimetypes, os, re, tempfile, threading, time, warnings, weakref, hashlib
from types import FunctionType
@ -94,6 +94,7 @@ if py3k:
from urllib.parse import urlencode, quote as urlquote, unquote as urlunquote
urlunquote = functools.partial(urlunquote, encoding='latin1')
from http.cookies import SimpleCookie, Morsel, CookieError
from collections import defaultdict
from collections.abc import MutableMapping as DictMixin
from types import ModuleType as new_module
import pickle
@ -126,7 +127,7 @@ else: # 2.x
from imp import new_module
from StringIO import StringIO as BytesIO
import ConfigParser as configparser
from collections import MutableMapping as DictMixin
from collections import MutableMapping as DictMixin, defaultdict
from inspect import getargspec
unicode = unicode
@ -1137,6 +1138,399 @@ class Bottle(object):
# HTTP and WSGI Tools ##########################################################
###############################################################################
# Multipart parsing stuff
class StopMarkupException(BottleException):
pass
HYPHEN = tob('-')
CR = tob('\r')
LF = tob('\n')
CRLF = CR + LF
LFCRLF = LF + CR + LF
HYPHENx2 = HYPHEN * 2
CRLFx2 = CRLF * 2
CRLF_LEN = len(CRLF)
CRLFx2_LEN = len(CRLFx2)
MULTIPART_BOUNDARY_PATT = re.compile(r'^multipart/.+?boundary=(.+?)(;|$)')
class MPHeadersEaeter:
end_headers_patt = re.compile(tob(r'(\r\n\r\n)|(\r(\n\r?)?)$'))
def __init__(self):
self.headers_end_expected = None
self.eat_meth = self._eat_first_crlf_or_last_hyphens
self._meth_map = {
CR: self._eat_lf,
HYPHEN: self._eat_last_hyphen
}
self.stopped = False
def eat(self, chunk, base):
pos = self.eat_meth(chunk, base)
if pos is None: return
if self.eat_meth != self._eat_headers:
if self.stopped:
raise StopMarkupException()
base = pos
self.eat_meth = self._eat_headers
return self.eat(chunk, base)
# found headers section end, reset eater
self.eat_meth = self._eat_first_crlf_or_last_hyphens
return pos
def _eat_last_hyphen(self, chunk, base):
chunk_start = chunk[base: base + 2]
if not chunk_start: return
if chunk_start == HYPHEN:
self.stopped = True
return base + 1
raise HTTPError(422, 'Last hyphen was expected, got (first 2 symbols slice): %s' % chunk_start)
def _eat_lf(self, chunk, base):
chunk_start = chunk[base: base + 1]
if not chunk_start: return
if chunk_start == LF: return base + 1
invalid_sequence = CR + chunk_start
raise HTTPError(422, 'Malformed headers, found invalid sequence: %s' % invalid_sequence)
def _eat_first_crlf_or_last_hyphens(self, chunk, base):
chunk_start = chunk[base: base + 2]
if not chunk_start: return
if chunk_start == CRLF: return base + 2
if len(chunk_start) == 1:
self.eat_meth = self._meth_map.get(chunk_start)
elif chunk_start == HYPHENx2:
self.stopped = True
return base + 2
if self.eat_meth is None:
raise HTTPError(422, 'Malformed headers, invalid section start: %s' % chunk_start)
def _eat_headers(self, chunk, base):
expected = self.headers_end_expected
if expected is not None:
expected_len = len(expected)
chunk_start = chunk[base:expected_len]
if chunk_start == expected:
self.headers_end_expected = None
return base + expected_len - CRLFx2_LEN
chunk_start_len = len(chunk_start)
if not chunk_start_len: return
if chunk_start_len < expected_len:
if expected.startswith(chunk_start):
self.headers_end_expected = expected[chunk_start_len:]
return
self.headers_end_expected = None
if expected == LF: # we saw CRLFCR
invalid_sequence = CR + chunk_start[0:1]
# NOTE we don not catch all CRLF-malformed errors, but only obvious ones
# to stop doing useless work
raise HTTPError(422, 'Malformed headers, found invalid sequence: %s' % invalid_sequence)
else:
assert expected_len >= 2 # (CR)LFCRLF or (CRLF)CRLF
self.headers_end_expected = None
assert self.headers_end_expected is None
s = self.end_headers_patt.search(chunk, base)
if s is None: return
end_found = s.start(1)
if end_found >= 0: return end_found
end_head = s.group(2)
if end_head is not None:
self.headers_end_expected = CRLFx2[len(end_head):]
class MPBodyMarkup:
def __init__(self, boundary):
self.markups = []
self.error = None
if CR in boundary:
raise HTTPError(422, 'The `CR` must not be in the boundary: %s' % boundary)
boundary = HYPHENx2 + boundary
self.boundary = boundary
token = CRLF + boundary
self.tlen = len(token)
self.token = token
self.trest = self.trest_len = None
self.abspos = 0
self.abs_start_section = 0
self.headers_eater = MPHeadersEaeter()
self.cur_meth = self._eat_start_boundary
self._eat_headers = self.headers_eater.eat
self.stopped = False
self.idx = idx = defaultdict(list) # 1-based indices for each token symbol
for i, c in enumerate(token, start=1):
idx[c].append([i, token[:i]])
def _match_tail(self, s, start, end):
idxs = self.idx.get(s[end - 1])
if idxs is None: return
slen = end - start
assert slen <= self.tlen
for i, thead in idxs: # idxs is 1-based index
search_pos = slen - i
if search_pos < 0: return
if s[start + search_pos:end] == thead: return i # if s_tail == token_head
def _iter_markup(self, chunk):
if self.stopped:
raise StopMarkupException()
cur_meth = self.cur_meth
abs_start_section = self.abs_start_section
start_next_sec = 0
skip_start = 0
tlen = self.tlen
eat_data, eat_headers = self._eat_data, self._eat_headers
while True:
try:
end_section = cur_meth(chunk, start_next_sec)
except StopMarkupException:
self.stopped = True
return
if end_section is None: break
if cur_meth == eat_headers:
sec_name = 'headers'
start_next_sec = end_section + CRLFx2_LEN
cur_meth = eat_data
skip_start = 0
elif cur_meth == eat_data:
sec_name = 'data'
start_next_sec = end_section + tlen
skip_start = CRLF_LEN
cur_meth = eat_headers
else:
assert cur_meth == self._eat_start_boundary
sec_name = 'data'
start_next_sec = end_section + tlen
skip_start = CRLF_LEN
cur_meth = eat_headers
# if the body starts with a hyphen,
# we will have a negative abs_end_section equal to the length of the CRLF
abs_end_section = self.abspos + end_section
if abs_end_section < 0:
assert abs_end_section == -CRLF_LEN
end_section = -self.abspos
yield sec_name, (abs_start_section, self.abspos + end_section)
abs_start_section = self.abspos + start_next_sec + skip_start
self.abspos += len(chunk)
self.cur_meth = cur_meth
self.abs_start_section = abs_start_section
def _eat_start_boundary(self, chunk, base):
if self.trest is None:
chunk_start = chunk[base: base + 1]
if not chunk_start: return
if chunk_start == CR: return self._eat_data(chunk, base)
boundary = self.boundary
if chunk.startswith(boundary): return base - CRLF_LEN
if chunk_start != boundary[:1]:
raise HTTPError(
422, 'Invalid multipart/formdata body start, expected hyphen or CR, got: %s' % chunk_start)
self.trest = boundary
self.trest_len = len(boundary)
end_section = self._eat_data(chunk, base)
if end_section is not None: return end_section
def _eat_data(self, chunk, base):
chunk_len = len(chunk)
token, tlen, trest, trest_len = self.token, self.tlen, self.trest, self.trest_len
start = base
match_tail = self._match_tail
part = None
while True:
end = start + tlen
if end > chunk_len:
part = chunk[start:]
break
if trest is not None:
if chunk[start:start + trest_len] == trest:
data_end = start + trest_len - tlen
self.trest_len = self.trest = None
return data_end
else:
trest_len = trest = None
matched_len = match_tail(chunk, start, end)
if matched_len is not None:
if matched_len == tlen:
self.trest_len = self.trest = None
return start
else:
trest_len, trest = tlen - matched_len, token[matched_len:]
start += tlen
# process the tail of the chunk
if part:
part_len = len(part)
if trest is not None:
if part_len < trest_len:
if trest.startswith(part):
trest_len -= part_len
trest = trest[part_len:]
part = None
else:
trest_len = trest = None
else:
if part.startswith(trest):
data_end = start + trest_len - tlen
self.trest_len = self.trest = None
return data_end
trest_len = trest = None
if part is not None:
assert trest is None
matched_len = match_tail(part, 0, part_len)
if matched_len is not None:
trest_len, trest = tlen - matched_len, token[matched_len:]
self.trest_len, self.trest = trest_len, trest
def _parse(self, chunk):
for name, start_end in self._iter_markup(chunk):
self.markups.append([name, start_end])
def parse(self, chunk):
if self.error is not None: return
try:
self._parse(chunk)
except Exception as exc:
self.error = exc
class MPBytesIOProxy:
def __init__(self, src, start, end):
self._src = src
self._st = start
self._end = end
self._pos = start
def tell(self):
return self._pos - self._st
def seek(self, pos):
if pos < 0: pos = 0
self._pos = min(self._st + pos, self._end)
def read(self, sz=None):
max_sz = self._end - self._pos
if max_sz <= 0:
return tob('')
if sz is not None and sz > 0:
sz = min(sz, max_sz)
else:
sz = max_sz
self._src.seek(self._pos)
self._pos += sz
return self._src.read(sz)
def writable(self):
return False
def fileno(self):
raise OSError('Not supported')
def closed(self):
return self._src.closed()
def close(self):
pass
class MPHeader:
def __init__(self, name, value, options):
self.name = name
self.value = value
self.options = options
class MPFieldStorage:
_patt = re.compile(tonat('(.+?)(=(.+?))?(;|$)'))
def __init__(self):
self.name = None
self.value = None
self.filename = None
self.file = None
self.ctype = None
self.headers = {}
def read(self, src, headers_section, data_section, max_read):
start, end = headers_section
sz = end - start
has_read = sz
if has_read > max_read:
raise HTTPError(413, 'Request entity too large')
src.seek(start)
headers_raw = tonat(src.read(sz))
for header_raw in headers_raw.splitlines():
header = self.parse_header(header_raw)
self.headers[header.name] = header
if header.name == 'Content-Disposition':
self.name = header.options['name']
self.filename = header.options.get('filename')
elif header.name == 'Content-Type':
self.ctype = header.value
if self.name is None:
raise HTTPError(422, 'Noname field found while parsing multipart/formdata body: %s' % header_raw)
if self.filename is not None:
self.file = MPBytesIOProxy(src, *data_section)
else:
start, end = data_section
sz = end - start
if sz:
has_read += sz
if has_read > max_read:
raise HTTPError(413, 'Request entity too large')
src.seek(start)
self.value = tonat(src.read(sz))
else:
self.value = ''
return has_read
@classmethod
def parse_header(cls, s):
htype, rest = s.split(':', 1)
opt_iter = cls._patt.finditer(rest)
hvalue = next(opt_iter).group(1).strip()
dct = {}
for it in opt_iter:
k = it.group(1).strip()
v = it.group(3)
if v is not None:
v = v.strip('"')
dct[k.lower()] = v
return MPHeader(name=htype, value=hvalue, options=dct)
@classmethod
def iter_items(cls, src, markup, max_read):
iter_markup = iter(markup)
# check & skip empty data (body should start from empty data)
null_data = next(iter_markup, None)
if null_data is None: return
sec_name, [start, end] = null_data
assert sec_name == 'data'
if end > 0:
raise HTTPError(
422, 'Malformed multipart/formdata, unexpected data before the first boundary at: [%d:%d]'
% (start, end))
headers = next(iter_markup, None)
data = next(iter_markup, None)
while headers:
sec_name, headers_slice = headers
assert sec_name == 'headers'
if not data:
raise HTTPError(
422, 'Malformed multipart/formdata, no data found for the field at: [%d:%d]'
% tuple(headers_slice))
sec_name, data_slice = data
assert sec_name == 'data'
field = cls()
has_read = field.read(src, headers_slice, data_slice, max_read=max_read)
max_read -= has_read
yield field
headers = next(iter_markup, None)
data = next(iter_markup, None)
class BaseRequest(object):
""" A wrapper for WSGI environment dictionaries that adds a lot of
@ -1326,6 +1720,10 @@ class BaseRequest(object):
@DictProperty('environ', 'bottle.request.body', read_only=True)
def _body(self):
mp_markup = None
mp_boundary_match = MULTIPART_BOUNDARY_PATT.match(self.environ.get('CONTENT_TYPE', ''))
if mp_boundary_match is not None:
mp_markup = MPBodyMarkup(tob(mp_boundary_match.group(1)))
try:
read_func = self.environ['wsgi.input'].read
except KeyError:
@ -1335,12 +1733,15 @@ class BaseRequest(object):
body, body_size, is_temp_file = BytesIO(), 0, False
for part in body_iter(read_func, self.MEMFILE_MAX):
body.write(part)
if mp_markup is not None:
mp_markup.parse(part)
body_size += len(part)
if not is_temp_file and body_size > self.MEMFILE_MAX:
body, tmp = NamedTemporaryFile(mode='w+b'), body
body.write(tmp.getvalue())
del tmp
is_temp_file = True
body.multipart_markup = mp_markup
self.environ['wsgi.input'] = body
body.seek(0)
return body
@ -1378,7 +1779,7 @@ class BaseRequest(object):
def POST(self):
""" The values of :attr:`forms` and :attr:`files` combined into a single
:class:`FormsDict`. Values are either strings (form values) or
instances of :class:`cgi.FieldStorage` (file uploads).
instances of :class:`MPBytesIOProxy` (file uploads).
"""
post = FormsDict()
# We default to application/x-www-form-urlencoded for everything that
@ -1389,18 +1790,15 @@ class BaseRequest(object):
post[key] = value
return post
safe_env = {'QUERY_STRING': ''} # Build a safe environment for cgi
for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'):
if key in self.environ: safe_env[key] = self.environ[key]
args = dict(fp=self.body, environ=safe_env, keep_blank_values=True)
if py3k:
args['encoding'] = 'utf8'
post.recode_unicode = False
data = cgi.FieldStorage(**args)
self['_cgi.FieldStorage'] = data #http://bugs.python.org/issue18394
data = data.list or []
for item in data:
body = self.body
markup = body.multipart_markup
if markup is None:
raise HTTPError(400, '`boundary` required for mutlipart content')
elif markup.error is not None:
raise markup.error
for item in MPFieldStorage.iter_items(body, markup.markups, self.MEMFILE_MAX):
if item.filename is None:
post[item.name] = item.value
else: