SQL注入是PHP運用最常見的漏洞之一,很多開發人員都會時刻提防着它,防SQL注入的普遍做法是對數據輸入進行過濾,以及對發送到數據庫的數據進行轉義。其實就是永遠不要相信用戶輸入數據。爲了更好的理解SQL注入,筆者今天自己嘗試用SQL“攻擊”自己了一次。以下是建立攻擊的一個過程。
1、建立用戶表
我用PHPMyAdmin在我test數據庫建立了一張user表,表中有三個字段,分別是用戶名、密碼、郵箱,然後插入了一條測試數據
用戶名是:周運金,密碼是test(用了MD5加密)
2、建立登陸頁面form.html
<html>
<head>
<title>Sql注入</title>
<meta http-equiv="content-type"content="text/html;charset=utf-8">
</head>
<body>
<form action="validate.php" method="post">
<fieldset >
<legend>Sql注入</legend>
<table>
<tr>
<td>用戶名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密 碼:</td>
<td><input type="text" name="password"></td>
</tr>
<tr>
<td><input type="submit" value="提交"></td>
<td><input type="reset" value="重置"></td>
</tr>
</table>
</fieldset>
</form>
</body>
</html>
效果:
3、創建處理登陸Validate.php
<?php
//面向對象的連接方式
$mysqli =new mysqli("localhost","root","123","test");
if(!$mysqli ){
echo mysqli_connect_error();
}
$mysqli->set_charset("utf8"); $mysqli->query("set names 'utf8'");
$name=$_POST['username'];
$pwd=md5($_POST['password']);
$sql="select * from user where username='$name' and password='$pwd'";
$query=$mysqli->query($sql);
if($query == false){
echo $mysqli->error;
}
else{
$rows =$query->num_rows;
if($rows){
header("Location:manage.php");
}else{
echo "您的用戶名或密碼輸入有誤,<a href='form.html'>請重新登錄!</a>";
}
}
$mysqli->close();
?>
這裏說明一下,原始MySQL在PHP5.5之後已經被php拋棄,採用面像對象連接的MYSQLI。
4、建立登陸成功的頁面manage.php
<?php
echo "You are a manager";
?>
這樣就完成了一個有SQL注入漏洞的登陸程序了。很明顯程序沒有對用戶輸入的數據進行處理就直接放進sql語句裏面了。這是很危險的做法。
接下來就開始攻擊一下吧,做法其實很簡單隻要去拼湊sql語句就好了。
我先輸入正確的用戶名和密碼:
爲了顯示密碼我這裏沒有用密碼框了。輸入完之後成功登陸
接下來正式注入了,用戶名輸入:’ or 1=1# 密碼隨便輸入
點擊提交之後:
居然登陸成功了。哈哈哈,接下來分析一下注入後SQL語句吧:
我在sql語句那裏設置了一個斷點,看一下拼接後的sql
這裏可以看到拼接後的sql語句是:
select * from user where username=” or 1=1#’ and password=’202cb962ac59075b964b07152d234b70’這裏的#是mysql的註釋符,意思就是忽略後面的sql語句,這樣的話就不用驗證了,而且在username後面還有一句邏輯語句or 1=1,這樣的話這條語句永遠成立,所以就通過了驗證。
接下來就談談常見的方SQL注入方法:
1、最常見的就是採用mysqli_real_escape_string函數進行轉義一些特殊的字符比如\n、\r、\、’、” 等(在查找mysql_real_escape_string函數的時候發現PHP文檔說這個函數在php5.5之後就被拋棄了,改用mysqli_real_escape_string,看來PHP要全面使用面向對象的mysqli了),就像剛纔的注入有個單引號,用了這個函數之後就會被轉義,這樣拼接就失敗了。我們來看看再用之前的’ or 1=1#去拼接,加入這個函數之後會怎樣:
$name=mysqli_real_escape_string($mysqli,$_POST['username']); //必須使用數據庫連接,這樣看來是專門爲防sql注入準備的,比較安全
$pwd=mysqli_real_escape_string($mysqli,md5($_POST['password']));
結果不能登錄了,設個斷點看一下轉義之後sql語句:
這個是PHP文檔給出的例子
<?php
// Assume this is a simple comments form with a name and comment.
$name = mysqli_real_escape_string($conn, $_POST['name']);
$comments = mysqli_real_escape_string($conn, $_POST['comments']);
// Here is where most of the action happens. But see note below
// on dumping back out from the database
// We should use the ENT_QUOTES flag second parameter...
$name = htmlspecialchars($name);
$comments = htmlspecialchars($comments);
$insert_sql = "INSERT INTO tbl_comments ( c_id, c_name, c_comments ) VALUES ( DEFAULT, '" . $name . "', '" . $comments . "')";
$res = mysqli_query($conn, $insert_sql);
if ( $res === false ) {
// Something went wrong, handle it
}
// Now output page showing comments
?>
看到單引號‘被轉義成了/’了這樣的話拼接就沒用了。
二、打開magic_quotes_gpc來防止SQL注入
這個原理跟第一個的原理類似,是將GET、POST、COOKIE傳過來的數據進行自動轉義,相當於用addslshes()函數進行轉義。但是這種方式沒有辦法防止當參數是數字型的sql攻擊,因爲數字是沒有單引號或者雙引號的。解決的辦法是用intaval()函數強制將字符數據轉換成數字。如果開啓了magic_quotes_gpc=on,在第一個方法中記得用stripslashes函數去掉/
三、自定義過濾函數
以下是W3C給出的一個過濾函數我將轉義函數改了
function check_input($value,$con)
{
// 去除斜槓
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}
// 如果不是數字則加引號
if (!is_numeric($value))
{
$value = "'" . mysqli_real_escape_string($con,$value) . "'";
}
return $value;
}
這個函數考慮到使用mysqli_real_escape_string比使用addslshes()更加安全
最後注意了:
在寫這篇文章的時候,筆者參考了一篇文章說sql注入可以繞開以上的方法了,嚇得得我一身冷汗,看了之後果然覺得厲害。文章中建議使用不要用mysql_query了而是用PDO和MYSQLi來代替mysql_query,心想還好我用的是mysqli,它採用了mysqli的Prepared Statement機制可以有效解決sql注入。大家可以參考一下
文章地址:參考文章
總結:
1、sql注入的方式其實很簡單,但是後果卻是很致命的,所以開發人員一定記住永遠不要去相信客戶的數據。
2、防sql注入的方法有很多,但是一定得保持一定得技術更新,因爲黑客的技術越來越厲害了,要經常更新這方面的防護知識,隨時保持最新的防漏洞知識。