王保勝, 楊新鋒
(南陽理工學院, 南陽 473000)
隨著研究用腳本語言攻擊站點的人數的增加,SQL Injection攻擊也倍受關注,攻擊者利用SQL Injection漏洞可以在幾分鐘內,甚至幾十秒時間內,入侵至目標服務器,獲取未授權權限,竊取不公開信息,修改相關信息,甚至刪除數據,幾十秒鐘的時間足夠在數據庫里面注入數以萬計的非法數據[1-2]。造成這些后果的主要原因就是程序員在編程的失誤或者粗心,然而這些問題完全是可以避免的。國外對于SQL Injection的研究已經相當靠前,由于計算機在中國國內的發展時間比較短,國內對SQL Injection攻擊這方面的研究比較少[3-4]。
SQL Injection是攻擊者把影響程序正常反應的SQL命令添加到Web表單的輸入區域,或者是頁面請求執行的字符命令中,而達到對服務器的欺騙,從而執行這個影響程序正常反應的SQL字符命令的攻擊方式[5-6]。
SQL Injection攻擊網站一般都是通過80端口,通過欺騙等手段提交惡意代碼[7]。通過人眼根本無法分辨,甚至通過軟件也無法測試出來,因為SQL Injection訪問網站的方法與正常訪問者的訪問方式并無不同之處,只有管理員查看web日志的時候,才能捕捉一些攻擊信息。
如果軟件或者網站存在SQL Injection漏洞,攻擊者就能夠對數據庫里面的數據進行非法操作,在非法得到一定的服務器權限后,攻擊者甚至可以在服務器中通過欺騙的手段添加木馬,安全性較低的數據庫甚至可能被得到服務器的管理員權限,這就說明一個數據庫的不安全,有可能導致整個站點的內容都受到攻擊的威脅。
SQL Injection的本質是攻擊者把影響程序正常反應的SQL命令添加到Web表單的輸入區域,或者是頁面請求執行的字符命令中,而達到對服務器的欺騙,從而執行這個影響程序正常反應的SQL字符命令,并最終將執行結果返回給攻擊者[8-9]。
SQL Injection流程,如圖1所示。

圖1 SQL Injection流程
(1)判斷Web系統腳本語言是使用了哪種計算機語言,找到注入點,確定是否含有SQL Injection漏洞。
(2)判斷Web系統的數據庫類型。
(3)判斷一個數據庫中表和相應字段的結構。
(4)構造sql Injection語句,從而得到表中數據內容。
(5)查找網站管理的后臺,使用得到的管理員賬號及密碼登錄后臺。
(6)結合站點其他漏洞,上傳一個Webshell。
(7)對用戶進一步提權,從而得到服務器的系統權限,隊整個站點進行非法操作。
(8)刪除侵入痕跡,本地保留侵入方法,以待下一次侵入使用。
下面以管理者和程序員的身份,從防御的角度來談一下如何防止SQL Injection。
在編寫PHP代碼的時候,要充分考慮用戶輸入信息的安全性。
2.1.1 初始化變量
編寫程序的時候一定要初始化函數里面的變量,代碼如下:
if ($admin){
echo‘恭喜你,登陸成功!’;
include(‘admin.php’);
}else{
echo‘你不是管理員,請停止你的非法操作!’;
}
?>
假設后臺登錄的這個頁面的地址是http://127.0.0.1/login.php,那么在瀏覽器的地址欄里面提交:http://127.0.0.1/login.php?admin =1,顯而易見,問題現了,每個人都是管理員了,只要是進行這個操作的瀏覽者,都可以使用管理員權限了。
或者將配置文件里面設置register_global =off這樣就能巧妙地那么就能避免了。重新設計代碼:
設計代碼實現后,再重新在地址欄中輸入提交原來的地址:http://127.0.0.1/login.php?admin=1,就會發現,已經不能進去了。原因就是在代碼中對變量“$admin”進行了初始化了,令$admin = 0,現在訪問者就無法通過這個漏洞獲取管理員權限,也沒有辦法進入系統了。由此可以看到,變量的初始化是多么的重要了,變量的初始化,能夠減少漏洞的產生。
$admin=0; //初始化變量
if ($_POST[‘admin_user’]&& $_POST[‘admin_pass’]){
//假如用戶名和密碼沒有進過處理則不運行代碼
//……
$admin=1;
}else{
$admin=0;
}
if($admin){
echo‘恭喜你,登陸成功!’;
include(‘admin.php’);
}else{
echo‘你不是管理員,請停止你的非法操作!’;
}
?>
2.1.2 過濾變量
提交數據的方式有兩種,一種是用get,另一種是用post提交。而很多種SQL Injection都是從get提交方式上面著手,尋找利用這方面的漏洞。這些傳值一定會利用SQL語句,打破原來的SQL語句,構造新的SQL語句。SQL語句的四大句無外乎select、update、delete和insert,SQL Injection就是利用這4種語句構造輸入語句的。可以利用如下代碼發現和避免這些問題:
fuction inject_($sql_str){
return eregi(‘select|insert|update|delete’||
function verify_id($id=null){
//是否為空判斷
if($id){exit(‘沒有提交參數!’):}
//注入判斷
else if(inject_check($id)){exit(‘提交的參數非法!’);}
//數字判斷
else if(!is_numeric($id)){exit(‘提交的參數非法!’);}
$id=intval($id);//整型化
Returm $id;
}
?>
這樣就能夠進行校驗了,上面的程序代碼可以進一步改寫成:
if (inject_check($_GET[‘id’])){ exit(‘你提交的信息不是正常信息,請檢重新提交合法信息!’);
}else{
//這里調用了一個過濾的函數;對$id的值進行過濾防止非法數值的傳入
$id=verifty_id($_GET[‘id’]));
echo‘你提交的信息不是正常信息,進行下一步操作!’;
}
?>
如此設計代碼,問題似乎都已經被解決了。Get傳值的類型解決了,就要考慮與它不同的post傳值的類型了,當然少不了要考慮大批量一起傳輸數據。
用戶在提交的數據中可能會有對數據庫造成影響的字符,比如'_','%'這些字符都具有特殊的意義,那就應該對這些字符進行處理。
在PHP配置文件里面設置magic_quotes_gpc = off,用戶提交的不符合數據庫規則的數據,都不會自動的在前面加' '的,這樣就避免了被注入的可能,代碼實現如下:
function str_check($str){
//判斷magic_quotes_gpc是否打開
if(!get_magic_quotes_gpc()){
$str=addslashes($str);//進行過濾
}
$str=str_replace("_","\_",$str);//把‘_’過濾掉
$str=str_replace("%","\%",$str);//把‘%’過濾掉
return $str;
}
?>
然而,在大批量的數據面前應該怎么考慮呢?只需要將數據進行HTML標記轉換,代碼實現:
2.2.1 登錄注冊頁面的安全防范
通注冊頁面采用短信驗證或者郵箱驗證,防止水軍注水,緩解服務器壓力,注冊者使用驗證碼,或者使用郵件里面的鏈接完成對賬號的注冊功能。
注冊的時候,對輸入的賬號進行正則判斷,調用的正則方程式,而且跟數據里的已有賬號作對比,已存在的賬號,不能繼續注冊。注冊的時候前臺用js代碼進行判斷,目的是減輕服務器的壓力,同時,PHP代碼也是需要判斷,后臺代碼進行判斷的目的是防止攻擊者屏蔽js腳本,非法傳入數據對
function post_check($post){
//判斷magic_quotes_gpc是否為打開
if(!get_magic_quotes_gpc()){
//進行magic_quotes_gpc沒有打開的情況對提交數據的過濾
$post=addslashes($post);
}
$post=str_replace("_","\_",$post);//把‘_’過濾掉
$post=str_replace("%","\%",$post);//把‘%’過濾掉
$post=htmlspecialchars($post);//html標記轉換
return $post;
}
?>
服務器的安全產生威脅。注冊和登錄的時候,對輸入的密碼進行HTML標記轉換,然后把它的MD5值寫入數據庫,或者和數據庫里面的值作對比。代碼如下:
$data['username']= $wget['username'];
$wget['password']=htmlspecialchars ($wget['password']);
$data['password']= md5($wget['password']);
郵箱注冊注冊完了,需要驗證,數據庫對郵箱注冊的賬號有一個拍段,沒有進過驗證的賬號,是不能夠登錄的。
2.2.2 連接數據庫
很過PHP程序員使用@來抑制程序的報錯,代碼如下所示:
$con=@mysql_connect("localhost","peter","abc123";
if(!$con){
die('Could not connect:'.mysql_error()};
}
這種做法不科學,不能真正意義上解決安全問題,登錄數據庫可以采用thinkPHP框架的連接數據庫的方式,其核心代碼如下:
'DB_TYPE'?'mysql',//數據庫數據
'DB_HOST'?'127.0.0.1',//服務器地址
'DB_NAME'?'lagou',//數據庫名
'DB_USER'?'root',//用戶名
'DB_PWD'?'',//密碼
'DB_PORT'?'3306',//端口
'DB_PREFIX'?'lg_',//數據庫表前綴
'DB_CHARSET'?'utf8',//字符集
'DB_DEBUG'?TRUE,//數據庫調試模式開啟后可以記錄SQL日志3.2.3新增
2.2.3 變量及危險函數的處理
對于每一個變量,尤其是危險函數的變量,都可以用“‘’”引起來,然后對變量進行HTML標記轉換,這樣就限制住了攻擊者的非法輸入,將用戶輸入的“”,“’”等轉換成字符,這樣就避免了SQL漏洞的產生。使用htmlspecialchars()函數將這些危險符號轉換成為字符,不再具有其符號意義。例如登錄時過濾的代碼:
$data['username']= $wget['username'];
$wget['password']=htmlspecialchars ($wget['password']);
$data['password']= md5($wget['password']);
每一個函數的變量都進行初始化,并且傳值時都進過過濾,保證傳進來的都是安全的數據。
正確配置php.ini文件,才能夠確保腳本運行環境和資源的安全性。
可以使用任何有編輯功能的工具打開“php.ini”文件,對這個文件里面的代碼進行更改,但是要注意的是有些東西更改后會造成數據庫的不安全的,不要對這個文件修改的太多,只修改有助于提高數據庫安全性的地方就可以了,如圖2所示。
(1)打開PHP配置中的安全模式:
safe_mode = on
(2)用戶組的安全:safe_mode_gid = off
(3)安全模式下執行程序主目錄:safe_mode_exec_dir推薦不要去執行任何一個程序。
(4)安全模式下的包含文件:
safe_mode_include_dir
(5)控制php腳本能訪問的目錄,使用open_basedir選項,這樣就能夠控制PHP腳本,讓訪問者使其只能訪問它所指定的目錄。
(6)關閉危險函數:disable_functions = system,passthru,exec,shell_exec,popen,phpinfo
(7)關閉PHP版本信息:
expose_php = Off
(8)關閉注冊全局變量選項:
register_globals = Off
(9)打開“magic_quotes_gpc”:
magic_quotes_gpc = On
(10)錯誤信息的控制:
display_errors = Off
(11)錯誤日志:log_errors = On
本文對基于PHP的SQL Injection進行了一定程度上的研究和總結,對于整個數據庫安全問題的研究,在各個方面都有所討論。但是,SQL Injection不僅僅出現在PHP中,計算機技術的迅速發展,難保系統不會受到更嚴峻的挑戰。本文提出thinkPHP框架的使用可以避免很多出現SQL漏洞的可能性,只需要考慮少量的問題就能夠避免SQL Injection的問題,但是不是所有的PHP開發人員都會使用框架開發,那些面向過程開發的程序員面臨的問題更加多,他們需要處理的問題也更加多,這方面還需要繼續研究。另外一個問題就是對于系統的復雜度是否也對這些解決辦法有所影響,也有待去完善和繼續探究。
[1] 方自遠.SQL注入攻擊及其檢測防御技術研究[J].智能計算機與應用,2016,6(6):87-89.
[2] 張志超,王丹,趙文兵,等.一種基于神經網絡的SQL注入漏洞的檢測模型[J].計算機與現代化,2016(10):67-71.
[3] 李虎軍,林學華,張遼寧.SQL注入攻擊與防護探析[J].安徽電子信息職業技術學院學報,2016,15(2):58-63.
[4] 李俊鋒.基于Apache+PHP+Mysql網站SQL注入防護探討[J].網絡空間安全,2016(11):93-95.
[5] 韓宸望,林暉,黃川.基于SQL語法樹的SQL注入過濾方法研究[J].網絡與信息安全學報,2016,2(11):00113.1-8.
[6] 張慧琳,丁羽,張利華,等.基于敏感字符的SQL注入攻擊防御方法[J].計算機研究與發展,2016,53(10):2262-2276.
[7] 王苗苗,錢步仁,許瑩瑩,等.基于通用規則的SQL注入攻擊檢測與防御系統的研究[J].電子設計工程,2017,25(5):24-29.
[8] 仇善梁.開發者視角下網站SQL注入漏洞及防范[J].河北軟件職業技術學院學報,2016,18(4):52-55.
[9] 趙陽,郭玉翠.新型SQL注入攻擊的研究與防范[J].計算機系統應用,2016,25(6):52-55.