As Kamil Stawiarski explained in some great articles :
“A lot companies consolidates databases into one appliance – like for example Oracle Exadata. So you can have a lot of different databases in one physical cluster. And what if I tell you that you can execute any OS command as an oracle user, having just access to a database user with appropriate privileges? What if I tell you that DBA=SYSDBA? And not just SYSDBA for one database but for every database in a cluster?” Ref1
This is possible using only three elements thanks to the PREPROCESSOR feature introduced in oracle 11G Ref2 :
- CREATE ANY DIRECTORY
- UTL_FILE
- CREATE EXTERNAL TABLE
Also think about the new multitenant architecture ! (dba=sysdba for all pluggable database)
UPDATE 05/03/2017 : A new parameter was introduced in 12.2.0.1 that is meant to prevent the pre-processor threat “PDB_OS_CREDENTIAL” in CDB databases (thank’s Franck Pachot who informed me about it)
UPDATE 07/03/2017 : See this blog post for a different solution https://mahmoudhatem.wordpress.com/2017/03/07/create-any-directory-threats-pdbs-and-the-path_prefix-clause/
Here is the basic principle based on Kamil example :
Step 1 : Generate the script
declare v_file utl_file.file_type; begin v_file:=utl_file.fopen('TEMP_DIR','execute_me.sh','w'); utl_file.put_line(v_file,'export PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin'); utl_file.put_line(v_file,'export ORACLE_HOME=/oracle11/product/11.2.0/dbhome_1'); utl_file.put_line(v_file,'export ORACLE_SID=testdb'); utl_file.put_line(v_file,'export PATH=$ORACLE_HOME/bin:$PATH'); utl_file.put_line(v_file,'ps -ef | grep pmon'); utl_file.fclose(v_file); end; /
Step 2 : create the external table
CREATE TABLE exec_command ( txt varchar2(4000) ) ORGANIZATION EXTERNAL ( TYPE ORACLE_LOADER DEFAULT DIRECTORY temp_dir ACCESS PARAMETERS ( RECORDS DELIMITED BY NEWLINE PREPROCESSOR exec_dir:'bash' FIELDS TERMINATED BY ',' MISSING FIELD VALUES ARE NULL ( txt ) ) LOCATION ('execute_me.sh') );
Step 3 : call it
select * from exec_command;
Output : that’s it !
It would be great to have some kind of ACL which will limit the files that can be executed by the PREPROCESSOR and controlled only by the sysdba user or oracle user owner for example.Sadly this not possible with the current oracle database version (until 12CR1) ! so what to do ? wait and hope that this will be enhanced in feature release ?
A better idea is to just develop your own patch using dynamic tracing tools ! so here i will show how we can do that ! (i will show only the general idea so the script developed here is far from being perfect nor exhaustive )
TEST ENV : ORACLE 11.2.0.4/OEL6/UEK4/Systemtap 3.0
First i used strace to get an idea on the execution flaw (process forked,exec,etc) :
As expected the script “/tmp/execute_me.sh” is executed inside another child process.
Time for memory reference tracing !
Using intel pin tools “pinatrace.so” i traced the memory access of the target process (for more info on intel pin Ref3 ) and searched for the occurrence of the script to be executed in raw format (“/tmp/execute_me.sh”). Gotcha “skudmio_prep” ! The process is also forked inside this function “<fork@plt>” and our execution script is pointed by register RDX. That’s it ! not too complicated ! All we have to do now is write our systemtap script for live patching our function (See luca Canali example on live patching ):
This systemtap script will check the script passed for execution and if it’s in the white list will be allowed to execute else it will be denied and replaced by a predefined script.In this example only the “/tmp/execute_me.sh” script is allowed to execute other scripts will be replaced with the “/deny” script (contain echo Access denied).
probe process("oracle").function("skudmio_prep") { if ( user_string(register("rdx")) == "/tmp/execute_me.sh" ) { printf("Access granted %s\n",user_string(register("rdx"))); } else { printf("Access denied %s\n",user_string(register("rdx"))); replace_string(register("rdx"),"/deny"); } } function replace_string(old_text:long,new_text:string) %{ char *oldtext; char *newtext; oldtext = (char *) STAP_ARG_old_text; newtext = (char *) STAP_ARG_new_text; strcpy(oldtext,newtext); %}
Example output :
Executing the systemtap script :
Executing the external table :
That’s it 😀 Your own preprocessor ACL with live patching !
DOWNLOAD : acl_preprocessing.stp
Hatem great post…
Thank you Adam 🙂
This is true, but there is not unexpected or hidden. The docs go almost overboard in telling this 🙂
https://docs.oracle.com/database/121/DBSEG/guidelines.htm#DBSEG501
============================
Guidelines for Securing the ORACLE_LOADER Access Driver
Oracle provides a set of guidelines to secure the ORACLE_LOADER access driver.
Create a separate operating system directory to store the access driver preprocessors. You (or the operating system manager) may need to create multiple directories if different Oracle Database users will run different preprocessors. If you want to prevent one set of users from using one preprocessor while allowing those users access to another preprocessor, then place the preprocessors in separate directories. If all the users need equal access, then you can place the preprocessors together in one directory. After you create these operating system directories, in SQL*Plus, you can create a directory object for each directory.
Grant the operating system user ORACLE the correct operating system privileges to run the access driver preprocessor. In addition, protect the preprocessor program from WRITE access by operating system users other than the user responsible for managing the preprocessor program.
Grant the EXECUTE privilege to each user who will run the preprocessor program in the directory object. Do not grant this user the WRITE privilege on the directory object. Never grant users both the EXECUTE and WRITE privilege for directory objects.
Grant the WRITE privilege sparingly to anyone who will manage directory objects that contain preprocessors. This prevents database users from accidentally or maliciously overwriting the preprocessor program.
Create a separate operating system directory and directory object for any data files that are required for external tables. Ensure that these are separate from the directory and directory object used by the access directory preprocessor.
Work with the operating system manager to ensure that only the appropriate operating system users have access to this directory. Grant the ORACLE operating system user READ access to any directory that has a directory object with READ privileges granted to database users. Similarly, grant the ORACLE operating system user WRITE access to any directory that has the WRITE privilege granted to database users.
Create a separate operating system directory and directory object for any files that the access driver generates. This includes log files, bad files, and discarded files. You and the operating system manager must ensure that this directory and directory object have the proper protections, similar to those described in Guideline 5. The database user may need to access these files when resolving problems in data files, so you and the operating system manager must determine a way for this user to read those files.
Grant the CREATE ANY DIRECTORY and DROP ANY DIRECTORY privileges sparingly. Users who have these privileges and users who have been granted the DBA role have full access to all directory objects.
Consider auditing the DROP ANY DIRECTORY privilege. See “Auditing System Privileges” for more information about auditing privileges.
Consider auditing the directory object. See “Auditing Object Actions” for more information.
============================
Similarly, giving out “create any directory” is just setting yourself up to get burned – a few nasty UTL_FILE calls against anything V$DATAFILE and whoosh…the database is gone.
Cheers,
Connor
Thank you for your comment ! as you stated the doc already warned us about the “create any directory” privilege and the preprocessor clause .But i think that a more granular control would have been appreciated.A DBA for one database (or PDB) does not need to see/get access to all other databases (or PDBs) in the same host or execute any os scripts he want (At least the os command could execute with a lower privileged user) .Also as you mentioned there is another threat beside executing os command a malicious user can overwrite for example the database files with some nasty UTL_FILE script but i kept that for another blog post 🙂
As Franck Pachot replied in Twitter It seem that a new parameter was introduced in 12.2.0.1 PDB_OS_CREDENTIAL to prevent the pre-processor threat on consolidated environment : The operating system interactions that are done as the OS user name specified in the credential include : – External table pre-processors.
This was a very interesting read to me as I haven’t looked closer at systemtap so far.
Side note: this measure will not prevent me from overwriting the whitelisted “execute_me.sh” with malicious code, if I still have the privileges you mentioned, or will it?
Cheers,
Uwe
Hi , yes it will not prevent you from overwriting it but as mentioned in the oracle documentation you should protect the preprocessor program from WRITE access by operating system users other than the user responsible for managing the preprocessor program.
Hatem, can this create any directory permission pose risk to other servers in the domain. I mean with this permission the grantee can elevate his permissions and do anything on the server or on the cluster if it’s rac. But can other servers in the domain also get impacted? Any thoughts?
Indeed this can open other possibility after getting control of the server.