1. 程式人生 > >php防止sql註入的方法(轉)

php防止sql註入的方法(轉)

原來 nta puts post提交 支持 允許 line php代碼 開發人員

【一、在服務器端配置】

安全,PHP代碼編寫是一方面,PHP的配置更是非常關鍵。

我們php手手工安裝的,php的默認配置文件在 /usr/local/apache2/conf/php.ini,我們最主要就是要配置php.ini中的內容,讓我們執行 php能夠更安全。整個PHP中的安全設置主要是為了防止phpshell和SQL Injection的攻擊,一下我們慢慢探討。我們先使用任何編輯工具打開 /etc/local/apache2/conf/php.ini,如果你是采用其他方式安裝,配置文件可能不在該目錄。

(1) 打開php的安全模式

php的安全模式是個非常重要的內嵌的安全機制,能夠控制一些php中的函數,比如system(),

同時把很多文件操作函數進行了權限控制,也不允許對某些關鍵文件的文件,比如/etc/passwd,

但是默認的php.ini是沒有打開安全模式的,我們把它打開:

safe_mode = on

(2) 用戶組安全

當safe_mode打開時,safe_mode_gid被關閉,那麽php腳本能夠對文件進行訪問,而且相同

組的用戶也能夠對文件進行訪問。

建議設置為:

safe_mode_gid = off

如果不進行設置,可能我們無法對我們服務器網站目錄下的文件進行操作了,比如我們需要

對文件進行操作的時候。

(3) 安全模式下執行程序主目錄

如果安全模式打開了,但是卻是要執行某些程序的時候,可以指定要執行程序的主目錄:

safe_mode_exec_dir = D:/usr/bin

一般情況下是不需要執行什麽程序的,所以推薦不要執行系統程序目錄,可以指向一個目錄,

然後把需要執行的程序拷貝過去,比如:

safe_mode_exec_dir = D:/tmp/cmd

但是,我更推薦不要執行任何程序,那麽就可以指向我們網頁目錄:

safe_mode_exec_dir = D:/usr/www

(4) 安全模式下包含文件

如果要在安全模式下包含某些公共文件,那麽就修改一下選項:

safe_mode_include_dir = D:/usr/www/include/

其實一般php腳本中包含文件都是在程序自己已經寫好了,這個可以根據具體需要設置。

(5) 控制php腳本能訪問的目錄

使用open_basedir選項能夠控制PHP腳本只能訪問指定的目錄,這樣能夠避免PHP腳本訪問

不應該訪問的文件,一定程度上限制了phpshell的危害,我們一般可以設置為只能訪問網站目錄:

open_basedir = D:/usr/www

(6) 關閉危險函數

如果打開了安全模式,那麽函數禁止是可以不需要的,但是我們為了安全還是考慮進去。比如,

我們覺得不希望執行包括system()等在那的能夠執行命令的php函數,或者能夠查看php信息的

phpinfo()等函數,那麽我們就可以禁止它們:

disable_functions = system,passthru,exec,shell_exec,popen,phpinfo

如果你要禁止任何文件和目錄的操作,那麽可以關閉很多文件操作

disable_functions = chdir,chroot,dir,getcwd,opendir,readdir,scandir,fopen,unlink,delete,copy,mkdir, rmdir,rename,file,file_get_contents,fputs,fwrite,chgrp,chmod,chown

以上只是列了部分不叫常用的文件處理函數,你也可以把上面執行命令函數和這個函數結合,

就能夠抵制大部分的phpshell了。

(7) 關閉PHP版本信息在http頭中的泄漏

我們為了防止黑客獲取服務器中php版本的信息,可以關閉該信息斜路在http頭中:

expose_php = Off

比如黑客在 telnet www.12345.com 80 的時候,那麽將無法看到PHP的信息。

(8) 關閉註冊全局變量

在PHP中提交的變量,包括使用POST或者GET提交的變量,都將自動註冊為全局變量,能夠直接訪問,

這是對服務器非常不安全的,所以我們不能讓它註冊為全局變量,就把註冊全局變量選項關閉:

register_globals = Off

當然,如果這樣設置了,那麽獲取對應變量的時候就要采用合理方式,比如獲取GET提交的變量var,

那麽就要用$_GET[‘var‘]來進行獲取,這個php程序員要註意。

(9) 打開magic_quotes_gpc來防止SQL註入

SQL註入是非常危險的問題,小則網站後臺被入侵,重則整個服務器淪陷,

所以一定要小心。php.ini中有一個設置:

magic_quotes_gpc = Off

這個默認是關閉的,如果它打開後將自動把用戶提交對sql的查詢進行轉換,

比如把 ‘ 轉為 \‘等,這對防止sql註射有重大作用。所以我們推薦設置為:

magic_quotes_gpc = On

(10) 錯誤信息控制

一般php在沒有連接到數據庫或者其他情況下會有提示錯誤,一般錯誤信息中會包含php腳本當

前的路徑信息或者查詢的SQL語句等信息,這類信息提供給黑客後,是不安全的,所以一般服務器建議禁止錯誤提示:

display_errors = Off

如果你卻是是要顯示錯誤信息,一定要設置顯示錯誤的級別,比如只顯示警告以上的信息:

error_reporting = E_WARNING & E_ERROR

當然,我還是建議關閉錯誤提示。

(11) 錯誤日誌

建議在關閉display_errors後能夠把錯誤信息記錄下來,便於查找服務器運行的原因:

log_errors = On

同時也要設置錯誤日誌存放的目錄,建議根apache的日誌存在一起:

error_log = D:/usr/local/apache2/logs/php_error.log

註意:給文件必須允許apache用戶的和組具有寫的權限。

MYSQL的降權運行

新建立一個用戶比如mysqlstart

net user mysqlstart fuckmicrosoft /add

net localgroup users mysqlstart /del

不屬於任何組

如果MYSQL裝在d:\mysql ,那麽,給 mysqlstart 完全控制 的權限

然後在系統服務中設置,MYSQL的服務屬性,在登錄屬性當中,選擇此用戶 mysqlstart 然後輸入密碼,確定。

重新啟動 MYSQL服務,然後MYSQL就運行在低權限下了。

如果是在windos平臺下搭建的apache我們還需要註意一點,apache默認運行是system權限,

這很恐怖,這讓人感覺很不爽.那我們就給apache降降權限吧。

net user apache fuckmicrosoft /add

net localgroup users apache /del

ok.我們建立了一個不屬於任何組的用戶apche。

我們打開計算機管理器,選服務,點apache服務的屬性,我們選擇log on,選擇this account,我們填入上面所建立的賬戶和密碼,

重啟apache服務,ok,apache運行在低權限下了。

實際上我們還可以通過設置各個文件夾的權限,來讓apache用戶只能執行我們想讓它能幹的事情,給每一個目錄建立一個單獨能讀寫的用戶。

這也是當前很多虛擬主機提供商的流行配置方法哦,不過這種方法用於防止這裏就顯的有點大材小用了。


【二、在PHP代碼編寫】

雖然國內很多PHP程序員仍在依靠addslashes防止SQL註入,還是建議大家加強中文防止SQL註入的檢查。addslashes的問題在於黑客可以用0xbf27來代替單引號,而addslashes只是將0xbf27修改為0xbf5c27,成為一個有效的多字節字符,其中的0xbf5c仍會被看作是單引號,所以addslashes無法成功攔截。
當然addslashes也不是毫無用處,它是用於單字節字符串的處理,多字節字符還是用mysql_real_escape_string吧。
另外對於php手冊中get_magic_quotes_gpc的舉例:
if (!get_magic_quotes_gpc()) {
$lastname = addslashes($_POST[‘lastname’]);
} else {
$lastname = $_POST[‘lastname’];
}

最好對magic_quotes_gpc已經開放的情況下,還是對$_POST[’lastname’]進行檢查一下。
再說下mysql_real_escape_string和mysql_escape_string這2個函數的區別:
mysql_real_escape_string 必須在(PHP 4 >= 4.3.0, PHP 5)的情況下才能使用。否則只能用 mysql_escape_string ,兩者的區別是:mysql_real_escape_string 考慮到連接的
當前字符集,而mysql_escape_string 不考慮。
總結一下:
* addslashes() 是強行加\;
* mysql_real_escape_string() 會判斷字符集,但是對PHP版本有要求;
* mysql_escape_string不考慮連接的當前字符集。
-------------------------------------------------------------------------------------------------
在PHP編碼的時候,如果考慮到一些比較基本的安全問題,首先一點:
1. 初始化你的變量
為什麽這麽說呢?我們看下面的代碼:
PHP代碼

1 2 3 4 5 6 7 8 9 10 11 <?php if ($admin) { echo ‘登陸成功!‘; include(‘admin.php‘); } else { echo ‘你不是管理員,無法進行管理!‘; } ?>


好,我們看上面的代碼好像是能正常運行,沒有問題,那麽加入我提交一個非法的參數過去呢,那麽效果會如何呢?比如我們的這個頁是http://daybook.diandian.com/login.php,那麽我們提交:http://daybook.diandian.com/login.php?admin=1,呵呵,你想一些,我們是不是直接就是管理員了,直接進行管理。
當然,可能我們不會犯這麽簡單錯的錯誤,那麽一些很隱秘的錯誤也可能導致這個問題,比如phpwind論壇有個漏洞,導致能夠直接拿到管理員權限,就是因為有個$skin變量沒有初始化,導致了後面一系列問題。那麽我們如何避免上面的問題呢?首先,從php.ini入手,把php.ini裏面的register_global =off,就是不是所有的註冊變量為全局,那麽就能避免了。但是,我們不是服務器管理員,只能從代碼上改進了,那麽我們如何改進上面的代碼呢?我們改寫如下:
PHP代碼

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php $admin = 0; // 初始化變量 if ($_POST[‘admin_user‘] && $_POST[‘admin_pass‘]) { // 判斷提交的管理員用戶名和密碼是不是對的相應的處理代碼 // ... $admin = 1; } else { $admin = 0; } if ($admin) { echo ‘登陸成功!‘; include(‘admin.php‘); } else { echo ‘你不是管理員,無法進行管理!‘; } ?>


那麽這時候你再提交http://daybook.diandian.com/login.php?admin=1就不好使了,因為我們在一開始就把變量初始化為 $admin = 0 了,那麽你就無法通過這個漏洞獲取管理員權限。
2. 防止SQL Injection (sql註射)
SQL 註射應該是目前程序危害最大的了,包括最早從asp到php,基本上都是國內這兩年流行的技術,基本原理就是通過對提交變量的不過濾形成註入點然後使惡意用戶能夠提交一些sql查詢語句,導致重要數據被竊取、數據丟失或者損壞,或者被入侵到後臺管理。
那麽我們既然了解了基本的註射入侵的方式,那麽我們如何去防範呢?這個就應該我們從代碼去入手了。
我們知道Web上提交數據有兩種方式,一種是get、一種是post,那麽很多常見的sql註射就是從get方式入手的,而且註射的語句裏面一定是包含一些sql語句的,因為沒有sql語句,那麽如何進行,sql語句有四大句:select 、update、delete、insert,那麽我們如果在我們提交的數據中進行過濾是不是能夠避免這些問題呢?
於是我們使用正則就構建如下函數:
PHP代碼

1 2 3 4 5 6 7 8 9 10 11 12 13 <?php function inject_check($sql_str) { return eregi(‘select|insert|update|delete|‘| function verify_id($id=null) { if (!$id) { exit(‘沒有提交參數!‘); } // 是否為空判斷 elseif (inject_check($id)) { exit(‘提交的參數非法!‘); } // 註射判斷 elseif (!is_numeric($id)) { exit(‘提交的參數非法!‘); } // 數字判斷 $id = intval($id); // 整型化 return $id; } ?>


呵呵,那麽我們就能夠進行校驗了,於是我們上面的程序代碼就變成了下面的:
PHP代碼

1 2 3 4 5 6 7 8 9 10 11 <?php if (inject_check($_GET[‘id‘])) { exit(‘你提交的數據非法,請檢查後重新提交!‘); } else { $id = verify_id($_GET[‘id‘]); // 這裏引用了我們的過濾函數,對$id進行過濾 echo ‘提交的數據合法,請繼續!‘; } ?>


好,問題到這裏似乎都解決了,但是我們有沒有考慮過post提交的數據,大批量的數據呢?
比如一些字符可能會對數據庫造成危害,比如 ‘ _ ‘, ‘ %‘,這些字符都有特殊意義,那麽我們如果進行控制呢?還有一點,就是當我們的php.ini裏面的magic_quotes_gpc = off的時候,那麽提交的不符合數據庫規則的數據都是不會自動在前面加‘ ‘的,那麽我們要控制這些問題,於是構建如下函數:
PHP代碼

1 2 3 4 5 6 7 8 9 10 11 12 13 <?php function str_check( $str ) { if (!get_magic_quotes_gpc()) // 判斷magic_quotes_gpc是否打開 { $str = addslashes($str); // 進行過濾 } $str = str_replace("_", "\_", $str); // 把 ‘_‘過濾掉 $str = str_replace("%", "\%", $str); // 把‘ % ‘過濾掉 return $str; } ?>


我們又一次的避免了服務器被淪陷的危險。
最後,再考慮提交一些大批量數據的情況,比如發貼,或者寫文章、新聞,我們需要一些函數來幫我們過濾和進行轉換,再上面函數的基礎上,我們構建如下函數:
PHP代碼

1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php function post_check($post) { if (!get_magic_quotes_gpc()) // 判斷magic_quotes_gpc是否為打開 { $post = addslashes($post); // 進行magic_quotes_gpc沒有打開的情況對提交數據的過濾 } $post = str_replace("_", "\_", $post); // 把 ‘_‘過濾掉 $post = str_replace("%", "\%", $post); // 把‘ % ‘過濾掉 $post = nl2br($post); // 回車轉換 $post= htmlspecialchars($post); // html標記轉換 return $post; } ?>


呵呵,基本到這裏,我們把一些情況都說了一遍,其實我覺得自己講的東西還很少,至少我才只講了兩方面,再整個安全中是很少的內容了,考慮下一次講更多,包括php安全配置,apache安全等等,讓我們的安全正的是一個整體,作到最安全。
最後在告訴你上面表達的:1. 初始化你的變量 2. 一定記得要過濾你的變量

一個是沒有對輸入的數據進行過濾(過濾輸入),還有一個是沒有對發送到數據庫的數據進行轉義(轉義輸出)。這兩個重要的步驟缺一不可,需要同時加以特別關註以減少程序錯誤。
對於攻擊者來說,進行SQL註入攻擊需要思考和試驗,對數據庫方案進行有根有據的推理非常有必要(當然假設攻擊者看不到你的源程序和數據庫方案),考慮以下簡單的登錄表單:

復制代碼代碼如下:
<form action="/login.php" method="POST">
<p>Username: <input type="text" name="username" /></p>
<p>Password: <input type="password" name="password" /></p>
<p><input type="submit" value="Log In" /></p>
</form>


作為一個攻擊者,他會從推測驗證用戶名和密碼的查詢語句開始。通過查看源文件,他就能開始猜測你的習慣。
比如命名習慣。通常會假設你表單中的字段名為與數據表中的字段名相同。當然,確保它們不同未必是一個可靠的安全措施。
第一次猜測,一般會使用下面例子中的查詢:

復制代碼代碼如下:
<?php
$password_hash = md5($_POST[‘password‘]);

$sql = "SELECT count(*)
FROM users
WHERE username = ‘{$_POST[‘username‘]}‘
AND password = ‘$password_hash‘";
?>


使用用戶密碼的MD5值原來是一個通行的做法,但現在並不是特別安全了。最近的研究表明MD5算法有缺陷,而且大量MD5數據庫降低了MD5反向破解的難度。請訪問http://md5.rednoize.com/ 查看演示(原文如此,山東大學教授王小雲的研究表明可以很快的找到MD5的“碰撞”,就是可以產生相同的MD5值的不同兩個文件和字串。MD5是信息摘要算法,而不是加密算法,反向破解也就無從談起了。不過根據這個成果,在上面的特例中,直接使用md5是危險的。)。
最好的保護方法是在密碼上附加一個你自己定義的字符串,例如:

復制代碼代碼如下:
<?php
$salt = ‘SHIFLETT‘;
$password_hash = md5($salt . md5($_POST[‘password‘] . $salt));
?>


當然,攻擊者未必在第一次就能猜中,他們常常還需要做一些試驗。有一個比較好的試驗方式是把單引號作為用戶名錄入,原因是這樣可能會暴露一些重要信息。有很多開發人員在Mysql語句執行出錯時會調用函數mysql_error()來報告錯誤。見下面的例子:

復制代碼代碼如下:
<?php
mysql_query($sql) or exit(mysql_error());
?>


雖然該方法在開發中十分有用,但它能向攻擊者暴露重要信息。如果攻擊者把單引號做為用戶名,mypass做為密碼,查詢語句就會變成:

復制代碼代碼如下:
<?php
$sql = "SELECT *
FROM users
WHERE username = ‘‘‘
AND password = ‘a029d0df84eb5549c641e04a9ef389e5‘";
?>


當該語句發送到MySQL後,系統就會顯示如下錯誤信息:

復制代碼代碼如下:
You have an error in your SQL syntax. Check the manual that corresponds to your
MySQL server version for the right syntax to use near ‘WHERE username = ‘‘‘ AND
password = ‘a029d0df84eb55


不費吹灰之力,攻擊者已經知道了兩個字段名(username和password)以及他們出現在查詢中的順序。除此以外,攻擊者還知道了數據沒有正確進行過濾(程序沒有提示非法用戶名)和轉義(出現了數據庫錯誤),同時整個WHERE條件的格式也暴露了,這樣,攻擊者就可以嘗試操縱符合查詢的記錄了。
在這一點上,攻擊者有很多選擇。一是嘗試填入一個特殊的用戶名,以使查詢無論用戶名密碼是否符合,都能得到匹配:

復制代碼代碼如下:
myuser‘ or ‘foo‘ = ‘foo‘ --


假定將mypass作為密碼,整個查詢就會變成:

復制代碼代碼如下:
<?php

$sql = "SELECT *
FROM users
WHERE username = ‘myuser‘ or ‘foo‘ = ‘foo‘ --
AND password = ‘a029d0df84eb5549c641e04a9ef389e5‘";

?>


幸運的是,SQL註入是很容易避免的。正如前面所提及的,你必須堅持過濾輸入和轉義輸出。
雖然兩個步驟都不能省略,但只要實現其中的一個就能消除大多數的SQL註入風險。如果你只是過濾輸入而沒有轉義輸出,你很可能會遇到數據庫錯誤(合法的數據也可能影響SQL查詢的正確格式),但這也不可靠,合法的數據還可能改變SQL語句的行為。另一方面,如果你轉義了輸出,而沒有過濾輸入,就能保證數據不會影響SQL語句的格式,同時也防止了多種常見SQL註入攻擊的方法。
當然,還是要堅持同時使用這兩個步驟。過濾輸入的方式完全取決於輸入數據的類型(見第一章的示例),但轉義用於向數據庫發送的輸出數據只要使用同一個函數即可。對於MySQL用戶,可以使用函數mysql_real_escape_string( ):

復制代碼代碼如下:
<?php
$clean = array();
$mysql = array();

$clean[‘last_name‘] = "O‘Reilly";
$mysql[‘last_name‘] = mysql_real_escape_string($clean[‘last_name‘]);

$sql = "INSERT
INTO user (last_name)
VALUES (‘{$mysql[‘last_name‘]}‘)";
?>


盡量使用為你的數據庫設計的轉義函數。如果沒有,使用函數addslashes()是最終的比較好的方法。
當所有用於建立一個SQL語句的數據被正確過濾和轉義時,實際上也就避免了SQL註入的風險。如果你正在使用支持參數化查詢語句和占位符的數據庫操作類(如PEAR::DB, PDO等),你就會多得到一層保護。見下面的使用PEAR::DB的例子:

復制代碼代碼如下:
<?php
$sql = ‘INSERT
INTO user (last_name)
VALUES (?)‘;
$dbh->query($sql, array($clean[‘last_name‘]));
?>


由於在上例中數據不能直接影響查詢語句的格式,SQL註入的風險就降低了。PEAR::DB會自動根據你的數據庫的要求進行轉義,所以你只需要過濾輸出即可。
如果你正在使用參數化查詢語句,輸入的內容就只會作為數據來處理。這樣就沒有必要進行轉義了,盡管你可能認為這是必要的一步(如果你希望堅持轉義輸出習慣的話)。實際上,這時是否轉義基本上不會產生影響,因為這時沒有特殊字符需要轉換。在防止SQL註入這一點上,參數化查詢語句為你的程序提供了強大的保護。
註:關於SQL註入,不得不說的是現在大多虛擬主機都會把magic_quotes_gpc選項打開,在這種情況下所有的客戶端GET和POST的數據都會自動進行addslashes處理,所以此時對字符串值的SQL註入是不可行的,但要防止對數字值的SQL註入,如用intval()等函數進行處理。但如果你編寫的是通用軟件,則需要讀取服務器的magic_quotes_gpc後進行相應處理。

php防止sql註入的方法(轉)