mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-08-31 07:36:06 +00:00
Merge branch 'master' into feature/add-SAP-HANA-support
This commit is contained in:
commit
9e92d3cc94
68 changed files with 34950 additions and 2635 deletions
|
|
@ -1088,7 +1088,7 @@ def heuristicCheckSqlInjection(place, parameter):
|
|||
if casting:
|
||||
errMsg = "possible %s casting detected (e.g. '" % ("integer" if origValue.isdigit() else "type")
|
||||
|
||||
platform = conf.url.split('.')[-1].lower()
|
||||
platform = (extractRegexResult(r"\.(?P<result>\w+)(?:\?|\Z)", conf.url) or conf.url.split('.')[-1]).lower()
|
||||
if platform == WEB_PLATFORM.ASP:
|
||||
errMsg += "%s=CInt(request.querystring(\"%s\"))" % (parameter, parameter)
|
||||
elif platform == WEB_PLATFORM.ASPX:
|
||||
|
|
@ -1238,7 +1238,7 @@ def checkDynamicContent(firstPage, secondPage):
|
|||
kb.heavilyDynamic = True
|
||||
|
||||
secondPage, _, _ = Request.queryPage(content=True)
|
||||
findDynamicContent(firstPage, secondPage)
|
||||
findDynamicContent(firstPage, secondPage, merge=True)
|
||||
|
||||
def checkStability():
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1229,6 +1229,9 @@ class Agent(object):
|
|||
def removePayloadDelimiters(self, value):
|
||||
"""
|
||||
Removes payload delimiters from inside the input string
|
||||
|
||||
>>> agent.removePayloadDelimiters(agent.addPayloadDelimiters("1 AND 1=1")) == "1 AND 1=1"
|
||||
True
|
||||
"""
|
||||
|
||||
return value.replace(PAYLOAD_DELIMITER, "") if value else value
|
||||
|
|
@ -1236,6 +1239,9 @@ class Agent(object):
|
|||
def extractPayload(self, value):
|
||||
"""
|
||||
Extracts payload from inside of the input string
|
||||
|
||||
>>> agent.extractPayload("prefix" + agent.addPayloadDelimiters("1 AND 1=1") + "suffix") == "1 AND 1=1"
|
||||
True
|
||||
"""
|
||||
|
||||
_ = re.escape(PAYLOAD_DELIMITER)
|
||||
|
|
|
|||
|
|
@ -655,7 +655,7 @@ def paramToDict(place, parameters=None):
|
|||
kb.base64Originals[parameter] = oldValue = value
|
||||
value = urldecode(value, convall=True)
|
||||
value = decodeBase64(value, binary=False, encoding=conf.encoding or UNICODE_ENCODING)
|
||||
parameters = re.sub(r"\b%s(\b|\Z)" % re.escape(oldValue), value, parameters)
|
||||
parameters = re.sub(r"\b%s(\b|\Z)" % re.escape(oldValue), value.replace('\\', r'\\'), parameters)
|
||||
except:
|
||||
errMsg = "parameter '%s' does not contain " % parameter
|
||||
errMsg += "valid Base64 encoded value ('%s')" % value
|
||||
|
|
@ -2467,7 +2467,7 @@ def getSQLSnippet(dbms, sfile, **variables):
|
|||
retVal = retVal.replace(_, randomStr())
|
||||
|
||||
for _ in re.findall(r"%RANDINT\d+%", retVal, re.I):
|
||||
retVal = retVal.replace(_, randomInt())
|
||||
retVal = retVal.replace(_, getText(randomInt()))
|
||||
|
||||
variables = re.findall(r"(?<!\bLIKE ')%(\w+)%", retVal, re.I)
|
||||
|
||||
|
|
@ -3237,11 +3237,15 @@ def aliasToDbmsEnum(dbms):
|
|||
|
||||
return retVal
|
||||
|
||||
def findDynamicContent(firstPage, secondPage):
|
||||
def findDynamicContent(firstPage, secondPage, merge=False):
|
||||
"""
|
||||
This function checks if the provided pages have dynamic content. If they
|
||||
are dynamic, proper markings will be made
|
||||
|
||||
Note: with merge=True the newly found markings are accumulated into the
|
||||
existing ones (e.g. when refining across multiple original-page samples)
|
||||
instead of replacing them
|
||||
|
||||
>>> findDynamicContent("Lorem ipsum dolor sit amet, congue tation referrentur ei sed. Ne nec legimus habemus recusabo, natum reque et per. Facer tritani reprehendunt eos id, modus constituam est te. Usu sumo indoctum ad, pri paulo molestiae complectitur no.", "Lorem ipsum dolor sit amet, congue tation referrentur ei sed. Ne nec legimus habemus recusabo, natum reque et per. <script src='ads.js'></script>Facer tritani reprehendunt eos id, modus constituam est te. Usu sumo indoctum ad, pri paulo molestiae complectitur no.")
|
||||
>>> kb.dynamicMarkings
|
||||
[('natum reque et per. ', 'Facer tritani repreh')]
|
||||
|
|
@ -3254,7 +3258,9 @@ def findDynamicContent(firstPage, secondPage):
|
|||
singleTimeLogMessage(infoMsg)
|
||||
|
||||
blocks = list(SequenceMatcher(None, firstPage, secondPage).get_matching_blocks())
|
||||
kb.dynamicMarkings = []
|
||||
|
||||
if not merge:
|
||||
kb.dynamicMarkings = []
|
||||
|
||||
# Removing too small matching blocks
|
||||
for block in blocks[:]:
|
||||
|
|
@ -3283,7 +3289,7 @@ def findDynamicContent(firstPage, secondPage):
|
|||
suffix = suffix[:DYNAMICITY_BOUNDARY_LENGTH]
|
||||
|
||||
for _ in (firstPage, secondPage):
|
||||
match = re.search(r"(?s)%s(.+)%s" % (re.escape(prefix), re.escape(suffix)), _)
|
||||
match = re.search(r"(?s)%s(.+?)%s" % (re.escape(prefix), re.escape(suffix)), _)
|
||||
if match:
|
||||
infix = match.group(1)
|
||||
if infix[0].isalnum():
|
||||
|
|
@ -3292,7 +3298,9 @@ def findDynamicContent(firstPage, secondPage):
|
|||
suffix = trimAlphaNum(suffix)
|
||||
break
|
||||
|
||||
kb.dynamicMarkings.append((prefix if prefix else None, suffix if suffix else None))
|
||||
marking = (prefix if prefix else None, suffix if suffix else None)
|
||||
if marking not in kb.dynamicMarkings: # Note: avoiding duplicates (e.g. when accumulating markings across samples)
|
||||
kb.dynamicMarkings.append(marking)
|
||||
|
||||
if len(kb.dynamicMarkings) > 0:
|
||||
infoMsg = "dynamic content marked for removal (%d region%s)" % (len(kb.dynamicMarkings), 's' if len(kb.dynamicMarkings) > 1 else '')
|
||||
|
|
@ -3311,11 +3319,11 @@ def removeDynamicContent(page):
|
|||
if prefix is None and suffix is None:
|
||||
continue
|
||||
elif prefix is None:
|
||||
page = re.sub(r"(?s)^.+%s" % re.escape(suffix), suffix.replace('\\', r'\\'), page)
|
||||
page = re.sub(r"(?s)^.+?%s" % re.escape(suffix), suffix.replace('\\', r'\\'), page)
|
||||
elif suffix is None:
|
||||
page = re.sub(r"(?s)%s.+$" % re.escape(prefix), prefix.replace('\\', r'\\'), page)
|
||||
else:
|
||||
page = re.sub(r"(?s)%s.+%s" % (re.escape(prefix), re.escape(suffix)), "%s%s" % (prefix.replace('\\', r'\\'), suffix.replace('\\', r'\\')), page)
|
||||
page = re.sub(r"(?s)%s.+?%s" % (re.escape(prefix), re.escape(suffix)), "%s%s" % (prefix.replace('\\', r'\\'), suffix.replace('\\', r'\\')), page)
|
||||
|
||||
return page
|
||||
|
||||
|
|
@ -4122,6 +4130,9 @@ def intersect(containerA, containerB, lowerCase=False):
|
|||
def decodeStringEscape(value):
|
||||
"""
|
||||
Decodes escaped string values (e.g. "\\t" -> "\t")
|
||||
|
||||
>>> decodeStringEscape("a" + chr(92) + "tb") == "a" + chr(9) + "b"
|
||||
True
|
||||
"""
|
||||
|
||||
retVal = value
|
||||
|
|
@ -4136,6 +4147,9 @@ def decodeStringEscape(value):
|
|||
def encodeStringEscape(value):
|
||||
"""
|
||||
Encodes escaped string values (e.g. "\t" -> "\\t")
|
||||
|
||||
>>> encodeStringEscape("a" + chr(9) + "b") == "a" + chr(92) + "tb"
|
||||
True
|
||||
"""
|
||||
|
||||
retVal = value
|
||||
|
|
@ -4283,7 +4297,10 @@ def safeSQLIdentificatorNaming(name, isTable=False):
|
|||
'[begin]'
|
||||
>>> getText(safeSQLIdentificatorNaming("foobar"))
|
||||
'foobar'
|
||||
>>> kb.forceDbms = popValue()
|
||||
>>> kb.forcedDbms = DBMS.FIREBIRD
|
||||
>>> getText(safeSQLIdentificatorNaming("foo bar"))
|
||||
'"foo bar"'
|
||||
>>> kb.forcedDbms = popValue()
|
||||
"""
|
||||
|
||||
retVal = name
|
||||
|
|
@ -4303,9 +4320,9 @@ def safeSQLIdentificatorNaming(name, isTable=False):
|
|||
if not conf.noEscape:
|
||||
retVal = unsafeSQLIdentificatorNaming(retVal)
|
||||
|
||||
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER): # Note: in SQLite double-quotes are treated as string if column/identifier is non-existent (e.g. SELECT "foobar" FROM users)
|
||||
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER, DBMS.CLICKHOUSE): # Note: in SQLite double-quotes are treated as string if column/identifier is non-existent (e.g. SELECT "foobar" FROM users)
|
||||
retVal = "`%s`" % retVal
|
||||
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE):
|
||||
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE, DBMS.FIREBIRD, DBMS.DERBY, DBMS.MAXDB):
|
||||
retVal = "\"%s\"" % retVal
|
||||
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL, DBMS.HANA):
|
||||
retVal = "\"%s\"" % retVal.upper()
|
||||
|
|
@ -4342,9 +4359,9 @@ def unsafeSQLIdentificatorNaming(name):
|
|||
retVal = name
|
||||
|
||||
if isinstance(name, six.string_types):
|
||||
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER):
|
||||
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER, DBMS.CLICKHOUSE):
|
||||
retVal = name.replace("`", "")
|
||||
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE):
|
||||
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE, DBMS.FIREBIRD, DBMS.DERBY, DBMS.MAXDB):
|
||||
retVal = name.replace("\"", "")
|
||||
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL, DBMS.HANA):
|
||||
retVal = name.replace("\"", "").upper()
|
||||
|
|
|
|||
|
|
@ -299,8 +299,8 @@ class Dump(object):
|
|||
colType = columns[column]
|
||||
|
||||
column = unsafeSQLIdentificatorNaming(column)
|
||||
maxlength1 = max(maxlength1, len(column or ""))
|
||||
maxlength2 = max(maxlength2, len(colType or ""))
|
||||
maxlength1 = max(maxlength1, getConsoleLength(column or ""))
|
||||
maxlength2 = max(maxlength2, getConsoleLength(colType or ""))
|
||||
|
||||
maxlength1 = max(maxlength1, len("COLUMN"))
|
||||
lines1 = "-" * (maxlength1 + 2)
|
||||
|
|
@ -337,10 +337,10 @@ class Dump(object):
|
|||
colType = columns[column]
|
||||
|
||||
column = unsafeSQLIdentificatorNaming(column)
|
||||
blank1 = " " * (maxlength1 - len(column))
|
||||
blank1 = " " * (maxlength1 - getConsoleLength(column))
|
||||
|
||||
if colType is not None:
|
||||
blank2 = " " * (maxlength2 - len(colType))
|
||||
blank2 = " " * (maxlength2 - getConsoleLength(colType))
|
||||
self._write("| %s%s | %s%s |" % (column, blank1, colType, blank2))
|
||||
else:
|
||||
self._write("| %s%s |" % (column, blank1))
|
||||
|
|
|
|||
|
|
@ -2071,7 +2071,7 @@ def _setKnowledgeBaseAttributes(flushAll=True):
|
|||
kb.cache = AttribDict()
|
||||
kb.cache.addrinfo = {}
|
||||
kb.cache.content = LRUDict(capacity=16)
|
||||
kb.cache.comparison = {}
|
||||
kb.cache.comparison = LRUDict(capacity=256)
|
||||
kb.cache.encoding = LRUDict(capacity=256)
|
||||
kb.cache.alphaBoundaries = None
|
||||
kb.cache.hashRegex = None
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from lib.core.enums import OS
|
|||
from thirdparty import six
|
||||
|
||||
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
|
||||
VERSION = "1.10.6.50"
|
||||
VERSION = "1.10.6.95"
|
||||
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)
|
||||
|
|
@ -62,7 +62,7 @@ LOWER_RATIO_BOUND = 0.02
|
|||
UPPER_RATIO_BOUND = 0.98
|
||||
|
||||
# For filling in case of dumb push updates
|
||||
DUMMY_JUNK = "fooj0Zo4"
|
||||
DUMMY_JUNK = "Phah5jue"
|
||||
|
||||
# Markers for special cases when parameter values contain html encoded characters
|
||||
PARAMETER_AMP_MARKER = "__PARAMETER_AMP__"
|
||||
|
|
@ -476,7 +476,7 @@ URI_INJECTABLE_REGEX = r"//[^/]*/([^\.*?]+)\Z"
|
|||
SENSITIVE_DATA_REGEX = r"(\s|=)(?P<result>[^\s=]*\b%s\b[^\s]*)\s"
|
||||
|
||||
# Options to explicitly mask in anonymous (unhandled exception) reports (along with anything carrying the <hostname> inside)
|
||||
SENSITIVE_OPTIONS = ("hostname", "answers", "data", "dnsDomain", "googleDork", "authCred", "proxyCred", "tbl", "db", "col", "user", "cookie", "proxy", "fileRead", "fileWrite", "fileDest", "testParameter", "authCred", "sqlQuery", "requestFile", "csrfToken", "csrfData", "csrfUrl", "testParameter")
|
||||
SENSITIVE_OPTIONS = ("hostname", "answers", "data", "dnsDomain", "googleDork", "proxyCred", "tbl", "db", "col", "user", "cookie", "proxy", "fileRead", "fileWrite", "fileDest", "authCred", "sqlQuery", "requestFile", "csrfToken", "csrfData", "csrfUrl", "testParameter")
|
||||
|
||||
# Maximum number of threads (avoiding connection issues and/or DoS)
|
||||
MAX_NUMBER_OF_THREADS = 10
|
||||
|
|
@ -554,7 +554,7 @@ UNSAFE_DUMP_FILEPATH_REPLACEMENT = '_'
|
|||
RESTORE_MERGED_OPTIONS = ("col", "db", "dbms", "os", "dnsDomain", "privEsc", "tbl", "regexp", "string", "textOnly", "threads", "timeSec", "tmpPath", "uChar", "user")
|
||||
|
||||
# Parameters to be ignored in detection phase (upper case)
|
||||
IGNORE_PARAMETERS = ("__VIEWSTATE", "__VIEWSTATEENCRYPTED", "__VIEWSTATEGENERATOR", "__EVENTARGUMENT", "__EVENTTARGET", "__EVENTVALIDATION", "ASPSESSIONID", "ASP.NET_SESSIONID", "JSESSIONID", "CFID", "CFTOKEN")
|
||||
IGNORE_PARAMETERS = ("__VIEWSTATE", "__VIEWSTATEENCRYPTED", "__VIEWSTATEGENERATOR", "__EVENTARGUMENT", "__EVENTTARGET", "__EVENTVALIDATION", "__SCROLLPOSITIONX", "__SCROLLPOSITIONY", "__PREVIOUSPAGE", "ASPSESSIONID", "ASP.NET_SESSIONID", "JSESSIONID", "PHPSESSID", "SESSID", "CFID", "CFTOKEN")
|
||||
|
||||
# Regular expression used for recognition of ASP.NET control parameters
|
||||
ASP_NET_CONTROL_REGEX = r"(?i)\Actl\d+\$"
|
||||
|
|
@ -626,7 +626,7 @@ DUMMY_SQL_INJECTION_CHARS = ";()'"
|
|||
DUMMY_USER_INJECTION = r"(?i)[^\w](AND|OR)\s+[^\s]+[=><]|\bUNION\b.+\bSELECT\b|\bSELECT\b.+\bFROM\b|\b(CONCAT|information_schema|SLEEP|DELAY|FLOOR\(RAND)\b"
|
||||
|
||||
# Extensions skipped by crawler
|
||||
CRAWL_EXCLUDE_EXTENSIONS = frozenset(("3ds", "3g2", "3gp", "7z", "DS_Store", "a", "aac", "accdb", "access", "adp", "ai", "aif", "aiff", "apk", "ar", "asf", "au", "avi", "bak", "bin", "bin", "bk", "bkp", "bmp", "btif", "bz2", "c", "cab", "caf", "cfg", "cgm", "cmx", "com", "conf", "config", "cpio", "cpp", "cr2", "cue", "dat", "db", "dbf", "deb", "debug", "djvu", "dll", "dmg", "dmp", "dng", "doc", "docx", "dot", "dotx", "dra", "dsk", "dts", "dtshd", "dvb", "dwg", "dxf", "dylib", "ear", "ecelp4800", "ecelp7470", "ecelp9600", "egg", "elf", "env", "eol", "eot", "epub", "error", "exe", "f4v", "fbs", "fh", "fla", "flac", "fli", "flv", "fpx", "fst", "fvt", "g3", "gif", "go", "gz", "h", "h261", "h263", "h264", "ico", "ief", "img", "ini", "ipa", "iso", "jar", "java", "jpeg", "jpg", "jpgv", "jpm", "js", "jxr", "ktx", "lock", "log", "lvp", "lz", "lzma", "lzo", "m3u", "m4a", "m4v", "mar", "mdb", "mdi", "mid", "mj2", "mka", "mkv", "mmr", "mng", "mov", "movie", "mp3", "mp4", "mp4a", "mpeg", "mpg", "mpga", "msi", "mxu", "nef", "npx", "nrg", "o", "oga", "ogg", "ogv", "old", "otf", "ova", "ovf", "pbm", "pcx", "pdf", "pea", "pgm", "php", "pic", "pid", "pkg", "png", "pnm", "ppm", "pps", "ppt", "pptx", "ps", "psd", "py", "pya", "pyc", "pyo", "pyv", "qt", "rar", "ras", "raw", "rb", "rgb", "rip", "rlc", "rs", "run", "rz", "s3m", "s7z", "scm", "scpt", "service", "sgi", "shar", "sil", "smv", "so", "sock", "socket", "sqlite", "sqlitedb", "sub", "svc", "swf", "swo", "swp", "sys", "tar", "tbz2", "temp", "tga", "tgz", "tif", "tiff", "tlz", "tmp", "toast", "torrent", "ts", "ts", "ttf", "uvh", "uvi", "uvm", "uvp", "uvs", "uvu", "vbox", "vdi", "vhd", "vhdx", "viv", "vmdk", "vmx", "vob", "vxd", "war", "wav", "wax", "wbmp", "wdp", "weba", "webm", "webp", "whl", "wm", "wma", "wmv", "wmx", "woff", "woff2", "wvx", "xbm", "xif", "xls", "xlsx", "xlt", "xm", "xpi", "xpm", "xwd", "xz", "yaml", "yml", "z", "zip", "zipx"))
|
||||
CRAWL_EXCLUDE_EXTENSIONS = frozenset(("3ds", "3g2", "3gp", "7z", "DS_Store", "a", "aac", "accdb", "access", "adp", "ai", "aif", "aiff", "apk", "ar", "asf", "au", "avi", "bak", "bin", "bk", "bkp", "bmp", "btif", "bz2", "c", "cab", "caf", "cfg", "cgm", "cmx", "com", "conf", "config", "cpio", "cpp", "cr2", "cue", "dat", "db", "dbf", "deb", "debug", "djvu", "dll", "dmg", "dmp", "dng", "doc", "docx", "dot", "dotx", "dra", "dsk", "dts", "dtshd", "dvb", "dwg", "dxf", "dylib", "ear", "ecelp4800", "ecelp7470", "ecelp9600", "egg", "elf", "env", "eol", "eot", "epub", "error", "exe", "f4v", "fbs", "fh", "fla", "flac", "fli", "flv", "fpx", "fst", "fvt", "g3", "gif", "go", "gz", "h", "h261", "h263", "h264", "ico", "ief", "img", "ini", "ipa", "iso", "jar", "java", "jpeg", "jpg", "jpgv", "jpm", "js", "jxr", "ktx", "lock", "log", "lvp", "lz", "lzma", "lzo", "m3u", "m4a", "m4v", "mar", "mdb", "mdi", "mid", "mj2", "mka", "mkv", "mmr", "mng", "mov", "movie", "mp3", "mp4", "mp4a", "mpeg", "mpg", "mpga", "msi", "mxu", "nef", "npx", "nrg", "o", "oga", "ogg", "ogv", "old", "otf", "ova", "ovf", "pbm", "pcx", "pdf", "pea", "pgm", "php", "pic", "pid", "pkg", "png", "pnm", "ppm", "pps", "ppt", "pptx", "ps", "psd", "py", "pya", "pyc", "pyo", "pyv", "qt", "rar", "ras", "raw", "rb", "rgb", "rip", "rlc", "rs", "run", "rz", "s3m", "s7z", "scm", "scpt", "service", "sgi", "shar", "sil", "smv", "so", "sock", "socket", "sqlite", "sqlitedb", "sub", "svc", "swf", "swo", "swp", "sys", "tar", "tbz2", "temp", "tga", "tgz", "tif", "tiff", "tlz", "tmp", "toast", "torrent", "ts", "ttf", "uvh", "uvi", "uvm", "uvp", "uvs", "uvu", "vbox", "vdi", "vhd", "vhdx", "viv", "vmdk", "vmx", "vob", "vxd", "war", "wav", "wax", "wbmp", "wdp", "weba", "webm", "webp", "whl", "wm", "wma", "wmv", "wmx", "woff", "woff2", "wvx", "xbm", "xif", "xls", "xlsx", "xlt", "xm", "xpi", "xpm", "xwd", "xz", "yaml", "yml", "z", "zip", "zipx"))
|
||||
|
||||
# Patterns often seen in HTTP headers containing custom injection marking character '*'
|
||||
PROBLEMATIC_CUSTOM_INJECTION_PATTERNS = r"(;q=[^;']+)|(\*/\*)"
|
||||
|
|
@ -828,7 +828,7 @@ MAX_HELP_OPTION_LENGTH = 18
|
|||
MAX_CONNECT_RETRIES = 100
|
||||
|
||||
# Strings for detecting formatting errors
|
||||
FORMAT_EXCEPTION_STRINGS = ("Type mismatch", "Error converting", "Please enter a", "Conversion failed", "String or binary data would be truncated", "Failed to convert", "unable to interpret text value", "Input string was not in a correct format", "System.FormatException", "java.lang.NumberFormatException", "ValueError: invalid literal", "TypeMismatchException", "CF_SQL_INTEGER", "CF_SQL_NUMERIC", " for CFSQLTYPE ", "cfqueryparam cfsqltype", "InvalidParamTypeException", "Invalid parameter type", "Attribute validation error for tag", "is not of type numeric", "<cfif Not IsNumeric(", "invalid input syntax for integer", "invalid input syntax for type", "invalid number", "character to number conversion error", "unable to interpret text value", "String was not recognized as a valid", "Convert.ToInt", "cannot be converted to a ", "InvalidDataException", "Arguments are of the wrong type", "Invalid conversion")
|
||||
FORMAT_EXCEPTION_STRINGS = ("Type mismatch", "Error converting", "Please enter a", "Conversion failed", "String or binary data would be truncated", "Failed to convert", "unable to interpret text value", "Input string was not in a correct format", "System.FormatException", "java.lang.NumberFormatException", "ValueError: invalid literal", "TypeMismatchException", "CF_SQL_INTEGER", "CF_SQL_NUMERIC", " for CFSQLTYPE ", "cfqueryparam cfsqltype", "InvalidParamTypeException", "Invalid parameter type", "Attribute validation error for tag", "is not of type numeric", "<cfif Not IsNumeric(", "invalid input syntax for integer", "invalid input syntax for type", "invalid number", "character to number conversion error", "String was not recognized as a valid", "Convert.ToInt", "cannot be converted to a ", "InvalidDataException", "Arguments are of the wrong type", "Invalid conversion")
|
||||
|
||||
# Regular expression used for extracting ASP.NET view state values
|
||||
VIEWSTATE_REGEX = r'(?i)(?P<name>__VIEWSTATE[^"]*)[^>]+value="(?P<result>[^"]+)'
|
||||
|
|
@ -842,13 +842,13 @@ LIMITED_ROWS_TEST_NUMBER = 15
|
|||
# Default adapter to use for bottle server
|
||||
RESTAPI_DEFAULT_ADAPTER = "wsgiref"
|
||||
|
||||
# Default REST-JSON API server listen address
|
||||
# Default REST API server listen address
|
||||
RESTAPI_DEFAULT_ADDRESS = "127.0.0.1"
|
||||
|
||||
# Default REST-JSON API server listen port
|
||||
# Default REST API server listen port
|
||||
RESTAPI_DEFAULT_PORT = 8775
|
||||
|
||||
# Unsupported options by REST-JSON API server
|
||||
# Unsupported options by REST API server
|
||||
RESTAPI_UNSUPPORTED_OPTIONS = ("sqlShell", "wizard", "evalCode", "alert")
|
||||
|
||||
# Use "Supplementary Private Use Area-A"
|
||||
|
|
@ -973,14 +973,14 @@ for key, value in os.environ.items():
|
|||
_ = key[len(SQLMAP_ENVIRONMENT_PREFIX) + 1:].upper()
|
||||
if _ in globals():
|
||||
original = globals()[_]
|
||||
if isinstance(original, int):
|
||||
if isinstance(original, bool):
|
||||
globals()[_] = value.lower() in ('1', 'true')
|
||||
elif isinstance(original, int):
|
||||
try:
|
||||
globals()[_] = int(value)
|
||||
except ValueError:
|
||||
pass
|
||||
elif isinstance(original, bool):
|
||||
globals()[_] = value.lower() in ('1', 'true')
|
||||
elif isinstance(original, (list, tuple)):
|
||||
globals()[_] = [__.strip() for __ in _.split(',')]
|
||||
globals()[_] = [__.strip() for __ in value.split(',')]
|
||||
else:
|
||||
globals()[_] = value
|
||||
|
|
|
|||
|
|
@ -87,8 +87,10 @@ class Wordlist(six.Iterator):
|
|||
errMsg += "sure that you haven't made any changes to it"
|
||||
raise SqlmapInstallationException(errMsg)
|
||||
except StopIteration:
|
||||
self.adjust()
|
||||
retVal = next(self.iter).rstrip()
|
||||
if self.index > len(self.filenames): # Note: no more sources (filenames + custom) to switch to
|
||||
raise
|
||||
self.adjust() # Note: switch to the next source and retry (gracefully skipping empty ones)
|
||||
continue
|
||||
if not self.proc_count or self.counter % self.proc_count == self.proc_id:
|
||||
break
|
||||
return retVal
|
||||
|
|
|
|||
|
|
@ -235,13 +235,13 @@ def checkCharEncoding(encoding, warn=True):
|
|||
# Reference: http://docs.python.org/library/codecs.html
|
||||
try:
|
||||
codecs.lookup(encoding)
|
||||
except:
|
||||
except LookupError:
|
||||
encoding = None
|
||||
|
||||
if encoding:
|
||||
try:
|
||||
six.text_type(getBytes(randomStr()), encoding)
|
||||
except:
|
||||
except (UnicodeDecodeError, LookupError):
|
||||
if warn:
|
||||
warnMsg = "invalid web page charset '%s'" % encoding
|
||||
singleTimeLogMessage(warnMsg, logging.WARN, encoding)
|
||||
|
|
@ -258,10 +258,11 @@ def getHeuristicCharEncoding(page):
|
|||
|
||||
>>> getHeuristicCharEncoding(b"<html></html>")
|
||||
'ascii'
|
||||
>>> getHeuristicCharEncoding(b'<!DOCTYPE html><html><head><meta charset="windows-1251"><title>\\xd2\\xe5\\xf1\\xf2</title></head><body>\\xc2 \\xf1\\xee\\xee\\xf2\\xe2\\xe5\\xf2\\xf1\\xf2\\xe2\\xe8\\xe8 \\xf1 \\xef\\xf0\\xe8\\xed\\xf6\\xe8\\xef\\xe0\\xec\\xe8 \\xf0\\xe0\\xe1\\xee\\xf2\\xfb \\xf3\\xf2\\xe8\\xeb\\xe8\\xf2\\xfb \\xe0\\xe2\\xf2\\xee\\xec\\xe0\\xf2\\xe8\\xf7\\xe5\\xf1\\xea\\xee\\xe3\\xee \\xee\\xef\\xf0\\xe5\\xe4\\xe5\\xeb\\xe5\\xed\\xe8\\xff \\xea\\xee\\xe4\\xe8\\xf0\\xee\\xe2\\xea\\xe8, \\xed\\xe0\\xec \\xf2\\xf0\\xe5\\xe1\\xf3\\xe5\\xf2\\xf1\\xff \\xef\\xf0\\xe5\\xe4\\xee\\xf1\\xf2\\xe0\\xe2\\xe8\\xf2\\xfc \\xe7\\xed\\xe0\\xf7\\xe8\\xf2\\xe5\\xeb\\xfc\\xed\\xee \\xe1\\xee\\xeb\\xe5\\xe5 \\xe4\\xeb\\xe8\\xed\\xed\\xfb\\xe9 \\xf4\\xf0\\xe0\\xe3\\xec\\xe5\\xed\\xf2 \\xf2\\xe5\\xea\\xf1\\xf2\\xe0 \\xed\\xe0 \\xf0\\xf3\\xf1\\xf1\\xea\\xee\\xec \\xff\\xe7\\xfb\\xea\\xe5. \\xdd\\xf2\\xee \\xed\\xe5\\xee\\xb1\\xf5\\xee\\xe4\\xe8\\xec\\xee \\xe4\\xeb\\xff \\xf2\\xee\\xe3\\xee, \\xf7\\xf2\\xee\\xf1\\xfb \\xf1\\xf2\\xe0\\xf2\\xe8\\xf1\\xf2\\xe8\\xf7\\xe5\\xf1\\xea\\xe8\\xe9 \\xe0\\xed\\xe0\\xeb\\xe8\\xe7\\xe0\\xf2\\xee\\xf0 \\xf7\\xe0\\xf1\\xf2\\xee\\xf2\\xed\\xee\\xf1\\xf2\\xe8 \\xf1\\xe8\\xec\\xe2\\xee\\xeb\\xee\\xe2 \\xe8 \\xe4\\xe2\\xf3\\xf5\\xe1\\xf3\\xea\\xe2\\xe5\\xed\\xed\\xfb\\xf5 \\xf1\\xee\\xf7\\xe5\\xf2\\xe0\\xed\\xe8\\xb9 \\xf1\\xec\\xee\\xe3 \\xf1 \\xe2\\xfb\\xf1\\xee\\xea\\xee\\xb9 \\xf1\\xf2\\xe5\\xef\\xe5\\xed\\xfc\\xf2 \\xf3\\xe2\\xe5\\xf0\\xe5\\xed\\xed\\xee\\xf1\\xf2\\xe8 \\xe7\\xe0\\xf4\\xe8\\xea\\xf1\\xe8\\xf0\\xee\\xe2\\xe0\\xf2\\xfc \\xe8\\xec\\xe5\\xed\\xed\\xee \\xf1\\xf2\\xe0\\xed\\xe4\\xe0\\xf0\\xf2 Windows-1251, \\xe0 \\xed\\xe5 MacCyrillic \\xe8\\xeb\\xe8 ISO-8859-5. \\xd0\\xf3\\xf1\\xf1\\xea\\xe8\\xb9 \\xff\\xe7\\xfb\\xea \\xee\\xe1\\xbb\\xe0\\xe4\\xe0\\xe5\\xf2 \\xf3\\xed\\xe8\\xea\\xe0\\xeb\\xfc\\xed\\xfb\\xec \\xf0\\xe0\\xf1\\xef\\xf0\\xe5\\xe4\\xe5\\xeb\\xe5\\xed\\xe8\\xe5\\xec \\xe3\\xeb\\xe0\\xf1\\xed\\xfb\\xf5 \\xe8 \\xf1\\xee\\xe3\\xeb\\xe0\\xf1\\xed\\xfb\\xf5 \\xe1\\xf3\\xea\\xe2, \\xf2\\xe0\\xea\\xe8\\xf5 \\xea\\xe0\\xea \\xee, \\xe5, \\xe0, \\xe8, \\xed, \\xf2, \\xea\\xee\\xf2\\xee\\xf0\\xfb\\xe5 \\xe2 \\xf0\\xe0\\xe7\\xed\\xfb\\xf5 \\xea\\xee\\xe4\\xee\\xe2\\xfb\\xf5 \\xf1\\xf2\\xf0\\xe0\\xed\\xe8\\xf6\\xe0\\xf5 \\xe7\\xe0\\xed\\xe8\\xec\\xe0\\xf3\\xf2 \\xf1\\xee\\xe2\\xe5\\xf0\\xf8\\xe5\\xed\\xed\\xee \\xf0\\xe0\\xe7\\xed\\xfb\\xe5 \\xef\\xee\\xf7\\xe8\\xf6\\xe8\\xe8 \\xe2 \\xf2\\xe0\\xe1\\xeb\\xe8\\xf6\\xe5 \\xe1\\xe0\\xb9\\xf2\\xee\\xe2. \\xca\\xee\\xe3\\xe4\\xe0 \\xf2\\xe5\\xea\\xf1\\xf2\\xe0 \\xf1\\xf2\\xe0\\xed\\xee\\xe2\\xe8\\xf2\\xf1\\xff \\xe4\\xee\\xf1\\xf2\\xe0\\xf2\\xee\\xf7\\xed\\xee \\xec\\xed\\xee\\xe3\\xee, \\xe2\\xe5\\xf0\\xee\\xff\\xf2\\xed\\xee\\xf1\\xf2\\xfc \\xee\\xf8\\xe8\\xe1\\xea\\xe8 \\xf1\\xed\\xe8\\xe6\\xe0\\xe5\\xf2\\xf1\\xff \\xef\\xf0\\xe0\\xea\\xf2\\xe8\\xf7\\xe5\\xf1\\xea\\xe8 \\xe4\\xee \\xed\\xf3\\xeb\\xff. \\xcc\\xfb \\xe4\\xee\\xe1\\xe0\\xe2\\xeb\\xff\\xe5\\xec \\xe5\\xf9\\xe5 \\xed\\xe5\\xf1\\xea\\xee\\xeb\\xfc\\xea\\xee \\xef\\xf0\\xe5\\xe4\\xeb\\xee\\xe6\\xe5\\xed\\xe8\\xb9, \\xf7\\xf2\\xee\\xf1\\xfb \\xf0\\xe0\\xf1\\xf8\\xe8\\xf0\\xe8\\xf2\\xfc \\xe2\\xfb\\xe1\\xee\\xf0\\xea\\xf3 \\xe4\\xe0\\xed\\xed\\xfb\\xf5 \\xe4\\xeb\\xff \\xea\\xee\\xf0\\xf0\\xe5\\xea\\xf2\\xed\\xee\\xe3\\xee \\xf2\\xe5\\xf1\\xf2\\xe8\\xf0\\xee\\xe2\\xe0\\xed\\xe8\\xff \\xe2\\xe0\\xf8\\xe5\\xb9 \\xe1\\xe8\\xe1\\xeb\\xe8\\xee\\xf2\\xe5\\xea\\xe8 \\xe2 \\xf1\\xf0\\xe5\\xe4\\xe5 Python.</body></html>')
|
||||
'windows-1251'
|
||||
"""
|
||||
|
||||
key = (len(page), hash(page))
|
||||
|
||||
retVal = kb.cache.encoding.get(key)
|
||||
if retVal is None:
|
||||
retVal = detect(page[:HEURISTIC_PAGE_SIZE_THRESHOLD])["encoding"]
|
||||
|
|
@ -281,6 +282,8 @@ def decodePage(page, contentEncoding, contentType, percentDecode=True):
|
|||
'<html>foo&bar</html>'
|
||||
>>> getText(decodePage(b"	", None, "text/html; charset=utf-8"))
|
||||
'\\t'
|
||||
>>> getText(decodePage(b"J", None, "text/html; charset=utf-8"))
|
||||
'J'
|
||||
"""
|
||||
|
||||
if not page or (conf.nullConnection and len(page) < 2):
|
||||
|
|
@ -345,14 +348,13 @@ def decodePage(page, contentEncoding, contentType, percentDecode=True):
|
|||
if not kb.disableHtmlDecoding:
|
||||
# e.g. 	Ãëàâà
|
||||
if b"&#" in page:
|
||||
page = re.sub(b"&#x([0-9a-f]{1,2});", lambda _: decodeHex(_.group(1) if len(_.group(1)) == 2 else b"0%s" % _.group(1)), page)
|
||||
page = re.sub(b"(?i)&#x([0-9a-f]{1,2});", lambda _: decodeHex(_.group(1) if len(_.group(1)) == 2 else b"0%s" % _.group(1)), page)
|
||||
page = re.sub(b"&#(\\d{1,3});", lambda _: six.int2byte(int(_.group(1))) if int(_.group(1)) < 256 else _.group(0), page)
|
||||
|
||||
# e.g. %20%28%29
|
||||
if percentDecode:
|
||||
if b"%" in page:
|
||||
page = re.sub(b"%([0-9a-f]{2})", lambda _: decodeHex(_.group(1)), page)
|
||||
page = re.sub(b"%([0-9A-F]{2})", lambda _: decodeHex(_.group(1)), page) # Note: %DeepSee_SQL in CACHE
|
||||
page = re.sub(b"(?i)%([0-9a-f]{2})", lambda _: decodeHex(_.group(1)), page)
|
||||
|
||||
# e.g. &
|
||||
page = re.sub(b"&([^;]+);", lambda _: six.int2byte(HTML_ENTITIES[getText(_.group(1))]) if HTML_ENTITIES.get(getText(_.group(1)), 256) < 256 else _.group(0), page)
|
||||
|
|
|
|||
|
|
@ -1163,7 +1163,7 @@ class Connect(object):
|
|||
postUrlEncode = False
|
||||
|
||||
if conf.hpp:
|
||||
if not any(conf.url.lower().endswith(_.lower()) for _ in (WEB_PLATFORM.ASP, WEB_PLATFORM.ASPX)):
|
||||
if (extractRegexResult(r"\.(?P<result>\w+)(?:\?|\Z)", conf.url) or "").lower() not in (WEB_PLATFORM.ASP, WEB_PLATFORM.ASPX):
|
||||
warnMsg = "HTTP parameter pollution should work only against "
|
||||
warnMsg += "ASP(.NET) targets"
|
||||
singleTimeWarnMessage(warnMsg)
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
|
|||
try:
|
||||
result = _urllib.request.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
|
||||
except _urllib.error.HTTPError as ex:
|
||||
result = ex
|
||||
result = error = ex
|
||||
|
||||
# Dirty hack for https://github.com/sqlmapproject/sqlmap/issues/4046
|
||||
try:
|
||||
|
|
@ -178,7 +178,7 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
|
|||
if not hasattr(result, "read"):
|
||||
def _(self, length=None):
|
||||
try:
|
||||
retVal = getSafeExString(ex) # Note: pyflakes mistakenly marks 'ex' as undefined (NOTE: tested in both Python2 and Python3)
|
||||
retVal = getSafeExString(error)
|
||||
except:
|
||||
retVal = ""
|
||||
return getBytes(retVal)
|
||||
|
|
|
|||
|
|
@ -231,8 +231,8 @@ class UDF(object):
|
|||
errMsg += "but the database underlying operating system is Linux"
|
||||
raise SqlmapMissingMandatoryOptionException(errMsg)
|
||||
|
||||
self.udfSharedLibName = os.path.basename(self.udfLocalFile).split(".")[0]
|
||||
self.udfSharedLibExt = os.path.basename(self.udfLocalFile).split(".")[1]
|
||||
self.udfSharedLibName = os.path.splitext(os.path.basename(self.udfLocalFile))[0]
|
||||
self.udfSharedLibExt = os.path.splitext(self.udfLocalFile)[1][1:]
|
||||
|
||||
msg = "how many user-defined functions do you want to create "
|
||||
msg += "from the shared library? "
|
||||
|
|
|
|||
|
|
@ -152,6 +152,8 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
|
|||
lastChar = 0
|
||||
elif conf.lastChar is not None and (isinstance(conf.lastChar, int) or (hasattr(conf.lastChar, "isdigit") and conf.lastChar.isdigit())):
|
||||
lastChar = int(conf.lastChar)
|
||||
if kb.fileReadMode: # Note: file content is retrieved hex-encoded (2 chars per byte), mirroring the firstChar handling above
|
||||
lastChar <<= 1
|
||||
elif hasattr(lastChar, "isdigit") and lastChar.isdigit() or isinstance(lastChar, int):
|
||||
lastChar = int(lastChar)
|
||||
else:
|
||||
|
|
@ -512,6 +514,8 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
|
|||
threadData.shared.value = [None] * length
|
||||
threadData.shared.index = [firstChar] # As list for python nested function scoping
|
||||
threadData.shared.start = firstChar
|
||||
threadData.shared.retrieved = 0
|
||||
threadData.shared.endIndex = 0
|
||||
|
||||
try:
|
||||
def blindThread():
|
||||
|
|
@ -537,7 +541,11 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
|
|||
break
|
||||
|
||||
with kb.locks.value:
|
||||
threadData.shared.value[currentCharIndex - 1 - firstChar] = val
|
||||
idx = currentCharIndex - 1 - firstChar
|
||||
threadData.shared.value[idx] = val
|
||||
threadData.shared.retrieved += 1
|
||||
if idx > threadData.shared.endIndex:
|
||||
threadData.shared.endIndex = idx
|
||||
currentValue = list(threadData.shared.value)
|
||||
|
||||
if kb.threadContinue:
|
||||
|
|
@ -545,25 +553,18 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
|
|||
progress.progress(threadData.shared.index[0])
|
||||
elif conf.verbose >= 1:
|
||||
startCharIndex = 0
|
||||
endCharIndex = 0
|
||||
|
||||
for i in xrange(length):
|
||||
if currentValue[i] is not None:
|
||||
endCharIndex = max(endCharIndex, i)
|
||||
endCharIndex = threadData.shared.endIndex
|
||||
|
||||
output = ''
|
||||
|
||||
if endCharIndex > conf.progressWidth:
|
||||
startCharIndex = endCharIndex - conf.progressWidth
|
||||
|
||||
count = threadData.shared.start
|
||||
count = threadData.shared.start + threadData.shared.retrieved
|
||||
|
||||
for i in xrange(startCharIndex, endCharIndex + 1):
|
||||
output += '_' if currentValue[i] is None else filterControlChars(currentValue[i] if len(currentValue[i]) == 1 else ' ', replacement=' ')
|
||||
|
||||
for i in xrange(length):
|
||||
count += 1 if currentValue[i] is not None else 0
|
||||
|
||||
if startCharIndex > 0:
|
||||
output = ".." + output[2:]
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ class DataStore(object):
|
|||
username = None
|
||||
password = None
|
||||
|
||||
RESTAPI_READONLY_OPTIONS = ("api", "taskid", "database")
|
||||
|
||||
# API objects
|
||||
class Database(object):
|
||||
filepath = None
|
||||
|
|
@ -91,7 +93,7 @@ class Database(object):
|
|||
self.connection = sqlite3.connect(self.database, timeout=3, isolation_level=None, check_same_thread=False)
|
||||
self.cursor = self.connection.cursor()
|
||||
self.lock = threading.Lock()
|
||||
logger.debug("REST-JSON API %s connected to IPC database" % who)
|
||||
logger.debug("REST API %s connected to IPC database" % who)
|
||||
|
||||
def disconnect(self):
|
||||
if self.cursor:
|
||||
|
|
@ -296,6 +298,19 @@ def setRestAPILog():
|
|||
def is_admin(token):
|
||||
return safeCompareStrings(DataStore.admin_token, token)
|
||||
|
||||
def validate_task_options(taskid, options, caller):
|
||||
if not isinstance(options, dict):
|
||||
logger.warning("[%s] Invalid JSON options provided to %s()" % (taskid, caller))
|
||||
return "Invalid JSON options"
|
||||
|
||||
for key in options:
|
||||
if key in RESTAPI_UNSUPPORTED_OPTIONS or key in RESTAPI_READONLY_OPTIONS:
|
||||
logger.warning("[%s] Unsupported option '%s' provided to %s()" % (taskid, key, caller))
|
||||
return "Unsupported option '%s'" % key
|
||||
elif key not in DataStore.tasks[taskid].options:
|
||||
logger.warning("[%s] Unknown option '%s' provided to %s()" % (taskid, key, caller))
|
||||
return "Unknown option '%s'" % key
|
||||
|
||||
@hook('before_request')
|
||||
def check_authentication():
|
||||
if not any((DataStore.username, DataStore.password)):
|
||||
|
|
@ -490,6 +505,10 @@ def option_set(taskid):
|
|||
logger.warning("[%s] Invalid JSON options provided to option_set()" % taskid)
|
||||
return jsonize({"success": False, "message": "Invalid JSON options"})
|
||||
|
||||
message = validate_task_options(taskid, request.json, "option_set")
|
||||
if message:
|
||||
return jsonize({"success": False, "message": message})
|
||||
|
||||
for option, value in request.json.items():
|
||||
DataStore.tasks[taskid].set_option(option, value)
|
||||
|
||||
|
|
@ -511,10 +530,13 @@ def scan_start(taskid):
|
|||
logger.warning("[%s] Invalid JSON options provided to scan_start()" % taskid)
|
||||
return jsonize({"success": False, "message": "Invalid JSON options"})
|
||||
|
||||
for key in request.json:
|
||||
if key in RESTAPI_UNSUPPORTED_OPTIONS:
|
||||
logger.warning("[%s] Unsupported option '%s' provided to scan_start()" % (taskid, key))
|
||||
return jsonize({"success": False, "message": "Unsupported option '%s'" % key})
|
||||
if DataStore.tasks[taskid].engine_process() is not None and not DataStore.tasks[taskid].engine_has_terminated():
|
||||
logger.warning("[%s] Scan already running" % taskid)
|
||||
return jsonize({"success": False, "message": "Scan already running"})
|
||||
|
||||
message = validate_task_options(taskid, request.json, "scan_start")
|
||||
if message:
|
||||
return jsonize({"success": False, "message": message})
|
||||
|
||||
# Initialize sqlmap engine's options with user's provided options, if any
|
||||
for option, value in request.json.items():
|
||||
|
|
@ -596,7 +618,7 @@ def scan_data(taskid):
|
|||
json_data_message.append({"status": status, "type": content_type, "value": dejsonize(value)})
|
||||
|
||||
# Read all error messages from the IPC database
|
||||
for error in DataStore.current_db.execute("SELECT error FROM errors WHERE taskid = ? ORDER BY id ASC", (taskid,)):
|
||||
for error, in DataStore.current_db.execute("SELECT error FROM errors WHERE taskid = ? ORDER BY id ASC", (taskid,)):
|
||||
json_errors_message.append(error)
|
||||
|
||||
logger.debug("(%s) Retrieved scan data and error messages" % taskid)
|
||||
|
|
@ -684,9 +706,12 @@ def version(token=None):
|
|||
|
||||
def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=RESTAPI_DEFAULT_ADAPTER, username=None, password=None, database=None):
|
||||
"""
|
||||
REST-JSON API server
|
||||
REST API server
|
||||
"""
|
||||
|
||||
if not all((username, password)):
|
||||
logger.critical("REST API server requires both username and password")
|
||||
|
||||
DataStore.admin_token = encodeHex(os.urandom(16), binary=False)
|
||||
DataStore.username = username
|
||||
DataStore.password = password
|
||||
|
|
@ -702,7 +727,7 @@ def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=REST
|
|||
s.bind((host, 0))
|
||||
port = s.getsockname()[1]
|
||||
|
||||
logger.info("Running REST-JSON API server at '%s:%d'.." % (host, port))
|
||||
logger.info("Running REST API server at '%s:%d'.." % (host, port))
|
||||
logger.info("Admin (secret) token: %s" % DataStore.admin_token)
|
||||
logger.debug("IPC database: '%s'" % Database.filepath)
|
||||
|
||||
|
|
@ -762,7 +787,7 @@ def _client(url, options=None):
|
|||
|
||||
def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, username=None, password=None):
|
||||
"""
|
||||
REST-JSON API client
|
||||
REST API client
|
||||
"""
|
||||
|
||||
DataStore.username = username
|
||||
|
|
@ -770,20 +795,20 @@ def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, username=Non
|
|||
|
||||
dbgMsg = "Example client access from command line:"
|
||||
dbgMsg += "\n\t$ taskid=$(curl http://%s:%d/task/new 2>1 | grep -o -I '[a-f0-9]\\{16\\}') && echo $taskid" % (host, port)
|
||||
dbgMsg += "\n\t$ curl -H \"Content-Type: application/json\" -X POST -d '{\"url\": \"http://testphp.vulnweb.com/artists.php?artist=1\"}' http://%s:%d/scan/$taskid/start" % (host, port)
|
||||
dbgMsg += "\n\t$ curl -H \"Content-Type: application/json\" -X POST -d '{\"url\": \"http://testasp.vulnweb.com/showforum.asp?id=1\"}' http://%s:%d/scan/$taskid/start" % (host, port)
|
||||
dbgMsg += "\n\t$ curl http://%s:%d/scan/$taskid/data" % (host, port)
|
||||
dbgMsg += "\n\t$ curl http://%s:%d/scan/$taskid/log" % (host, port)
|
||||
logger.debug(dbgMsg)
|
||||
|
||||
addr = "http://%s:%d" % (host, port)
|
||||
logger.info("Starting REST-JSON API client to '%s'..." % addr)
|
||||
logger.info("Starting REST API client to '%s'..." % addr)
|
||||
|
||||
try:
|
||||
_client(addr)
|
||||
except Exception as ex:
|
||||
if not isinstance(ex, _urllib.error.HTTPError) or ex.code == _http_client.UNAUTHORIZED:
|
||||
errMsg = "There has been a problem while connecting to the "
|
||||
errMsg += "REST-JSON API server at '%s' " % addr
|
||||
errMsg += "REST API server at '%s' " % addr
|
||||
errMsg += "(%s)" % getSafeExString(ex)
|
||||
logger.critical(errMsg)
|
||||
return
|
||||
|
|
@ -900,7 +925,7 @@ def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, username=Non
|
|||
|
||||
elif command in ("help", "?"):
|
||||
msg = "help Show this help message\n"
|
||||
msg += "new ARGS Start a new scan task with provided arguments (e.g. 'new -u \"http://testphp.vulnweb.com/artists.php?artist=1\"')\n"
|
||||
msg += "new ARGS Start a new scan task with provided arguments (e.g. 'new -u \"http://testasp.vulnweb.com/showforum.asp?id=1\"')\n"
|
||||
msg += "use TASKID Switch current context to different task (e.g. 'use c04d8c5c7582efb4')\n"
|
||||
msg += "data Retrieve and show data for current task\n"
|
||||
msg += "log Retrieve and show log for current task\n"
|
||||
|
|
|
|||
|
|
@ -616,7 +616,7 @@ def _finalize(retVal, results, processes, attack_info=None):
|
|||
removals.add((user, hash_))
|
||||
hashDBWrite(hash_, word)
|
||||
|
||||
for item in attack_info or []:
|
||||
for item in list(attack_info or []):
|
||||
if (item[0][0], item[0][1]) in removals:
|
||||
attack_info.remove(item)
|
||||
|
||||
|
|
@ -1081,7 +1081,7 @@ def dictionaryAttack(attack_dict):
|
|||
|
||||
if item and hash_ not in keys:
|
||||
resumed = hashDBRetrieve(hash_)
|
||||
if not resumed:
|
||||
if resumed is None:
|
||||
attack_info.append(item)
|
||||
user_hash.append(item[0])
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ class HashDB(object):
|
|||
if retVal is None:
|
||||
retVal = self._read_cache.get(hash_)
|
||||
|
||||
if not retVal:
|
||||
if retVal is None:
|
||||
for _ in xrange(HASHDB_RETRIEVE_RETRIES):
|
||||
try:
|
||||
for row in self.cursor.execute("SELECT value FROM storage WHERE id=?", (hash_,)):
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from lib.core.common import singleTimeWarnMessage
|
|||
from lib.core.common import unArrayizeValue
|
||||
from lib.core.common import unsafeSQLIdentificatorNaming
|
||||
from lib.core.compat import xrange
|
||||
from lib.core.convert import getConsoleLength
|
||||
from lib.core.convert import getUnicode
|
||||
from lib.core.data import conf
|
||||
from lib.core.data import kb
|
||||
|
|
@ -58,7 +59,7 @@ def pivotDumpTable(table, colList, count=None, blind=True, alias=None):
|
|||
logger.info(infoMsg)
|
||||
|
||||
for column in colList:
|
||||
lengths[column] = len(column)
|
||||
lengths[column] = getConsoleLength(column)
|
||||
entries[column] = []
|
||||
|
||||
return entries, lengths
|
||||
|
|
@ -169,7 +170,7 @@ def pivotDumpTable(table, colList, count=None, blind=True, alias=None):
|
|||
|
||||
value = "" if isNoneValue(value) else unArrayizeValue(value)
|
||||
|
||||
lengths[column] = max(lengths[column], len(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value))))
|
||||
lengths[column] = max(lengths[column], getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value))))
|
||||
entries[column].append(value)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
|
|
|
|||
|
|
@ -74,6 +74,11 @@ def safecharencode(value):
|
|||
def safechardecode(value, binary=False):
|
||||
"""
|
||||
Reverse function to safecharencode
|
||||
|
||||
>>> safechardecode(u'test123') == u'test123'
|
||||
True
|
||||
>>> safechardecode(safecharencode(u'test\x01\x02\xaf')) == u'test\x01\x02\xaf'
|
||||
True
|
||||
"""
|
||||
|
||||
retVal = value
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import socket
|
|||
from lib.core.common import getSafeExString
|
||||
from lib.core.common import popValue
|
||||
from lib.core.common import pushValue
|
||||
from lib.core.common import readInput
|
||||
from lib.core.common import urlencode
|
||||
from lib.core.convert import getBytes
|
||||
from lib.core.convert import getUnicode
|
||||
|
|
@ -24,7 +23,6 @@ from lib.core.enums import HTTP_HEADER
|
|||
from lib.core.enums import REDIRECTION
|
||||
from lib.core.exception import SqlmapBaseException
|
||||
from lib.core.exception import SqlmapConnectionException
|
||||
from lib.core.exception import SqlmapUserQuitException
|
||||
from lib.core.settings import BING_REGEX
|
||||
from lib.core.settings import DUCKDUCKGO_REGEX
|
||||
from lib.core.settings import DUMMY_SEARCH_USER_AGENT
|
||||
|
|
@ -37,152 +35,102 @@ from thirdparty.six.moves import http_client as _http_client
|
|||
from thirdparty.six.moves import urllib as _urllib
|
||||
from thirdparty.socks import socks
|
||||
|
||||
def _fetch(url, headers, data=None):
|
||||
"""
|
||||
Fetches and returns the (decoded) content of a search engine results page
|
||||
(or None in case of a connection issue)
|
||||
"""
|
||||
|
||||
retVal = None
|
||||
|
||||
try:
|
||||
req = _urllib.request.Request(url, data=getBytes(data) if data else None, headers=headers)
|
||||
conn = _urllib.request.urlopen(req)
|
||||
|
||||
requestMsg = "HTTP request:\n%s %s" % ("POST" if data else "GET", url)
|
||||
requestMsg += " %s" % _http_client.HTTPConnection._http_vsn_str
|
||||
logger.log(CUSTOM_LOGGING.TRAFFIC_OUT, requestMsg)
|
||||
|
||||
page = conn.read()
|
||||
responseHeaders = conn.info()
|
||||
|
||||
responseMsg = "HTTP response (%s - %d):\n" % (conn.msg, conn.code)
|
||||
if conf.verbose <= 4:
|
||||
responseMsg += getUnicode(responseHeaders, UNICODE_ENCODING)
|
||||
elif conf.verbose > 4:
|
||||
responseMsg += "%s\n%s\n" % (responseHeaders, page)
|
||||
logger.log(CUSTOM_LOGGING.TRAFFIC_IN, responseMsg)
|
||||
|
||||
page = decodePage(page, responseHeaders.get(HTTP_HEADER.CONTENT_ENCODING), responseHeaders.get(HTTP_HEADER.CONTENT_TYPE))
|
||||
retVal = getUnicode(page) # Note: if decodePage call fails (Issue #4202)
|
||||
except _urllib.error.HTTPError as ex:
|
||||
try:
|
||||
retVal = getUnicode(ex.read())
|
||||
except Exception:
|
||||
pass
|
||||
except (_urllib.error.URLError, _http_client.error, socket.error, socket.timeout, socks.ProxyError):
|
||||
pass
|
||||
|
||||
return retVal
|
||||
|
||||
def _search(dork):
|
||||
"""
|
||||
This method performs the effective search on Google providing
|
||||
the google dork and the Google session cookie
|
||||
This method performs the effective search using the provided dork,
|
||||
trying the available search engines in order of (current) scraping
|
||||
reliability and returning the results of the first one that yields any
|
||||
(so that the failure of a single engine does not break the feature)
|
||||
"""
|
||||
|
||||
if not dork:
|
||||
return None
|
||||
|
||||
page = None
|
||||
data = None
|
||||
requestHeaders = {}
|
||||
responseHeaders = {}
|
||||
retVal = []
|
||||
seen = set()
|
||||
|
||||
requestHeaders[HTTP_HEADER.USER_AGENT] = dict(conf.httpHeaders).get(HTTP_HEADER.USER_AGENT, DUMMY_SEARCH_USER_AGENT)
|
||||
requestHeaders[HTTP_HEADER.ACCEPT_ENCODING] = HTTP_ACCEPT_ENCODING_HEADER_VALUE
|
||||
requestHeaders[HTTP_HEADER.COOKIE] = GOOGLE_CONSENT_COOKIE
|
||||
|
||||
try:
|
||||
req = _urllib.request.Request("https://www.google.com/ncr", headers=requestHeaders)
|
||||
conn = _urllib.request.urlopen(req)
|
||||
except Exception as ex:
|
||||
errMsg = "unable to connect to Google ('%s')" % getSafeExString(ex)
|
||||
raise SqlmapConnectionException(errMsg)
|
||||
requestHeaders = {
|
||||
HTTP_HEADER.USER_AGENT: dict(conf.httpHeaders).get(HTTP_HEADER.USER_AGENT, DUMMY_SEARCH_USER_AGENT),
|
||||
HTTP_HEADER.ACCEPT_ENCODING: HTTP_ACCEPT_ENCODING_HEADER_VALUE,
|
||||
HTTP_HEADER.COOKIE: GOOGLE_CONSENT_COOKIE,
|
||||
}
|
||||
|
||||
gpage = conf.googlePage if conf.googlePage > 1 else 1
|
||||
logger.info("using search result page #%d" % gpage)
|
||||
|
||||
url = "https://www.google.com/search?" # NOTE: if consent fails, try to use the "http://"
|
||||
url += "q=%s&" % urlencode(dork, convall=True)
|
||||
url += "num=100&hl=en&complete=0&safe=off&filter=0&btnG=Search"
|
||||
url += "&start=%d" % ((gpage - 1) * 100)
|
||||
encoded = urlencode(dork, convall=True)
|
||||
|
||||
try:
|
||||
req = _urllib.request.Request(url, headers=requestHeaders)
|
||||
conn = _urllib.request.urlopen(req)
|
||||
# Note: (name, url, POST data, regex, regex flags, match->link). Ordered by current scraping reliability; tried in turn until one yields results (DuckDuckGo currently being the only consistently scrapeable one)
|
||||
engines = (
|
||||
("DuckDuckGo", "https://html.duckduckgo.com/html/", "q=%s&s=%d" % (encoded, (gpage - 1) * 30), DUCKDUCKGO_REGEX, re.I | re.S, lambda match: match.group(1).replace("&", "&")),
|
||||
("Bing", "https://www.bing.com/search?q=%s&first=%d" % (encoded, (gpage - 1) * 10 + 1), None, BING_REGEX, re.I | re.S, lambda match: match.group(1)),
|
||||
("Google", "https://www.google.com/search?q=%s&num=100&hl=en&complete=0&safe=off&filter=0&btnG=Search&start=%d" % (encoded, (gpage - 1) * 100), None, GOOGLE_REGEX, re.I, lambda match: match.group(1) or match.group(2)),
|
||||
)
|
||||
|
||||
requestMsg = "HTTP request:\nGET %s" % url
|
||||
requestMsg += " %s" % _http_client.HTTPConnection._http_vsn_str
|
||||
logger.log(CUSTOM_LOGGING.TRAFFIC_OUT, requestMsg)
|
||||
for name, url, data, regex, flags, extract in engines:
|
||||
page = _fetch(url, requestHeaders, data)
|
||||
|
||||
page = conn.read()
|
||||
code = conn.code
|
||||
status = conn.msg
|
||||
responseHeaders = conn.info()
|
||||
if not page:
|
||||
continue
|
||||
|
||||
responseMsg = "HTTP response (%s - %d):\n" % (status, code)
|
||||
count = 0
|
||||
for match in re.finditer(regex, page, flags):
|
||||
link = _urllib.parse.unquote(extract(match))
|
||||
if link and link not in seen:
|
||||
seen.add(link)
|
||||
retVal.append(link)
|
||||
count += 1
|
||||
|
||||
if conf.verbose <= 4:
|
||||
responseMsg += getUnicode(responseHeaders, UNICODE_ENCODING)
|
||||
elif conf.verbose > 4:
|
||||
responseMsg += "%s\n%s\n" % (responseHeaders, page)
|
||||
if count:
|
||||
logger.info("found %d usable link%s using %s" % (count, 's' if count != 1 else "", name))
|
||||
break # Note: stop at the first engine that actually returns results (others are only fallbacks)
|
||||
|
||||
logger.log(CUSTOM_LOGGING.TRAFFIC_IN, responseMsg)
|
||||
except _urllib.error.HTTPError as ex:
|
||||
try:
|
||||
page = ex.read()
|
||||
responseHeaders = ex.info()
|
||||
except Exception as _:
|
||||
warnMsg = "problem occurred while trying to get "
|
||||
warnMsg += "an error page information (%s)" % getSafeExString(_)
|
||||
logger.critical(warnMsg)
|
||||
return None
|
||||
except (_urllib.error.URLError, _http_client.error, socket.error, socket.timeout, socks.ProxyError):
|
||||
errMsg = "unable to connect to Google"
|
||||
raise SqlmapConnectionException(errMsg)
|
||||
|
||||
page = decodePage(page, responseHeaders.get(HTTP_HEADER.CONTENT_ENCODING), responseHeaders.get(HTTP_HEADER.CONTENT_TYPE))
|
||||
|
||||
page = getUnicode(page) # Note: if decodePage call fails (Issue #4202)
|
||||
|
||||
retVal = [_urllib.parse.unquote(match.group(1) or match.group(2)) for match in re.finditer(GOOGLE_REGEX, page, re.I)]
|
||||
|
||||
if not retVal and "detected unusual traffic" in page:
|
||||
warnMsg = "Google has detected 'unusual' traffic from "
|
||||
warnMsg += "used IP address disabling further searches"
|
||||
|
||||
if conf.proxyList:
|
||||
# Note: switch proxy (if available) when an abuse/captcha page was served (instead of pointlessly falling through to the next engine from the same blocked IP)
|
||||
if conf.proxyList and (("detected unusual traffic" in page) or ("issue with the Tor Exit Node you are currently using" in page)):
|
||||
warnMsg = "%s has detected 'unusual' traffic from the used IP address" % name
|
||||
raise SqlmapBaseException(warnMsg)
|
||||
else:
|
||||
logger.critical(warnMsg)
|
||||
|
||||
if not retVal:
|
||||
message = "no usable links found. What do you want to do?"
|
||||
message += "\n[1] (re)try with DuckDuckGo (default)"
|
||||
message += "\n[2] (re)try with Bing"
|
||||
message += "\n[3] quit"
|
||||
choice = readInput(message, default='1')
|
||||
|
||||
if choice == '3':
|
||||
raise SqlmapUserQuitException
|
||||
elif choice == '2':
|
||||
url = "https://www.bing.com/search?q=%s&first=%d" % (urlencode(dork, convall=True), (gpage - 1) * 10 + 1)
|
||||
regex = BING_REGEX
|
||||
else:
|
||||
url = "https://html.duckduckgo.com/html/"
|
||||
data = "q=%s&s=%d" % (urlencode(dork, convall=True), (gpage - 1) * 30)
|
||||
regex = DUCKDUCKGO_REGEX
|
||||
|
||||
try:
|
||||
req = _urllib.request.Request(url, data=getBytes(data), headers=requestHeaders)
|
||||
conn = _urllib.request.urlopen(req)
|
||||
|
||||
requestMsg = "HTTP request:\nGET %s" % url
|
||||
requestMsg += " %s" % _http_client.HTTPConnection._http_vsn_str
|
||||
logger.log(CUSTOM_LOGGING.TRAFFIC_OUT, requestMsg)
|
||||
|
||||
page = conn.read()
|
||||
code = conn.code
|
||||
status = conn.msg
|
||||
responseHeaders = conn.info()
|
||||
page = decodePage(page, responseHeaders.get("Content-Encoding"), responseHeaders.get("Content-Type"))
|
||||
|
||||
responseMsg = "HTTP response (%s - %d):\n" % (status, code)
|
||||
|
||||
if conf.verbose <= 4:
|
||||
responseMsg += getUnicode(responseHeaders, UNICODE_ENCODING)
|
||||
elif conf.verbose > 4:
|
||||
responseMsg += "%s\n%s\n" % (responseHeaders, page)
|
||||
|
||||
logger.log(CUSTOM_LOGGING.TRAFFIC_IN, responseMsg)
|
||||
except _urllib.error.HTTPError as ex:
|
||||
try:
|
||||
page = ex.read()
|
||||
page = decodePage(page, ex.headers.get("Content-Encoding"), ex.headers.get("Content-Type"))
|
||||
except socket.timeout:
|
||||
warnMsg = "connection timed out while trying "
|
||||
warnMsg += "to get error page information (%d)" % ex.code
|
||||
logger.critical(warnMsg)
|
||||
return None
|
||||
except:
|
||||
errMsg = "unable to connect"
|
||||
raise SqlmapConnectionException(errMsg)
|
||||
|
||||
page = getUnicode(page) # Note: if decodePage call fails (Issue #4202)
|
||||
|
||||
retVal = [_urllib.parse.unquote(match.group(1).replace("&", "&")) for match in re.finditer(regex, page, re.I | re.S)]
|
||||
|
||||
if not retVal and "issue with the Tor Exit Node you are currently using" in page:
|
||||
warnMsg = "DuckDuckGo has detected 'unusual' traffic from "
|
||||
warnMsg += "used (Tor) IP address"
|
||||
|
||||
if conf.proxyList:
|
||||
raise SqlmapBaseException(warnMsg)
|
||||
else:
|
||||
logger.critical(warnMsg)
|
||||
warnMsg = "no usable links found (search engines might be blocking the used IP address)"
|
||||
logger.critical(warnMsg)
|
||||
|
||||
return retVal
|
||||
|
||||
|
|
@ -206,6 +154,7 @@ def search(dork):
|
|||
return search(dork)
|
||||
else:
|
||||
raise
|
||||
|
||||
finally:
|
||||
kb.choices.redirect = popValue()
|
||||
|
||||
|
|
|
|||
|
|
@ -122,6 +122,8 @@ class SQLAlchemy(GenericConnector):
|
|||
|
||||
try:
|
||||
self.cursor = self.connector.execute(query)
|
||||
if hasattr(self.connector, "commit"): # Note: SQLAlchemy 2.0+ dropped implicit autocommit (otherwise DML changes - e.g. via --sql-query - would be silently lost)
|
||||
self.connector.commit()
|
||||
retVal = True
|
||||
except (_sqlalchemy.exc.OperationalError, _sqlalchemy.exc.ProgrammingError) as ex:
|
||||
logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex))
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ class xrange(object):
|
|||
True
|
||||
>>> list(xrange(0, 7, 2)) == list(range(0, 7, 2))
|
||||
True
|
||||
>>> list(xrange(8, 0, -2)) == list(range(8, 0, -2))
|
||||
True
|
||||
>>> foobar = xrange(1, 10)
|
||||
>>> 7 in foobar
|
||||
True
|
||||
|
|
@ -33,6 +35,12 @@ class xrange(object):
|
|||
False
|
||||
>>> foobar[0]
|
||||
1
|
||||
>>> 6 in xrange(8, 0, -2)
|
||||
True
|
||||
>>> 0 in xrange(8, 0, -2)
|
||||
False
|
||||
>>> xrange(0, 10, 2).index(4)
|
||||
2
|
||||
"""
|
||||
|
||||
__slots__ = ['_slice']
|
||||
|
|
@ -71,10 +79,17 @@ class xrange(object):
|
|||
return self._len()
|
||||
|
||||
def _len(self):
|
||||
return max(0, 1 + int((self.stop - 1 - self.start) // self.step))
|
||||
if self.step > 0:
|
||||
lo, hi, step = self.start, self.stop, self.step
|
||||
else: # Note: normalizing for descending ranges (negative step)
|
||||
lo, hi, step = self.stop, self.start, -self.step
|
||||
return max(0, (hi - lo + step - 1) // step)
|
||||
|
||||
def __contains__(self, value):
|
||||
return (self.start <= value < self.stop) and (value - self.start) % self.step == 0
|
||||
if self.step > 0:
|
||||
return self.start <= value < self.stop and (value - self.start) % self.step == 0
|
||||
else:
|
||||
return self.stop < value <= self.start and (value - self.start) % self.step == 0
|
||||
|
||||
def __getitem__(self, index):
|
||||
if isinstance(index, slice):
|
||||
|
|
@ -98,7 +113,7 @@ class xrange(object):
|
|||
return self.start + self.step * i
|
||||
|
||||
def index(self, i):
|
||||
if self.start <= i < self.stop:
|
||||
return i - self.start
|
||||
if i in self:
|
||||
return (i - self.start) // self.step # Note: also accounts for step != 1 (and descending ranges)
|
||||
else:
|
||||
raise ValueError("%d is not in list" % i)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue