段壽建
(保山學院信息學院,保山 678000)
PHP原名為Personal Home Page的縮寫,后更名為“PHP:Hypertext Preprocessor”,這種將名稱放到定義中的寫法被稱作遞歸縮寫[1]。PHP是一種開源的、跨平臺的、獨立架構的、解釋性的、面向對象的、快速的、健壯的、安全性高的Web編程語言。PHP是一個開放源代碼的項目,用戶可以免費使用PHP來開發中小型的Web項目。PHP能運行在Windows、Linux等絕大多數操作系統環境中。
MySQL是一個小型關系型數據庫管理系統,由瑞典MySQL AB公司開發,目前屬于Oracle旗下產品[2]。MySQL被廣泛地應用在Internet上的中小型網站中,由于其體積小、速度快、總體擁有成本低,尤其是開放源碼這一特點,許多中小型網站為了降低網站總體擁有成本而選擇了MySQL作為網站數據庫。
PHP常與開源免費的Web服務Apache和數據庫MySQL配合使用于Linux平臺上(簡稱LAMP),具有最高的性價比,號稱“Web架構黃金組合”。
SQL注入是存在于Web應用程序中的一種漏洞,Web應用程序對用戶輸入數據的合法性沒有判斷,攻擊者通過構造一些畸形輸入,在應用程序中預先定義好的查詢語句結尾加上額外的SQL語句元素,欺騙數據庫服務器執行非授權的任意查詢,獲取其沒有合法權限查詢的信息[3]。SQL注入不是Web或數據庫服務器中的缺陷,而是由于編程實踐較差且缺乏經驗而導致的。SQL注入漏洞的產生需要滿足以下兩個條件:一是參數用戶可控,前端傳給后端的參數內容是用戶可以控制的;二是參數代入數據庫查詢,傳入的參數拼接到SQL語句,并且帶入數據庫查詢。當黑客想要對Web程序進行滲透注入時,需要做以下準備:搜集目標信息;尋找注入點;構造SQL語句進行注入[4]。
本文以一個實際的網站后臺登錄系統開發為例,開發環境為如下:WampServer版本:2.2,Apache版本:2.2.22,PHP 版本:5.4.3,MySQL 版本:5.5.24。
數據庫命名為news,管理員數據表命名為ad?min_user,包含用戶名、密碼、真實姓名、電話號碼、電子郵箱、權限6個字段,各字段的定義如表1所示。

表1 管理員用戶表
用PHPMyAdmin創建數據表,插入一條數據,用戶名為“abc”,密碼為“123”的 md5 值,即:“202cb962ac 59075b964b07152d234b70”。
(1)基本頁面布局
將登錄頁面命名為login.php,制作用戶登錄表單,命名為 form1,method="post"action="check.php",用表格布局頁面如圖1所示。

圖1 用戶登錄界面布局圖
用戶名框命名為:userName,類型為單行文本,密碼框命名為:userPassword,類型為密碼,驗證碼框命名為yzm,類型為單行文本,驗證碼圖片由yzm.php隨機生成四位字符,并生成有干擾的圖片輸出在頁面,生成的字符放入Session變量中,以備一下步的驗證。
(2)使用JavaScript實現客戶端驗證
在提交按鈕上增加事件onClick="return jc()",在login.php中加入JavaScript代碼如下:
<script>
functionjc()
{
if(document.form1.userName.value=="")//檢查用戶名是否為空
{
window.alert("用戶名不能為空!");//彈出提示
document.form1.userName.focus();//讓用戶名框獲得焦點
return false;//不提交表單
}
if(document.form1.userPassword.value=="")//檢查密碼是否為空
{
window.alert("密碼不能為空!");//彈出提示
document.form1.userPassword.focus();//讓密碼框獲得焦點
return false;//不提交表單
}
i(fdocument.form1.yzm.value.length<4)
{
window.alert("請輸入 4 位驗證碼!");//檢查驗證碼是否為空
document.form1.yzm.focus();//讓驗證框獲得焦點
return false;//不提交表單
}
return true;//提交表單
}
</script>
login.php頁面填報的數據經客戶端驗證通過后,將數據提交到check.php頁面,功能是檢測驗證碼是否正確,然后檢測用戶名和密碼是否正確,代碼如下:
<?php
//1.設置Session路徑并開啟Session功能
session_save_path("session/");
session_start();
//2.獲取用戶輸入的三個參數
$userName=$_POST["userName"];
$userPassword=md5( $_POST["userPassword"]);//使 用 md5加密
$yzm=$_POST["yzm"];
//3.先檢查驗證碼是否正確
if(strtoupper($yzm)!=strtoupper($_SESSION["yzm"]))
{
echo"<script>window.alert('驗證碼錯誤!')</script>";
echo"<meta http-equiv=REFRESH CONTENT='0;URL=log?in.php'>";
}
//4.連接數據庫、設置時區和字符集
$dblink=mysqli_connect("127.0.0.1","root","","news")or die("數據庫打開失敗");
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)//如果正確,跳轉到后臺主頁index.php
{
$_SESSION["isLogin"]=1;
$_SESSION["userName"]=$userName;
echo"<meta http-equiv=REFRESH CONTENT='0;URL=in?dex.php'>";
}
else//如果不正確,跳轉到后臺登錄頁面login.php
{
echo"<script>window.alert('用戶名或密碼錯誤!')</script>";
echo"<meta http-equiv=REFRESH CONTENT='0;URL=log?in.php'>";
}
?>
當驗證碼、用戶名和密碼都正確后,頁面跳轉到后臺主頁index.php,該頁面的布局如圖2所示,具體代碼與本文的SQL注入關系不大,就不再贅述。

圖2 后臺主頁布局圖
程序員按以上步驟完成login.php、check.php和in?dex.php的開發,正常情況下,在login.php中,輸入用戶名為:abc,密碼:123,即可以正常登錄到index.php。但此時程序存在SQL注入漏洞,如瀏覽者在用戶名處輸入“1’or 1=1#”,密碼處輸入任意字符,輸入正確的驗證碼,如圖3所示,就可以登錄后臺了。
在check.php中,驗證用戶名和密碼是否正確的SQL語句是:
$sql="SELECT*FROM admin_user where userName='$userName'and userPassword='$userPassword'"
當瀏覽者按上述的方式輸入,運行后構造的語句是:
SELECT*FROM admin_user where userName='1'or 1=1#'and userPassword='c4ca4238a0b923820dcc509a6f75849b'
以上加粗部分為的用戶名的內容,注入在SQL語句中,就把原本的and條件變成了or條件,而且其中一個為真,后面的內容被當作注釋處理了,“1=1”部分也可以換成其他的為真的表達式,效果是一樣的,如“1'or 1#”。另外,將“#”換成“--(空格)”也是同樣的效果。

圖3
產生以上問題的關鍵在于單引號的區配,和#注釋的作用,在正常的登錄中,用戶名是不應該包含單引號或#號的,所以防范措施可以先過濾如單引、#號這樣的特殊字符,可以用PHP的str_replace函數過濾,如:在check.php中接收到用戶名后做過濾,即在:$userName=$_POST["userName"]后增加如下代碼:
$userName=str_replace("'","",$userName);//替換單引號
$userName=str_replace("#","",$userName);//替換#
$userName=str_replace("-","",$userName);//替換-
$userName=str_replace("","",$userName);//替換空格
將單引號、#號、-號、空格替換后,上例的SQL注入就會失效,在實際編程中,可以把以上的替換做成函數,把與MySQL相關的一些特殊字符一并過濾。
另外,PHP的addslashes()函數也可以解決以上問題,addslashes()函數的作用是返回在預定義字符之前添加反斜杠的字符串,預定義字符是:單引號(')、雙引號(")、反斜杠()、NULL。
在上例中,也可以將 $userName=$_POST["user?Name"],修改為:
$userName=addslashes($_POST["userName"]);
即可在單引號上進行轉義,同樣能解決上例中的注入問題。
SQL注入是從正常的WWW端口訪問,而且表面上跟一般的Web頁面沒什么區別,所以目前市面的防火墻都不會對SQL注入發出警報,SQL注入攻擊主要針對的是Web開發過程中,程序員編程的不嚴密、編程實踐較差且缺乏經驗而產生的程序漏洞,因此要防范SQL注入攻擊的主要方法就是提高編程能力,同時也需要對服務器端進行合理配置[5]。本文通過詳細介紹使用PHP和MySQL技術制作通用的用戶登錄程序,通過實例演示SQL注入攻擊,分析原因并實現安全防范的教學案例,對便于程序員理解SQL注入的原理、舉一反三、提高編程能力防范SQL注入有一定的指導意義。