diff --git a/lib/core/common.py b/lib/core/common.py
index 77fc7bfa4..ff981fc77 100755
--- a/lib/core/common.py
+++ b/lib/core/common.py
@@ -86,6 +86,7 @@ from lib.core.exception import SqlmapSilentQuitException
from lib.core.exception import SqlmapSyntaxException
from lib.core.exception import SqlmapSystemException
from lib.core.exception import SqlmapUserQuitException
+from lib.core.exception import SqlmapValueException
from lib.core.log import LOGGER_HANDLER
from lib.core.optiondict import optDict
from lib.core.settings import BANNER
@@ -1638,7 +1639,9 @@ def safeStringFormat(format_, params):
match = re.search(r"(\A|[^A-Za-z0-9])(%s)([^A-Za-z0-9]|\Z)", retVal)
if match:
if count >= len(params):
- raise Exception("wrong number of parameters during string formatting")
+ warnMsg = "wrong number of parameters during string formatting. "
+ warnMsg += "Please report by e-mail content \"%r | %r | %r\" to 'dev@sqlmap.org'" % (format_, params, retVal)
+ raise SqlmapValueException(warnMsg)
else:
retVal = re.sub(r"(\A|[^A-Za-z0-9])(%s)([^A-Za-z0-9]|\Z)", r"\g<1>%s\g<3>" % params[count], retVal, 1)
count += 1
@@ -1738,7 +1741,7 @@ def posixToNtSlashes(filepath):
'C:\\\\Windows'
"""
- return filepath.replace('/', '\\')
+ return filepath.replace('/', '\\') if filepath else filepath
def ntToPosixSlashes(filepath):
"""
@@ -1749,7 +1752,7 @@ def ntToPosixSlashes(filepath):
'C:/Windows'
"""
- return filepath.replace('\\', '/')
+ return filepath.replace('\\', '/') if filepath else filepath
def isHexEncodedString(subject):
"""
diff --git a/lib/core/enums.py b/lib/core/enums.py
index 785daf95e..64e074ca2 100644
--- a/lib/core/enums.py
+++ b/lib/core/enums.py
@@ -177,6 +177,7 @@ class HTTP_HEADER:
TRANSFER_ENCODING = "Transfer-Encoding"
URI = "URI"
VIA = "Via"
+ X_POWERED_BY = "X-Powered-By"
class EXPECTED:
BOOL = "bool"
diff --git a/lib/parse/payloads.py b/lib/parse/payloads.py
index 89e077098..24e481b12 100644
--- a/lib/parse/payloads.py
+++ b/lib/parse/payloads.py
@@ -20,13 +20,13 @@ def cleanupVals(text, tag):
text = text.split(',')
if isinstance(text, basestring):
- text = int(text) if text.isdigit() else str(text)
+ text = int(text) if text.isdigit() else text
elif isinstance(text, list):
count = 0
for _ in text:
- text[count] = int(_) if _.isdigit() else str(_)
+ text[count] = int(_) if _.isdigit() else _
count += 1
if len(text) == 1 and tag not in ("clause", "where"):
diff --git a/lib/request/basic.py b/lib/request/basic.py
index 21ecd2a34..d5bcc96d4 100755
--- a/lib/request/basic.py
+++ b/lib/request/basic.py
@@ -103,7 +103,7 @@ def forgeHeaders(items=None):
kb.mergeCookies = not _ or _[0] in ("y", "Y")
if kb.mergeCookies and kb.injection.place != PLACE.COOKIE:
- _ = lambda x: re.sub(r"(?i)\b%s=[^%s]+" % (re.escape(cookie.name), conf.cookieDel or DEFAULT_COOKIE_DELIMITER), "%s=%s" % (cookie.name, getUnicode(cookie.value)), x)
+ _ = lambda x: re.sub(r"(?i)\b%s=[^%s]+" % (re.escape(cookie.name), conf.cookieDel or DEFAULT_COOKIE_DELIMITER), ("%s=%s" % (cookie.name, getUnicode(cookie.value))).replace('\\', r'\\'), x)
headers[HTTP_HEADER.COOKIE] = _(headers[HTTP_HEADER.COOKIE])
if PLACE.COOKIE in conf.parameters:
@@ -156,6 +156,8 @@ def checkCharEncoding(encoding, warn=True):
if delimiter in encoding:
encoding = encoding[:encoding.find(delimiter)].strip()
+ encoding = encoding.replace(""", "")
+
# popular typos/errors
if "8858" in encoding:
encoding = encoding.replace("8858", "8859") # iso-8858 -> iso-8859
@@ -189,6 +191,8 @@ def checkCharEncoding(encoding, warn=True):
encoding = "ascii"
elif encoding.find("utf8") > 0:
encoding = "utf8"
+ elif encoding.find("utf-8") > 0:
+ encoding = "utf-8"
# Reference: http://philip.html5.org/data/charsets-2.html
if encoding in translate:
diff --git a/lib/request/connect.py b/lib/request/connect.py
index 93414961e..ccb3588d1 100644
--- a/lib/request/connect.py
+++ b/lib/request/connect.py
@@ -456,6 +456,9 @@ class Connect(object):
for cookie in conf.cj:
if cookie.value is None:
cookie.value = ""
+ else:
+ for char in (r"\r", r"\n"):
+ cookie.value = re.sub(r"(%s)([^ \t])" % char, r"\g<1>\t\g<2>", cookie.value)
conn = urllib2.urlopen(req)
diff --git a/lib/request/httpshandler.py b/lib/request/httpshandler.py
index 5cf0613ca..9ada558d2 100644
--- a/lib/request/httpshandler.py
+++ b/lib/request/httpshandler.py
@@ -5,6 +5,7 @@ Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/)
See the file 'doc/COPYING' for copying permission
"""
+import distutils.version
import httplib
import socket
import urllib2
@@ -13,6 +14,7 @@ from lib.core.common import getSafeExString
from lib.core.data import kb
from lib.core.data import logger
from lib.core.exception import SqlmapConnectionException
+from lib.core.settings import PYVERSION
ssl = None
try:
@@ -84,7 +86,10 @@ class HTTPSConnection(httplib.HTTPSConnection):
logger.debug("SSL connection error occurred ('%s')" % getSafeExString(ex))
if not success:
- raise SqlmapConnectionException("can't establish SSL connection")
+ errMsg = "can't establish SSL connection"
+ if distutils.version.LooseVersion(PYVERSION) < distutils.version.LooseVersion("2.7.10"):
+ errMsg += " (please retry with Python >= 2.7.10)"
+ raise SqlmapConnectionException(errMsg)
class HTTPSHandler(urllib2.HTTPSHandler):
def https_open(self, req):
diff --git a/plugins/dbms/maxdb/enumeration.py b/plugins/dbms/maxdb/enumeration.py
index 95ec6a385..7b9d1d20a 100644
--- a/plugins/dbms/maxdb/enumeration.py
+++ b/plugins/dbms/maxdb/enumeration.py
@@ -7,16 +7,20 @@ See the file 'doc/COPYING' for copying permission
from lib.core.common import Backend
from lib.core.common import randomStr
+from lib.core.common import readInput
from lib.core.common import safeSQLIdentificatorNaming
from lib.core.common import unsafeSQLIdentificatorNaming
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
+from lib.core.data import paths
from lib.core.data import queries
from lib.core.exception import SqlmapMissingMandatoryOptionException
from lib.core.exception import SqlmapNoneDataException
+from lib.core.exception import SqlmapUserQuitException
from lib.core.settings import CURRENT_DB
from lib.utils.pivotdumptable import pivotDumpTable
+from lib.techniques.brute.use import columnExists
from plugins.generic.enumeration import Enumeration as GenericEnumeration
class Enumeration(GenericEnumeration):
@@ -91,7 +95,7 @@ class Enumeration(GenericEnumeration):
return kb.data.cachedTables
- def getColumns(self, onlyColNames=False):
+ def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False):
self.forceDbmsEnum()
if conf.db is None or conf.db == CURRENT_DB:
@@ -111,6 +115,17 @@ class Enumeration(GenericEnumeration):
conf.db = safeSQLIdentificatorNaming(conf.db)
+ if conf.col:
+ colList = conf.col.split(",")
+ else:
+ colList = []
+
+ if conf.excludeCol:
+ colList = [_ for _ in colList if _ not in conf.excludeCol.split(',')]
+
+ for col in colList:
+ colList[colList.index(col)] = safeSQLIdentificatorNaming(col)
+
if conf.tbl:
tblList = conf.tbl.split(",")
else:
@@ -129,6 +144,43 @@ class Enumeration(GenericEnumeration):
for tbl in tblList:
tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True)
+ if bruteForce:
+ resumeAvailable = False
+
+ for tbl in tblList:
+ for db, table, colName, colType in kb.brute.columns:
+ if db == conf.db and table == tbl:
+ resumeAvailable = True
+ break
+
+ if resumeAvailable and not conf.freshQueries or colList:
+ columns = {}
+
+ for column in colList:
+ columns[column] = None
+
+ for tbl in tblList:
+ for db, table, colName, colType in kb.brute.columns:
+ if db == conf.db and table == tbl:
+ columns[colName] = colType
+
+ if conf.db in kb.data.cachedColumns:
+ kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns
+ else:
+ kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = {safeSQLIdentificatorNaming(tbl, True): columns}
+
+ return kb.data.cachedColumns
+
+ message = "do you want to use common column existence check? [y/N/q] "
+ test = readInput(message, default="Y" if "Y" in message else "N")
+
+ if test[0] in ("n", "N"):
+ return
+ elif test[0] in ("q", "Q"):
+ raise SqlmapUserQuitException
+ else:
+ return columnExists(paths.COMMON_COLUMNS)
+
rootQuery = queries[Backend.getIdentifiedDbms()].columns
for tbl in tblList:
@@ -141,6 +193,12 @@ class Enumeration(GenericEnumeration):
return {conf.db: kb.data.cachedColumns[conf.db]}
+ if dumpMode and colList:
+ table = {}
+ table[safeSQLIdentificatorNaming(tbl)] = dict((_, None) for _ in colList)
+ kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = table
+ continue
+
infoMsg = "fetching columns "
infoMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl)
infoMsg += "on database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
diff --git a/plugins/dbms/mysql/takeover.py b/plugins/dbms/mysql/takeover.py
index ebd7bdc03..9c02c7a68 100644
--- a/plugins/dbms/mysql/takeover.py
+++ b/plugins/dbms/mysql/takeover.py
@@ -60,7 +60,7 @@ class Takeover(GenericTakeover):
else:
self.__plugindir = "%s/lib/mysql/plugin" % self.__basedir
- self.__plugindir = ntToPosixSlashes(normalizePath(self.__plugindir))
+ self.__plugindir = ntToPosixSlashes(normalizePath(self.__plugindir)) or '.'
self.udfRemoteFile = "%s/%s.%s" % (self.__plugindir, self.udfSharedLibName, self.udfSharedLibExt)
@@ -74,7 +74,7 @@ class Takeover(GenericTakeover):
# NOTE: specifying the relative path as './udf.dll'
# saves in @@datadir on both MySQL 4.1 and MySQL 5.0
- self.__datadir = "."
+ self.__datadir = '.'
self.__datadir = ntToPosixSlashes(normalizePath(self.__datadir))
# The DLL can be in either C:\WINDOWS, C:\WINDOWS\system,
diff --git a/plugins/dbms/sybase/enumeration.py b/plugins/dbms/sybase/enumeration.py
index 09a0356af..e0707d9b4 100644
--- a/plugins/dbms/sybase/enumeration.py
+++ b/plugins/dbms/sybase/enumeration.py
@@ -9,19 +9,23 @@ from lib.core.common import Backend
from lib.core.common import filterPairValues
from lib.core.common import isTechniqueAvailable
from lib.core.common import randomStr
+from lib.core.common import readInput
from lib.core.common import safeSQLIdentificatorNaming
from lib.core.common import unArrayizeValue
from lib.core.common import unsafeSQLIdentificatorNaming
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
+from lib.core.data import paths
from lib.core.data import queries
from lib.core.dicts import SYBASE_TYPES
from lib.core.enums import PAYLOAD
from lib.core.exception import SqlmapMissingMandatoryOptionException
from lib.core.exception import SqlmapNoneDataException
+from lib.core.exception import SqlmapUserQuitException
from lib.core.settings import CURRENT_DB
from lib.utils.pivotdumptable import pivotDumpTable
+from lib.techniques.brute.use import columnExists
from plugins.generic.enumeration import Enumeration as GenericEnumeration
class Enumeration(GenericEnumeration):
@@ -159,7 +163,7 @@ class Enumeration(GenericEnumeration):
return kb.data.cachedTables
- def getColumns(self, onlyColNames=False):
+ def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False):
self.forceDbmsEnum()
if conf.db is None or conf.db == CURRENT_DB:
@@ -208,6 +212,43 @@ class Enumeration(GenericEnumeration):
for tbl in tblList:
tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl)
+ if bruteForce:
+ resumeAvailable = False
+
+ for tbl in tblList:
+ for db, table, colName, colType in kb.brute.columns:
+ if db == conf.db and table == tbl:
+ resumeAvailable = True
+ break
+
+ if resumeAvailable and not conf.freshQueries or colList:
+ columns = {}
+
+ for column in colList:
+ columns[column] = None
+
+ for tbl in tblList:
+ for db, table, colName, colType in kb.brute.columns:
+ if db == conf.db and table == tbl:
+ columns[colName] = colType
+
+ if conf.db in kb.data.cachedColumns:
+ kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns
+ else:
+ kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = {safeSQLIdentificatorNaming(tbl, True): columns}
+
+ return kb.data.cachedColumns
+
+ message = "do you want to use common column existence check? [y/N/q] "
+ test = readInput(message, default="Y" if "Y" in message else "N")
+
+ if test[0] in ("n", "N"):
+ return
+ elif test[0] in ("q", "Q"):
+ raise SqlmapUserQuitException
+ else:
+ return columnExists(paths.COMMON_COLUMNS)
+
rootQuery = queries[Backend.getIdentifiedDbms()].columns
if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
@@ -225,7 +266,7 @@ class Enumeration(GenericEnumeration):
return {conf.db: kb.data.cachedColumns[conf.db]}
- if colList:
+ if dumpMode and colList:
table = {}
table[safeSQLIdentificatorNaming(tbl)] = dict((_, None) for _ in colList)
kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = table
diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py
index 8070fc0ad..23e0b4cf6 100644
--- a/plugins/generic/databases.py
+++ b/plugins/generic/databases.py
@@ -238,7 +238,7 @@ class Databases:
return kb.data.cachedTables
- message = "do you want to use common table existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS,) else "[y/N/q]")
+ message = "do you want to use common table existence check? %s " % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS,) else "[y/N/q]")
test = readInput(message, default="Y" if "Y" in message else "N")
if test[0] in ("n", "N"):
diff --git a/sqlmap.py b/sqlmap.py
index fdbc7afa1..af95008e9 100755
--- a/sqlmap.py
+++ b/sqlmap.py
@@ -111,7 +111,10 @@ def main():
except SqlmapUserQuitException:
errMsg = "user quit"
- logger.error(errMsg)
+ try:
+ logger.error(errMsg)
+ except KeyboardInterrupt:
+ pass
except (SqlmapSilentQuitException, bdb.BdbQuit):
pass
@@ -121,18 +124,30 @@ def main():
except SqlmapBaseException as ex:
errMsg = getSafeExString(ex)
- logger.critical(errMsg)
+ try:
+ logger.critical(errMsg)
+ except KeyboardInterrupt:
+ pass
+
raise SystemExit
except KeyboardInterrupt:
print
+
errMsg = "user aborted"
- logger.error(errMsg)
+ try:
+ logger.error(errMsg)
+ except KeyboardInterrupt:
+ pass
except EOFError:
print
errMsg = "exit"
- logger.error(errMsg)
+
+ try:
+ logger.error(errMsg)
+ except KeyboardInterrupt:
+ pass
except SystemExit:
pass
@@ -142,32 +157,35 @@ def main():
errMsg = unhandledExceptionMessage()
excMsg = traceback.format_exc()
- if any(_ in excMsg for _ in ("No space left", "Disk quota exceeded")):
- errMsg = "no space left on output device"
- logger.error(errMsg)
- raise SystemExit
+ try:
+ if any(_ in excMsg for _ in ("No space left", "Disk quota exceeded")):
+ errMsg = "no space left on output device"
+ logger.error(errMsg)
+ raise SystemExit
- elif "bad marshal data (unknown type code)" in excMsg:
- match = re.search(r"\s*(.+)\s+ValueError", excMsg)
- errMsg = "one of your .pyc files are corrupted%s" % (" ('%s')" % match.group(1) if match else "")
- errMsg += ". Please delete .pyc files on your system to fix the problem"
- logger.error(errMsg)
- raise SystemExit
+ elif "bad marshal data (unknown type code)" in excMsg:
+ match = re.search(r"\s*(.+)\s+ValueError", excMsg)
+ errMsg = "one of your .pyc files are corrupted%s" % (" ('%s')" % match.group(1) if match else "")
+ errMsg += ". Please delete .pyc files on your system to fix the problem"
+ logger.error(errMsg)
+ raise SystemExit
- for match in re.finditer(r'File "(.+?)", line', excMsg):
- file_ = match.group(1)
- file_ = os.path.relpath(file_, os.path.dirname(__file__))
- file_ = file_.replace("\\", '/')
- file_ = re.sub(r"\.\./", '/', file_).lstrip('/')
- excMsg = excMsg.replace(match.group(1), file_)
+ for match in re.finditer(r'File "(.+?)", line', excMsg):
+ file_ = match.group(1)
+ file_ = os.path.relpath(file_, os.path.dirname(__file__))
+ file_ = file_.replace("\\", '/')
+ file_ = re.sub(r"\.\./", '/', file_).lstrip('/')
+ excMsg = excMsg.replace(match.group(1), file_)
- errMsg = maskSensitiveData(errMsg)
- excMsg = maskSensitiveData(excMsg)
+ errMsg = maskSensitiveData(errMsg)
+ excMsg = maskSensitiveData(excMsg)
- logger.critical(errMsg)
- kb.stickyLevel = logging.CRITICAL
- dataToStdout(excMsg)
- createGithubIssue(errMsg, excMsg)
+ logger.critical(errMsg)
+ kb.stickyLevel = logging.CRITICAL
+ dataToStdout(excMsg)
+ createGithubIssue(errMsg, excMsg)
+ except KeyboardInterrupt:
+ pass
finally:
if conf.get("showTime"):
diff --git a/waf/safe3.py b/waf/safe3.py
new file mode 100644
index 000000000..70db14140
--- /dev/null
+++ b/waf/safe3.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+
+"""
+Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/)
+See the file 'doc/COPYING' for copying permission
+"""
+
+import re
+
+from lib.core.enums import HTTP_HEADER
+from lib.core.settings import WAF_ATTACK_VECTORS
+
+__product__ = "Safe3 Web Application Firewall"
+
+def detect(get_page):
+ retval = False
+
+ for vector in WAF_ATTACK_VECTORS:
+ page, headers, code = get_page(get=vector)
+ retval = re.search(r"Safe3WAF", headers.get(HTTP_HEADER.X_POWERED_BY, ""), re.I) is not None
+ retval |= re.search(r"Safe3 Web Firewall", headers.get(HTTP_HEADER.SERVER, ""), re.I) is not None
+ if retval:
+ break
+
+ return retval
+
diff --git a/waf/safedog.py b/waf/safedog.py
index 31b706e18..8d11c511c 100644
--- a/waf/safedog.py
+++ b/waf/safedog.py
@@ -7,6 +7,7 @@ See the file 'doc/COPYING' for copying permission
import re
+from lib.core.enums import HTTP_HEADER
from lib.core.settings import WAF_ATTACK_VECTORS
__product__ = "Safedog Web Application Firewall (Safedog)"
@@ -16,7 +17,9 @@ def detect(get_page):
for vector in WAF_ATTACK_VECTORS:
page, headers, code = get_page(get=vector)
- retval = re.search(r"WAF/2.0", headers.get("X-Powered-By", ""), re.I) is not None
+ retval = re.search(r"WAF/2\.0", headers.get(HTTP_HEADER.X_POWERED_BY, ""), re.I) is not None
+ retval |= re.search(r"Safedog", headers.get(HTTP_HEADER.SERVER, ""), re.I) is not None
+ retval |= re.search(r"safedog", headers.get(HTTP_HEADER.SET_COOKIE, ""), re.I) is not None
if retval:
break
diff --git a/xml/errors.xml b/xml/errors.xml
index eb4670fd9..a06f67a79 100644
--- a/xml/errors.xml
+++ b/xml/errors.xml
@@ -17,6 +17,7 @@
+