4
$Id: common.py 355 2009-01-28 14:53:11Z inquisb $
6
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
8
Copyright (c) 2006-2009 Bernardo Damele A. G. <bernardo.damele@gmail.com>
9
and Daniele Bellucci <daniele.bellucci@gmail.com>
11
sqlmap is free software; you can redistribute it and/or modify it under
12
the terms of the GNU General Public License as published by the Free
13
Software Foundation version 2 of the License.
15
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
16
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
20
You should have received a copy of the GNU General Public License along
21
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
22
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
36
from lib.core.convert import urldecode
37
from lib.core.data import conf
38
from lib.core.data import kb
39
from lib.core.data import logger
40
from lib.core.data import temp
41
from lib.core.exception import sqlmapFilePathException
42
from lib.core.data import paths
43
from lib.core.settings import SQL_STATEMENTS
44
from lib.core.settings import VERSION_STRING
47
def paramToDict(place, parameters=None):
49
Split the parameters into names and values, check if these parameters
50
are within the testable parameters and return in a dictionary.
52
@param place: where sqlmap has to work, can be GET, POST or Cookie.
55
@param parameters: parameters string in the format for instance
56
'p1=v1&p2=v2' (GET and POST) or 'p1=v1;p2=v2' (Cookie).
57
@type parameters: C{str}
59
@return: the parameters in a dictionary.
63
testableParameters = {}
65
if conf.parameters.has_key(place) and not parameters:
66
parameters = conf.parameters[place]
68
parameters = parameters.replace(", ", ",")
71
splitParams = parameters.split(";")
73
splitParams = parameters.split("&")
75
for element in splitParams:
76
elem = element.split("=")
79
parameter = elem[0].replace(" ", "")
81
condition = not conf.testParameter
82
condition |= parameter in conf.testParameter
87
testableParameters[parameter] = value
89
if conf.testParameter and not testableParameters:
90
paramStr = ", ".join(test for test in conf.testParameter)
92
if len(conf.testParameter) > 1:
93
warnMsg = "the testable parameters '%s' " % paramStr
94
warnMsg += "you provided are not into the %s" % place
96
parameter = conf.testParameter[0]
98
warnMsg = "the testable parameter '%s' " % paramStr
99
warnMsg += "you provided is not into the %s" % place
101
if conf.multipleTargets:
102
warnMsg += ", skipping to next url"
106
elif len(conf.testParameter) != len(testableParameters.keys()):
107
for parameter in conf.testParameter:
108
if not testableParameters.has_key(parameter):
109
warnMsg = "the testable parameter '%s' " % parameter
110
warnMsg += "you provided is not into the %s" % place
113
return testableParameters
116
def formatDBMSfp(versions=None):
118
This function format the back-end DBMS fingerprint value and return its
119
values formatted as a human readable string.
121
@return: detected back-end DBMS based upon fingerprint techniques.
126
versions = kb.dbmsVersion
128
if isinstance(versions, str):
129
return "%s %s" % (kb.dbms, versions)
130
elif isinstance(versions, (list, set, tuple)):
131
return "%s %s" % (kb.dbms, " and ".join([version for version in versions]))
133
warnMsg = "unable to extensively fingerprint the back-end "
134
warnMsg += "DBMS version"
140
def __formatFingerprintString(values, chain=" or "):
141
string = "|".join([v for v in values])
142
return string.replace("|", chain)
145
def formatFingerprint(target, info):
147
This function format the back-end operating system fingerprint value
148
and return its values formatted as a human readable string.
150
Example of info (kb.headersFp) dictionary:
153
'distrib': set(['Ubuntu']),
154
'type': set(['Linux']),
155
'technology': set(['PHP 5.2.6', 'Apache 2.2.9']),
156
'release': set(['8.10'])
159
Example of info (kb.bannerFp) dictionary:
162
'sp': set(['Service Pack 4']),
163
'dbmsVersion': '8.00.194',
164
'dbmsServicePack': '0',
165
'distrib': set(['2000']),
166
'dbmsRelease': '2000',
167
'type': set(['Windows'])
170
@return: detected back-end operating system based upon fingerprint
177
if info and "type" in info:
178
infoStr += "%s operating system: %s" % (target, __formatFingerprintString(info["type"]))
180
if "distrib" in info:
181
infoStr += " %s" % __formatFingerprintString(info["distrib"])
183
if "release" in info:
184
infoStr += " %s" % __formatFingerprintString(info["release"])
187
infoStr += " %s" % __formatFingerprintString(info["sp"])
189
if "codename" in info:
190
infoStr += " (%s)" % __formatFingerprintString(info["codename"])
192
if "technology" in info:
193
infoStr += "\nweb application technology: %s" % __formatFingerprintString(info["technology"], ", ")
198
def getHtmlErrorFp():
200
This function parses the knowledge base htmlFp list and return its
201
values formatted as a human readable string.
203
@return: list of possible back-end DBMS based upon error messages
213
if len(kb.htmlFp) == 1:
214
htmlVer = kb.htmlFp[0]
216
elif len(kb.htmlFp) > 1:
217
htmlParsed = " or ".join([htmlFp for htmlFp in kb.htmlFp])
224
This method returns the web application document root based on the
225
detected absolute files paths in the knowledge base.
231
logMsg = "retrieved the possible injectable "
232
logMsg += "file absolute system paths: "
233
logMsg += "'%s'" % ", ".join(path for path in kb.absFilePaths)
236
warnMsg = "unable to retrieve the injectable file "
237
warnMsg += "absolute system path"
240
for absFilePath in kb.absFilePaths:
241
if conf.path in absFilePath:
242
index = absFilePath.index(conf.path)
243
docRoot = absFilePath[:index]
247
logMsg = "retrieved the remote web server "
248
logMsg += "document root: '%s'" % docRoot
251
warnMsg = "unable to retrieve the remote web server "
252
warnMsg += "document root"
258
def getDirectories():
260
This method calls a function that returns the web application document
261
root and injectable file absolute system path.
263
@return: a set of paths (document root and absolute system path).
265
@todo: replace this function with a site crawling functionality.
270
kb.docRoot = getDocRoot()
273
directories.add(kb.docRoot)
275
pagePath = re.search("^/(.*)/", conf.path)
277
if kb.docRoot and pagePath:
278
pagePath = pagePath.groups()[0]
280
directories.add("%s/%s" % (kb.docRoot, pagePath))
285
def filePathToString(filePath):
286
string = filePath.replace("/", "_").replace("\\", "_")
287
string = string.replace(" ", "_").replace(":", "_")
292
def dataToStdout(data):
293
sys.stdout.write(data)
297
def dataToSessionFile(data):
298
if not conf.sessionFile:
301
conf.sessionFP.write(data)
302
conf.sessionFP.flush()
305
def dataToDumpFile(dumpFile, data):
310
def strToHex(string):
312
@param string: string to be converted into its hexadecimal value.
315
@return: the hexadecimal converted string.
321
for character in string:
322
if character == "\n":
325
hexChar = "%2x" % ord(character)
326
hexChar = hexChar.replace(" ", "0")
327
hexChar = hexChar.upper()
334
def fileToStr(fileName):
336
@param fileName: file path to read the content and return as a no
338
@type fileName: C{file.open}
340
@return: the file content as a string without TAB and NEWLINE.
344
filePointer = open(fileName, "r")
345
fileText = filePointer.read()
347
fileText = fileText.replace(" ", "")
348
fileText = fileText.replace("\t", "")
349
fileText = fileText.replace("\r", "")
350
fileText = fileText.replace("\n", " ")
355
def fileToHex(fileName):
357
@param fileName: file path to read the content and return as an
359
@type fileName: C{file.open}
361
@return: the file content as an hexadecimal string.
365
fileText = fileToStr(fileName)
366
hexFile = strToHex(fileText)
371
def readInput(message, default=None):
373
@param message: message to display on terminal.
374
@type message: C{str}
376
@return: a string read from keyboard as input.
380
if conf.batch and default:
381
infoMsg = "%s%s" % (message, str(default))
384
debugMsg = "used the default behaviour, running in batch mode"
385
logger.debug(debugMsg)
389
data = raw_input("[%s] [INPUT] %s" % (time.strftime("%X"), message))
394
def randomRange(start=0, stop=1000):
396
@param start: starting number.
399
@param stop: last number.
402
@return: a random number within the range.
406
return int(random.randint(start, stop))
409
def randomInt(length=4):
411
@param length: length of the random string.
414
@return: a random string of digits.
418
return int("".join([random.choice(string.digits) for _ in xrange(0, length)]))
421
def randomStr(length=5):
423
@param length: length of the random string.
426
@return: a random string of characters.
430
return "".join([random.choice(string.letters) for _ in xrange(0, length)])
433
def sanitizeStr(string):
435
@param string: string to sanitize: cast to str datatype and replace
436
newlines with one space and strip carriage returns.
439
@return: sanitized string
443
cleanString = str(string)
444
cleanString = cleanString.replace("\n", " ").replace("\r", "")
449
def checkFile(filename):
451
@param filename: filename to check if it exists.
452
@type filename: C{str}
455
if not os.path.exists(filename):
456
raise sqlmapFilePathException, "unable to read file '%s'" % filename
459
def replaceNewlineTabs(string):
460
replacedString = string.replace("\n", "__NEWLINE__").replace("\t", "__TAB__")
461
replacedString = replacedString.replace(temp.delimiter, "__DEL__")
463
return replacedString
468
This function prints sqlmap banner with its version
472
%s coded by Bernardo Damele A. G. <bernardo.damele@gmail.com>
473
and Daniele Bellucci <daniele.bellucci@gmail.com>
477
def parsePasswordHash(password):
480
if not password or password == " ":
483
if kb.dbms == "Microsoft SQL Server" and password != "NULL":
484
hexPassword = password
485
password = "%s\n" % hexPassword
486
password += "%sheader: %s\n" % (blank, hexPassword[:6])
487
password += "%ssalt: %s\n" % (blank, hexPassword[6:14])
488
password += "%smixedcase: %s\n" % (blank, hexPassword[14:54])
490
if kb.dbmsVersion[0] not in ( "2005", "2008" ):
491
password += "%suppercase: %s" % (blank, hexPassword[54:])
496
def cleanQuery(query):
499
for sqlStatements in SQL_STATEMENTS.values():
500
for sqlStatement in sqlStatements:
501
sqlStatementEsc = sqlStatement.replace("(", "\\(")
502
queryMatch = re.search("(%s)" % sqlStatementEsc, query, re.I)
505
upperQuery = upperQuery.replace(queryMatch.group(1), sqlStatement.upper())
512
paths.SQLMAP_SHELL_PATH = "%s/shell" % paths.SQLMAP_ROOT_PATH
513
paths.SQLMAP_TXT_PATH = "%s/txt" % paths.SQLMAP_ROOT_PATH
514
paths.SQLMAP_XML_PATH = "%s/xml" % paths.SQLMAP_ROOT_PATH
515
paths.SQLMAP_XML_BANNER_PATH = "%s/banner" % paths.SQLMAP_XML_PATH
516
paths.SQLMAP_OUTPUT_PATH = "%s/output" % paths.SQLMAP_ROOT_PATH
517
paths.SQLMAP_DUMP_PATH = paths.SQLMAP_OUTPUT_PATH + "/%s/dump"
518
paths.SQLMAP_FILES_PATH = paths.SQLMAP_OUTPUT_PATH + "/%s/files"
521
paths.SQLMAP_HISTORY = "%s/.sqlmap_history" % paths.SQLMAP_ROOT_PATH
522
paths.SQLMAP_CONFIG = "%s/sqlmap-%s.conf" % (paths.SQLMAP_ROOT_PATH, randomStr())
523
paths.FUZZ_VECTORS = "%s/fuzz_vectors.txt" % paths.SQLMAP_TXT_PATH
524
paths.ERRORS_XML = "%s/errors.xml" % paths.SQLMAP_XML_PATH
525
paths.QUERIES_XML = "%s/queries.xml" % paths.SQLMAP_XML_PATH
526
paths.GENERIC_XML = "%s/generic.xml" % paths.SQLMAP_XML_BANNER_PATH
527
paths.MSSQL_XML = "%s/mssql.xml" % paths.SQLMAP_XML_BANNER_PATH
528
paths.MYSQL_XML = "%s/mysql.xml" % paths.SQLMAP_XML_BANNER_PATH
529
paths.ORACLE_XML = "%s/oracle.xml" % paths.SQLMAP_XML_BANNER_PATH
530
paths.PGSQL_XML = "%s/postgresql.xml" % paths.SQLMAP_XML_BANNER_PATH
535
Returns whether we are frozen via py2exe.
536
This will affect how we find out where we are located.
537
Reference: http://www.py2exe.org/index.cgi/WhereAmI
540
return hasattr(sys, "frozen")
543
def parseTargetUrl():
545
Parse target url and set some attributes into the configuration
552
if not re.search("^http[s]*://", conf.url):
553
if ":443/" in conf.url:
554
conf.url = "https://" + conf.url
556
conf.url = "http://" + conf.url
558
__urlSplit = urlparse.urlsplit(conf.url)
559
__hostnamePort = __urlSplit[1].split(":")
561
conf.scheme = __urlSplit[0]
562
conf.path = __urlSplit[2]
563
conf.hostname = __hostnamePort[0]
565
if len(__hostnamePort) == 2:
566
conf.port = int(__hostnamePort[1])
567
elif conf.scheme == "https":
573
conf.parameters["GET"] = urldecode(__urlSplit[3]).replace("%", "%%")
575
conf.url = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, conf.path)
578
def expandAsteriskForColumns(expression):
579
# If the user provided an asterisk rather than the column(s)
580
# name, sqlmap will retrieve the columns itself and reprocess
581
# the SQL query string (expression)
582
asterisk = re.search("^SELECT\s+\*\s+FROM\s+([\w\.\_]+)\s*", expression, re.I)
585
infoMsg = "you did not provide the fields in your query. "
586
infoMsg += "sqlmap will retrieve the column names itself"
589
dbTbl = asterisk.group(1)
591
if dbTbl and "." in dbTbl:
592
conf.db, conf.tbl = dbTbl.split(".")
596
columnsDict = conf.dbmsHandler.getColumns(onlyColNames=True)
598
if columnsDict and conf.db in columnsDict and conf.tbl in columnsDict[conf.db]:
599
columns = columnsDict[conf.db][conf.tbl].keys()
601
columnsStr = ", ".join([column for column in columns])
602
expression = expression.replace("*", columnsStr, 1)
604
infoMsg = "the query with column names is: "
605
infoMsg += "%s" % expression
611
def getRange(count, dump=False, plusOne=False):
618
if isinstance(conf.limitStop, int) and conf.limitStop > 0 and conf.limitStop < limitStop:
619
limitStop = conf.limitStop
621
if isinstance(conf.limitStart, int) and conf.limitStart > 0 and conf.limitStart <= limitStop:
622
limitStart = conf.limitStart
624
if kb.dbms == "Oracle" or plusOne == True:
625
indexRange = range(limitStart, limitStop + 1)
627
indexRange = range(limitStart - 1, limitStop)
632
def parseUnionPage(output, expression, partial=False, condition=None):
635
outCond1 = ( output.startswith(temp.start) and output.endswith(temp.stop) )
636
outCond2 = ( output.startswith("__START__") and output.endswith("__STOP__") )
638
if outCond1 or outCond2:
640
regExpr = '%s(.*?)%s' % (temp.start, temp.stop)
642
regExpr = '__START__(.*?)__STOP__'
644
output = re.findall(regExpr, output, re.S)
646
if condition == None:
648
kb.resumedQueries and conf.url in kb.resumedQueries.keys()
649
and expression in kb.resumedQueries[conf.url].keys()
652
if partial or not condition:
653
logOutput = "".join(["__START__%s__STOP__" % replaceNewlineTabs(value) for value in output])
654
dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], expression, logOutput))
661
if "__DEL__" in entry:
662
entry = entry.split("__DEL__")
664
entry = entry.split(temp.delimiter)
667
data.append(entry[0])
676
if len(data) == 1 and isinstance(data[0], str):