#!/usr/bin/python """ Oracle Database PL/SQL Fuzzing Tool Copyright (c) 2005, 2006 Joxean Koret, joxeankoret [at] yahoo.es This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ import sys import cx_Oracle global connection funnydata = ("TEST", "SYS", "XMLREF", '" || XMLREF() || "', 'TEST" A A ', "'", '"', "A"*30, "A"*100, "A"*128,"A"*256,"A"*512,"A"*1024, "A"*2048,"A"*3000,"A"*4000,"A"*5000,"A"*6000,"A"*7000,"A"*8000,"A"*10000,"A"*15000,"A"*20000,"A"*25000, "A"*30000,"A"*32767, -1, -2, 0, 1, 2, 2147483647, -2147483647, 2147483648, -2147483648, "ROWID", "PRIMARY KEY", "%s%s%s%s%s%s%s", "%x%x%x%x%x%x", "%d%d%d%d%d%d", "GRANT DBA TO TEST", "GRANT DBA TO PUBLIC", "SELECT * FROM DBA_USERS", "' OR '1'='1", "AA' or ""TEST"".""XMLREF"" ","V1", "TEST.V1", '"TEST"."V1"', None) def fuzzData(data, index): global connection for x in funnydata: try: if type(x) is int: print "Data is number",x else: print "Data is " + str(x)[0:30] + " of length " + str(len(str(x))) varList = [] for var in range(index): varList.append(x) cur = connection.cursor() cur.execute(data, varList) except: error = str(sys.exc_info()[1]) if error.upper().find("ORA-00933") > -1 or error.upper().find("ORA-01756:") > -1 or error.upper().find("ORA-00923:") > -1: print "*** POSSIBLE SQL INJECTION FOUND ***" elif error.upper().find("ORA-03113") > -1: if len(str(x)) > 50: print "*** POSSIBLE BUFFER OVERFLOW ***" else: print "*** INSTANCE CRASHED ***" print "Reconnecting ... " connect() elif error.upper().find("ORA-00600") > -1: print "*** INTERNAL ERROR ***" elif error.upper().find("PLS-00306:") > -1: print "Currently unfuzzable :(" continue elif error.upper().find("ORA-03114") > -1: print "We are not connected :?" connect() print error def connect(): global connection link = "test/test@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.1.10)(PORT=1521)))" link += "(CONNECT_DATA=(SERVICE_NAME=orcl)))" connection = cx_Oracle.connect(link) connection.rollback() connection.commit() def isFunc(data, index, cursorData): global connection try: varList = [] data = """BEGIN """+ data + """(""" index = 0 for x in cursorData: index += 1 if index == 1: data += str(x[1]) + "=>:" + str(index) else: data += "," + str(x[1]) + "=>:" + str(index) data += """); end;""" for var in range(index): varList.append(None) cur = connection.cursor() cur.execute(data, varList) return 0 except: error = str(sys.exc_info()[1]) if error.upper().find("PLS-00221")> -1: return 1 else: return 0 def die(msg): print msg sys.exit(0) def main(): global connection fuzzPackages = """ select distinct owner "Owner", package_name "Package", package_name "Package", package_name "Package", object_name "Program_Unit" from sys.all_arguments x where argument_name is not null and not exists (select 1 from sys.all_arguments y where x.owner = y.owner and x.package_name = y.owner and x.object_name = y.object_name and x.data_level = y.data_level and y.data_type not in ('VARCHAR2', 'RAW', 'NCHAR', 'BINARY_INTEGER', 'BINARY_FLOAT', 'CHAR', 'NVARCHAR2', 'NUMBER', 'FLOAT', 'LONG RAW') and rownum = 1) order by owner, package_name, object_name """ packageProcedures = """ select position "Position", argument_name "Argument", data_type "Data type", initcap(in_out) "In_Out", owner sdev_link_owner, package_name sdev_link_name, 'PACKAGE' sdev_link_type from sys.all_arguments where argument_name is not null and owner = :1 and (:2 is null or instr(upper(object_name),upper(:3)) > 0 or instr(upper(package_name),upper(:4)) > 0 ) and object_name = :5 and data_type in ('VARCHAR2', 'RAW', 'NCHAR', 'BINARY_INTEGER', 'BINARY_FLOAT', 'CHAR', 'NVARCHAR2', 'NUMBER', 'FLOAT', 'LONG RAW') order by owner, package_name, object_name, position """ connect() bStart = False try: cursor = connection.cursor() cursor.execute(fuzzPackages) result = """ BEGIN """ pkgName = "" func = 0 print "Running first query. It may take a long while ... " totalProcs = 0 for pkgData in cursor.fetchall(): totalProcs += 1 if not pkgData[1] is None: pkgName = pkgData[0] + "." + pkgData[1] + "." + pkgData[4] else: pkgName = pkgData[0] + "." + pkgData[4] procCursor = connection.cursor() procCursor.execute(packageProcedures, pkgData) procCursorData = procCursor.fetchall() func = isFunc(pkgName, len(procCursorData), procCursorData) if int(func) == 0: data = """BEGIN """ + pkgName + """(""" else: data = """SELECT """ + pkgName + """(""" index = 0 prevX = None for x in procCursorData: if x == prevX: continue prevX = x index += 1 if index == 1: if func == 0: data += str(x[1]) + "=>:" + str(index) else: data += ":" + str(index) else: if func == 0: data += "," + str(x[1]) + "=>:" + str(index) else: data += ", :" + str(index) if func == 0: data += """); end;""" else: data += """) from dual """ print "----------" print data print "----------" fuzzData(data, index) connection.close() except Exception, e: print "Error",e print "While fuzzing index",totalProcs,"relative to",pkgName raise e print print "Fuzzed",totalProcs,"procedure(s) and function(s)." print "Done." if __name__ == "__main__": main()