Added support to directly connect also to Microsoft SQL Server database.

Fixed direct connection to always use the same query as of UNION query SQL injection (= one query with multiple columns/entries output).
Minor fixes to Firebird/Access/SQLite connectors to use connector's execute()/fetchall() as wrapper for third-party libraries' methods.
Forced conf.timeout to 10 seconds when directly connecting to database.
Slightly improved regular expression to parse -d parameter.
Added import check for all connectors' third-party libraries.
Code refactoring:
* Moved conf.direct request to direct() function in lib/request/direct.py (code reused where needed).
* Back-delegated to generic connector close() and other methods.
This commit is contained in:
Bernardo Damele 2010-03-31 10:50:47 +00:00
parent d583cc07e7
commit 5fdebb5d5b
22 changed files with 205 additions and 223 deletions

View File

@ -65,9 +65,6 @@ def action():
raise sqlmapUnsupportedDBMSException, errMsg
if conf.direct:
conf.dbmsConnector.connect()
print "%s\n" % conf.dbmsHandler.getFingerprint()
# Techniques options

View File

@ -79,6 +79,9 @@ def setHandler():
conf.dbmsConnector = dbmsConn()
if conf.direct:
logger.debug("forcing timeout to 10 seconds")
conf.timeout = 10
conf.dbmsConnector.connect()
if handler.checkDbms():

View File

@ -606,14 +606,14 @@ def parseTargetDirect():
details = None
for dbms in SUPPORTED_DBMS:
details = re.search("^(?P<dbms>%s)://(?P<credentials>(?P<dbmsUser>.+?)\:(?P<dbmsPass>.+?)\@)?(?P<remote>(?P<hostname>.+?)\:(?P<port>[\d]+)\/)?(?P<dbmsDb>.+?)$" % dbms, conf.direct, re.I)
details = re.search("^(?P<dbms>%s)://(?P<credentials>(?P<user>.+?)\:(?P<pass>.+?)\@)?(?P<remote>(?P<hostname>.+?)\:(?P<port>[\d]+)\/)?(?P<db>[\w\d\.\_\-\/]+?)$" % dbms, conf.direct, re.I)
if details:
conf.dbms = details.group('dbms')
if details.group('credentials'):
conf.dbmsUser = details.group('dbmsUser')
conf.dbmsPass = details.group('dbmsPass')
conf.dbmsUser = details.group('user')
conf.dbmsPass = details.group('pass')
else:
conf.dbmsUser = str()
conf.dbmsPass = str()
@ -625,30 +625,31 @@ def parseTargetDirect():
conf.hostname = "localhost"
conf.port = 0
conf.dbmsDb = details.group('dbmsDb')
conf.dbmsDb = details.group('db')
conf.parameters[None] = "direct connection"
break
if not details:
errMsg = "invalid target details, valid syntax is for instance: 'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME'"
errMsg += " and/or: 'access://DATABASE_FILEPATH'"
errMsg = "invalid target details, valid syntax is for instance "
errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' "
errMsg += "or 'access://DATABASE_FILEPATH'"
raise sqlmapSyntaxException, errMsg
# TODO: add details for others python DBMS libraries
dbmsDict = { "Microsoft SQL Server": [MSSQL_ALIASES, "python-pymssql", "http://pymssql.sourceforge.net/"],
"MySQL": [MYSQL_ALIASES, "python-mysqldb", "http://mysql-python.sourceforge.net/"],
"PostgreSQL": [PGSQL_ALIASES, "python-psycopg2", "http://initd.org/psycopg/"],
"Oracle": [ORACLE_ALIASES, "python cx_Oracle", "http://cx-oracle.sourceforge.net/"],
"SQLite": [SQLITE_ALIASES, "", ""],
"Access": [ACCESS_ALIASES, "", ""],
"Firebird": [FIREBIRD_ALIASES, "", ""] }
"SQLite": [SQLITE_ALIASES, "python-pysqlite2", "http://pysqlite.googlecode.com/"],
"Access": [ACCESS_ALIASES, "python-pyodbc", "http://pyodbc.googlecode.com/"],
"Firebird": [FIREBIRD_ALIASES, "python-kinterbasdb", "http://kinterbasdb.sourceforge.net/"] }
for dbmsName, data in dbmsDict.items():
if conf.dbms in data[0]:
try:
if dbmsName == "Microsoft SQL Server":
import _mssql
import pymssql
elif dbmsName == "MySQL":
import MySQLdb
@ -656,6 +657,12 @@ def parseTargetDirect():
import psycopg2
elif dbmsName == "Oracle":
import cx_Oracle
elif dbmsName == "SQLite":
import sqlite3
elif dbmsName == "Access":
import pyodbc
elif dbmsName == "Firebird":
import kinterbasdb
except ImportError, _:
errMsg = "sqlmap requires %s third-party library " % data[1]
errMsg += "in order to directly connect to the database "

View File

@ -42,6 +42,7 @@ from lib.core.settings import SQL_STATEMENTS
from lib.request.basic import decodePage
from lib.request.basic import forgeHeaders
from lib.request.basic import parseResponse
from lib.request.direct import direct
from lib.request.comparison import comparison
@ -265,39 +266,7 @@ class Connect:
"""
if conf.direct:
values = None
select = False
if kb.dbms == "Oracle" and value.startswith("SELECT ") and " FROM " not in value:
value = "%s FROM DUAL" % value
for sqlTitle, sqlStatements in SQL_STATEMENTS.items():
for sqlStatement in sqlStatements:
if value.lower().startswith(sqlStatement) and sqlTitle == "SQL SELECT statement":
select = True
break
if select:
values = conf.dbmsConnector.select(value)
else:
values = conf.dbmsConnector.execute(value)
if values is None or len(values) == 0:
return None
elif content:
if len(values) == 1:
if len(values[0]) == 1:
return str(list(values)[0][0]), None
else:
return list(values), None
else:
return values, None
else:
for value in values:
if value[0] in (1, -1):
return True
else:
return False
return direct(value, content)
get = None
post = None

64
lib/request/direct.py Normal file
View File

@ -0,0 +1,64 @@
#!/usr/bin/env python
"""
$Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2007-2010 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 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.agent import agent
from lib.core.data import conf
from lib.core.data import kb
from lib.core.settings import SQL_STATEMENTS
def direct(query, content=True):
output = None
select = False
query = agent.payloadDirect(query)
if kb.dbms == "Oracle" and query.startswith("SELECT ") and " FROM " not in query:
query = "%s FROM DUAL" % query
for sqlTitle, sqlStatements in SQL_STATEMENTS.items():
for sqlStatement in sqlStatements:
if query.lower().startswith(sqlStatement) and sqlTitle == "SQL SELECT statement":
select = True
break
if select:
output = conf.dbmsConnector.select(query)
else:
output = conf.dbmsConnector.execute(query)
if output is None or len(output) == 0:
return None
elif content:
if len(output) == 1:
if len(output[0]) == 1:
return str(list(output)[0][0])
else:
return list(output)
else:
return output
else:
for line in output:
if line[0] in (1, -1):
return True
else:
return False

View File

@ -37,6 +37,7 @@ from lib.core.data import logger
from lib.core.data import queries
from lib.core.data import temp
from lib.request.connect import Connect as Request
from lib.request.direct import direct
from lib.core.settings import SQL_STATEMENTS
from lib.techniques.inband.union.use import unionUse
from lib.techniques.blind.inference import bisection
@ -352,33 +353,7 @@ def getValue(expression, blind=True, inband=True, fromUser=False, expected=None,
"""
if conf.direct:
expression = agent.payloadDirect(expression)
values = None
select = False
if kb.dbms == "Oracle" and expression.startswith("SELECT ") and " FROM " not in expression:
expression = "%s FROM DUAL" % expression
for sqlTitle, sqlStatements in SQL_STATEMENTS.items():
for sqlStatement in sqlStatements:
if expression.lower().startswith(sqlStatement) and sqlTitle == "SQL SELECT statement":
select = True
break
if select:
values = conf.dbmsConnector.select(expression)
else:
values = conf.dbmsConnector.execute(expression)
if values is None or len(values) == 0:
return None
elif len(values) == 1:
if len(values[0]) == 1:
return str(list(values)[0][0])
else:
return list(values)
else:
return values
return direct(expression)
expression = cleanQuery(expression)
expression = expandAsteriskForColumns(expression)
@ -418,25 +393,7 @@ def goStacked(expression, silent=False):
expression = cleanQuery(expression)
if conf.direct:
expression = agent.payloadDirect(expression)
values = None
select = False
if kb.dbms == "Oracle" and expression.startswith("SELECT ") and " FROM " not in expression:
expression = "%s FROM DUAL" % expression
for sqlTitle, sqlStatements in SQL_STATEMENTS.items():
for sqlStatement in sqlStatements:
if expression.lower().startswith(sqlStatement) and sqlTitle == "SQL SELECT statement":
select = True
break
if select:
values = conf.dbmsConnector.select(expression)
else:
values = conf.dbmsConnector.execute(expression)
return None, None
return direct(expression), None
debugMsg = "query: %s" % expression
logger.debug(debugMsg)

View File

@ -45,15 +45,14 @@ class Connector(GenericConnector):
def __init__(self):
GenericConnector.__init__(self)
def connect(self, reuse=True):
if reuse and self.connector:
return
def connect(self):
self.initConnection()
self.checkFileDb()
try:
#self.connector = pyodbc.connect(driver='{Microsoft Access Driver (*.mdb)}', dbq=self.db, uid='Admin')
self.connector = pyodbc.connect('Driver={Microsoft Access Driver (*.mdb)};Dbq=%s;Uid=Admin;Pwd=;' % self.db)
self.connector.timeout = conf.timeout
except pyodbc.OperationalError, msg:
raise sqlmapConnectionException, msg[1]
@ -63,7 +62,7 @@ class Connector(GenericConnector):
def fetchall(self):
try:
return self.cursor.fetchall()
except pyodbc.OperationalError, msg:
except pyodbc.ProgrammingError, msg:
logger.log(8, msg[1])
return None
@ -80,18 +79,5 @@ class Connector(GenericConnector):
self.connector.commit()
def select(self, query):
try:
self.cursor.execute(query)
return self.cursor.fetchall()
except pyodbc.ProgrammingError, msg:
logger.log(8, msg[1])
return None
def setCursor(self):
self.cursor = self.connector.cursor()
def close(self):
self.cursor.close()
self.connector.close()
self.closed()
self.execute(query)
return self.fetchall()

View File

@ -140,6 +140,7 @@ class Fingerprint(GenericFingerprint):
def checkDbms(self):
if conf.dbms in ACCESS_ALIASES:
setDbms("Microsoft Access")
if not conf.extensiveFp:
return True

View File

@ -37,20 +37,21 @@ class Connector(GenericConnector):
"""
Homepage: http://kinterbasdb.sourceforge.net/
User guide: http://kinterbasdb.sourceforge.net/dist_docs/usage.html
Debian package: python-kinterbasdb
License: BSD
"""
def __init__(self):
GenericConnector.__init__(self)
def connect(self, reuse=True):
if reuse and self.connector:
return
def connect(self):
self.initConnection()
if not self.hostname:
self.checkFileDb()
try:
self.connector = kinterbasdb.connect(database=self.db, user=self.user, password=self.password, timeout={'period': conf.timeout})
self.connector = kinterbasdb.connect(host=self.hostname, database=self.db, user=self.user, password=self.password, timeout={'period': conf.timeout})
except kinterbasdb.OperationalError, msg:
raise sqlmapConnectionException, msg[1]
@ -77,13 +78,5 @@ class Connector(GenericConnector):
self.connector.commit()
def select(self, query):
self.cursor.execute(query)
return self.cursor.fetchall()
def setCursor(self):
self.cursor = self.connector.cursor()
def close(self):
self.cursor.close()
self.connector.close()
self.closed()
self.execute(query)
return self.fetchall()

View File

@ -23,10 +23,12 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
try:
import _mssql
import pymssql
except ImportError, _:
pass
from lib.core.data import conf
from lib.core.data import logger
from lib.core.exception import sqlmapConnectionException
@ -45,3 +47,38 @@ class Connector(GenericConnector):
def __init__(self):
GenericConnector.__init__(self)
def connect(self):
self.initConnection()
try:
self.connector = pymssql.connect(host="%s:%d" % (self.hostname, self.port), user=self.user, password=self.password, database=self.db, login_timeout=conf.timeout, timeout=conf.timeout)
except pymssql.OperationalError, msg:
raise sqlmapConnectionException, msg
self.setCursor()
self.connected()
def fetchall(self):
try:
return self.cursor.fetchall()
except (pymssql.ProgrammingError, pymssql.OperationalError, _mssql.MssqlDatabaseException), msg:
logger.log(8, msg)
return None
def execute(self, query):
logger.debug(query)
try:
self.cursor.execute(query)
except (pymssql.OperationalError, pymssql.ProgrammingError), msg:
logger.log(8, msg)
except pymssql.InternalError, msg:
raise sqlmapConnectionException, msg
def select(self, query):
self.execute(query)
value = self.fetchall()
self.connector.commit()
return value

View File

@ -61,7 +61,7 @@ class Enumeration(GenericEnumeration):
else:
dbs = [conf.db]
if kb.unionPosition:
if kb.unionPosition or conf.direct:
for db in dbs:
if conf.excludeSysDbs and db in self.excludeDbsList:
infoMsg = "skipping system database '%s'" % db
@ -75,7 +75,7 @@ class Enumeration(GenericEnumeration):
if value:
kb.data.cachedTables[db] = value
if not kb.data.cachedTables:
if not kb.data.cachedTables and not conf.direct:
for db in dbs:
if conf.excludeSysDbs and db in self.excludeDbsList:
infoMsg = "skipping system database '%s'" % db

View File

@ -97,6 +97,11 @@ class Fingerprint(GenericFingerprint):
infoMsg = "testing Microsoft SQL Server"
logger.info(infoMsg)
# NOTE: SELECT LEN(@@VERSION)=LEN(@@VERSION) FROM DUAL does not work connecting
# directly to the Microsoft SQL Server database
if conf.direct:
result = True
else:
payload = agent.fullPayload(" AND LEN(@@VERSION)=LEN(@@VERSION)")
result = Request.queryPage(payload)

View File

@ -47,10 +47,7 @@ class Connector(GenericConnector):
def __init__(self):
GenericConnector.__init__(self)
def connect(self, reuse=True):
if reuse and self.connector:
return
def connect(self):
self.initConnection()
try:
@ -83,11 +80,3 @@ class Connector(GenericConnector):
def select(self, query):
self.execute(query)
return self.fetchall()
def setCursor(self):
self.cursor = self.connector.cursor()
def close(self):
self.cursor.close()
self.connector.close()
self.closed()

View File

@ -154,9 +154,6 @@ class Fingerprint(GenericFingerprint):
* http://dev.mysql.com/doc/refman/6.0/en/news-6-0-x.html (manual has been withdrawn)
"""
infoMsg = "testing MySQL"
logger.info(infoMsg)
if conf.dbms in MYSQL_ALIASES and kb.dbmsVersion and kb.dbmsVersion[0].isdigit():
setDbms("MySQL %s" % kb.dbmsVersion[0])
@ -168,6 +165,9 @@ class Fingerprint(GenericFingerprint):
if not conf.extensiveFp:
return True
infoMsg = "testing MySQL"
logger.info(infoMsg)
randInt = str(randomInt(1))
payload = agent.fullPayload(" AND CONNECTION_ID()=CONNECTION_ID()")
result = Request.queryPage(payload)

View File

@ -37,19 +37,13 @@ class Connector(GenericConnector):
Homepage: http://cx-oracle.sourceforge.net/
User guide: http://cx-oracle.sourceforge.net/README.txt
API: http://cx-oracle.sourceforge.net/html/index.html
Debian package: -
License: http://cx-oracle.sourceforge.net/LICENSE.txt
Possible connectors: -
"""
def __init__(self):
GenericConnector.__init__(self)
def connect(self, reuse=True):
if reuse and self.connector:
return
def connect(self):
self.initConnection()
self.__dsn = cx_Oracle.makedsn(self.hostname, self.port, self.db)
@ -87,11 +81,3 @@ class Connector(GenericConnector):
def select(self, query):
self.execute(query)
return self.fetchall()
def setCursor(self):
self.cursor = self.connector.cursor()
def close(self):
self.cursor.close()
self.connector.close()
self.closed()

View File

@ -96,7 +96,7 @@ class Enumeration(GenericEnumeration):
else:
kb.data.cachedUsersRoles[user] = list(roles)
if not kb.data.cachedUsersRoles:
if not kb.data.cachedUsersRoles and not conf.direct:
conditionChar = "="
if conf.user:

View File

@ -78,9 +78,6 @@ class Fingerprint(GenericFingerprint):
return value
def checkDbms(self):
logMsg = "testing Oracle"
logger.info(logMsg)
if conf.dbms in ORACLE_ALIASES:
setDbms("Oracle")
@ -89,6 +86,9 @@ class Fingerprint(GenericFingerprint):
if not conf.extensiveFp:
return True
logMsg = "testing Oracle"
logger.info(logMsg)
# NOTE: SELECT ROWNUM=ROWNUM FROM DUAL does not work connecting
# directly to the Oracle database
if conf.direct:

View File

@ -46,10 +46,7 @@ class Connector(GenericConnector):
def __init__(self):
GenericConnector.__init__(self)
def connect(self, reuse=True):
if reuse and self.connector:
return
def connect(self):
self.initConnection()
try:
@ -82,11 +79,3 @@ class Connector(GenericConnector):
def select(self, query):
self.execute(query)
return self.fetchall()
def setCursor(self):
self.cursor = self.connector.cursor()
def close(self):
self.cursor.close()
self.connector.close()
self.closed()

View File

@ -86,9 +86,6 @@ class Fingerprint(GenericFingerprint):
* http://www.postgresql.org/docs/8.4/interactive/release.html (up to 8.4.2)
"""
infoMsg = "testing PostgreSQL"
logger.info(infoMsg)
if conf.dbms in PGSQL_ALIASES:
setDbms("PostgreSQL")
@ -97,6 +94,9 @@ class Fingerprint(GenericFingerprint):
if not conf.extensiveFp:
return True
infoMsg = "testing PostgreSQL"
logger.info(infoMsg)
randInt = str(randomInt(1))
payload = agent.fullPayload(" AND %s::int=%s" % (randInt, randInt))

View File

@ -39,7 +39,7 @@ class Connector(GenericConnector):
User guide: http://docs.python.org/release/2.5/lib/module-sqlite3.html
API: http://docs.python.org/library/sqlite3.html
Debian package: python-pysqlite2
License: zlib/libpng
License: MIT
Possible connectors: http://wiki.python.org/moin/SQLite
"""
@ -47,11 +47,9 @@ class Connector(GenericConnector):
def __init__(self):
GenericConnector.__init__(self)
def connect(self, reuse=True):
if reuse and self.connector:
return
def connect(self):
self.initConnection()
self.checkFileDb()
try:
self.connector = sqlite3.connect(database=self.db, timeout=conf.timeout)
@ -75,19 +73,11 @@ class Connector(GenericConnector):
self.cursor.execute(query)
except sqlite3.OperationalError, msg:
logger.log(8, msg[0])
except sqlite3.Error, msg:
except sqlite3.DatabaseError, msg:
raise sqlmapConnectionException, msg[0]
self.connector.commit()
def select(self, query):
self.cursor.execute(query)
return self.cursor.fetchall()
def setCursor(self):
self.cursor = self.connector.cursor()
def close(self):
self.cursor.close()
self.connector.close()
self.closed()
self.execute(query)
return self.fetchall()

View File

@ -22,8 +22,11 @@ with sqlmap; if not, write to the Free Software Foundation, Inc., 51
Franklin St, Fifth Floor, Boston, MA 021101301 USA
"""
import os
from lib.core.data import conf
from lib.core.data import logger
from lib.core.exception import sqlmapFilePathException
from lib.core.exception import sqlmapUndefinedMethod
class Connector:
@ -48,12 +51,29 @@ class Connector:
logger.info(infoMsg)
def closed(self):
self.connector = None
self.cursor = None
infoMsg = "connection to %s server %s" % (conf.dbms, self.hostname)
infoMsg += ":%d closed" % self.port
logger.info(infoMsg)
self.connector = None
self.cursor = None
def setCursor(self):
self.cursor = self.connector.cursor()
def getCursor(self):
return self.cursor
def close(self):
self.cursor.close()
self.connector.close()
self.closed()
def checkFileDb(self):
if not os.path.exists(self.db):
errMsg = "the provided database file '%s' does not exist" % self.db
raise sqlmapFilePathException, errMsg
def connect(self):
errMsg = "'connect' method must be defined "
errMsg += "into the specific DBMS plugin"
@ -73,16 +93,3 @@ class Connector:
errMsg = "'select' method must be defined "
errMsg += "into the specific DBMS plugin"
raise sqlmapUndefinedMethod, errMsg
def setCursor(self):
errMsg = "'setCursor' method must be defined "
errMsg += "into the specific DBMS plugin"
raise sqlmapUndefinedMethod, errMsg
def getCursor(self):
return self.cursor
def close(self):
errMsg = "'close' method must be defined "
errMsg += "into the specific DBMS plugin"
raise sqlmapUndefinedMethod, errMsg

View File

@ -149,7 +149,7 @@ class Enumeration:
if value:
kb.data.cachedUsers = value
if not kb.data.cachedUsers:
if not kb.data.cachedUsers and not conf.direct:
infoMsg = "fetching number of database users"
logger.info(infoMsg)
@ -232,7 +232,7 @@ class Enumeration:
else:
kb.data.cachedUsersPasswords[user].append(password)
if not kb.data.cachedUsersPasswords:
if not kb.data.cachedUsersPasswords and not conf.direct:
if conf.user:
if "," in conf.user:
users = conf.user.split(",")
@ -464,7 +464,7 @@ class Enumeration:
else:
kb.data.cachedUsersPrivileges[user] = list(privileges)
if not kb.data.cachedUsersPrivileges:
if not kb.data.cachedUsersPrivileges and not conf.direct:
conditionChar = "="
if conf.user:
@ -649,7 +649,7 @@ class Enumeration:
if value:
kb.data.cachedDbs = value
if not kb.data.cachedDbs:
if not kb.data.cachedDbs and not conf.direct:
infoMsg = "fetching number of databases"
logger.info(infoMsg)
@ -733,7 +733,7 @@ class Enumeration:
else:
kb.data.cachedTables[db].append(table)
if not kb.data.cachedTables:
if not kb.data.cachedTables and not conf.direct:
if conf.db:
if "," in conf.db:
dbs = conf.db.split(",")
@ -881,7 +881,7 @@ class Enumeration:
table[conf.tbl] = columns
kb.data.cachedColumns[conf.db] = table
if not kb.data.cachedColumns:
if not kb.data.cachedColumns and not conf.direct:
infoMsg = "fetching number of columns "
infoMsg += "for table '%s'" % conf.tbl
infoMsg += " on database '%s'" % conf.db
@ -1298,8 +1298,10 @@ class Enumeration:
colList = conf.col.split(",")
kb.data.cachedColumns[conf.db] = {}
kb.data.cachedColumns[conf.db][conf.tbl] = {}
for column in colList:
kb.data.cachedColumns[conf.db][conf.tbl][column] = None
elif not kb.data.cachedColumns:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
errMsg = "information_schema not available, "
@ -1359,7 +1361,7 @@ class Enumeration:
index += 1
if not kb.data.dumpedTable:
if not kb.data.dumpedTable and not conf.direct:
infoMsg = "fetching number of "
if conf.col:
infoMsg += "columns '%s' " % colString