mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2024-11-25 19:13:48 +03:00
Some more drei updates
This commit is contained in:
parent
291b491c3e
commit
1f05e85408
|
@ -22,6 +22,7 @@ if sys.version_info >= (3, 0):
|
||||||
xrange = range
|
xrange = range
|
||||||
text_type = str
|
text_type = str
|
||||||
string_types = (str,)
|
string_types = (str,)
|
||||||
|
unichr = chr
|
||||||
else:
|
else:
|
||||||
text_type = unicode
|
text_type = unicode
|
||||||
string_types = (basestring,)
|
string_types = (basestring,)
|
||||||
|
@ -88,7 +89,7 @@ def safechardecode(value, binary=False):
|
||||||
while True:
|
while True:
|
||||||
match = re.search(HEX_ENCODED_CHAR_REGEX, retVal)
|
match = re.search(HEX_ENCODED_CHAR_REGEX, retVal)
|
||||||
if match:
|
if match:
|
||||||
retVal = retVal.replace(match.group("result"), (unichr if isinstance(value, text_type) else chr)(ord(binascii.unhexlify(match.group("result").lstrip("\\x")))))
|
retVal = retVal.replace(match.group("result"), unichr(ord(binascii.unhexlify(match.group("result").lstrip("\\x")))))
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
|
|
||||||
# Stress test against Python3
|
# Stress test against Python3
|
||||||
|
|
||||||
export SQLMAP_DREI=1
|
# export SQLMAP_DREI=1
|
||||||
for i in $(find . -iname "*.py" | grep -v __init__); do python3 -c 'import '`echo $i | cut -d '.' -f 2 | cut -d '/' -f 2- | sed 's/\//./g'`''; done
|
# for i in $(find . -iname "*.py" | grep -v __init__); do python3 -c 'import '`echo $i | cut -d '.' -f 2 | cut -d '/' -f 2- | sed 's/\//./g'`''; done
|
||||||
unset SQLMAP_DREI
|
# unset SQLMAP_DREI
|
||||||
source `dirname "$0"`"/junk.sh"
|
# source `dirname "$0"`"/junk.sh"
|
||||||
|
|
||||||
|
for i in $(find . -iname "*.py" | grep -v __init__); do pylint --py3k $i; done
|
||||||
|
|
|
@ -90,7 +90,7 @@ class BigArray(list):
|
||||||
self.chunks[-1] = pickle.loads(bz2.decompress(f.read()))
|
self.chunks[-1] = pickle.loads(bz2.decompress(f.read()))
|
||||||
except IOError as ex:
|
except IOError as ex:
|
||||||
errMsg = "exception occurred while retrieving data "
|
errMsg = "exception occurred while retrieving data "
|
||||||
errMsg += "from a temporary file ('%s')" % ex.message
|
errMsg += "from a temporary file ('%s')" % ex
|
||||||
raise SqlmapSystemException(errMsg)
|
raise SqlmapSystemException(errMsg)
|
||||||
|
|
||||||
return self.chunks[-1].pop()
|
return self.chunks[-1].pop()
|
||||||
|
@ -112,7 +112,7 @@ class BigArray(list):
|
||||||
return filename
|
return filename
|
||||||
except (OSError, IOError) as ex:
|
except (OSError, IOError) as ex:
|
||||||
errMsg = "exception occurred while storing data "
|
errMsg = "exception occurred while storing data "
|
||||||
errMsg += "to a temporary file ('%s'). Please " % ex.message
|
errMsg += "to a temporary file ('%s'). Please " % ex
|
||||||
errMsg += "make sure that there is enough disk space left. If problem persists, "
|
errMsg += "make sure that there is enough disk space left. If problem persists, "
|
||||||
errMsg += "try to set environment variable 'TEMP' to a location "
|
errMsg += "try to set environment variable 'TEMP' to a location "
|
||||||
errMsg += "writeable by the current user"
|
errMsg += "writeable by the current user"
|
||||||
|
@ -129,7 +129,7 @@ class BigArray(list):
|
||||||
self.cache = Cache(index, pickle.loads(bz2.decompress(f.read())), False)
|
self.cache = Cache(index, pickle.loads(bz2.decompress(f.read())), False)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
errMsg = "exception occurred while retrieving data "
|
errMsg = "exception occurred while retrieving data "
|
||||||
errMsg += "from a temporary file ('%s')" % ex.message
|
errMsg += "from a temporary file ('%s')" % ex
|
||||||
raise SqlmapSystemException(errMsg)
|
raise SqlmapSystemException(errMsg)
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
|
|
|
@ -5,6 +5,7 @@ Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
|
||||||
See the file 'LICENSE' for copying permission
|
See the file 'LICENSE' for copying permission
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
import binascii
|
import binascii
|
||||||
import codecs
|
import codecs
|
||||||
import collections
|
import collections
|
||||||
|
@ -47,6 +48,8 @@ from extra.beep.beep import beep
|
||||||
from extra.cloak.cloak import decloak
|
from extra.cloak.cloak import decloak
|
||||||
from extra.safe2bin.safe2bin import safecharencode
|
from extra.safe2bin.safe2bin import safecharencode
|
||||||
from lib.core.bigarray import BigArray
|
from lib.core.bigarray import BigArray
|
||||||
|
from lib.core.compat import cmp
|
||||||
|
from lib.core.compat import round
|
||||||
from lib.core.compat import xrange
|
from lib.core.compat import xrange
|
||||||
from lib.core.convert import base64pickle
|
from lib.core.convert import base64pickle
|
||||||
from lib.core.convert import base64unpickle
|
from lib.core.convert import base64unpickle
|
||||||
|
@ -179,7 +182,9 @@ from thirdparty.odict import OrderedDict
|
||||||
from thirdparty.six.moves import configparser as _configparser
|
from thirdparty.six.moves import configparser as _configparser
|
||||||
from thirdparty.six.moves import http_client as _http_client
|
from thirdparty.six.moves import http_client as _http_client
|
||||||
from thirdparty.six.moves import input as _input
|
from thirdparty.six.moves import input as _input
|
||||||
|
from thirdparty.six.moves import reload_module as _reload_module
|
||||||
from thirdparty.six.moves import urllib as _urllib
|
from thirdparty.six.moves import urllib as _urllib
|
||||||
|
from thirdparty.six.moves import zip as _zip
|
||||||
from thirdparty.termcolor.termcolor import colored
|
from thirdparty.termcolor.termcolor import colored
|
||||||
|
|
||||||
class UnicodeRawConfigParser(_configparser.RawConfigParser):
|
class UnicodeRawConfigParser(_configparser.RawConfigParser):
|
||||||
|
@ -610,7 +615,7 @@ def paramToDict(place, parameters=None):
|
||||||
if parameter in (conf.base64Parameter or []):
|
if parameter in (conf.base64Parameter or []):
|
||||||
try:
|
try:
|
||||||
oldValue = value
|
oldValue = value
|
||||||
value = value.decode("base64")
|
value = decodeBase64(value, binary=False)
|
||||||
parameters = re.sub(r"\b%s\b" % re.escape(oldValue), value, parameters)
|
parameters = re.sub(r"\b%s\b" % re.escape(oldValue), value, parameters)
|
||||||
except:
|
except:
|
||||||
errMsg = "parameter '%s' does not contain " % parameter
|
errMsg = "parameter '%s' does not contain " % parameter
|
||||||
|
@ -2278,7 +2283,7 @@ def getFileItems(filename, commentPrefix='#', unicoded=True, lowercase=False, un
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with openFile(filename, 'r', errors="ignore") if unicoded else open(filename, 'r') as f:
|
with openFile(filename, 'r', errors="ignore") if unicoded else open(filename, 'r') as f:
|
||||||
for line in (f.readlines() if unicoded else f.xreadlines()): # xreadlines doesn't return unicode strings when codec.open() is used
|
for line in f:
|
||||||
if commentPrefix:
|
if commentPrefix:
|
||||||
if line.find(commentPrefix) != -1:
|
if line.find(commentPrefix) != -1:
|
||||||
line = line[:line.find(commentPrefix)]
|
line = line[:line.find(commentPrefix)]
|
||||||
|
@ -2452,15 +2457,39 @@ def getUnicode(value, encoding=None, noneToNull=False):
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
return six.text_type(str(value), errors="ignore") # encoding ignored for non-basestring instances
|
return six.text_type(str(value), errors="ignore") # encoding ignored for non-basestring instances
|
||||||
|
|
||||||
def decodeHex(value):
|
def decodeHex(value, binary=True):
|
||||||
"""
|
"""
|
||||||
Returns byte representation of provided hexadecimal value
|
Returns a decoded representation of provided hexadecimal value
|
||||||
|
|
||||||
>>> decodeHex("313233") == b"123"
|
>>> decodeHex("313233") == b"123"
|
||||||
True
|
True
|
||||||
|
>>> decodeHex("313233", binary=False) == u"123"
|
||||||
|
True
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return bytes.fromhex(getUnicode(value)) if hasattr(bytes, "fromhex") else value.decode("hex")
|
retVal = codecs.decode(value, "hex")
|
||||||
|
|
||||||
|
if not binary:
|
||||||
|
retVal = getUnicode(retVal)
|
||||||
|
|
||||||
|
return retVal
|
||||||
|
|
||||||
|
def decodeBase64(value, binary=True):
|
||||||
|
"""
|
||||||
|
Returns a decoded representation of provided Base64 value
|
||||||
|
|
||||||
|
>>> decodeBase64("MTIz") == b"123"
|
||||||
|
True
|
||||||
|
>>> decodeBase64("MTIz", binary=False) == u"123"
|
||||||
|
True
|
||||||
|
"""
|
||||||
|
|
||||||
|
retVal = base64.b64decode(value)
|
||||||
|
|
||||||
|
if not binary:
|
||||||
|
retVal = getUnicode(retVal)
|
||||||
|
|
||||||
|
return retVal
|
||||||
|
|
||||||
def getBytes(value, encoding=UNICODE_ENCODING, errors="strict"):
|
def getBytes(value, encoding=UNICODE_ENCODING, errors="strict"):
|
||||||
"""
|
"""
|
||||||
|
@ -2475,7 +2504,7 @@ def getBytes(value, encoding=UNICODE_ENCODING, errors="strict"):
|
||||||
if isinstance(value, six.text_type):
|
if isinstance(value, six.text_type):
|
||||||
if INVALID_UNICODE_PRIVATE_AREA:
|
if INVALID_UNICODE_PRIVATE_AREA:
|
||||||
for char in xrange(0xF0000, 0xF00FF + 1):
|
for char in xrange(0xF0000, 0xF00FF + 1):
|
||||||
value = value.replace(unichr(char), "%s%02x" % (SAFE_HEX_MARKER, char - 0xF0000))
|
value = value.replace(six.unichr(char), "%s%02x" % (SAFE_HEX_MARKER, char - 0xF0000))
|
||||||
|
|
||||||
retVal = value.encode(encoding, errors)
|
retVal = value.encode(encoding, errors)
|
||||||
retVal = re.sub(r"%s([0-9a-f]{2})" % SAFE_HEX_MARKER, lambda _: decodeHex(_.group(1)), retVal)
|
retVal = re.sub(r"%s([0-9a-f]{2})" % SAFE_HEX_MARKER, lambda _: decodeHex(_.group(1)), retVal)
|
||||||
|
@ -2525,7 +2554,13 @@ def longestCommonPrefix(*sequences):
|
||||||
return sequences[0]
|
return sequences[0]
|
||||||
|
|
||||||
def commonFinderOnly(initial, sequence):
|
def commonFinderOnly(initial, sequence):
|
||||||
return longestCommonPrefix(*filter(lambda _: _.startswith(initial), sequence))
|
"""
|
||||||
|
Returns parts of sequence which start with the given initial string
|
||||||
|
|
||||||
|
>>> commonFinderOnly("abcd", ["abcdefg", "foobar", "abcde"])
|
||||||
|
['abcdefg', 'abcde']
|
||||||
|
"""
|
||||||
|
return longestCommonPrefix([_ for _ in sequence if _.startswith(initial)])
|
||||||
|
|
||||||
def pushValue(value):
|
def pushValue(value):
|
||||||
"""
|
"""
|
||||||
|
@ -2811,13 +2846,13 @@ def runningAsAdmin():
|
||||||
if PLATFORM in ("posix", "mac"):
|
if PLATFORM in ("posix", "mac"):
|
||||||
_ = os.geteuid()
|
_ = os.geteuid()
|
||||||
|
|
||||||
isAdmin = isinstance(_, (int, float, long)) and _ == 0
|
isAdmin = isinstance(_, (float, six.integer_types)) and _ == 0
|
||||||
elif IS_WIN:
|
elif IS_WIN:
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
_ = ctypes.windll.shell32.IsUserAnAdmin()
|
_ = ctypes.windll.shell32.IsUserAnAdmin()
|
||||||
|
|
||||||
isAdmin = isinstance(_, (int, float, long)) and _ == 1
|
isAdmin = isinstance(_, (float, six.integer_types)) and _ == 1
|
||||||
else:
|
else:
|
||||||
errMsg = "sqlmap is not able to check if you are running it "
|
errMsg = "sqlmap is not able to check if you are running it "
|
||||||
errMsg += "as an administrator account on this platform. "
|
errMsg += "as an administrator account on this platform. "
|
||||||
|
@ -3318,6 +3353,8 @@ def unArrayizeValue(value):
|
||||||
|
|
||||||
>>> unArrayizeValue(['1'])
|
>>> unArrayizeValue(['1'])
|
||||||
'1'
|
'1'
|
||||||
|
>>> unArrayizeValue(['1', '2'])
|
||||||
|
'1'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isListLike(value):
|
if isListLike(value):
|
||||||
|
@ -3326,8 +3363,8 @@ def unArrayizeValue(value):
|
||||||
elif len(value) == 1 and not isListLike(value[0]):
|
elif len(value) == 1 and not isListLike(value[0]):
|
||||||
value = value[0]
|
value = value[0]
|
||||||
else:
|
else:
|
||||||
_ = filter(lambda _: _ is not None, (_ for _ in flattenValue(value)))
|
value = [_ for _ in flattenValue(value) if _ is not None]
|
||||||
value = _[0] if len(_) > 0 else None
|
value = value[0] if len(value) > 0 else None
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -3459,7 +3496,7 @@ def decodeIntToUnicode(value):
|
||||||
elif Backend.isDbms(DBMS.MSSQL):
|
elif Backend.isDbms(DBMS.MSSQL):
|
||||||
retVal = getUnicode(raw, "UTF-16-BE")
|
retVal = getUnicode(raw, "UTF-16-BE")
|
||||||
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE):
|
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE):
|
||||||
retVal = unichr(value)
|
retVal = six.unichr(value)
|
||||||
else:
|
else:
|
||||||
retVal = getUnicode(raw, conf.encoding)
|
retVal = getUnicode(raw, conf.encoding)
|
||||||
else:
|
else:
|
||||||
|
@ -3600,7 +3637,7 @@ def createGithubIssue(errMsg, excMsg):
|
||||||
choice = None
|
choice = None
|
||||||
|
|
||||||
if choice:
|
if choice:
|
||||||
ex = None
|
_excMsg = None
|
||||||
errMsg = errMsg[errMsg.find("\n"):]
|
errMsg = errMsg[errMsg.find("\n"):]
|
||||||
|
|
||||||
req = _urllib.request.Request(url="https://api.github.com/search/issues?q=%s" % _urllib.parse.quote("repo:sqlmapproject/sqlmap Unhandled exception (#%s)" % key))
|
req = _urllib.request.Request(url="https://api.github.com/search/issues?q=%s" % _urllib.parse.quote("repo:sqlmapproject/sqlmap Unhandled exception (#%s)" % key))
|
||||||
|
@ -3621,12 +3658,13 @@ def createGithubIssue(errMsg, excMsg):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
data = {"title": "Unhandled exception (#%s)" % key, "body": "```%s\n```\n```\n%s```" % (errMsg, excMsg)}
|
data = {"title": "Unhandled exception (#%s)" % key, "body": "```%s\n```\n```\n%s```" % (errMsg, excMsg)}
|
||||||
req = _urllib.request.Request(url="https://api.github.com/repos/sqlmapproject/sqlmap/issues", data=json.dumps(data), headers={"Authorization": "token %s" % GITHUB_REPORT_OAUTH_TOKEN.decode("base64")})
|
req = _urllib.request.Request(url="https://api.github.com/repos/sqlmapproject/sqlmap/issues", data=json.dumps(data), headers={"Authorization": "token %s" % decodeBase64(GITHUB_REPORT_OAUTH_TOKEN, binary=False)})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
content = _urllib.request.urlopen(req).read()
|
content = _urllib.request.urlopen(req).read()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
content = None
|
content = None
|
||||||
|
_excMsg = getSafeExString(ex)
|
||||||
|
|
||||||
issueUrl = re.search(r"https://github.com/sqlmapproject/sqlmap/issues/\d+", content or "")
|
issueUrl = re.search(r"https://github.com/sqlmapproject/sqlmap/issues/\d+", content or "")
|
||||||
if issueUrl:
|
if issueUrl:
|
||||||
|
@ -3640,8 +3678,8 @@ def createGithubIssue(errMsg, excMsg):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
warnMsg = "something went wrong while creating a Github issue"
|
warnMsg = "something went wrong while creating a Github issue"
|
||||||
if ex:
|
if _excMsg:
|
||||||
warnMsg += " ('%s')" % getSafeExString(ex)
|
warnMsg += " ('%s')" % _excMsg
|
||||||
if "Unauthorized" in warnMsg:
|
if "Unauthorized" in warnMsg:
|
||||||
warnMsg += ". Please update to the latest revision"
|
warnMsg += ". Please update to the latest revision"
|
||||||
logger.warn(warnMsg)
|
logger.warn(warnMsg)
|
||||||
|
@ -4403,7 +4441,7 @@ def checkSystemEncoding():
|
||||||
warnMsg = "temporary switching to charset 'cp1256'"
|
warnMsg = "temporary switching to charset 'cp1256'"
|
||||||
logger.warn(warnMsg)
|
logger.warn(warnMsg)
|
||||||
|
|
||||||
reload(sys)
|
_reload_module(sys)
|
||||||
sys.setdefaultencoding("cp1256")
|
sys.setdefaultencoding("cp1256")
|
||||||
|
|
||||||
def evaluateCode(code, variables=None):
|
def evaluateCode(code, variables=None):
|
||||||
|
@ -4741,7 +4779,7 @@ def splitFields(fields, delimiter=','):
|
||||||
commas.extend(zeroDepthSearch(fields, ','))
|
commas.extend(zeroDepthSearch(fields, ','))
|
||||||
commas = sorted(commas)
|
commas = sorted(commas)
|
||||||
|
|
||||||
return [fields[x + 1:y] for (x, y) in zip(commas, commas[1:])]
|
return [fields[x + 1:y] for (x, y) in _zip(commas, commas[1:])]
|
||||||
|
|
||||||
def pollProcess(process, suppress_errors=False):
|
def pollProcess(process, suppress_errors=False):
|
||||||
"""
|
"""
|
||||||
|
@ -4807,7 +4845,7 @@ def parseRequestFile(reqFile, checkParams=True):
|
||||||
for match in re.finditer(BURP_XML_HISTORY_REGEX, content, re.I | re.S):
|
for match in re.finditer(BURP_XML_HISTORY_REGEX, content, re.I | re.S):
|
||||||
port, request = match.groups()
|
port, request = match.groups()
|
||||||
try:
|
try:
|
||||||
request = request.decode("base64")
|
request = decodeBase64(request, binary=False)
|
||||||
except binascii.Error:
|
except binascii.Error:
|
||||||
continue
|
continue
|
||||||
_ = re.search(r"%s:.+" % re.escape(HTTP_HEADER.HOST), request)
|
_ = re.search(r"%s:.+" % re.escape(HTTP_HEADER.HOST), request)
|
||||||
|
|
|
@ -6,6 +6,7 @@ See the file 'LICENSE' for copying permission
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import binascii
|
import binascii
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import uuid
|
import uuid
|
||||||
|
@ -163,13 +164,44 @@ class WichmannHill(random.Random):
|
||||||
self.__whseed(x, y, z)
|
self.__whseed(x, y, z)
|
||||||
|
|
||||||
def patchHeaders(headers):
|
def patchHeaders(headers):
|
||||||
if not hasattr(headers, "headers"):
|
if headers is not None and not hasattr(headers, "headers"):
|
||||||
headers.headers = ["%s: %s\r\n" % (header, headers[header]) for header in headers]
|
headers.headers = ["%s: %s\r\n" % (header, headers[header]) for header in headers]
|
||||||
|
|
||||||
|
def cmp(a, b):
|
||||||
|
"""
|
||||||
|
>>> cmp("a", "b")
|
||||||
|
-1
|
||||||
|
>>> cmp(2, 1)
|
||||||
|
1
|
||||||
|
"""
|
||||||
|
|
||||||
|
if a < b:
|
||||||
|
return -1
|
||||||
|
elif a > b:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
# Reference: https://github.com/urllib3/urllib3/blob/master/src/urllib3/filepost.py
|
# Reference: https://github.com/urllib3/urllib3/blob/master/src/urllib3/filepost.py
|
||||||
def choose_boundary():
|
def choose_boundary():
|
||||||
return uuid.uuid4().hex
|
return uuid.uuid4().hex
|
||||||
|
|
||||||
|
# Reference: http://python3porting.com/differences.html
|
||||||
|
def round(x, d=0):
|
||||||
|
"""
|
||||||
|
>>> round(2.0)
|
||||||
|
2.0
|
||||||
|
>>> round(2.5)
|
||||||
|
3.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
p = 10 ** d
|
||||||
|
if x > 0:
|
||||||
|
return float(math.floor((x * p) + 0.5))/p
|
||||||
|
else:
|
||||||
|
return float(math.ceil((x * p) - 0.5))/p
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 0):
|
if sys.version_info >= (3, 0):
|
||||||
xrange = range
|
xrange = range
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -171,7 +171,7 @@ def htmlunescape(value):
|
||||||
retVal = retVal.replace(code, value)
|
retVal = retVal.replace(code, value)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
retVal = re.sub(r"&#x([^ ;]+);", lambda match: unichr(int(match.group(1), 16)), retVal)
|
retVal = re.sub(r"&#x([^ ;]+);", lambda match: six.unichr(int(match.group(1), 16)), retVal)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
return retVal
|
return retVal
|
||||||
|
|
|
@ -59,6 +59,7 @@ from lib.core.common import setOptimize
|
||||||
from lib.core.common import setPaths
|
from lib.core.common import setPaths
|
||||||
from lib.core.common import singleTimeWarnMessage
|
from lib.core.common import singleTimeWarnMessage
|
||||||
from lib.core.common import urldecode
|
from lib.core.common import urldecode
|
||||||
|
from lib.core.compat import round
|
||||||
from lib.core.compat import xrange
|
from lib.core.compat import xrange
|
||||||
from lib.core.data import conf
|
from lib.core.data import conf
|
||||||
from lib.core.data import kb
|
from lib.core.data import kb
|
||||||
|
@ -2096,11 +2097,14 @@ def _useWizardInterface():
|
||||||
choice = readInput(message, default='1')
|
choice = readInput(message, default='1')
|
||||||
|
|
||||||
if choice == '2':
|
if choice == '2':
|
||||||
map(lambda _: conf.__setitem__(_, True), WIZARD.INTERMEDIATE)
|
options = WIZARD.INTERMEDIATE
|
||||||
elif choice == '3':
|
elif choice == '3':
|
||||||
map(lambda _: conf.__setitem__(_, True), WIZARD.ALL)
|
options = WIZARD.ALL
|
||||||
else:
|
else:
|
||||||
map(lambda _: conf.__setitem__(_, True), WIZARD.BASIC)
|
options = WIZARD.BASIC
|
||||||
|
|
||||||
|
for _ in options:
|
||||||
|
conf.__setitem__(_, True)
|
||||||
|
|
||||||
logger.debug("muting sqlmap.. it will do the magic for you")
|
logger.debug("muting sqlmap.. it will do the magic for you")
|
||||||
conf.verbose = 0
|
conf.verbose = 0
|
||||||
|
|
|
@ -15,9 +15,10 @@ import sys
|
||||||
from lib.core.enums import DBMS
|
from lib.core.enums import DBMS
|
||||||
from lib.core.enums import DBMS_DIRECTORY_NAME
|
from lib.core.enums import DBMS_DIRECTORY_NAME
|
||||||
from lib.core.enums import OS
|
from lib.core.enums import OS
|
||||||
|
from thirdparty import six
|
||||||
|
|
||||||
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
|
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
|
||||||
VERSION = "1.3.5.5"
|
VERSION = "1.3.5.6"
|
||||||
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
|
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
|
||||||
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
|
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)
|
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)
|
||||||
|
@ -839,7 +840,7 @@ for key, value in os.environ.items():
|
||||||
def _reversible(ex):
|
def _reversible(ex):
|
||||||
if isinstance(ex, UnicodeDecodeError):
|
if isinstance(ex, UnicodeDecodeError):
|
||||||
if INVALID_UNICODE_PRIVATE_AREA:
|
if INVALID_UNICODE_PRIVATE_AREA:
|
||||||
return (u"".join(unichr(int('000f00%2x' % (_ if isinstance(_, int) else ord(_)), 16)) for _ in ex.object[ex.start:ex.end]), ex.end)
|
return (u"".join(six.unichr(int('000f00%2x' % (_ if isinstance(_, int) else ord(_)), 16)) for _ in ex.object[ex.start:ex.end]), ex.end)
|
||||||
else:
|
else:
|
||||||
return (u"".join(INVALID_UNICODE_CHAR_FORMAT % (_ if isinstance(_, int) else ord(_)) for _ in ex.object[ex.start:ex.end]), ex.end)
|
return (u"".join(INVALID_UNICODE_CHAR_FORMAT % (_ if isinstance(_, int) else ord(_)) for _ in ex.object[ex.start:ex.end]), ex.end)
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ from lib.core.common import getUnicode
|
||||||
from lib.core.common import randomStr
|
from lib.core.common import randomStr
|
||||||
from lib.core.common import readXmlFile
|
from lib.core.common import readXmlFile
|
||||||
from lib.core.common import shellExec
|
from lib.core.common import shellExec
|
||||||
|
from lib.core.compat import round
|
||||||
from lib.core.data import conf
|
from lib.core.data import conf
|
||||||
from lib.core.data import logger
|
from lib.core.data import logger
|
||||||
from lib.core.data import paths
|
from lib.core.data import paths
|
||||||
|
|
|
@ -155,7 +155,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
|
||||||
try:
|
try:
|
||||||
thread.start()
|
thread.start()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
errMsg = "error occurred while starting new thread ('%s')" % ex.message
|
errMsg = "error occurred while starting new thread ('%s')" % ex
|
||||||
logger.critical(errMsg)
|
logger.critical(errMsg)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
|
||||||
except (SqlmapConnectionException, SqlmapValueException) as ex:
|
except (SqlmapConnectionException, SqlmapValueException) as ex:
|
||||||
print()
|
print()
|
||||||
kb.threadException = True
|
kb.threadException = True
|
||||||
logger.error("thread %s: %s" % (threading.currentThread().getName(), ex.message))
|
logger.error("thread %s: '%s'" % (threading.currentThread().getName(), ex))
|
||||||
|
|
||||||
if conf.get("verbose") > 1:
|
if conf.get("verbose") > 1:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
|
@ -12,7 +12,6 @@ import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import urllib
|
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
from lib.core.common import dataToStdout
|
from lib.core.common import dataToStdout
|
||||||
|
@ -29,6 +28,7 @@ from lib.core.settings import IS_WIN
|
||||||
from lib.core.settings import VERSION
|
from lib.core.settings import VERSION
|
||||||
from lib.core.settings import ZIPBALL_PAGE
|
from lib.core.settings import ZIPBALL_PAGE
|
||||||
from lib.core.settings import UNICODE_ENCODING
|
from lib.core.settings import UNICODE_ENCODING
|
||||||
|
from thirdparty.six.moves import urllib as _urllib
|
||||||
|
|
||||||
def update():
|
def update():
|
||||||
if not conf.updateAll:
|
if not conf.updateAll:
|
||||||
|
@ -71,7 +71,7 @@ def update():
|
||||||
logger.error(errMsg)
|
logger.error(errMsg)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
archive = urllib.urlretrieve(ZIPBALL_PAGE)[0]
|
archive = _urllib.request.urlretrieve(ZIPBALL_PAGE)[0]
|
||||||
|
|
||||||
with zipfile.ZipFile(archive) as f:
|
with zipfile.ZipFile(archive) as f:
|
||||||
for info in f.infolist():
|
for info in f.infolist():
|
||||||
|
|
|
@ -345,14 +345,14 @@ def decodePage(page, contentEncoding, contentType):
|
||||||
def _(match):
|
def _(match):
|
||||||
retVal = match.group(0)
|
retVal = match.group(0)
|
||||||
try:
|
try:
|
||||||
retVal = unichr(int(match.group(1)))
|
retVal = six.unichr(int(match.group(1)))
|
||||||
except (ValueError, OverflowError):
|
except (ValueError, OverflowError):
|
||||||
pass
|
pass
|
||||||
return retVal
|
return retVal
|
||||||
page = re.sub(r"&#(\d+);", _, page)
|
page = re.sub(r"&#(\d+);", _, page)
|
||||||
|
|
||||||
# e.g. ζ
|
# e.g. ζ
|
||||||
page = re.sub(r"&([^;]+);", lambda _: unichr(htmlEntities[_.group(1)]) if htmlEntities.get(_.group(1), 0) > 255 else _.group(0), page)
|
page = re.sub(r"&([^;]+);", lambda _: six.unichr(htmlEntities[_.group(1)]) if htmlEntities.get(_.group(1), 0) > 255 else _.group(0), page)
|
||||||
|
|
||||||
return page
|
return page
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ from lib.core.common import Backend
|
||||||
from lib.core.common import calculateDeltaSeconds
|
from lib.core.common import calculateDeltaSeconds
|
||||||
from lib.core.common import clearConsoleLine
|
from lib.core.common import clearConsoleLine
|
||||||
from lib.core.common import dataToStdout
|
from lib.core.common import dataToStdout
|
||||||
|
from lib.core.common import decodeBase64
|
||||||
from lib.core.common import extractRegexResult
|
from lib.core.common import extractRegexResult
|
||||||
from lib.core.common import firstNotNone
|
from lib.core.common import firstNotNone
|
||||||
from lib.core.common import flattenValue
|
from lib.core.common import flattenValue
|
||||||
|
@ -121,14 +122,14 @@ def _oneShotUnionUse(expression, unpack=True, limited=False):
|
||||||
break
|
break
|
||||||
|
|
||||||
try:
|
try:
|
||||||
value.decode("base64")
|
decodeBase64(value)
|
||||||
except binascii.Error:
|
except binascii.Error:
|
||||||
base64 = False
|
base64 = False
|
||||||
break
|
break
|
||||||
|
|
||||||
if base64:
|
if base64:
|
||||||
for child in root:
|
for child in root:
|
||||||
child.attrib[column] = child.attrib.get(column, "").decode("base64") or NULL
|
child.attrib[column] = decodeBase64(child.attrib.get(column, ""), binary=False) or NULL
|
||||||
|
|
||||||
for child in root:
|
for child in root:
|
||||||
row = []
|
row = []
|
||||||
|
|
|
@ -20,6 +20,7 @@ import tempfile
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from lib.core.common import dataToStdout
|
from lib.core.common import dataToStdout
|
||||||
|
from lib.core.common import decodeBase64
|
||||||
from lib.core.common import getSafeExString
|
from lib.core.common import getSafeExString
|
||||||
from lib.core.common import saveConfig
|
from lib.core.common import saveConfig
|
||||||
from lib.core.common import unArrayizeValue
|
from lib.core.common import unArrayizeValue
|
||||||
|
@ -294,7 +295,7 @@ def check_authentication():
|
||||||
request.environ["PATH_INFO"] = "/error/401"
|
request.environ["PATH_INFO"] = "/error/401"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
creds = match.group(1).decode("base64")
|
creds = decodeBase64(match.group(1), binary=False)
|
||||||
except:
|
except:
|
||||||
request.environ["PATH_INFO"] = "/error/401"
|
request.environ["PATH_INFO"] = "/error/401"
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -50,6 +50,7 @@ from lib.core.common import Backend
|
||||||
from lib.core.common import checkFile
|
from lib.core.common import checkFile
|
||||||
from lib.core.common import clearConsoleLine
|
from lib.core.common import clearConsoleLine
|
||||||
from lib.core.common import dataToStdout
|
from lib.core.common import dataToStdout
|
||||||
|
from lib.core.common import decodeBase64
|
||||||
from lib.core.common import getBytes
|
from lib.core.common import getBytes
|
||||||
from lib.core.common import getFileItems
|
from lib.core.common import getFileItems
|
||||||
from lib.core.common import getPublicTypeMembers
|
from lib.core.common import getPublicTypeMembers
|
||||||
|
@ -915,15 +916,15 @@ def dictionaryAttack(attack_dict):
|
||||||
hash_ = hash_.lower()
|
hash_ = hash_.lower()
|
||||||
|
|
||||||
if hash_regex in (HASH.MD5_BASE64, HASH.SHA1_BASE64, HASH.SHA256_BASE64, HASH.SHA512_BASE64):
|
if hash_regex in (HASH.MD5_BASE64, HASH.SHA1_BASE64, HASH.SHA256_BASE64, HASH.SHA512_BASE64):
|
||||||
item = [(user, hash_.decode("base64").encode("hex")), {}]
|
item = [(user, decodeBase64(hash_, binary=False).encode("hex")), {}]
|
||||||
elif hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC, HASH.SHA224_GENERIC, HASH.SHA256_GENERIC, HASH.SHA384_GENERIC, HASH.SHA512_GENERIC, HASH.APACHE_SHA1):
|
elif hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC, HASH.SHA224_GENERIC, HASH.SHA256_GENERIC, HASH.SHA384_GENERIC, HASH.SHA512_GENERIC, HASH.APACHE_SHA1):
|
||||||
item = [(user, hash_), {}]
|
item = [(user, hash_), {}]
|
||||||
elif hash_regex in (HASH.SSHA,):
|
elif hash_regex in (HASH.SSHA,):
|
||||||
item = [(user, hash_), {"salt": hash_.decode("base64")[20:]}]
|
item = [(user, hash_), {"salt": decodeBase64(hash_, binary=False)[20:]}]
|
||||||
elif hash_regex in (HASH.SSHA256,):
|
elif hash_regex in (HASH.SSHA256,):
|
||||||
item = [(user, hash_), {"salt": hash_.decode("base64")[32:]}]
|
item = [(user, hash_), {"salt": decodeBase64(hash_, binary=False)[32:]}]
|
||||||
elif hash_regex in (HASH.SSHA512,):
|
elif hash_regex in (HASH.SSHA512,):
|
||||||
item = [(user, hash_), {"salt": hash_.decode("base64")[64:]}]
|
item = [(user, hash_), {"salt": decodeBase64(hash_, binary=False)[64:]}]
|
||||||
elif hash_regex in (HASH.ORACLE_OLD, HASH.POSTGRES):
|
elif hash_regex in (HASH.ORACLE_OLD, HASH.POSTGRES):
|
||||||
item = [(user, hash_), {'username': user}]
|
item = [(user, hash_), {'username': user}]
|
||||||
elif hash_regex in (HASH.ORACLE,):
|
elif hash_regex in (HASH.ORACLE,):
|
||||||
|
|
|
@ -33,6 +33,7 @@ from lib.core.settings import MAX_INT
|
||||||
from lib.core.settings import NULL
|
from lib.core.settings import NULL
|
||||||
from lib.core.unescaper import unescaper
|
from lib.core.unescaper import unescaper
|
||||||
from lib.request import inject
|
from lib.request import inject
|
||||||
|
from thirdparty import six
|
||||||
|
|
||||||
def pivotDumpTable(table, colList, count=None, blind=True, alias=None):
|
def pivotDumpTable(table, colList, count=None, blind=True, alias=None):
|
||||||
lengths = {}
|
lengths = {}
|
||||||
|
@ -142,7 +143,7 @@ def pivotDumpTable(table, colList, count=None, blind=True, alias=None):
|
||||||
if column == colList[0]:
|
if column == colList[0]:
|
||||||
if isNoneValue(value):
|
if isNoneValue(value):
|
||||||
try:
|
try:
|
||||||
for pivotValue in filterNone((" " if pivotValue == " " else None, "%s%s" % (pivotValue[0], unichr(ord(pivotValue[1]) + 1)) if len(pivotValue) > 1 else None, unichr(ord(pivotValue[0]) + 1))):
|
for pivotValue in filterNone((" " if pivotValue == " " else None, "%s%s" % (pivotValue[0], six.unichr(ord(pivotValue[1]) + 1)) if len(pivotValue) > 1 else None, six.unichr(ord(pivotValue[0]) + 1))):
|
||||||
value = _(column, pivotValue)
|
value = _(column, pivotValue)
|
||||||
if not isNoneValue(value):
|
if not isNoneValue(value):
|
||||||
break
|
break
|
||||||
|
|
|
@ -7,6 +7,8 @@ See the file 'LICENSE' for copying permission
|
||||||
|
|
||||||
import numbers
|
import numbers
|
||||||
|
|
||||||
|
from lib.core.compat import cmp
|
||||||
|
|
||||||
class xrange(object):
|
class xrange(object):
|
||||||
"""
|
"""
|
||||||
Advanced (re)implementation of xrange (supports slice/copy/etc.)
|
Advanced (re)implementation of xrange (supports slice/copy/etc.)
|
||||||
|
|
|
@ -22,6 +22,7 @@ from lib.utils.brute import columnExists
|
||||||
from lib.utils.pivotdumptable import pivotDumpTable
|
from lib.utils.pivotdumptable import pivotDumpTable
|
||||||
from plugins.generic.enumeration import Enumeration as GenericEnumeration
|
from plugins.generic.enumeration import Enumeration as GenericEnumeration
|
||||||
from thirdparty import six
|
from thirdparty import six
|
||||||
|
from thirdparty.six.moves import zip as _zip
|
||||||
|
|
||||||
class Enumeration(GenericEnumeration):
|
class Enumeration(GenericEnumeration):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -207,7 +208,7 @@ class Enumeration(GenericEnumeration):
|
||||||
table = {}
|
table = {}
|
||||||
columns = {}
|
columns = {}
|
||||||
|
|
||||||
for columnname, datatype, length in zip(retVal[0]["%s.columnname" % kb.aliasName], retVal[0]["%s.datatype" % kb.aliasName], retVal[0]["%s.len" % kb.aliasName]):
|
for columnname, datatype, length in _zip(retVal[0]["%s.columnname" % kb.aliasName], retVal[0]["%s.datatype" % kb.aliasName], retVal[0]["%s.len" % kb.aliasName]):
|
||||||
columns[safeSQLIdentificatorNaming(columnname)] = "%s(%s)" % (datatype, length)
|
columns[safeSQLIdentificatorNaming(columnname)] = "%s(%s)" % (datatype, length)
|
||||||
|
|
||||||
table[tbl] = columns
|
table[tbl] = columns
|
||||||
|
|
|
@ -27,6 +27,7 @@ from lib.utils.brute import columnExists
|
||||||
from lib.utils.pivotdumptable import pivotDumpTable
|
from lib.utils.pivotdumptable import pivotDumpTable
|
||||||
from plugins.generic.enumeration import Enumeration as GenericEnumeration
|
from plugins.generic.enumeration import Enumeration as GenericEnumeration
|
||||||
from thirdparty import six
|
from thirdparty import six
|
||||||
|
from thirdparty.six.moves import zip as _zip
|
||||||
|
|
||||||
class Enumeration(GenericEnumeration):
|
class Enumeration(GenericEnumeration):
|
||||||
def getUsers(self):
|
def getUsers(self):
|
||||||
|
@ -279,7 +280,7 @@ class Enumeration(GenericEnumeration):
|
||||||
table = {}
|
table = {}
|
||||||
columns = {}
|
columns = {}
|
||||||
|
|
||||||
for name, type_ in filterPairValues(zip(retVal[0]["%s.name" % kb.aliasName], retVal[0]["%s.usertype" % kb.aliasName])):
|
for name, type_ in filterPairValues(_zip(retVal[0]["%s.name" % kb.aliasName], retVal[0]["%s.usertype" % kb.aliasName])):
|
||||||
columns[name] = SYBASE_TYPES.get(int(type_) if hasattr(type_, "isdigit") and type_.isdigit() else type_, type_)
|
columns[name] = SYBASE_TYPES.get(int(type_) if hasattr(type_, "isdigit") and type_.isdigit() else type_, type_)
|
||||||
|
|
||||||
table[safeSQLIdentificatorNaming(tbl, True)] = columns
|
table[safeSQLIdentificatorNaming(tbl, True)] = columns
|
||||||
|
|
|
@ -46,6 +46,7 @@ from lib.request import inject
|
||||||
from lib.utils.hash import attackDumpedTable
|
from lib.utils.hash import attackDumpedTable
|
||||||
from lib.utils.pivotdumptable import pivotDumpTable
|
from lib.utils.pivotdumptable import pivotDumpTable
|
||||||
from thirdparty import six
|
from thirdparty import six
|
||||||
|
from thirdparty.six.moves import zip as _zip
|
||||||
|
|
||||||
class Entries:
|
class Entries:
|
||||||
"""
|
"""
|
||||||
|
@ -224,7 +225,7 @@ class Entries:
|
||||||
|
|
||||||
if retVal:
|
if retVal:
|
||||||
entries, _ = retVal
|
entries, _ = retVal
|
||||||
entries = zip(*[entries[colName] for colName in colList])
|
entries = _zip(*[entries[colName] for colName in colList])
|
||||||
else:
|
else:
|
||||||
query = rootQuery.inband.query % (colString, conf.db, tbl)
|
query = rootQuery.inband.query % (colString, conf.db, tbl)
|
||||||
elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2):
|
elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2):
|
||||||
|
|
|
@ -43,6 +43,7 @@ from lib.request import inject
|
||||||
from lib.utils.hash import attackCachedUsersPasswords
|
from lib.utils.hash import attackCachedUsersPasswords
|
||||||
from lib.utils.hash import storeHashesToFile
|
from lib.utils.hash import storeHashesToFile
|
||||||
from lib.utils.pivotdumptable import pivotDumpTable
|
from lib.utils.pivotdumptable import pivotDumpTable
|
||||||
|
from thirdparty.six.moves import zip as _zip
|
||||||
|
|
||||||
class Users:
|
class Users:
|
||||||
"""
|
"""
|
||||||
|
@ -192,7 +193,7 @@ class Users:
|
||||||
retVal = pivotDumpTable("(%s) AS %s" % (query, kb.aliasName), ['%s.name' % kb.aliasName, '%s.password' % kb.aliasName], blind=False)
|
retVal = pivotDumpTable("(%s) AS %s" % (query, kb.aliasName), ['%s.name' % kb.aliasName, '%s.password' % kb.aliasName], blind=False)
|
||||||
|
|
||||||
if retVal:
|
if retVal:
|
||||||
for user, password in filterPairValues(zip(retVal[0]["%s.name" % kb.aliasName], retVal[0]["%s.password" % kb.aliasName])):
|
for user, password in filterPairValues(_zip(retVal[0]["%s.name" % kb.aliasName], retVal[0]["%s.password" % kb.aliasName])):
|
||||||
if user not in kb.data.cachedUsersPasswords:
|
if user not in kb.data.cachedUsersPasswords:
|
||||||
kb.data.cachedUsersPasswords[user] = [password]
|
kb.data.cachedUsersPasswords[user] = [password]
|
||||||
else:
|
else:
|
||||||
|
@ -237,7 +238,7 @@ class Users:
|
||||||
retVal = pivotDumpTable("(%s) AS %s" % (query, kb.aliasName), ['%s.name' % kb.aliasName, '%s.password' % kb.aliasName], blind=True)
|
retVal = pivotDumpTable("(%s) AS %s" % (query, kb.aliasName), ['%s.name' % kb.aliasName, '%s.password' % kb.aliasName], blind=True)
|
||||||
|
|
||||||
if retVal:
|
if retVal:
|
||||||
for user, password in filterPairValues(zip(retVal[0]["%s.name" % kb.aliasName], retVal[0]["%s.password" % kb.aliasName])):
|
for user, password in filterPairValues(_zip(retVal[0]["%s.name" % kb.aliasName], retVal[0]["%s.password" % kb.aliasName])):
|
||||||
password = "0x%s" % hexencode(password, conf.encoding).upper()
|
password = "0x%s" % hexencode(password, conf.encoding).upper()
|
||||||
|
|
||||||
if user not in kb.data.cachedUsersPasswords:
|
if user not in kb.data.cachedUsersPasswords:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user