MySQL安全攻防實戰指南之體系結構篇
原創【51CTO.com獨家特稿】本文將為讀者詳細介紹MySQL數據庫在體系結構方面的安全問題以及相應的攻擊方法,在后續的文章中我們將介紹相應的防御措施。
一、MySQL支持的機器體系結構
MySQL自稱是當今世界上最流行的開源數據庫,并且是免費發行,同時還能運行在各種平臺之上——這也正是黑客們對它趨之若鶩的原因。與其它大型數據庫相比較而言,它的配置工作要容易、簡單得多,并且性能也說的過去。盡管它的用法相對來說簡單一些,但是在安全配置方面還是有大量的工作要做的,這也是許多數據庫存在安全隱患的原因之一。
一般情況下,人們都是通過二進制文件程序包來安裝MySQL數據庫服務器,這時它可以安裝到如下所示的系統:Linux x86、Linux IA64、Linux AMD64、Windows、Solaris、FreeBSD、Mac OS X、HP-UX、IBM AIX、QNX、Novell Netware、OpenBSD、SGI IRIX、DEC OSF;如果從源代碼進行安裝的話,那么它能支持的平臺還要多。
#p#
二、MySQL的部署
由于MySQL服務器是如此流行,以至于在網絡中的任何角落幾乎都能找到它的身影,實際上,它不僅安裝到專用服務器中,就連桌面機器中也常見它的蹤跡。
在一般的配置中,客戶端會通過TCP 3306端口連接到MySQL。對于Windows平臺而言,還可以通過有名管道使用MySQL,不過一般不推薦這樣配置。默認情況下,以有名管道模式運行的MySQL會同時偵聽TCP 3306端口和一個名為MySQL的有名管道。與諸如Oracle之類的數據庫系統相比,MySQL使用的網絡協議就比較簡單了,并且默認情況下是使用明文通信,當然也可以使用SSL來保護通信。啟用SSL協議后,數據庫仍然是一TCP 3306端口。
您可以很輕松地檢測一臺主機上運行的MySQL的版本,因為只要連接該機器,它就會返回數據庫的主要版本號和次要版本號,某些版本還會提供操作系統的提示信息。任何能夠抓取旗標的TCP端口掃描器都能夠返回MySQL版本。
MySQL最普遍的應用是為動態web應用程序提供后端支持,當我們掃描網絡時見到的MySQL通常出現在Apache/PHP應用程序的后端,甚至有時候它竟跟web服務器運行在同一臺主機上。在一些大型組織中,它常用作日志記錄服務器,用于存放入侵檢測系統日志、web日志或者其他審計任務。有些情況下,特別是在開發環境中,MySQL經常被安裝到一臺桌面機器上,所以在PC上掃描到它也就不足為奇了。
由于MySQL通信協議是明文通信,因此人們喜歡在MySQL服務器所在主機上同時安裝一個SSH服務器,并使用端口轉發技術通過加密隧道連接至3306端口。這種方法有幾種好處:數據在運輸中是加密的,并強迫執行一個額外的認證步驟,同時還給該數據庫的連接提供了額外的審計記錄。
此外,有人認為MySQL服務器應該與web服務器安裝到同一臺機器上,因為這樣可以避免遠程連接,但是這種配置本身就是不安全的。之所以這樣說,是因為MySQL的數據表是以文件的形式存儲的,并且通常沒有鎖定,那么一旦Web應用程序出現文件泄露漏洞,這就使得攻擊者能夠下載數據庫的所有內容。從另一方面來說,Web應用程序中的SQL注入漏洞也可能導致攻擊者得以修改web服務器上的腳本內容。恰當的文件權限可以防止此類問題,但是除此之外,將web服務器和數據庫服務器置于同一臺機器上還為攻擊者敞開了許多其它的方便之門。
#p#
三、WinMySQLAdmin簡介
當MySQL安裝到Windows平臺的時候,通常會提供WinMySQLAdmin工具。當這個工具第一次運行時,它會將自身添加到運行它的用戶所在的啟動組。當它運行時,WinMySQLAdmin會自動地啟動MySQL,這會導致運行于Windows主機的MySQL的實例在無意間被運行。
此外,當WinMySQLAdmin運行于沒有默認MySQL用戶帳戶的主機時,它會要求用戶創建一個用戶名和口令對,并將這些憑證以明文形式存儲在系統根目錄(例如,c:\winnt)下的my.ini文件中。這個文件通常對該主機上的任何用戶來說都是可讀的。
#p#
四、缺省用戶名和口令
MySQL的默認配置隨平臺、部署模式、發行版本(源代碼或者二進制文件)和初始配置的不同而異,在有些情況下MySQL服務器一經安裝就可能被遠程攻擊者所攻陷——如果缺省配置有問題的話。舉例來說,MySQL 4.0.20的一些默認配置下,表mysql.user中有四條缺省記錄,有兩條用于root帳戶,另兩條用于匿名帳戶。并且,有一個帶有root權限的遠程表項是給主機build上的根用戶的。這些表中的表項的具體含義,我們會在后面加以詳解,現在我們只需知道的是:
如果您位于本地主機,那么您可以作為密碼為空的root根用戶通過身份驗證,并獲得數據庫的完全控制權。如果您位于本地主機,您可以使用任何用戶名通過身份驗證,并獲得對該數據庫的來賓訪問權限。如果您位于一個遠程主機上,同時能夠控制服務器的名稱解析,這樣的話就可以使你的主機名看起來是“build”,所以您就可以作為密碼為空的root用戶通過身份驗證,從而獲得數據庫的完全控制權。如果您位于一臺名為build的遠程主機上,那么您就可以使用任何用戶名來通過身份驗證,并獲得數據庫的來賓訪問權限。
在Windows主機上,根帳戶的存在會導致任何本地用戶都能將自身權限提升為本地系統級訪問權限;而在默認情形下,MySQL就是運行在系統權限級別的。糟糕的是,攻擊者只需簡單將其機器命名為build,那么他就輕而易舉地獲得了對運行MySQL服務的機器的遠程系統級訪問權限。當然,攻擊者必須位于目標所在的同一個NetBIOS名字域中,或者能夠偽造一個DNS響應。針對這一問題的防御方法是:
- 安裝MySQL時禁用網絡連接。
- 安裝之后立即刪除mysql.user表中除本地主機root帳戶之外的全部帳戶。
- 為本地主機root帳戶設置一個復雜的密碼。
#p#
五、身份驗證協議中的安全漏洞
MySQL使用一個專有協議進行身份驗證和發送接收數據。這個協議比較簡單,所以可以為MySQL輕松編寫一個定制的客戶端。換句話說,MySQL身份驗證協議的各版本中的嚴重漏洞可能會導致服務器立即被攻陷。下面我們介紹之前發現的一些漏洞,以及針對這些漏洞的攻擊方法。
描述這些攻擊方法之前,我們先來大致介紹涉及身份驗證協議的包格式和加密機制。當客戶端連接服務器時,服務器問候數據包,其中包含下列域:
- Packet Length (3 bytes)
- Packet Number (1 byte)
- Protocol Version (1 byte)
- Server Version String (以null結尾)
- Server Thread ID (4 bytes)
- Challenge String (以null結尾)
- Server Capabilities Flags (2 bytes)
- Server Character Set (1 byte)
- Server Status (2 bytes)
- Padding (數據包的其余部分)
就身份驗證協議而言,有關的內容是Protocol Version和Challenge域,但是Server Version String域則對確定服務器容易受到哪些身份驗證漏洞的攻擊非常有幫助。客戶端然后會發送一個身份驗證數據包給服務器:
- Packet Length (3 bytes)
- Packet Number (1 byte)
- Client Capabilities (2 bytes)
- Max packet size (3 bytes)
- Username (以null結尾)
- Password (challenge響應以null結尾)
下面我們對身份驗證協議中的安全漏洞進行介紹。MySQL身份驗證協議中已經發現過許多安全漏洞,我們這里略舉一二。
#p#
身份驗證協議4.1版本之前的基本密碼弱點
4.1版本之前的MySQL,無需知道密碼,只要知道密碼的hash值(包含在mysql.user表中)就能通過身份驗證——這意味著,攻擊者根本無需編寫密碼雜湊值的破解程序,因為修改標準MySQL客戶端讓它接受密碼雜湊而非密碼要容易多了。當然,用戶傾向于多處使用同一個密碼,尤其是root密碼,所以破解任何一個密碼的雜湊值,就極有可能在多個地方派上用場。
3.23.11版本之前的身份驗證算法
3.23.11版本之前的MySQL的身份驗證機制中有一個嚴重的缺陷,即攻擊者僅僅使用雜湊后的密碼的單個字符就能通過身份驗證。其實雜湊后的字符串由32個字符中的某些字符構成的,所以攻擊者只需進行少量猜測就能登錄。
3.23.54版本之前的CHANGE_USER
對于3.23.54版本之前的MySQL來說,如果用戶可以進行身份驗證,它就有機會提交一個超長字符串(來觸發緩沖區溢出)或者單字節字符串的CHANGE_USER命令來提權。
4.1.1、4.1.2和5.0.0版本中的身份驗證算法
通過提交一個精心制作的身份驗證數據分組,對于攻擊者而言,就有可能繞過在4.1.0到4.1.2以及5.0早期構建版的MySQL中的密碼身份驗證機制。下面的代碼取自sql_parse.cpp文件的check_connection代碼:
/*
舊版本的客戶端發送以null結束的字符串作為密碼;新客戶端使用尺寸(1字節)+字符串(并非以null作為終止符)作為密碼。 因此如果是空密碼的話,兩者都會發送'\0'。
*/
uint passwd_len= thd->client_capabilities & CLIENT_SECURE_CONNECTION ?
*passwd++ : strlen(passwd);
0x8000是在客戶端的能力標志中規定的,用戶可以指定所選的passwd_len字段。對于該攻擊,我們選擇0x14 (20),這是我們預期的SHA1雜湊長度。
現在要執行多道檢測以保證用戶是從授權連接的主機上進行身份驗證。通過這些檢測后,我們到達:
/* 檢查密碼:它應該為空或者是正確的 */
if (passwd_len == acl_user_tmp->salt_len)
{
if (acl_user_tmp->salt_len == 0 ||
acl_user_tmp->salt_len == SCRAMBLE_LENGTH &&
check_scramble(passwd, thd->scramble, acl_user_tmp->salt) == 0 ||
check_scramble_323(passwd, thd->scramble,
(ulong *) acl_user_tmp->salt) == 0)
{
acl_user= acl_user_tmp;
res= 0;
}
}
Check_scramble函數失敗,但是在check_scramble_323函數之內我們看到:
my_bool
check_scramble_323(const char *scrambled, const char *message,
ulong *hash_pass)
{
struct rand_struct rand_st;
ulong hash_message[2];
char buff[16],*to,extra;
const char *pos;
hash_password(hash_message, message, SCRAMBLE_LENGTH_323);
randominit(&rand_st,hash_pass[0] ^ hash_message[0],
hash_pass[1] ^ hash_message[1]);
to=buff;
for (pos=scrambled ; *pos ; pos++)
*to++=(char) (floor(my_rnd(&rand_st)*31)+64);
extra=(char) (floor(my_rnd(&rand_st)*31));
to=buff;
while (*scrambled)
{
if (*scrambled++ != (char) (*to++ ^ extra))
return 1; /* 密碼錯誤 */
}
return 0;
}
現在,用戶已經規定了一個他想要的雜湊字符串,本例為一個零長度的字符串。最后的循環對雜湊字符串與MySQL已知正確響應字符串進行逐字符比較,直到雜湊字符串中的所有字符都比較完為止。由于在雜湊字符串中根本沒有要比對的字符,所以函數會立即返回0,即允許用戶使用零長度字符串通過身份驗證。
這個缺陷的利用方法相當簡單,但是我們還是必須要編寫一個定制的MySQL客戶端才行。除了使用零長度字符串繞過身份驗證外,還可以使用長雜湊字符串來溢出基于堆棧的緩沖區。我們可以使用偽隨機數發生器my_rnd()函數輸出的字符來溢出該緩沖區。字符的范圍介于0x40到0x5f之間。對于一些平臺來說,執行任意代碼是可能的,但是該漏洞利用非常復雜,并且需要進行暴力破解,或者知道一個密碼雜湊值。
為了進行這些攻擊,攻擊者必須知道或者能猜出用戶的名稱,所以重命名MySQL的root帳戶不失為一個有效的預防措施。此外,有關賬戶還必須能夠從攻擊者的主機進行訪問,所以應用基于IP地址的登錄限制也能減輕這些漏洞所帶來的危害。
#p#
六、利用體系結構設計缺陷
下面,我們將介紹設計缺陷有哪些?如何利用這些缺陷?以及如何識別和抵御這些攻擊。
對于MySQL,歷史上曾經出現過多個設計缺陷,它們主要影響身份驗證協議,這些我們在上文中已經介紹過了。下面,我們將從更通用的、體系結構的角度來考察在MySQL中的各種安全弱點。
認證機制中的各種漏洞允許遠程用戶無需任何憑證就可以通過身份認證,這些應該屬于體系結構漏洞中最嚴重的一類。
我們知道,凡事都有其兩面性,MySQL的簡單性雖然給我們帶來了便利,但是它同時也成為了其最大的弱點。舉例來說,Microsoft SQL Server的一個極其有用的特性就是能夠在遠程數據庫服務器上執行查詢,比如我們可向服務器發送一個如下所示的查詢:
SQL Server中的OpenRowset語句允許您在SQL Server查詢過程中向另一個(運行不同的數據庫管理系統的)服務器提交查詢。但是這個特性很容易被濫用。據我們所知,最常用的濫用方式之一是可以通過它來掃描該SQL Server所在網絡的端口,因為它可以利用不同長度的響應時間來確定遠程主機是否存在,是否為SQL Server。
而對于MySQL來說,由于沒有對應的OpenRowset語句,因此MySQL也就不會遭受此類攻擊了。可問題是,如果軟件的行為太簡單,那么就可能缺少針對濫用的保護措施。
#p#
七、利用用戶定義的函數
幾乎每個數據庫管理系統都提供了調用定制的本機代碼的機制,例如SQL Server有擴展存儲過程的概念,Oracle有外部存儲過錯的概念,等等。雖然這些機制的名稱不同,但是基本原理是一致的,那就是用戶創建一個可動態裝載程序庫(在windows上為.dll;在linux中為共享對象.so),然后數據庫可以通過SQL語句來調用這個庫。
由于大多數數據庫運行在管理權限上,或者至少能控制他們自己代碼和數據,因此這就會招致一個嚴重的安全問題。如果一個MySQL用戶可以創建和執行一個惡意UDF,那么整個數據庫服務器的安全性就會岌岌可危。
觸發添加和使用UDF的過程的內容,我們前面已經講過一些了,這里將深入探討更多的細節,因為一旦MySQL本身被攻陷,那么也會殃及MySQL所在的主機,而有時候利用惡意UDF攻陷MySQL是件非常簡單的事情。
MySQL提供了一個機制,通過該機制可以擴展默認函數集,方法是編寫定制的包含用戶定義的函數或者UDFs的動態庫。這個機制可以通過CREATE FUNCTION語句加以訪問,而MySQL.func表中的表項則可以手動添加。包含函數的程序庫必須能夠從MySQL裝入動態庫時所用的路徑加以訪問。
#p#
攻擊者濫用這個機制的通常方式是,創建一個惡意程序庫,然后使用SELECT . . . INTO OUTFILE將其放到一個適當的目錄中。該程序庫就位之后,攻擊者需要更新或者插入mysql.func表,以便配置MySQL使其加載該程序庫并執行該函數。一個UDF程序庫示例源代碼如下所示:
#include
#include
/*
可以使用下列編譯器編譯該代碼:
gcc -g -c so_system.c
然后執行如下所示命令:
gcc -g -shared -W1,-soname,so_system.so.0 -o so_system.so.0.0 so_system.o -lc
*/
enum Item_result {STRING_RESULT, REAL_RESULT, INT_RESULT, ROW_RESULT};
typedef struct st_udf_args
{
unsigned int arg_count; /* 參數數目 */
enum Item_result *arg_type; /* 指向item_results的指針 */
char **args; /* 參數指針 */
unsigned long *lengths; /* 字符串參數的長度 */
char *maybe_null; /* 對于所有的maybe_null參數,都設為1 */
} UDF_ARGS;
typedef struct st_udf_init
{
char maybe_null; /* 如果函數返回NULL,則為1 */
unsigned int decimals; /* 用于實函數 */
unsigned long max_length; /* 用于串函數 */
char *ptr; /* 函數數據指針 */
char const_item; /* 如果結果與參數無關,則為0 */
} UDF_INIT;
int do_system( UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
{
if( args->arg_count != 1 )
return 0;
system( args->args[0] );
return 0;
}
函數可以通過如下所示的命令來添加至MySQL:
然后,mysql.func表看上去會是這樣的(您還可以通過手動方式更新):
于是,我們就可以像下面這樣來調用函數了:
即使攻擊者受文件權限的制約無法在目標系統上創建自己所有的程序庫,但是他仍然可以通過一個現有的函數來達到不可告人的目的。攻擊者的難點在于大多數函數的參數表不可能匹配MySQL UDF的原型:
int xxx( UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
當然,機智的攻擊者可能會通過調用一個當解釋傳給MySQL的參數時會出現某種可控故障的程序庫來設法執行任意的代碼。然而,現有的系統程序庫中的函數仍然可能會做壞事——舉例來說,在Windows系統中將ExitProcess作為MySQL UDF進行調用,這會導致MySQL立即退出——即使發起調用的用戶不具備Shutdown_priv權限:
您還可以鎖住當前登錄用戶的工作站,只要使用下列命令,其效果跟按下CTRL-ALT-DEL一樣:
然后工作站就會被鎖定。
總之,在MySQL中的UDF機制對于開發人員來說是極其靈活和有用的,但是也給攻擊者帶來了莫大的幫助。所以,防御此類攻擊的最好措施就是,仔細分配MySQL權限(特別是MySQL數據庫和mysql.func表有關的權限)、文件權限以及限制使用SELECT . . . INTO OUTFILE。
#p#
八、利用訪問控制系統中的漏洞
由于4.1.x版本之前版本沒有實現視圖,因此也就沒有記錄級別的安全機制。對于某些用戶來說,這可能是一個問題,并且在有些情況下缺少可用的安全配置,所以它們最好選擇更高版本。舉例來說,假設一個數據倉儲系統使用MySQL用戶來確定哪些用戶可以執行哪些動作,該用戶通常想要做的事情之一就是更改他們的口令,所以該軟件使用了一個表單查詢實現了該功能:
現在假設入庫腳本存在被可以替換
#p#
九、利用缺乏的安全特性
MySQL沒有內置訪問違例審計機制,但是它支持所有連接和查詢的完整日志記錄。 這對于安全性的影響是顯而易見的,在一些情況下,缺乏本地審計時一個非常大的安全問題。然而,由于提供了對調試日志記錄的支持,使得MySQL能夠通過--log選項記錄每一連接和語句到日志文件。
前面提到的MySQL存儲引擎大部分都不支持引用完整性或者事務。雖然這些特性是可用的,但是默認的存儲引擎MyISAM卻沒有實現這些特性。 因此,下面的討論只是針對MySQL的默認行為的,當然,我們這里討論的某些問題也可能出現在那些功能不是很豐富的數據庫管理系統中。
在一些缺乏引用完整性的應用程序中,可能會出現競態條件從而引起安全問題,舉例來說,假設我們的應用程序通過一個用戶表來執行其安全模型:
create table users( username varchar(200), password varchar( 200 ), userid int );
insert into users values ( 'admin', 'iamroot', 0 );
insert into users values ( 'fred', 'sesame', 1 );
insert into users values ( 'joe', 'joe', 2 );
這些用戶有權訪問一些資源,并使用編號進行標識:
create table resources( name varchar( 200 ), resourceid int );
insert into resources values( 'printer', 1 );
insert into resources values( 'filesystem', 2 );
insert into resources values( 'network', 3 );
這些資源的訪問權限是通過一個訪問控制表來實現的,表中的記錄決定用戶是否可以訪問各個資源:
create table accesscontrol( userid int, resourceid int, allowed int );
# Admin可以訪問下面三種資源:
insert into accesscontrol values ( 0, 1, 1 );
insert into accesscontrol values ( 0, 2, 1 );
insert into accesscontrol values ( 0, 3, 1 );
# Fred可以訪問文件系統和網絡:
insert into accesscontrol values ( 1, 2, 1 );
insert into accesscontrol values ( 1, 3, 1 );
#Joe只能訪問打印機
insert into accesscontrol values ( 2, 1, 1 );
#p#
假設我們沒有強制執行引用完整性,如果我們利用id 2刪除用戶joe,如下所示:
表accesscontrol中與joe有關的的所有記錄仍然在那里。如果我們借助下一個可用的id(即2)添加另一個用戶的話,那么這個用戶就會繼承joe在accesscontrol表中的記錄。
對于提供引用完整性的數據庫來說,我們就可以規定該用戶標識為accesscontrol表中的外鍵,這樣,當該用戶的記錄刪除時,那么在accesscontrol中對應的所有記錄也會自動刪除。
然而,在默認情形下缺乏事務支持也可能會導致安全隱患。舉例來說,假設實現前面的系統的公司具有審計每次密碼改變的要求, 同時假定使用了如下所示的查詢:
假定這兩次查詢之間的服務器連接失敗。用戶的密碼將已經更改,但是系統尚未進行審計。如果我們正在使用非默認的、支持事務的MySQL存儲引擎,那么我們就可以在第一個語句之前開始事務,然后在第二個語句之后提交事務,那么密碼改變就不會漏掉審計了。
在4.0版本之前,MySQL并不支持UNION語句。由于SQL注入是最常見的數據庫攻擊形式之一,而UNION又是攻擊者的指令表中的關鍵部分,因此在防御SQL注入攻擊方面,通常認為4.0版之前的MySQL要比其他數據庫系統更加安全。然而,應用開發人員卻對這一局限性頗為不滿;此外,如果您運行版本較老的MySQL的話,那就會受到目前已經修復的安全漏洞的威脅。所以,如果有一個功能受限同時又補丁齊全的MySQL就好了,但是看起來這是不可能的。如果讀者有志于此的話,抽時間您可以親自動手試試。
此外,4.1版本之前的MySQL并不支持子查詢,下面是一個子查詢的例子:
由于進行SQL注入攻擊時,攻擊者通常能控制查詢字符串的部分字符,所以如果缺乏子查詢的話,那么從安全的角度來說倒是一個優點。然而,現實中如果缺乏某些特性的話,MySQL是很難推薦給管理層的,更別說開發團隊了。“缺乏功能”有時候也是有好處的,例如Oracle沒有十分詳細的錯誤信息,但是對于SQL Server 來說,這可能從錯誤信息文本中檢索數據,不過還好MySQL并沒有效仿這一點。
#p#
十、小結
在本文中,我們為讀者詳細介紹了MySQL數據庫在體系結構方面的安全問題以及相應的攻擊方法,在后續的文章中我們將介紹相應的防御措施。
【編輯推薦】