段壽建
(保山學(xué)院信息學(xué)院,保山 678000)
PHP原名為Personal Home Page的縮寫,后更名為“PHP:Hypertext Preprocessor”,這種將名稱放到定義中的寫法被稱作遞歸縮寫[1]。PHP是一種開源的、跨平臺的、獨(dú)立架構(gòu)的、解釋性的、面向?qū)ο蟮摹⒖焖俚?、健壯的、安全性高的Web編程語言。PHP是一個開放源代碼的項(xiàng)目,用戶可以免費(fèi)使用PHP來開發(fā)中小型的Web項(xiàng)目。PHP能運(yùn)行在Windows、Linux等絕大多數(shù)操作系統(tǒng)環(huán)境中。
MySQL是一個小型關(guān)系型數(shù)據(jù)庫管理系統(tǒng),由瑞典MySQL AB公司開發(fā),目前屬于Oracle旗下產(chǎn)品[2]。MySQL被廣泛地應(yīng)用在Internet上的中小型網(wǎng)站中,由于其體積小、速度快、總體擁有成本低,尤其是開放源碼這一特點(diǎn),許多中小型網(wǎng)站為了降低網(wǎng)站總體擁有成本而選擇了MySQL作為網(wǎng)站數(shù)據(jù)庫。
PHP常與開源免費(fèi)的Web服務(wù)Apache和數(shù)據(jù)庫MySQL配合使用于Linux平臺上(簡稱LAMP),具有最高的性價比,號稱“Web架構(gòu)黃金組合”。
SQL注入是存在于Web應(yīng)用程序中的一種漏洞,Web應(yīng)用程序?qū)τ脩糨斎霐?shù)據(jù)的合法性沒有判斷,攻擊者通過構(gòu)造一些畸形輸入,在應(yīng)用程序中預(yù)先定義好的查詢語句結(jié)尾加上額外的SQL語句元素,欺騙數(shù)據(jù)庫服務(wù)器執(zhí)行非授權(quán)的任意查詢,獲取其沒有合法權(quán)限查詢的信息[3]。SQL注入不是Web或數(shù)據(jù)庫服務(wù)器中的缺陷,而是由于編程實(shí)踐較差且缺乏經(jīng)驗(yàn)而導(dǎo)致的。SQL注入漏洞的產(chǎn)生需要滿足以下兩個條件:一是參數(shù)用戶可控,前端傳給后端的參數(shù)內(nèi)容是用戶可以控制的;二是參數(shù)代入數(shù)據(jù)庫查詢,傳入的參數(shù)拼接到SQL語句,并且?guī)霐?shù)據(jù)庫查詢。當(dāng)黑客想要對Web程序進(jìn)行滲透注入時,需要做以下準(zhǔn)備:搜集目標(biāo)信息;尋找注入點(diǎn);構(gòu)造SQL語句進(jìn)行注入[4]。
本文以一個實(shí)際的網(wǎng)站后臺登錄系統(tǒng)開發(fā)為例,開發(fā)環(huán)境為如下:WampServer版本:2.2,Apache版本:2.2.22,PHP 版本:5.4.3,MySQL 版本:5.5.24。
數(shù)據(jù)庫命名為news,管理員數(shù)據(jù)表命名為ad?min_user,包含用戶名、密碼、真實(shí)姓名、電話號碼、電子郵箱、權(quán)限6個字段,各字段的定義如表1所示。
表1 管理員用戶表
用PHPMyAdmin創(chuàng)建數(shù)據(jù)表,插入一條數(shù)據(jù),用戶名為“abc”,密碼為“123”的 md5 值,即:“202cb962ac 59075b964b07152d234b70”。
(1)基本頁面布局
將登錄頁面命名為login.php,制作用戶登錄表單,命名為 form1,method="post"action="check.php",用表格布局頁面如圖1所示。
圖1 用戶登錄界面布局圖
用戶名框命名為:userName,類型為單行文本,密碼框命名為:userPassword,類型為密碼,驗(yàn)證碼框命名為yzm,類型為單行文本,驗(yàn)證碼圖片由yzm.php隨機(jī)生成四位字符,并生成有干擾的圖片輸出在頁面,生成的字符放入Session變量中,以備一下步的驗(yàn)證。
(2)使用JavaScript實(shí)現(xiàn)客戶端驗(yàn)證
在提交按鈕上增加事件onClick="return jc()",在login.php中加入JavaScript代碼如下:
<script>
functionjc()
{
if(document.form1.userName.value=="")//檢查用戶名是否為空
{
window.alert("用戶名不能為空!");//彈出提示
document.form1.userName.focus();//讓用戶名框獲得焦點(diǎn)
return false;//不提交表單
}
if(document.form1.userPassword.value=="")//檢查密碼是否為空
{
window.alert("密碼不能為空!");//彈出提示
document.form1.userPassword.focus();//讓密碼框獲得焦點(diǎn)
return false;//不提交表單
}
i(fdocument.form1.yzm.value.length<4)
{
window.alert("請輸入 4 位驗(yàn)證碼!");//檢查驗(yàn)證碼是否為空
document.form1.yzm.focus();//讓驗(yàn)證框獲得焦點(diǎn)
return false;//不提交表單
}
return true;//提交表單
}
</script>
login.php頁面填報的數(shù)據(jù)經(jīng)客戶端驗(yàn)證通過后,將數(shù)據(jù)提交到check.php頁面,功能是檢測驗(yàn)證碼是否正確,然后檢測用戶名和密碼是否正確,代碼如下:
<?php
//1.設(shè)置Session路徑并開啟Session功能
session_save_path("session/");
session_start();
//2.獲取用戶輸入的三個參數(shù)
$userName=$_POST["userName"];
$userPassword=md5( $_POST["userPassword"]);//使 用 md5加密
$yzm=$_POST["yzm"];
//3.先檢查驗(yàn)證碼是否正確
if(strtoupper($yzm)!=strtoupper($_SESSION["yzm"]))
{
echo"<script>window.alert('驗(yàn)證碼錯誤!')</script>";
echo"<meta http-equiv=REFRESH CONTENT='0;URL=log?in.php'>";
}
//4.連接數(shù)據(jù)庫、設(shè)置時區(qū)和字符集
$dblink=mysqli_connect("127.0.0.1","root","","news")or die("數(shù)據(jù)庫打開失敗");
date_default_timezone_set("Asia/Shanghai");
mysqli_set_charset($dblink,"gbk");
//5.判斷用戶名和密碼是否正確
$sql="SELECT*FROM admin_user where userName='$user?Name'and userPassword='$userPassword'";
$result=mysqli_query($dblink,$sql);
$hs=mysqli_num_rows($result);
if($hs>0)//如果正確,跳轉(zhuǎn)到后臺主頁index.php
{
$_SESSION["isLogin"]=1;
$_SESSION["userName"]=$userName;
echo"<meta http-equiv=REFRESH CONTENT='0;URL=in?dex.php'>";
}
else//如果不正確,跳轉(zhuǎn)到后臺登錄頁面login.php
{
echo"<script>window.alert('用戶名或密碼錯誤!')</script>";
echo"<meta http-equiv=REFRESH CONTENT='0;URL=log?in.php'>";
}
?>
當(dāng)驗(yàn)證碼、用戶名和密碼都正確后,頁面跳轉(zhuǎn)到后臺主頁index.php,該頁面的布局如圖2所示,具體代碼與本文的SQL注入關(guān)系不大,就不再贅述。
圖2 后臺主頁布局圖
程序員按以上步驟完成login.php、check.php和in?dex.php的開發(fā),正常情況下,在login.php中,輸入用戶名為:abc,密碼:123,即可以正常登錄到index.php。但此時程序存在SQL注入漏洞,如瀏覽者在用戶名處輸入“1’or 1=1#”,密碼處輸入任意字符,輸入正確的驗(yàn)證碼,如圖3所示,就可以登錄后臺了。
在check.php中,驗(yàn)證用戶名和密碼是否正確的SQL語句是:
$sql="SELECT*FROM admin_user where userName='$userName'and userPassword='$userPassword'"
當(dāng)瀏覽者按上述的方式輸入,運(yùn)行后構(gòu)造的語句是:
SELECT*FROM admin_user where userName='1'or 1=1#'and userPassword='c4ca4238a0b923820dcc509a6f75849b'
以上加粗部分為的用戶名的內(nèi)容,注入在SQL語句中,就把原本的and條件變成了or條件,而且其中一個為真,后面的內(nèi)容被當(dāng)作注釋處理了,“1=1”部分也可以換成其他的為真的表達(dá)式,效果是一樣的,如“1'or 1#”。另外,將“#”換成“--(空格)”也是同樣的效果。
圖3
產(chǎn)生以上問題的關(guān)鍵在于單引號的區(qū)配,和#注釋的作用,在正常的登錄中,用戶名是不應(yīng)該包含單引號或#號的,所以防范措施可以先過濾如單引、#號這樣的特殊字符,可以用PHP的str_replace函數(shù)過濾,如:在check.php中接收到用戶名后做過濾,即在:$userName=$_POST["userName"]后增加如下代碼:
$userName=str_replace("'","",$userName);//替換單引號
$userName=str_replace("#","",$userName);//替換#
$userName=str_replace("-","",$userName);//替換-
$userName=str_replace("","",$userName);//替換空格
將單引號、#號、-號、空格替換后,上例的SQL注入就會失效,在實(shí)際編程中,可以把以上的替換做成函數(shù),把與MySQL相關(guān)的一些特殊字符一并過濾。
另外,PHP的addslashes()函數(shù)也可以解決以上問題,addslashes()函數(shù)的作用是返回在預(yù)定義字符之前添加反斜杠的字符串,預(yù)定義字符是:單引號(')、雙引號(")、反斜杠()、NULL。
在上例中,也可以將 $userName=$_POST["user?Name"],修改為:
$userName=addslashes($_POST["userName"]);
即可在單引號上進(jìn)行轉(zhuǎn)義,同樣能解決上例中的注入問題。
SQL注入是從正常的WWW端口訪問,而且表面上跟一般的Web頁面沒什么區(qū)別,所以目前市面的防火墻都不會對SQL注入發(fā)出警報,SQL注入攻擊主要針對的是Web開發(fā)過程中,程序員編程的不嚴(yán)密、編程實(shí)踐較差且缺乏經(jīng)驗(yàn)而產(chǎn)生的程序漏洞,因此要防范SQL注入攻擊的主要方法就是提高編程能力,同時也需要對服務(wù)器端進(jìn)行合理配置[5]。本文通過詳細(xì)介紹使用PHP和MySQL技術(shù)制作通用的用戶登錄程序,通過實(shí)例演示SQL注入攻擊,分析原因并實(shí)現(xiàn)安全防范的教學(xué)案例,對便于程序員理解SQL注入的原理、舉一反三、提高編程能力防范SQL注入有一定的指導(dǎo)意義。