一、經典sql注入漏洞
當前例子1中並沒有對$_GET[‘name’]的值進行過濾,所以是存在sql注入漏洞的。
<?php
$name=$_GET['name'];
$conn=mysql_connect('localhost','root','root');
if($conn==null){exit("connect error !<br>");}
mysql_select_db("aaa",$conn);
$sql="select * from a1 where name='".$name."'";
$result=mysql_query($sql,$conn);
while($val=mysql_fetch_row($result)){
print_r($val);
print("<br>");
}
?>
對應檢測的payload如下:
http://127.0.0.1/test.php?name=a'or 1=1 --+ 頁面正常
http://127.0.0.1/test.php?name=a'or 1=2 %23 頁面報錯
其中%23對應#的URL編碼,在mysql中起到註釋的作用。
二、安全過濾
首先寬字節注入
與HTML編碼是沒有關係的,如果頁面中看到了<meta charset="utf-8">
就放棄嘗試的話,那是一個誤區,因爲這是當前頁面的編碼,而並非數據庫的編碼。
常見的寬字節有如下:
- GB2312
- GBK
- GB18030
- BIG5
- Shift_JIS
實際上只有兩個字節,寬字節帶來的安全問題主要是喫ASCII字符(一字節)的現象。
對於上面的例子1中的PHP程序中的$name變量進行安全過濾,如使用轉義函數
addslashes,
mysql_real_escape_string
mysql_escape_string
來進行過濾,則對應的payload全部失效。
還有一種情況是magic_quote_gpc(php5.4後廢除),php中的magic_quotes_gpc是配置在php.ini中的,他的作用類似addslashes(),就是對輸入的字符創中的字符進行轉義處理。他可以對_POST、$__GET以及進行數據庫操作的sql進行轉義處理,防止sql注入。
轉義函數影響的字符包括:
ASCII(NULL)字符\x00
換行字符\n,addslashes不轉義
回車字符\r,addslashes不轉義
反斜槓字符\
單引號字符'
雙引號字符"
\x1a,addslashes不轉義
對於例子1進行安全增強後,得到例子2,如下所示。
注意:三個轉義函數的功能稍有區別,同時,轉義只對字符型SQL注入防範有效,對於數值型SQL注入無效。
<?php
$name=$_GET['name'];
//$name=addslashes($name);
//$name=mysql_escape_string($name);
$conn=mysql_connect('localhost','root','root');
$name=mysql_real_escape_string($name);
if($conn==null){exit("connect error !<br>");}
mysql_select_db("aaa",$conn);
$sql="select * from a1 where name='".$name."'";
$result=mysql_query($sql,$conn);
while($val=mysql_fetch_row($result)){
print_r($val);
print("<br>");
}
?>
三、寬字節注入原理
通常來說,一個GBK編碼的的漢字,佔2個字節,一個utf-8的漢字,佔3個字節。在php中,我們可以通過輸出
echo strlen("我");
來進行測試。當頁面保存爲GBK時,頁面輸出的是2,當頁面保存爲utf-8時,頁面輸出的是3。
寬字節注入主要是源於程序員設置數據庫編碼與PHP編碼設置爲不同的兩個編碼那麼就有可能產生寬字節注入。
寬字節是指兩個字節寬度的編碼技術,如UNICODE,GBK,BIG5等。當mysql數據庫數據在處理和存儲過程中,涉及到字符集相關信息包括:
(1) character_set_client:客戶端發送過來的SQL語句編碼,也就是PHP發送的SQL查詢語句編碼字符集。
(2) character_set_connection:MySQL服務器接收客戶端SQL查詢語句後,在實施真正查詢之前SQL查詢語句編碼字符集。
(3) character_set_database:數據庫缺省編碼字符集。
(4) character_set_filesystem:文件系統編碼字符集。
(5) character_set_results:SQL語句執行結果編碼字符集。
(6) character_set_server:服務器缺省編碼字符集。
(7) character_set_system:系統缺省編碼字符集。
(8) character_sets_dir:字符集存放目錄,一般不要修改。
寬字節對轉義字符的影響發生在character_set_client=gbk的情況,也就是說,如果客戶端發送的數據字符集是gbk,則可能會喫掉轉義字符\,從而導致轉義失敗。
比如:PHP的編碼設置爲UTF-8而Mysql的編碼設置爲SET NAMES 'gbk’或是SET character_set_client =gbk,這樣配置會引發編碼轉換從而導致的注入漏洞。
這裏要說明一小點的是:
SET NAMES 'x’語句與這三個語句等價:
mysql>SET character_set_client =x;
mysql>SET character_set_results =x;
mysql>SET character_set_connection =x;
也就是說你設置了 SET NAMES ‘x’ 時就等於同時執行了上面的3條語句。
在我們正常情況下使用 addslashes函數或是開啓PHP的GPC(注:在php5.4已上已給刪除,並且需要說明特別說明一點,GPC無法過濾$_SERVER提交的參數)時過濾 GET、POST、COOKIE、REQUSET 提交的參數時,黑客們使用的預定義字符會給轉義成添加反斜槓的字符串如下面的例子:
單引號(')=(\')
雙引號(")=(\")
反斜槓(\)=(\\)
假如這個網站有寬字節注入那麼我們提交:http://127.0.0.1/test.php?name=%df%27
這時,假如我們現在使用的是addslashes來過濾,那麼就會發生如下的轉換過程.
%df%27=====(addslashes)======>%df%5c%27========>(數據庫GBK)====>運'
這裏可能有一些人沒看懂,我可以粗略的解釋一下。
前端輸入%df%27
時首先經過上面addslashes
函數轉義變成了%df%5c%27
(%5c是反斜槓\
),之後在數據庫查詢前因爲設置了GBK編碼,即是在漢字編碼範圍內兩個字節都會給重新編碼爲一個漢字。然後MySQL服務器就會對查詢語句進行GBK編碼即是%df%5c
轉換成了漢字"運
",而單引號就逃逸了出來,從而造成了注入漏洞
。
比如:
http://127.0.0.1/test.php?name=%df’ or 1=1 limit 1,1%23&pass=
其對應的sql語句就是:
select * from table_name where name=‘運’ or 1=1 limit 1,1#’ and password=”
說明:GBK編碼,它的編碼範圍是0×8140~0xFEFE(不包括xx7F)
,在遇到%df(ascii(223)) >ascii(128)
時自動拼接%5c
,因此喫掉‘\
’,而%27、%20小於ascii(128)
的字符就保留了。
例子3:
<?php
header("Content-Type:text/html;charset=gbk"); //爲了顯示,將頁面默認爲gbk
$name=$_GET['name'];
$name=addslashes($name);
$conn = mysqli_connect('127.0.0.1','root','');
mysqli_select_db($conn,'mysql');
mysqli_query($conn,"SET NAMES 'gbk'");
if($conn==null){exit("connect error !<br>");}
$sql="select * from user where user='".$name."'";
$result=mysqli_query($conn,$sql);
echo $sql;
while($val=mysqli_fetch_row($result)){
var_dump($val);
print("<br>");
}
?>
這是一個帶有寬字節注入漏洞的PHP代碼程序,對應的payload如下:
http://127.0.0.1/test/t3.php?name=%df' or 1=1 %20%23
對應的sql爲:
select * from a1 where name='運' or 1=1 #
原理就是:mysql_query("SETNAMES 'gbk'",$conn)
語句將編碼字符集修改爲gbk,此時%df\'
對應的編碼就是%df%5c’
,即漢字“運’
”,這樣單引號之前的轉義符號“\
”就被喫調了,從而造成注入。
例子4:
訪問:
http://www.2cto.com /test.php?username=test%d5′%20or%201=1%23&pwd=test
經過瀏覽器編碼的URL爲:
username=test%df%27%20or%201=1%23
經過php的URL編碼:
username=test 0xd5 0×27 0×20 or 0×20 1=1 0×23
(爲了便於閱讀,在字符串與16進制編碼之間加了空格)。
經過php的GPC自動轉義:
username=test 0xd5 0×5c 0×27 0×20 or 0×20 1=1 0×23
因爲在數據庫初始化連接的時候SET NAMES ‘gbk’
,0xd5 0×5c解碼後爲誠
,0×27解碼爲’
,0×20爲空格
,0×23爲mysql的註釋符#
最終的sql語句爲:
SELECT * FROM user WHERE username=’test誠’ or 1=1#’ and password=’test’;
最終閉合了單引號,達到了SQL注入的目的。
四、總結:
1:寬字節注入跟HTML頁面編碼無關。
2:Mysql編碼與過濾函數推薦使用mysql_real_escape_string(),mysql_set_charset()。
3:轉編碼函數同樣會引起寬字節注入,即使使用了安全的設置函數