PHP安全配置
一、CGI模式安裝安全
二、以Apache模塊安裝安全
當(dāng) PHP 以 Apache 模塊方式安裝時,它將繼承 Apache 用戶(通常為“nobody”)的權(quán)限。這對安全和認(rèn)證有一些影響。比如,如果用 PHP 來訪問數(shù)據(jù)庫,除非數(shù)據(jù)庫有自己的訪問控制,否則就要使“nobody”用戶可以訪問數(shù)據(jù)庫。這意味著惡意的腳本在不用提供用戶名和密碼時就可能訪問和修改數(shù)據(jù)庫。一個 web Spider 也完全有可能偶然發(fā)現(xiàn)數(shù)據(jù)庫的管理頁面,并且刪除所有的數(shù)據(jù)庫??梢酝ㄟ^ Apache 認(rèn)證來避免此問題,或者用 LDAP、.htaccess 等技術(shù)來設(shè)計自己的防問模型,并把這些代碼作為 PHP 腳本的一部份
一個常犯的對安全性不利的錯誤就是讓 Apache 擁有 root 權(quán)限,或者通過其它途徑斌予 Apache 更強(qiáng)大的功能。
把 Apache 用戶的權(quán)限提升為 root 是極度危險的做法,而且可能會危及到整個系統(tǒng)的安全。所以除非是安全專家,否則決不要考慮使用 su,chroot 或者以 root 權(quán)限運(yùn)行。
除此之外還有一些比較簡單的解決方案。比如說可以使用 open_basedir 來限制哪些目錄可以被 PHP 使用。也可以設(shè)置 Apache 的專屬區(qū)域,從而把所有的 web 活動都限制到非用戶和非系統(tǒng)文件之中。
三、文件系統(tǒng)安全
PHP 遵從大多數(shù)服務(wù)器系統(tǒng)中關(guān)于文件和目錄權(quán)限的安全機(jī)制。這就使管理員可以控制哪些文件在文件系統(tǒng)內(nèi)是可讀的。必須特別注意的是全局的可讀文件,并確保每一個有權(quán)限的用戶對這些文件的讀取動作都是安全的。
PHP 被設(shè)計為以用戶級別來訪問文件系統(tǒng),所以完全有可能通過編寫一段 PHP 代碼來讀取系統(tǒng)文件如 /etc/passwd,更改網(wǎng)絡(luò)連接以及發(fā)送大量打印任務(wù)等等。因此必須確保 PHP 代碼讀取和寫入的是合適的文件。
兩個重要措施來防止此類問題。
# 只給 PHP 的 web 用戶很有限的權(quán)限。
# 檢查所有提交上來的變量。
$username = $_SERVER['REMOTE_USER']; // 使用認(rèn)證機(jī)制 $userfile = $_POST['user_submitted_filename']; $homedir = "/home/$username"; $filepath = "$homedir/$userfile"; if (!ctype_alnum($username) || !preg_match('/^(?:[a-z0-9_-]|\.(?!\.))+$/iD', $userfile)) { die("Bad username/filename"); }
* Null字符問題
由于 PHP 的文件系統(tǒng)操作是基于 C 語言的函數(shù)的,所以它可能會以您意想不到的方式處理 Null 字符。 Null字符在 C 語言中用于標(biāo)識字符串結(jié)束,一個完整的字符串是從其開頭到遇見 Null 字符為止。
# 會被 Null 字符問題攻擊的代碼:
$file = $_GET['file']; // "http://www.cnblogs.com/etc/passwd\0" if (file_exists('/home/wwwrun/'.$file.'.php')) { // file_exists will return true as the file /home/wwwrun/http://www.cnblogs.com/etc/passwd exists include '/home/wwwrun/'.$file.'.php'; // the file /etc/passwd will be included }
# 驗(yàn)證輸入的正確做法
$file = $_GET['file']; // 對字符串進(jìn)行白名單檢查 switch ($file) { case 'main': case 'foo': case 'bar': include '/home/wwwrun/include/'.$file.'.php'; break; default: include '/home/wwwrun/include/main.php'; }
四、數(shù)據(jù)庫安全
* 設(shè)計數(shù)據(jù)庫
第一步一般都是創(chuàng)建數(shù)據(jù)庫,除非是使用第三方的數(shù)據(jù)庫服務(wù)。當(dāng)創(chuàng)建一個數(shù)據(jù)庫的時候,會指定一個所有者來執(zhí)行和新建語句。通常,只有所有者(或超級用戶)才有權(quán)對數(shù)據(jù)庫中的對象進(jìn)行任意操作。如果想讓其他用戶使用,就必須賦予他們權(quán)限。
應(yīng)用程序永遠(yuǎn)不要使用數(shù)據(jù)庫所有者或超級用戶帳號來連接數(shù)據(jù)庫,因?yàn)檫@些帳號可以執(zhí)行任意的操作,比如說修改數(shù)據(jù)庫結(jié)構(gòu)(例如刪除一個表)或者清空整個數(shù)據(jù)庫的內(nèi)容。
應(yīng)該為程序的每個方面創(chuàng)建不同的數(shù)據(jù)庫帳號,并賦予對數(shù)據(jù)庫對象的極有限的權(quán)限。僅分配給能完成其功能所需的權(quán)限,避免同一個用戶可以完成另一個用戶的事情。這樣即使攻擊者利用程序漏洞取得了數(shù)據(jù)庫的訪問權(quán)限,也最多只能做到和該程序一樣的影響范圍。
鼓勵用戶不要把所有的事務(wù)邏輯都用 web 應(yīng)用程序(即用戶的腳本)來實(shí)現(xiàn)。最好用視圖(view)、觸發(fā)器(trigger)或者規(guī)則(rule)在數(shù)據(jù)庫層面完成。當(dāng)系統(tǒng)升級的時候,需要為數(shù)據(jù)庫開辟新的接口,這時就必須重做所有的數(shù)據(jù)庫客戶端。除此之外,觸發(fā)器還可以透明和自動地處理字段,并在調(diào)試程序和跟蹤事實(shí)時提供有用的信息。
* 連接數(shù)據(jù)庫
把連接建立在 SSL 加密技術(shù)上可以增加客戶端和服務(wù)器端通信的安全性,或者 SSH 也可以用于加密客戶端和數(shù)據(jù)庫之間的連接。如果使用了這些技術(shù)的話,攻擊者要監(jiān)視服務(wù)器的通信或者得到數(shù)據(jù)庫的信息是很困難的。
* 加密存儲模型
SSL/SSH 能保護(hù)客戶端和服務(wù)器端交換的數(shù)據(jù),但 SSL/SSH 并不能保護(hù)數(shù)據(jù)庫中已有的數(shù)據(jù)。SSL 只是一個加密網(wǎng)絡(luò)數(shù)據(jù)流的協(xié)議。
如果攻擊者取得了直接訪問數(shù)據(jù)庫的許可(繞過 web 服務(wù)器),敏感數(shù)據(jù)就可能暴露或者被濫用,除非數(shù)據(jù)庫自己保護(hù)了這些信息。對數(shù)據(jù)庫內(nèi)的數(shù)據(jù)加密是減少這類風(fēng)險的有效途徑,但是只有很少的數(shù)據(jù)庫提供這些加密功能。
對于這個問題,有一個簡單的解決辦法,就是創(chuàng)建自己的加密機(jī)制,然后把它用在 PHP 程序內(nèi)。PHP 有幾個擴(kuò)展庫可以完成這個工作,比如說 Mcrypt 和 Mhash 等,它們包含多種加密運(yùn)算法則。腳本在插入數(shù)據(jù)庫之前先把數(shù)據(jù)加密,以后提取出來時再解密。
對某些真正隱蔽的數(shù)據(jù),如果不需要以明文的形式存在(即不用顯示),可以考慮用散列算法。使用散列算法最常見的例子就是把密碼經(jīng)過 MD5 加密后的散列存進(jìn)數(shù)據(jù)庫來代替原來的明文密碼。參見 crypt() 和 md5()。
* SQL注入
很多 web 開發(fā)者沒有注意到 SQL 查詢是可以被篡改的,因而把 SQL 查詢當(dāng)作可信任的命令。殊不知道,SQL 查詢可以繞開訪問控制,從而繞過身份驗(yàn)證和權(quán)限檢查。更有甚者,有可能通過 SQL 查詢?nèi)ミ\(yùn)行主機(jī)操作系統(tǒng)級的命令。
直接 SQL 命令注入就是攻擊者常用的一種創(chuàng)建或修改已有 SQL 語句的技術(shù),從而達(dá)到取得隱藏數(shù)據(jù),或覆蓋關(guān)鍵的值,甚至執(zhí)行數(shù)據(jù)庫主機(jī)操作系統(tǒng)命令的目的。這是通過應(yīng)用程序取得用戶輸入并與靜態(tài)參數(shù)組合成 SQL 查詢來實(shí)現(xiàn)的。下面將會給出一些真實(shí)的例子。
由于在缺乏對輸入的數(shù)據(jù)進(jìn)行驗(yàn)證,并且使用了超級用戶或其它有權(quán)創(chuàng)建新用戶的數(shù)據(jù)庫帳號來連接,攻擊者可以在數(shù)據(jù)庫中新建一個超級用戶。
Example #1 一段實(shí)現(xiàn)數(shù)據(jù)分頁顯示的代碼……也可以被用作創(chuàng)建一個超級用戶(PostgreSQL系統(tǒng))。
<?php $offset = $argv[0]; // 注意,沒有輸入驗(yàn)證! $query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; $result = pg_query($conn, $query); ?>
一般的用戶會點(diǎn)擊 $offset已被斌值的“上一頁”、“下一頁”的鏈接。原本代碼只會認(rèn)為 $offset是一個數(shù)值。然而,如果有人嘗試把以下語句先經(jīng)過 urlencode()處理,然后加入URL中的話:
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
select 'crack', usesysid, 't','t','crack'
from pg_shadow where usename='postgres';
--那么他就可以創(chuàng)建一個超級用戶了。注意那個 0;只不過是為了提供一個正確的偏移量以便補(bǔ)充完整原來的查詢,使它不要出錯而已。
Note:
-- 是 SQL 的注釋標(biāo)記,一般可以使用來它告訴 SQL 解釋器忽略后面的語句。
對顯示搜索結(jié)果的頁面下手是一個能得到密碼的可行辦法。攻擊者所要做的只不過是找出哪些提交上去的變量是用于 SQL 語句并且處理不當(dāng)?shù)摹6@類的變量通常都被用于 SELECT查詢中的條件語句,如 WHERE, ORDER BY, LIMIT 和 OFFSET。如果數(shù)據(jù)庫支持 UNION構(gòu)造的話,攻擊者還可能會把一個完整的 SQL 查詢附加到原來的語句上以便從任意數(shù)據(jù)表中得到密碼。因此,對密碼字段加密是很重要的。
Example #2 顯示文章……以及一些密碼(任何數(shù)據(jù)庫系統(tǒng))
<?php
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'
ORDER BY $order LIMIT $limit, $offset;";
$result = odbc_exec($conn, $query);
?>
可以在原來的查詢的基礎(chǔ)上添加另一個 SELECT查詢來獲得密碼:
'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--假如上述語句(使用 ' 和 --)被加入到 $query中的任意一個變量的話,那么就麻煩了。
SQL 中的 UPDATE 也會受到攻擊。這種查詢也可能像上面的例子那樣被插入或附加上另一個完整的請求。但是攻擊者更愿意對 SET子句下手,這樣他們就可以更改數(shù)據(jù)表中的一些數(shù)據(jù)。這種情況下必須要知道數(shù)據(jù)庫的結(jié)構(gòu)才能修改查詢成功進(jìn)行??梢酝ㄟ^表單上的變量名對字段進(jìn)行猜測,或者進(jìn)行暴力破解。對于存放用戶名和密碼的字段,命名的方法并不多。
Example #3 從重設(shè)密碼……到獲得更多權(quán)限(任何數(shù)據(jù)庫系統(tǒng))
<?php
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
但是惡意的用戶會把 ' or uid like'%admin%'; --作為變量的值提交給 $uid 來改變 admin 的密碼,或者把 $pwd 的值提交為 "hehehe', admin='yes', trusted=100 "(后面有個空格)去獲得更多的權(quán)限。這樣做的話,查詢語句實(shí)際上就變成了:
<?php
// $uid == ' or uid like'%admin%'; --
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --";
// $pwd == "hehehe', admin='yes', trusted=100 "
$query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE
...;";
?>
下面這個可怕的例子將會演示如何在某些數(shù)據(jù)庫上執(zhí)行系統(tǒng)命令。
Example #4 攻擊數(shù)據(jù)庫所在主機(jī)的操作系統(tǒng)(MSSQL Server)
<?php
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
?>
如果攻擊提交 a%' exec master..xp_cmdshell 'net user test testpass /ADD' --作為變量 $prod的值,那么 $query將會變成
<?php
$query = "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD'--";
$result = mssql_query($query);
?>
MSSQL 服務(wù)器會執(zhí)行這條 SQL 語句,包括它后面那個用于向系統(tǒng)添加用戶的命令。如果這個程序是以 sa運(yùn)行而 MSSQLSERVER 服務(wù)又有足夠的權(quán)限的話,攻擊者就可以獲得一個系統(tǒng)帳號來訪問主機(jī)了。
Note:
雖然以上的例子是針對某一特定的數(shù)據(jù)庫系統(tǒng)的,但是這并不代表不能對其它數(shù)據(jù)庫系統(tǒng)實(shí)施類似的攻擊。使用不同的方法,各種數(shù)據(jù)庫都有可能遭殃。
預(yù)防措施
也許有人會自我安慰,說攻擊者要知道數(shù)據(jù)庫結(jié)構(gòu)的信息才能實(shí)施上面的攻擊。沒錯,確實(shí)如此。但沒人能保證攻擊者一定得不到這些信息,一但他們得到了,數(shù)據(jù)庫有泄露的危險。如果你在用開放源代碼的軟件包來訪問數(shù)據(jù)庫,比如論壇程序,攻擊者就很容得到到相關(guān)的代碼。如果這些代碼設(shè)計不良的話,風(fēng)險就更大了。
這些攻擊總是建立在發(fā)掘安全意識不強(qiáng)的代碼上的。所以,永遠(yuǎn)不要信任外界輸入的數(shù)據(jù),特別是來自于客戶端的,包括選擇框、表單隱藏域和 cookie。就如上面的第一個例子那樣,就算是正常的查詢也有可能造成災(zāi)難。
•永遠(yuǎn)不要使用超級用戶或所有者帳號去連接數(shù)據(jù)庫。要用權(quán)限被嚴(yán)格限制的帳號。
•檢查輸入的數(shù)據(jù)是否具有所期望的數(shù)據(jù)格式。PHP 有很多可以用于檢查輸入的函數(shù),從簡單的變量函數(shù)和字符類型函數(shù)(比如 is_numeric(),ctype_digit())到復(fù)雜的 Perl 兼容正則表達(dá)式函數(shù)都可以完成這個工作。
•如果程序等待輸入一個數(shù)字,可以考慮使用 is_numeric()來檢查,或者直接使用 settype() 來轉(zhuǎn)換它的類型,也可以用 sprintf() 把它格式化為數(shù)字。
Example #5 一個實(shí)現(xiàn)分頁更安全的方法
<?php
settype($offset, 'integer');
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
// 請注意格式字符串中的 %d,如果用 %s 就毫無意義了
$query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
$offset);
?>
•使用數(shù)據(jù)庫特定的敏感字符轉(zhuǎn)義函數(shù)(比如 mysql_escape_string() 和 sql_escape_string())把用戶提交上來的非數(shù)字?jǐn)?shù)據(jù)進(jìn)行轉(zhuǎn)義。如果數(shù)據(jù)庫沒有專門的敏感字符轉(zhuǎn)義功能的話 addslashes() 和 str_replace()可以代替完成這個工作??纯吹谝粋€例子,此例顯示僅在查詢的靜態(tài)部分加上引號是不夠的,查詢很容易被攻破。
•要不擇手段避免顯示出任何有關(guān)數(shù)據(jù)庫的信心,尤其是數(shù)據(jù)庫結(jié)構(gòu)。參見錯誤報告和錯誤處理函數(shù)。
•也可以選擇使用數(shù)據(jù)庫的存儲過程和預(yù)定義指針等特性來抽象數(shù)庫訪問,使用戶不能直接訪問數(shù)據(jù)表和視圖。但這個辦法又有別的影響。
除此之外,在允許的情況下,使用代碼或數(shù)據(jù)庫系統(tǒng)保存查詢?nèi)罩疽彩且粋€好辦法。顯然,日志并不能防止任何攻擊,但利用它可以跟蹤到哪個程序曾經(jīng)被嘗試攻擊過。日志本身沒用,要查閱其中包含的信息才行。畢竟,更多的信息總比沒有要好。
五、錯誤報告
對于 PHP 的安全性來說錯誤報告是一把雙刃劍。一方面可以提高安全性,另一方面又有害。
攻擊系統(tǒng)時經(jīng)常使用的手法就是輸入不正確的數(shù)據(jù),然后查看錯誤提示的類型及上下文。這樣做有利于攻擊者收集服務(wù)器的信息以便尋找弱點(diǎn)。比如說,如果一個攻擊者知道了一個頁面所基于的表單信息,那么他就會嘗試修改變量:
Example #1 用自定義的 HTML 頁面攻擊變量
<form method="post" action="attacktarget?username=badfoo&password=badfoo">
<input type="hidden" name="username" value="badfoo" />
<input type="hidden" name="password" value="badfoo" />
</form>
通常 PHP 所返回的錯誤提示都能幫助開發(fā)者調(diào)試程序,它會提出哪個文件的哪些函數(shù)或代碼出錯,并指出錯誤發(fā)生的在文件的第幾行,這些就是 PHP 本身所能給出的信息。很多 PHP 開發(fā)者會使用 show_source()、highlight_string()或者 highlight_file()函數(shù)來調(diào)試代碼,但是在正式運(yùn)行的網(wǎng)站中,這種做法可能會暴露出隱藏的變量、未檢查的語法和其它的可能危及系統(tǒng)安全的信息。在運(yùn)行一些具有內(nèi)部調(diào)試處理的程序,或者使用通用調(diào)試技術(shù)是很危險的。如果讓攻擊者確定了程序是使用了哪種具體的調(diào)試技術(shù),他們會嘗試發(fā)送變量來打開調(diào)試功能:
Example #2 利用變量打開調(diào)式功能
<form method="post" action="attacktarget?errors=Y&showerrors=1&debug=1">
<input type="hidden" name="errors" value="Y" />
<input type="hidden" name="showerrors" value="1" />
<input type="hidden" name="debug" value="1" />
</form>
不管錯誤處理機(jī)制如何,可以探測系統(tǒng)錯誤的能力會給攻擊者提供更多信息。
比如說,PHP 的獨(dú)有的錯誤提示風(fēng)格可以說明系統(tǒng)在運(yùn)行 PHP。如果攻擊者在尋找一個 .html 為頁面,想知道其后臺的技術(shù)(為了尋找系統(tǒng)弱點(diǎn)),他們就會把錯誤的數(shù)據(jù)提交上去,然后就有可以得知系統(tǒng)是基于 PHP 的了。
一個函數(shù)錯誤就可能暴露系統(tǒng)正在使用的數(shù)據(jù)庫,或者為攻擊者提供有關(guān)網(wǎng)頁、程序或設(shè)計方面的有用信息。攻擊者往往會順藤摸瓜地找到開放的數(shù)據(jù)庫端口,以及頁面上某些 bug 或弱點(diǎn)等。比如說,攻擊者可以一些不正常的數(shù)據(jù)使程序出錯,來探測腳本中認(rèn)證的順序(通過錯誤提示的行號數(shù)字)以及腳本中其它位置可能泄露的信息。
一個文件系統(tǒng)或者 PHP 的錯誤就會暴露 web 服務(wù)器具有什么權(quán)限,以及文件在服務(wù)器上的組織結(jié)構(gòu)。開發(fā)者自己寫的錯誤代碼會加劇此問題,導(dǎo)致泄漏了原本隱藏的信息。
有三個常用的辦法處理這些問題。第一個是徹底地檢查所有函數(shù),并嘗試彌補(bǔ)大多數(shù)錯誤。第二個是對在線系統(tǒng)徹底關(guān)閉錯誤報告。第三個是使用 PHP 自定義的錯誤處理函數(shù)創(chuàng)建自己的錯誤處理機(jī)制。根據(jù)不同的安全策略,三種方法可能都適用。
一個能提前阻止這個問題發(fā)生的方法就是利用 error_reporting() 來幫助使代碼更安全并發(fā)現(xiàn)變量使用的危險之處。在發(fā)布程序之前,先打開 E_ALL 測試代碼,可以幫你很快找到變量使用不當(dāng)?shù)牡胤?。一旦?zhǔn)備正式發(fā)布,就應(yīng)該把 error_reporting() 的參數(shù)設(shè)為 0 來徹底關(guān)閉錯誤報告或者把 php.ini中的 display_errors 設(shè)為 off 來關(guān)閉所有的錯誤顯示以將代碼隔絕于探測。當(dāng)然,如果要遲一些再這樣做,就不要忘記打開 ini 文件內(nèi)的 log_errors 選項,并通過 error_log 指定用于記錄錯誤信息的文件。
Example #3 用 E_ALL 來查找危險的變量
<?php
if ($username) { // Not initialized or checked before usage
$good_login = 1;
}
if ($good_login == 1) { // If above test fails, not initialized or checked before usage
readfile ("/highly/sensitive/data/index.html");
}
?>
六、使用Register Globals
可能 PHP 中最具爭議的變化就是從 PHP » 4.2.0版開始配置文件中 register_globals的默認(rèn)值從 on 改為 off 了。對此選項的依賴是如此普遍以至于很多人根本不知道它的存在而以為 PHP 本來就是這么工作的。本節(jié)會解釋用這個指令如何寫出不安全的代碼,但要知道這個指令本身沒有不安全的地方,誤用才會。
當(dāng) register_globals 打開以后,各種變量都被注入代碼,例如來自 HTML 表單的請求變量。再加上 PHP 在使用變量之前是無需進(jìn)行初始化的,這就使得更容易寫出不安全的代碼。這是個很艱難的抉擇,但 PHP 社區(qū)還是決定默認(rèn)關(guān)閉此選項。當(dāng)打開時,人們使用變量時確實(shí)不知道變量是哪里來的,只能想當(dāng)然。但是 register_globals 的關(guān)閉改變了這種代碼內(nèi)部變量和客戶端發(fā)送的變量混雜在一起的糟糕情況。下面舉一個錯誤使用 register_globals 的例子:
Example #1 錯誤使用 register_globals = on 的例子
<?php
// 當(dāng)用戶合法的時候,賦值 $authorized = true
if (authenticated_user()) {
$authorized = true;
}
// 由于并沒有事先把 $authorized 初始化為 false,
// 當(dāng) register_globals 打開時,可能通過GET auth.php?authorized=1 來定義該變量值
// 所以任何人都可以繞過身份驗(yàn)證
if ($authorized) {
include "/highly/sensitive/data.php";
}
?>
當(dāng) register_globals = on 的時候,上面的代碼就會有危險了。如果是 off,$authorized 就不能通過如 URL 請求等方式來改變,這樣就好多了,盡管初始化變量是一個良好的編程習(xí)慣。比如說,如果在上面的代碼執(zhí)行之前加入 $authorized = false 的話,無論 register_globals 是 on 還是 off 都可以,因?yàn)橛脩魻顟B(tài)被初始化為未經(jīng)認(rèn)證。
另一個例子是關(guān)于會話的。當(dāng) register_globals = on 的時候,$username 也可以用在下面的代碼中,但要意識到 $username 也可能會從其它途徑進(jìn)來,比如說通過 URL 的 GET。
Example #2 使用會話時同時兼容 register_globals on 和 off 的例子
<?php
// 我們不知道 $username 的來源,但很清楚 $_SESSION 是
// 來源于會話數(shù)據(jù)
if (isset($_SESSION['username'])) {
echo "Hello <b>{$_SESSION['username']}</b>";
} else {
echo "Hello <b>Guest</b><br />";
echo "Would you like to login?";
}
?>
采取相應(yīng)的預(yù)防措施以便在偽造變量輸入的時候給予警告是完全有可能的。如果事先確切知道變量是哪里來的,就可以檢查所提交的數(shù)據(jù)是否是從不正當(dāng)?shù)谋韱翁峤欢鴣?。不過這不能保證變量未被偽造,這需要攻擊者去猜測應(yīng)該怎樣去偽造。如果不在乎請求數(shù)據(jù)來源的話,可以使用 $_REQUEST 數(shù)組,它包括了 GET、POST 和 COOKIE 的所有數(shù)據(jù)。詳情可參見本手冊的來自 PHP 之外的變量。
Example #3 探測有害變量
<?php
if (isset($_COOKIE['MAGIC_COOKIE'])) {
// MAGIC_COOKIE 來自 cookie
// 這樣做是確保是來自 cookie 的數(shù)據(jù)
} elseif (isset($_GET['MAGIC_COOKIE']) || isset($_POST['MAGIC_COOKIE'])) {
mail("admin@example.com", "Possible breakin attempt", $_SERVER['REMOTE_ADDR']);
echo "Security violation, admin has been alerted.";
exit;
} else {
// 這一次請求中并沒有設(shè)置 MAGIC_COOKIE 變量
}
?>
當(dāng)然,單純地關(guān)閉 register_globals 并不代表所有的代碼都安全了。對于每一段提交上來的數(shù)據(jù),都要對其進(jìn)行具體的檢查。永遠(yuǎn)要驗(yàn)證用戶數(shù)據(jù)和對變量進(jìn)行初始化!把 error_reporting() 設(shè)為 E_NOTICE 級別可以檢查未初始化的變量。
更多關(guān)于模擬 register_globals 為 on 或 off 的信息,請見此 FAQ。
Note: Superglobal 可用性說明:
自 PHP 4.1.0 起可以使用 Superglobal 數(shù)組,例如 $_GET,$_POST,和$_SERVER,等等。更多信息請閱讀手冊中的 superglobals 章節(jié)。
七、用戶提交的數(shù)據(jù)
很多 PHP 程序所存在的重大弱點(diǎn)并不是 PHP 語言本身的問題,而是編程者的安全意識不高而導(dǎo)致的。因此,必須時時注意每一段代碼可能存在的問題,去發(fā)現(xiàn)非正確數(shù)據(jù)提交時可能造成的影響。
Example #1 危險的變量用法
<?php
// 從用戶目錄中刪除一個文件,或者……能刪除更多的東西?
unlink ($evil_var);
// 記錄用戶的登陸,或者……能否在 /etc/passwd 添加數(shù)據(jù)?
fwrite ($fp, $evil_var);
// 執(zhí)行一些普通的命令,或者……可以執(zhí)行 rm -rf * ?
system ($evil_var);
exec ($evil_var);
?>
必須時常留意你的代碼,以確保每一個從客戶端提交的變量都經(jīng)過適當(dāng)?shù)臋z查,然后問自己以下一些問題:
•此腳本是否只能影響所預(yù)期的文件?
•非正常的數(shù)據(jù)被提交后能否產(chǎn)生作用?
•此腳本能用于計劃外的用途嗎?
•此腳本能否和其它腳本結(jié)合起來做壞事?
•是否所有的事務(wù)都被充分記錄了?
在寫代碼的時候問自己這些問題,否則以后可能要為了增加安全性而重寫代碼了。注意了這些問題的話,也許還不完全能保證系統(tǒng)的安全,但是至少可以提高安全性。
還可以考慮關(guān)閉 register_globals,magic_quotes 或者其它使編程更方便但會使某個變量的合法性,來源和其值被搞亂的設(shè)置。在開發(fā)時,可以使用 error_reporting(E_ALL) 模式幫助檢查變量使用前是否有被檢查或被初始化,這樣就可以防止某些非正常的數(shù)據(jù)的撓亂了。
八、魔術(shù)引號
* 魔術(shù)引號
當(dāng)打開時,所有的 '(單引號),"(雙引號),\(反斜線)和 NULL 字符都會被自動加上一個反斜線進(jìn)行轉(zhuǎn)義。這和 addslashes() 作用完全相同。
一共有三個魔術(shù)引號指令:
■magic_quotes_gpc 影響到 HTTP 請求數(shù)據(jù)(GET,POST 和 COOKIE)。不能在運(yùn)行時改變。在 PHP 中默認(rèn)值為 on。 參見 get_magic_quotes_gpc()。
■magic_quotes_runtime 如果打開的話,大部份從外部來源取得數(shù)據(jù)并返回的函數(shù),包括從數(shù)據(jù)庫和文本文件,所返回的數(shù)據(jù)都會被反斜線轉(zhuǎn)義。該選項可在運(yùn)行的時改變,在 PHP 中的默認(rèn)值為 off。 參見 set_magic_quotes_runtime() 和 get_magic_quotes_runtime()。
■magic_quotes_sybase 如果打開的話,將會使用單引號對單引號進(jìn)行轉(zhuǎn)義而非反斜線。此選項會完全覆蓋 magic_quotes_gpc。如果同時打開兩個選項的話,單引號將會被轉(zhuǎn)義成 ''。而雙引號、反斜線 和 NULL 字符將不會進(jìn)行轉(zhuǎn)義。 如何取得其值參見 ini_get()。
* 魔術(shù)引號的作用
優(yōu)點(diǎn):
■ 對初學(xué)者很有用 魔術(shù)引號在 PHP 中用來實(shí)現(xiàn)避免初學(xué)者的代碼更危險。盡管 SQL 注入在魔術(shù)引號打開的情況下仍然有可能實(shí)現(xiàn),但起碼系統(tǒng)的風(fēng)險減少很多了。
■ 方便使用 當(dāng)向數(shù)據(jù)庫中插入數(shù)據(jù)時,魔術(shù)引號所做的就是自動對所有的 GET、POST、COOKIE 數(shù)據(jù)運(yùn)用 addslashes() 函數(shù)
缺點(diǎn):
■可移植性 編程時認(rèn)為其打開或并閉都會影響到移植性。可以用 get_magic_quotes_gpc() 來檢查是否打開,并據(jù)此編程。
■ 性能 由于并不是每一段被轉(zhuǎn)義的數(shù)據(jù)都要插入數(shù)據(jù)庫的,如果所有進(jìn)入 PHP 的數(shù)據(jù)都被轉(zhuǎn)義的話,那么會對程序的執(zhí)行效率產(chǎn)生一定的影響。在運(yùn)行時調(diào)用轉(zhuǎn)義函數(shù)(如 addslashes())更有效率。 盡管 php.ini-dist 默認(rèn)打開了這個選項,但是 php.ini-recommended 默認(rèn)卻關(guān)閉了它,主要是出于性能的考慮。
■ 不便 由于不是所有數(shù)據(jù)都需要轉(zhuǎn)義,在不需要轉(zhuǎn)義的地方看到轉(zhuǎn)義的數(shù)據(jù)就很煩。比如說通過表單發(fā)送郵件,結(jié)果看到一大堆的 \'。針對這個問題,可以使用 stripslashes() 函數(shù)處理
* 關(guān)閉魔術(shù)引號
magic_quotes_gpc指令只能在系統(tǒng)級關(guān)閉,不能在運(yùn)行時。也就是說不能用 ini_set()。
Example #1 在服務(wù)器端關(guān)閉魔術(shù)引號
下面是一個通過 php.ini 文件把這些選項設(shè)為 Off的范例。更多信息請參見本手冊的怎樣修改配置設(shè)定。
; Magic quotes
;
; Magic quotes for incoming GET/POST/Cookie data.
magic_quotes_gpc = Off
; Magic quotes for runtime-generated data, e.g. data from SQL, from exec(), etc.
magic_quotes_runtime = Off
; Use Sybase-style magic quotes (escape ' with '' instead of \').
magic_quotes_sybase = Off
如果不能修改服務(wù)器端的配置文件,使用 .htaccess 也可以。范例如下:
php_flag magic_quotes_gpc Off
為了能寫出移植性較強(qiáng)的代碼(可以運(yùn)行于任何環(huán)境),例如不能修改服務(wù)器配置的情況,下面的例子可以在運(yùn)行時關(guān)閉 magic_quotes_gpc。但是這樣做比較低效,適當(dāng)?shù)男薷呐渲貌攀歉玫霓k法。
Example #2 在運(yùn)行時關(guān)閉魔術(shù)引號
<?php
if (get_magic_quotes_gpc()) {
function stripslashes_deep($value)
{
$value = is_array($value) ?
array_map('stripslashes_deep', $value) :
stripslashes($value);
return $value;
}
$_POST = array_map('stripslashes_deep', $_POST);
$_GET = array_map('stripslashes_deep', $_GET);
$_COOKIE = array_map('stripslashes_deep', $_COOKIE);
$_REQUEST = array_map('stripslashes_deep', $_REQUEST);
}
?>
九、隱藏PHP
一般而言,通過隱藏的手段提高安全性被認(rèn)為是作用不大的做法。但某些情況下,盡可能的多增加一份安全性都是值得的。
一些簡單的方法可以幫助隱藏 PHP,這樣做可以提高攻擊者發(fā)現(xiàn)系統(tǒng)弱點(diǎn)的難度。在 php.ini 文件里設(shè)置 expose_php = off ,可以減少他們能獲得的有用信息。
另一個策略就是讓 web 服務(wù)器用 PHP 解析不同擴(kuò)展名。無論是通過 .htaccess 文件還是 Apache 的配置文件,都可以設(shè)置能誤導(dǎo)攻擊者的文件擴(kuò)展名:
Example #1 把 PHP 隱藏為另一種語言
# 使PHP看上去像其它的編程語言
AddType application/x-httpd-php .asp .py .pl或者干脆徹底隱藏它:
Example #2 使用未知的擴(kuò)展名作為 PHP 的擴(kuò)展名
# 使 PHP 看上去像未知的文件類型
AddType application/x-httpd-php .bop .foo .133t或者把它隱藏為 HTML 頁面,這樣所有的 HTML 文件都會通過 PHP 引擎,會為服務(wù)器增加一些負(fù)擔(dān):
Example #3 用 HTML 做 PHP 的文件后綴
# 使 PHP 代碼看上去像 HTML 頁面
AddType application/x-httpd-php .htm .html要讓此方法生效,必須把 PHP 文件的擴(kuò)展名改為以上的擴(kuò)展名。這樣就通過隱藏來提高了安全性,雖然防御能力很低而且有些缺點(diǎn)。
十、保持更新
PHP 和其它的大型系統(tǒng)一樣,在持續(xù)的研究和改進(jìn)中。每一個版本都會有或多或少的改進(jìn)來增強(qiáng)安全性和修復(fù)任何缺陷,配置問題以及任何會影響到整個系統(tǒng)安全與性能的問題。
和其它系統(tǒng)級的腳本語言一樣,最好的途徑是經(jīng)常更新,并時刻留意最新版本及其改變
相關(guān)文章
PHP基礎(chǔ)教程(php入門基礎(chǔ)教程)一些code代碼
PHP基礎(chǔ)教程(適合有編程基礎(chǔ)的,但是對PHP很陌生的新手 --教程主要以code的形式表現(xiàn))2013-01-01