From 06af405efd0a175e9e50be3e50297ecb793218bc Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Fri, 28 May 2010 16:43:04 +0000 Subject: [PATCH] Adapted and merged in patch to support XML output (-x switch) - still in beta. Minor bug fixes and adjustments. --- doc/THANKS | 5 +- lib/controller/action.py | 43 ++- lib/core/dump.py | 112 ++++--- lib/core/optiondict.py | 2 - lib/core/target.py | 12 +- lib/core/xmldump.py | 526 +++++++++++++++++++++++++++++++++ lib/parse/cmdline.py | 3 + lib/takeover/abstraction.py | 3 +- lib/takeover/udf.py | 3 +- plugins/generic/enumeration.py | 19 +- sqlmap.conf | 4 +- sqlmap.py | 12 +- txt/common-outputs.txt | 22 +- xml/sqlmap.xsd | 284 ++++++++++++++++++ 14 files changed, 964 insertions(+), 86 deletions(-) create mode 100644 lib/core/xmldump.py create mode 100644 xml/sqlmap.xsd diff --git a/doc/THANKS b/doc/THANKS index 3c4962648..91b82b86b 100644 --- a/doc/THANKS +++ b/doc/THANKS @@ -157,6 +157,9 @@ Krzysztof Kotowicz Nicolas Krassas for reporting a bug +Alex Landa + for providing a patch adding support for XML output + Guido Landi for reporting a couple of bugs for the great technical discussions @@ -193,7 +196,7 @@ Enrico Milanese for reporting a bugs when using (-a) a single line User-Agent file for providing me with some ideas for the PHP backdoor -Alejo Murillo +Alejo Murillo Moya for suggesting a feature Roberto Nemirovsky diff --git a/lib/controller/action.py b/lib/controller/action.py index f858edec6..2cff46511 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -26,7 +26,6 @@ from lib.controller.handler import setHandler from lib.core.common import getHtmlErrorFp from lib.core.data import conf from lib.core.data import kb -from lib.core.dump import dumper from lib.core.exception import sqlmapUnsupportedDBMSException from lib.core.settings import SUPPORTED_DBMS from lib.techniques.blind.timebased import timeTest @@ -69,53 +68,53 @@ def action(): # Techniques options if conf.stackedTest: - dumper.string("stacked queries support", stackedTest()) + conf.dumper.technic("stacked queries support", stackedTest()) if conf.timeTest: - dumper.string("time based blind sql injection payload", timeTest()) + conf.dumper.technic("time based blind sql injection payload", timeTest()) if ( conf.unionUse or conf.unionTest ) and not kb.unionPosition: - dumper.string("valid union", unionTest()) + conf.dumper.technic("valid union", unionTest()) # Enumeration options if conf.getBanner: - dumper.string("banner", conf.dbmsHandler.getBanner()) + conf.dumper.banner(conf.dbmsHandler.getBanner()) if conf.getCurrentUser: - dumper.string("current user", conf.dbmsHandler.getCurrentUser()) + conf.dumper.currentUser(conf.dbmsHandler.getCurrentUser()) if conf.getCurrentDb: - dumper.string("current database", conf.dbmsHandler.getCurrentDb()) + conf.dumper.currentDb(conf.dbmsHandler.getCurrentDb()) if conf.isDba: - dumper.string("current user is DBA", conf.dbmsHandler.isDba()) + conf.dumper.dba(conf.dbmsHandler.isDba()) if conf.getUsers: - dumper.lister("database management system users", conf.dbmsHandler.getUsers()) + conf.dumper.users(conf.dbmsHandler.getUsers()) if conf.getPasswordHashes: - dumper.userSettings("database management system users password hashes", - conf.dbmsHandler.getPasswordHashes(), "password hash") + conf.dumper.userSettings("database management system users password hashes", + conf.dbmsHandler.getPasswordHashes(), "password hash") if conf.getPrivileges: - dumper.userSettings("database management system users privileges", - conf.dbmsHandler.getPrivileges(), "privilege") + conf.dumper.userSettings("database management system users privileges", + conf.dbmsHandler.getPrivileges(), "privilege") if conf.getRoles: - dumper.userSettings("database management system users roles", - conf.dbmsHandler.getRoles(), "role") + conf.dumper.userSettings("database management system users roles", + conf.dbmsHandler.getRoles(), "role") if conf.getDbs: - dumper.lister("available databases", conf.dbmsHandler.getDbs()) + conf.dumper.dbs(conf.dbmsHandler.getDbs()) if conf.getTables: - dumper.dbTables(conf.dbmsHandler.getTables()) + conf.dumper.dbTables(conf.dbmsHandler.getTables()) if conf.getColumns: - dumper.dbTableColumns(conf.dbmsHandler.getColumns()) + conf.dumper.dbTableColumns(conf.dbmsHandler.getColumns()) if conf.dumpTable: - dumper.dbTableValues(conf.dbmsHandler.dumpTable()) + conf.dumper.dbTableValues(conf.dbmsHandler.dumpTable()) if conf.dumpAll: conf.dbmsHandler.dumpAll() @@ -124,7 +123,7 @@ def action(): conf.dbmsHandler.search() if conf.query: - dumper.string(conf.query, conf.dbmsHandler.sqlQuery(conf.query)) + conf.dumper.query(conf.query, conf.dbmsHandler.sqlQuery(conf.query)) if conf.sqlShell: conf.dbmsHandler.sqlShell() @@ -135,7 +134,7 @@ def action(): # File system options if conf.rFile: - dumper.string("%s file saved to" % conf.rFile, conf.dbmsHandler.readFile(conf.rFile), sort=False) + conf.dumper.rFile(conf.rFile, conf.dbmsHandler.readFile(conf.rFile)) if conf.wFile: conf.dbmsHandler.writeFile(conf.wFile, conf.dFile, conf.wFileType) @@ -158,7 +157,7 @@ def action(): # Windows registry options if conf.regRead: - dumper.string("Registry key value data", conf.dbmsHandler.regRead()) + conf.dumper.registerValue(conf.dbmsHandler.regRead()) if conf.regAdd: conf.dbmsHandler.regAdd() diff --git a/lib/core/dump.py b/lib/core/dump.py index 5eb3c4b90..d2a71e6ce 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -40,7 +40,7 @@ class Dump: def __init__(self): self.__outputFile = None self.__outputFP = None - + def __write(self, data, n=True): if n: print data @@ -52,11 +52,19 @@ class Dump: self.__outputFP.flush() conf.loggedToOut = True - + + def __formatString(self, string): + string = unicode(string) + string = string.replace("__NEWLINE__", "\n").replace("__TAB__", "\t") + string = string.replace("__START__", "").replace("__STOP__", "") + string = string.replace("__DEL__", ", ") + + return string + def setOutputFile(self): self.__outputFile = "%s%slog" % (conf.outputPath, os.sep) - self.__outputFP = codecs.open(self.__outputFile, "a", conf.dataEncoding) - + self.__outputFP = codecs.open(self.__outputFile, "ab", conf.dataEncoding) + def string(self, header, data, sort=True): if isinstance(data, (list, tuple, set)): self.lister(header, data, sort) @@ -66,9 +74,7 @@ class Dump: data = unicode(data) if data: - data = data.replace("__NEWLINE__", "\n").replace("__TAB__", "\t") - data = data.replace("__START__", "").replace("__STOP__", "") - data = data.replace("__DEL__", ", ") + data = self.__formatString(data) if "\n" in data: self.__write("%s:\n---\n%s\n---\n" % (header, data)) @@ -97,7 +103,25 @@ class Dump: if elements: self.__write("") - + + def technic(self,header,data): + self.string(header, data) + + def banner(self,data): + self.string("banner", data) + + def currentUser(self,data): + self.string("current user", data) + + def currentDb(self,data): + self.string("current database", data) + + def dba(self,data): + self.string("current user is DBA", data) + + def users(self,users): + self.lister("database management system users", users) + def userSettings(self, header, userSettings, subHeader): self.__areAdmins = set() @@ -125,35 +149,8 @@ class Dump: self.__write(" %s: %s" % (subHeader, setting)) print - def dbColumns(self, dbColumns, colConsider, dbs): - for column in dbColumns.keys(): - if colConsider == "1": - colConsiderStr = "s like '" + column + "' were" - else: - colConsiderStr = " '%s' was" % column - - msg = "Column%s found in the " % colConsiderStr - msg += "following databases:" - self.__write(msg) - - printDbs = {} - - for db, tblData in dbs.items(): - for tbl, colData in tblData.items(): - for col, dataType in colData.items(): - if column.lower() in col.lower(): - if db in printDbs: - if tbl in printDbs[db]: - printDbs[db][tbl][col] = dataType - else: - printDbs[db][tbl] = { col: dataType } - else: - printDbs[db] = {} - printDbs[db][tbl] = { col: dataType } - - continue - - self.dbTableColumns(printDbs) + def dbs(self,dbs): + self.lister("available databases", dbs) def dbTables(self, dbTables): if not isinstance(dbTables, dict): @@ -268,7 +265,7 @@ class Dump: os.makedirs(dumpDbPath, 0755) dumpFileName = "%s%s%s.csv" % (dumpDbPath, os.sep, table) - dumpFP = codecs.open(dumpFileName, "w", conf.dataEncoding) + dumpFP = codecs.open(dumpFileName, "wb", conf.dataEncoding) count = int(tableValues["__infos__"]["count"]) separator = "" @@ -350,6 +347,45 @@ class Dump: logger.info("Table '%s.%s' dumped to CSV file '%s'" % (db, table, dumpFileName)) + def dbColumns(self, dbColumns, colConsider, dbs): + for column in dbColumns.keys(): + if colConsider == "1": + colConsiderStr = "s like '" + column + "' were" + else: + colConsiderStr = " '%s' was" % column + + msg = "Column%s found in the " % colConsiderStr + msg += "following databases:" + self.__write(msg) + + printDbs = {} + + for db, tblData in dbs.items(): + for tbl, colData in tblData.items(): + for col, dataType in colData.items(): + if column.lower() in col.lower(): + if db in printDbs: + if tbl in printDbs[db]: + printDbs[db][tbl][col] = dataType + else: + printDbs[db][tbl] = { col: dataType } + else: + printDbs[db] = {} + printDbs[db][tbl] = { col: dataType } + + continue + + self.dbTableColumns(printDbs) + + def query(self, query, queryRes): + self.string(query, queryRes) + + def rFile(self,filePath,fileData): + self.string("%s file saved to" % filePath,fileData,sort=False) + + def registerValue(self,registerData): + self.string("Registry key value data", registerData,sort=False) + # object to manage how to print the retrieved queries output to # standard output and sessions file dumper = Dump() diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 3820c9fca..4198d588b 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -110,8 +110,6 @@ optDict = { "limitStop": "integer", "firstChar": "integer", "lastChar": "integer", - "getNumOfTables": "integer", - "getNumOfDBs": "integer", "query": "string", "sqlShell": "boolean" }, diff --git a/lib/core/target.py b/lib/core/target.py index e23af7853..12a59c277 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -37,6 +37,7 @@ from lib.core.exception import sqlmapFilePathException from lib.core.exception import sqlmapGenericException from lib.core.exception import sqlmapSyntaxException from lib.core.session import resumeConfKb +from lib.core.xmldump import dumper as xmldumper def __setRequestParams(): """ @@ -202,6 +203,14 @@ def __createDumpDir(): if not os.path.isdir(conf.dumpPath): os.makedirs(conf.dumpPath, 0755) +def __configureDumper(): + if conf.xmlFile: + conf.dumper = xmldumper + else: + conf.dumper = dumper + + conf.dumper.setOutputFile() + def __createTargetDirs(): """ Create the output directory. @@ -215,10 +224,9 @@ def __createTargetDirs(): if not os.path.isdir(conf.outputPath): os.makedirs(conf.outputPath, 0755) - dumper.setOutputFile() - __createDumpDir() __createFilesDir() + __configureDumper() def initTargetEnv(): """ diff --git a/lib/core/xmldump.py b/lib/core/xmldump.py new file mode 100644 index 000000000..33724c49e --- /dev/null +++ b/lib/core/xmldump.py @@ -0,0 +1,526 @@ +#!/usr/bin/env python + + +import codecs +import re + +import xml.sax.saxutils as saxutils +from xml.dom.minidom import Document + +from lib.core.data import conf +from lib.core.data import logger +from lib.core.exception import sqlmapFilePathException + +TECHNIC_ELEM_NAME = "Technic" +TECHNICS_ELEM_NAME = "Technics" +BANNER_ELEM_NAME = "Banner" +COLUMNS_ELEM_NAME = "DatabaseColumns" +COLUMN_ELEM_NAME = "Column" +CELL_ELEM_NAME = "Cell" +COLUMN_ATTR = "column" +ROW_ELEM_NAME = "Row" +TABLES_ELEM_NAME = "tables" +DATABASE_COLUMNS_ELEM = "DB" +DB_TABLES_ELEM_NAME = "DBTables" +DB_TABLE_ELEM_NAME = "DBTable" +IS_DBA_ELEM_NAME = "isDBA" +FILE_CONTENT_ELEM_NAME = "FileContent" +DB_ATTR = "db" +UNKNOWN_COLUMN_TYPE= "unknown" +USER_SETTINGS_ELEM_NAME = "UserSettings" +USER_SETTING_ELEM_NAME = "UserSetting" +USERS_ELEM_NAME = "Users" +USER_ELEM_NAME = "User" +DB_USER_ELEM_NAME = "DBUser" +SETTINGS_ELEM_NAME = "Settings" +DBS_ELEM_NAME = "DBs" +DB_NAME_ELEM_NAME = "DBName" +DATABASE_ELEM_NAME = "Database" +TABLE_ELEM_NAME = "Table" +DB_TABLE_VALUES_ELEM_NAME = "DBTableValues" +DB_VALUES_ELEM = "DBValues" +QUERIES_ELEM_NAME = "Queries" +QUERY_ELEM_NAME = "Query" +REGISTERY_ENTRIES_ELEM_NAME = "RegistryEntries" +REGISTER_DATA_ELEM_NAME = "RegisterData" +DEFAULT_DB = "All" +MESSAGE_ELEM = "Message" +MESSAGES_ELEM_NAME = "Messages" +ERROR_ELEM_NAME = "Error" +LST_ELEM_NAME = "List" +LSTS_ELEM_NAME = "Lists" +CURRENT_USER_ELEM_NAME = "CurrentUser" +CURRENT_DB_ELEM_NAME = "CurrentDB" +MEMBER_ELEM = "Member" +ADMIN_USER = "Admin" +REGULAR_USER = "User" +STATUS_ELEM_NAME = "Status" +RESULTS_ELEM_NAME = "Results" +UNHANDLED_PROBLEM_TYPE = "Unhandled" +NAME_ATTR = "name" +TYPE_ATTR = "type" +VALUE_ATTR = "value" +SUCESS_ATTR = "success" +ENCODING = "utf-8" +NAME_SPACE_ATTR = 'http://www.w3.org/2001/XMLSchema-instance' +XMLNS_ATTR = "xmlns:xsi" +SCHEME_NAME = "sqlmap.xsd" +SCHEME_NAME_ATTR = "xsi:noNamespaceSchemaLocation" +CHARACTERS_TO_ENCODE = range(32) + range(127, 256) +ENTITIES = {'"':'"',"'":"'"} + +class XMLDump: + ''' + This class purpose is to dump the data into an xml format. + The format of the xml file is described in the scheme file xml/sqlmap.xsd + ''' + + def __init__(self): + self.__outputFile = None + self.__outputFP = None + self.__root = None + self.__doc = Document() + + def __addToRoot(self,element): + ''' + Adds element to the root element + ''' + self.__root.appendChild(element) + + def __write(self, data, n=True): + ''' + Writes the data into the file + ''' + if n: + self.__outputFP.write("%s\n" % data) + else: + self.__outputFP.write("%s " % data) + + self.__outputFP.flush() + + conf.loggedToOut = True + + def __getRootChild(self,elemName): + ''' + Returns the child of the root with the described name + ''' + elements = self.__root.getElementsByTagName(elemName) + if elements : + return elements[0] + + return elements + + def __createTextNode(self,data): + ''' + Creates a text node with utf8 data inside. + The text is escaped to an fit the xml text format. + ''' + if data is None : + return self.__doc.createTextNode(unicode("","utf-8")) + else : + string = self.__formatString(data) + escaped_data = saxutils.escape(unicode(string), ENTITIES) + return self.__doc.createTextNode(unicode(escaped_data, "utf-8")) + + def __createAttribute(self,attrName,attrValue): + ''' + Creates an attribute node with utf8 data inside. + The text is escaped to an fit the xml text format. + ''' + attr = self.__doc.createAttribute(attrName) + if attrValue is None : + attr.nodeValue = unicode("","utf-8") + else : + escaped_data = unicode(attrValue) + attr.nodeValue = unicode(escaped_data,"utf-8") + return attr + + def __formatString(self, string): + string = unicode(string) + string = string.replace("__NEWLINE__", "\n").replace("__TAB__", "\t") + string = string.replace("__START__", "").replace("__STOP__", "") + string = string.replace("__DEL__", ", ") + return string + + def string(self, header, data, sort=True): + ''' + Adds string element to the xml. + ''' + if isinstance(data, (list, tuple, set)): + self.lister(header, data, sort) + return + + messagesElem = self.__getRootChild(MESSAGES_ELEM_NAME) + if (not(messagesElem)): + messagesElem = self.__doc.createElement(MESSAGES_ELEM_NAME) + self.__addToRoot(messagesElem) + + if data: + data = self.__formatString(data) + else : + data = "" + + elem = self.__doc.createElement(MESSAGE_ELEM) + elem.setAttributeNode(self.__createAttribute(TYPE_ATTR, header)) + elem.appendChild(self.__createTextNode(data)) + messagesElem.appendChild(elem) + + def lister(self, header, elements, sort=True): + ''' + Adds information formatted as list element + ''' + lstElem = self.__doc.createElement(LST_ELEM_NAME) + lstElem.setAttributeNode(self.__createAttribute(TYPE_ATTR, header)) + if elements: + + if sort: + try: + elements = set(elements) + elements = list(elements) + elements.sort(key=lambda x: x.lower()) + except: + pass + + for element in elements: + memberElem = self.__doc.createElement(MEMBER_ELEM) + lstElem.appendChild(memberElem) + if isinstance(element, basestring): + memberElem.setAttributeNode(self.__createAttribute(TYPE_ATTR, "string")) + memberElem.appendChild(self.__createTextNode(element)) + elif isinstance(element, (list, tuple, set)): + memberElem.setAttributeNode(self.__createAttribute(TYPE_ATTR, "list")) + for e in element : + memberElemStr = self.__doc.createElement(MEMBER_ELEM) + memberElemStr.setAttributeNode(self.__createAttribute(TYPE_ATTR, "string")) + memberElemStr.appendChild(self.__createTextNode(unicode(e))) + memberElem.appendChild(memberElemStr) + listsElem = self.__getRootChild(LSTS_ELEM_NAME) + if not(listsElem): + listsElem = self.__doc.createElement(LSTS_ELEM_NAME) + self.__addToRoot(listsElem) + listsElem.appendChild(lstElem) + + def technic(self,technicType,data): + ''' + Adds information about the technic used to extract data from the db + ''' + technicElem = self.__doc.createElement(TECHNIC_ELEM_NAME) + technicElem.setAttributeNode(self.__createAttribute(TYPE_ATTR, technicType)) + textNode = self.__createTextNode(data) + technicElem.appendChild(textNode) + technicsElem = self.__getRootChild(TECHNICS_ELEM_NAME) + if not(technicsElem): + technicsElem = self.__doc.createElement(TECHNICS_ELEM_NAME) + self.__addToRoot(technicsElem) + technicsElem.appendChild(technicElem) + + def banner(self,data): + ''' + Adds information about the database banner to the xml. + The banner contains information about the type and the version of the database. + ''' + bannerElem = self.__doc.createElement(BANNER_ELEM_NAME) + bannerElem.appendChild(self.__createTextNode(data)) + self.__addToRoot(bannerElem) + + def currentUser(self,data): + ''' + Adds information about the current database user to the xml + ''' + currentUserElem = self.__doc.createElement(CURRENT_USER_ELEM_NAME) + textNode = self.__createTextNode(data) + currentUserElem.appendChild(textNode) + self.__addToRoot(currentUserElem) + + def currentDb(self,data): + ''' + Adds information about the current database is use to the xml + ''' + currentDBElem = self.__doc.createElement(CURRENT_DB_ELEM_NAME) + textNode = self.__createTextNode(data) + currentDBElem.appendChild(textNode) + self.__addToRoot(currentDBElem) + + def dba(self,isDBA): + ''' + Adds information to the xml that indicates whether the user has DBA privileges + ''' + isDBAElem = self.__doc.createElement(IS_DBA_ELEM_NAME) + isDBAElem.setAttributeNode(self.__createAttribute(VALUE_ATTR, unicode(isDBA))) + self.__addToRoot(isDBAElem) + + def users(self,users): + ''' + Adds a list of the existing users to the xml + ''' + usersElem = self.__doc.createElement(USERS_ELEM_NAME) + if isinstance(users, basestring): + users = [users] + if users: + for user in users: + userElem = self.__doc.createElement(DB_USER_ELEM_NAME) + usersElem.appendChild(userElem) + userElem.appendChild(self.__createTextNode(user)) + self.__addToRoot(usersElem) + + def dbs(self, dbs): + ''' + Adds a list of the existing databases to the xml + ''' + dbsElem = self.__doc.createElement(DBS_ELEM_NAME) + if dbs: + for db in dbs: + dbElem = self.__doc.createElement(DB_NAME_ELEM_NAME) + dbsElem.appendChild(dbElem) + dbElem.appendChild(self.__createTextNode(db)) + self.__addToRoot(dbsElem) + + def userSettings(self, header, userSettings, subHeader): + ''' + Adds information about the user's settings to the xml. + The information can be user's passwords, privileges and etc.. + ''' + self.__areAdmins = set() + userSettingsElem = self.__getRootChild(USER_SETTINGS_ELEM_NAME) + if (not(userSettingsElem)): + userSettingsElem = self.__doc.createElement(USER_SETTINGS_ELEM_NAME) + self.__addToRoot(userSettingsElem) + + userSettingElem = self.__doc.createElement(USER_SETTING_ELEM_NAME) + userSettingElem.setAttributeNode(self.__createAttribute(TYPE_ATTR, 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: + userElem = self.__doc.createElement(USER_ELEM_NAME) + userSettingElem.appendChild(userElem) + if user in self.__areAdmins: + userElem.setAttributeNode(self.__createAttribute(TYPE_ATTR, ADMIN_USER)) + else: + userElem.setAttributeNode(self.__createAttribute(TYPE_ATTR, REGULAR_USER)) + + settings = userSettings[user] + + settings.sort() + + for setting in settings: + settingsElem = self.__doc.createElement(SETTINGS_ELEM_NAME) + settingsElem.setAttributeNode(self.__createAttribute(TYPE_ATTR, subHeader)) + settingTextNode = self.__createTextNode(setting) + settingsElem.appendChild(settingTextNode) + userElem.appendChild(settingsElem) + userSettingsElem.appendChild(userSettingElem) + + def dbTables(self, dbTables): + ''' + Adds information of the existing db tables to the xml + ''' + if not isinstance(dbTables, dict): + self.string(TABLES_ELEM_NAME, dbTables) + return + + dbTablesElem = self.__doc.createElement(DB_TABLES_ELEM_NAME) + + for db, tables in dbTables.items(): + tables.sort(key=lambda x: x.lower()) + dbElem = self.__doc.createElement(DATABASE_ELEM_NAME) + dbElem.setAttributeNode(self.__createAttribute(NAME_ATTR,db)) + dbTablesElem.appendChild(dbElem) + for table in tables: + tableElem = self.__doc.createElement(DB_TABLE_ELEM_NAME) + tableElem.appendChild(self.__createTextNode(table)) + dbElem.appendChild(tableElem) + self.__addToRoot(dbTablesElem) + + def dbTableColumns(self, tableColumns): + ''' + Adds information about the columns of the existing tables to the xml + ''' + + columnsElem = self.__getRootChild(COLUMNS_ELEM_NAME) + if not(columnsElem): + columnsElem = self.__doc.createElement(COLUMNS_ELEM_NAME) + + for db, tables in tableColumns.items(): + if not db: + db = DEFAULT_DB + dbElem = self.__doc.createElement(DATABASE_COLUMNS_ELEM) + dbElem.setAttributeNode(self.__createAttribute(NAME_ATTR, db)) + columnsElem.appendChild(dbElem) + + for table, columns in tables.items(): + tableElem = self.__doc.createElement(TABLE_ELEM_NAME) + tableElem.setAttributeNode(self.__createAttribute(NAME_ATTR, table)) + + colList = columns.keys() + colList.sort(key=lambda x: x.lower()) + + for column in colList: + colType = columns[column] + colElem = self.__doc.createElement(COLUMN_ELEM_NAME) + if colType is not None: + colElem.setAttributeNode(self.__createAttribute(TYPE_ATTR, colType)) + else : + colElem.setAttributeNode(self.__createAttribute(TYPE_ATTR, UNKNOWN_COLUMN_TYPE)) + colElem.appendChild(self.__createTextNode(column)) + tableElem.appendChild(colElem) + + self.__addToRoot(columnsElem) + + def dbTableValues(self, tableValues): + ''' + Adds the values of specific table to the xml. + The values are organized according to the relevant row and column. + ''' + tableElem = self.__doc.createElement(DB_TABLE_VALUES_ELEM_NAME) + if (tableValues is not None): + db = tableValues["__infos__"]["db"] + if not db: + db = "All" + table = tableValues["__infos__"]["table"] + + count = int(tableValues["__infos__"]["count"]) + columns = tableValues.keys() + columns.sort(key=lambda x: x.lower()) + + tableElem.setAttributeNode(self.__createAttribute(DB_ATTR, db)) + tableElem.setAttributeNode(self.__createAttribute(NAME_ATTR, table)) + + for i in range(count): + rowElem = self.__doc.createElement(ROW_ELEM_NAME) + tableElem.appendChild(rowElem) + for column in columns: + if column != "__infos__": + info = tableValues[column] + value = info["values"][i] + + if re.search("^[\ *]*$", value): + value = "NULL" + + cellElem = self.__doc.createElement(CELL_ELEM_NAME) + cellElem.setAttributeNode(self.__createAttribute(COLUMN_ATTR, column)) + cellElem.appendChild(self.__createTextNode(value)) + rowElem.appendChild(cellElem) + + dbValuesElem = self.__getRootChild(DB_VALUES_ELEM) + if (not(dbValuesElem)): + dbValuesElem = self.__doc.createElement(DB_VALUES_ELEM) + self.__addToRoot(dbValuesElem) + + dbValuesElem.appendChild(tableElem) + + logger.info("Table '%s.%s' dumped to XML file" % (db, table)) + + def dbColumns(self, dbColumns, colConsider, dbs): + ''' + Adds information about the columns + ''' + for column in dbColumns.keys(): + printDbs = {} + for db, tblData in dbs.items(): + for tbl, colData in tblData.items(): + for col, dataType in colData.items(): + if column in col: + if db in printDbs: + if tbl in printDbs[db]: + printDbs[db][tbl][col] = dataType + else: + printDbs[db][tbl] = { col: dataType } + else: + printDbs[db] = {} + printDbs[db][tbl] = { col: dataType } + + continue + + self.dbTableColumns(printDbs) + + def query(self,query,queryRes): + ''' + Adds details of an executed query to the xml. + The query details are the query itself and it's results. + ''' + queryElem = self.__doc.createElement(QUERY_ELEM_NAME) + queryElem.setAttributeNode(self.__createAttribute(VALUE_ATTR, query)) + queryElem.appendChild(self.__createTextNode(queryRes)) + queriesElem = self.__getRootChild(QUERIES_ELEM_NAME) + if (not(queriesElem)): + queriesElem = self.__doc.createElement(QUERIES_ELEM_NAME) + self.__addToRoot(queriesElem) + queriesElem.appendChild(queryElem) + + def registerValue(self,registerData): + ''' + Adds information about an extracted registry key to the xml + ''' + registerElem = self.__doc.createElement(REGISTER_DATA_ELEM_NAME) + registerElem.appendChild(self.__createTextNode(registerData)) + registriesElem = self.__getRootChild(REGISTERY_ENTRIES_ELEM_NAME) + if (not(registriesElem)): + registriesElem = self.__doc.createElement(REGISTERY_ENTRIES_ELEM_NAME) + self.__addToRoot(registriesElem) + registriesElem.appendChild(registerElem) + + def rFile(self, filePath, data): + ''' + Adds an extracted file's content to the xml + ''' + fileContentElem = self.__doc.createElement(FILE_CONTENT_ELEM_NAME) + fileContentElem.setAttributeNode(self.__createAttribute(NAME_ATTR, filePath)) + fileContentElem.appendChild(self.__createTextNode(data)) + self.__addToRoot(fileContentElem) + + def setOutputFile(self): + ''' + Initiates the xml file from the configuration. + ''' + if (conf.xmlFile) : + try : + self.__outputFile = conf.xmlFile + self.__outputFP = codecs.open(self.__outputFile, "ab", conf.dataEncoding) + self.__root = self.__doc.createElementNS(NAME_SPACE_ATTR, RESULTS_ELEM_NAME) + self.__root.setAttributeNode(self.__createAttribute(XMLNS_ATTR,NAME_SPACE_ATTR)) + self.__root.setAttributeNode(self.__createAttribute(SCHEME_NAME_ATTR,SCHEME_NAME)) + self.__doc.appendChild(self.__root) + except IOError, e: + raise sqlmapFilePathException("Wrong filename provided for saving the xml file: %s" % conf.xmlFile) + + def finish(self, resultStatus, resultMsg=""): + ''' + Finishes the dumper operation: + 1. Adds the session status to the xml + 2. Writes the xml to the file + 3. Closes the xml file + ''' + if ((self.__outputFP is not None) and not(self.__outputFP.closed)): + statusElem = self.__doc.createElement(STATUS_ELEM_NAME) + statusElem.setAttributeNode(self.__createAttribute(SUCESS_ATTR,unicode(resultStatus))) + + if not(resultStatus) : + errorElem = self.__doc.createElement(ERROR_ELEM_NAME) + + if (isinstance(resultMsg, Exception)): + errorElem.setAttributeNode(self.__createAttribute(TYPE_ATTR, type(resultMsg).__name__)) + else : + errorElem.setAttributeNode(self.__createAttribute(TYPE_ATTR, UNHANDLED_PROBLEM_TYPE)) + + errorElem.appendChild(self.__createTextNode(unicode(resultMsg))) + statusElem.appendChild(errorElem) + + self.__addToRoot(statusElem) + self.__write(self.__doc.toprettyxml(encoding=ENCODING)) + self.__outputFP.close() + +def closeDumper(status, msg=""): + """ + Closes the dumper of the session + """ + + if hasattr(conf, "dumper") and hasattr(conf.dumper, "finish"): + conf.dumper.finish(status, msg) + +dumper = XMLDump() diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 376903570..2f1c99df4 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -414,6 +414,9 @@ def cmdLineParser(): # Miscellaneous options miscellaneous = OptionGroup(parser, "Miscellaneous") + miscellaneous.add_option("-x", dest="xmlFile", + help="Dump the data into an XML file") + miscellaneous.add_option("-s", dest="sessionFile", help="Save and resume all data retrieved " "on a session file") diff --git a/lib/takeover/abstraction.py b/lib/takeover/abstraction.py index 5cdc1b02f..1d0d74a5d 100644 --- a/lib/takeover/abstraction.py +++ b/lib/takeover/abstraction.py @@ -26,7 +26,6 @@ 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.dump import dumper from lib.core.exception import sqlmapUnsupportedFeatureException from lib.core.shell import autoCompletion from lib.takeover.udf import UDF @@ -90,7 +89,7 @@ class Abstraction(Web, UDF, xp_cmdshell): output = self.evalCmd(cmd) if output: - dumper.string("command standard output", output) + conf.dumper.string("command standard output", output) else: print "No output" else: diff --git a/lib/takeover/udf.py b/lib/takeover/udf.py index cc4fd79ed..91e8bab70 100644 --- a/lib/takeover/udf.py +++ b/lib/takeover/udf.py @@ -31,7 +31,6 @@ from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger from lib.core.data import queries -from lib.core.dump import dumper from lib.core.exception import sqlmapFilePathException from lib.core.exception import sqlmapMissingMandatoryOptionException from lib.core.exception import sqlmapUnsupportedFeatureException @@ -370,7 +369,7 @@ class UDF: output = self.udfEvalCmd(cmd, udfName=udfToCall) if output: - dumper.string("return value", output) + conf.dumper.string("return value", output) else: print "No return value" else: diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py index c66a2a2ea..eaf1ed635 100644 --- a/plugins/generic/enumeration.py +++ b/plugins/generic/enumeration.py @@ -30,12 +30,12 @@ from lib.core.common import parsePasswordHash from lib.core.common import readInput from lib.core.common import safeStringFormat from lib.core.convert import urlencode +from lib.core.convert import utf8decode from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger from lib.core.data import queries from lib.core.data import temp -from lib.core.dump import dumper from lib.core.exception import sqlmapMissingMandatoryOptionException from lib.core.exception import sqlmapNoneDataException from lib.core.exception import sqlmapUnsupportedFeatureException @@ -80,7 +80,7 @@ class Enumeration: if not kb.data.banner: if conf.unionUse or conf.unionTest: - dumper.string("valid union", unionTest()) + conf.dumper.technic("valid union", unionTest()) query = queries[kb.dbms].banner kb.data.banner = inject.getValue(query) @@ -775,7 +775,7 @@ class Enumeration: plusOne = True else: plusOne = False - indexRange = getRange(count, plusOne=plusOne) + indexRange = getRange(count) for index in indexRange: if kb.dbms in ("SQLite", "Firebird"): @@ -1183,7 +1183,7 @@ class Enumeration: data = self.dumpTable() if data: - dumper.dbTableValues(data) + conf.dumper.dbTableValues(data) def dumpFoundColumn(self, dbs, foundCols, colConsider): if not dbs: @@ -1192,7 +1192,7 @@ class Enumeration: logger.warn(warnMsg) return - dumper.dbColumns(foundCols, colConsider, dbs) + conf.dumper.dbColumns(foundCols, colConsider, dbs) message = "do you want to dump entries? [Y/n] " output = readInput(message, default="Y") @@ -1254,7 +1254,7 @@ class Enumeration: data = self.dumpTable() if data: - dumper.dbTableValues(data) + conf.dumper.dbTableValues(data) def searchDb(self): foundDbs = [] @@ -1620,10 +1620,10 @@ class Enumeration: def search(self): if conf.db: - dumper.lister("found databases", self.searchDb()) + conf.dumper.lister("found databases", self.searchDb()) if conf.tbl: - dumper.dbTables(self.searchTable()) + conf.dumper.dbTables(self.searchTable()) if conf.col: self.searchColumn() @@ -1691,6 +1691,7 @@ class Enumeration: try: query = raw_input("sql-shell> ") + query = utf8decode(query) except KeyboardInterrupt: print errMsg = "user aborted" @@ -1710,7 +1711,7 @@ class Enumeration: output = self.sqlQuery(query) if output and output != "Quit": - dumper.string(query, output) + conf.dumper.query(query, output) elif not output: pass diff --git a/sqlmap.conf b/sqlmap.conf index 8c2ccd57f..dc7df9794 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -442,10 +442,12 @@ regType = [Miscellaneous] +# Dump the data into an XML file. +xmlFile = + # Save and resume all data retrieved on a session file. sessionFile = - # Flush session file for current target. flushSession = False diff --git a/sqlmap.py b/sqlmap.py index 59345443e..453d96a97 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -52,6 +52,7 @@ from lib.core.data import paths from lib.core.exception import exceptionsTuple from lib.core.exception import unhandledException from lib.core.option import init +from lib.core.xmldump import closeDumper from lib.parse.cmdline import cmdLineParser def modulePath(): @@ -87,22 +88,29 @@ def main(): except exceptionsTuple, e: e = unicode(e) logger.error(e) + closeDumper(False, e) - except KeyboardInterrupt: + except KeyboardInterrupt, e: print errMsg = "user aborted" logger.error(errMsg) + closeDumper(False, e) - except EOFError: + except EOFError, e: print errMsg = "exit" logger.error(errMsg) + closeDumper(False, e) except: print errMsg = unhandledException() logger.error(errMsg) traceback.print_exc() + closeDumper(False, errMsg) + + else: + closeDumper(True) print "\n[*] shutting down at: %s\n" % time.strftime("%X") diff --git a/txt/common-outputs.txt b/txt/common-outputs.txt index 32ff95c2f..4acf9bcbf 100644 --- a/txt/common-outputs.txt +++ b/txt/common-outputs.txt @@ -1,14 +1,31 @@ +[Databases] +information_schema +mysql +public +master + [Tables] CHARACTER_SETS COLLATION_CHARACTER_SET_APPLICABILITY COLLATIONS COLUMN_PRIVILEGES COLUMNS +ENGINES +EVENTS +FILES +GLOBAL_STATUS +GLOBAL_VARIABLES KEY_COLUMN_USAGE +PARTITIONS +PLUGINS +PROCESSLIST PROFILING +REFERENTIAL_CONSTRAINTS ROUTINES SCHEMA_PRIVILEGES SCHEMATA +SESSION_STATUS +SESSION_VARIABLES STATISTICS TABLE_CONSTRAINTS TABLE_PRIVILEGES @@ -16,8 +33,3 @@ TABLES TRIGGERS USER_PRIVILEGES VIEWS - -[Databases] -information_schema -mysql -iabc \ No newline at end of file diff --git a/xml/sqlmap.xsd b/xml/sqlmap.xsd new file mode 100644 index 000000000..62e35e12b --- /dev/null +++ b/xml/sqlmap.xsd @@ -0,0 +1,284 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +