明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺(tái)!

理解 PHP 及其安全問題的基礎(chǔ)知識(shí)

[摘要]有時(shí)候,您的業(yè)務(wù)可能涉及到 PHP 應(yīng)用程序的安全性。當(dāng)您遇到審計(jì)任務(wù)時(shí),您知道如何執(zhí)行查找嗎?本系列將帶您進(jìn)入 PHP,并幫您在一定程序上了解它,讓您在進(jìn)行安全審計(jì)時(shí)知道查找什么。第 1 部分向您介紹 register_globals 設(shè)置。入門知識(shí)我在此假定您對(duì) PHP 的語(yǔ)法有一個(gè)大致的了解...
 
有時(shí)候,您的業(yè)務(wù)可能涉及到 PHP 應(yīng)用程序的安全性。當(dāng)您遇到審計(jì)任務(wù)時(shí),您知道如何執(zhí)行查找嗎?本系列將帶您進(jìn)入 PHP,并幫您在一定程序上了解它,讓您在進(jìn)行安全審計(jì)時(shí)知道查找什么。第 1 部分向您介紹 register_globals 設(shè)置。

入門知識(shí)
我在此假定您對(duì) PHP 的語(yǔ)法有一個(gè)大致的了解,至少能夠編寫“Hello World”之類的程序。如果您不具備基礎(chǔ)知識(shí),則請(qǐng)首先學(xué)習(xí) PHP 手冊(cè)和某些基本的 PHP 教程(參閱 參考資料)。很多出版商都有關(guān)于 PHP 的好書。建議初學(xué)者一開始先看看入門書籍或食譜形式的書籍。

在生產(chǎn)環(huán)境的準(zhǔn)確副本上執(zhí)行審計(jì)。您不需要復(fù)制硬件,但是需要確保軟件版本盡量和實(shí)際的完全一樣。PHP 配置必須精確匹配,這一點(diǎn)在 php.ini 文件中、在 .htaccess 文件的 Apache 指令中或在 httpd.conf 中已經(jīng)指定。您需要準(zhǔn)備一個(gè)單獨(dú)的環(huán)境,因?yàn)槟鷮@示和記錄可能包含敏感的密碼及其他信息的錯(cuò)誤。此外,您將嘗試中斷站點(diǎn)的安全性,這一點(diǎn)是您在活動(dòng)應(yīng)用程序中極力避免的。

第一步是將 PHP 的 error_reporting 設(shè)置更改為 E_ALL。設(shè)置更改后,每當(dāng)使用未初始化的變量、進(jìn)行錯(cuò)誤的文件訪問及發(fā)生其他(大多數(shù))無(wú)害錯(cuò)誤時(shí),PHP 都會(huì)報(bào)告一條警告消息,但也存在這是一個(gè)潛在攻擊矢量的可能性。這些錯(cuò)誤一般情況下只是表明編程草率,所以如果這是您的代碼,您把它們清除掉即可。

該設(shè)置如下所示:

error_reporting = E_ALL

如果您不知道 php.ini 文件在哪里,則可以通過創(chuàng)建包含以下文本的 .php 腳本來(lái)查找:

<?phpphpinfo();

輸出的上面部分有一行列出了 PHP 查找 php.ini 的位置:

圖 1. PHP 查找 php.ini 的位置
phpinfo() 輸出

值可能會(huì)有些變化,但 /usr/local/lib/php.ini 是大多數(shù) UNIX® 系統(tǒng)上的公共位置,C:\php\php.iniC:\WINDOWS\php.ini 是大多數(shù) Microsoft® Windows® 系統(tǒng)上的公共位置。如果該文件不存在,則創(chuàng)建一個(gè)并在文件中鍵入上面的 error_reporting 行即可。修改 php.ini 文件后,需要重啟 Web 服務(wù)器,PHP 才能啟用新設(shè)置。

如果您以前沒有創(chuàng)建 phpinfo() 頁(yè)面,則可以現(xiàn)在創(chuàng)建。第二個(gè)主要部分的標(biāo)簽是“配置”,它包含許多關(guān)于如何設(shè)置 PHP 的有用信息。該部分包括三列:設(shè)置名稱、本地值xmaster 值。主值是通過 php.ini 指令為您機(jī)器上的所有 PHP 腳本全局設(shè)置的值。本地值是對(duì)當(dāng)前腳本生效的值。對(duì)它有影響的有:.htaccess 設(shè)置、httpd.conf 的 <Location> 或 <Directory> 部分中的設(shè)置和 PHP 腳本中的 ini_set 調(diào)用。在運(yùn)行時(shí),只有某些設(shè)置是可更改的。請(qǐng)參閱 參考資料中的 PHP 手冊(cè)以獲取詳細(xì)信息。

還需要自定義的另外兩種設(shè)置是 display_errorslog_errors。您至少需要啟用這兩種設(shè)置中的一種,或者兩種都啟用。log_errors 通知 PHP 將注意、警告或錯(cuò)誤記錄在文件中,display_errors 將這些被記錄下來(lái)的注意、警告和錯(cuò)誤顯示在屏幕上。它們不互相排斥。至少啟用它們中的一個(gè),可以有效地發(fā)現(xiàn)可能導(dǎo)致安全漏洞的編程錯(cuò)誤。

應(yīng)該查找哪些種類的安全問題?
值得慶幸的是,導(dǎo)致安全漏洞的很多編程錯(cuò)誤在 PHP 中不可能存在。堆棧和緩沖溢出是 C 和 C++ 環(huán)境中兩個(gè)常見的問題。因?yàn)?PHP 可以為您管理記憶,所以 PHP 代碼不會(huì)導(dǎo)致堆棧和緩沖溢出。

然而,PHP 本身也是使用 C 語(yǔ)言編寫的,有時(shí)記憶問題深至 PHP 的核面。因此,您需要時(shí)時(shí)關(guān)注安全公報(bào)和更新。PHP 在其 Web 站點(diǎn)(參見 參考資料)公布新 PHP 版本并說(shuō)明是否包含安全修補(bǔ)程序。

PHP 應(yīng)用程序中的大多數(shù)問題與使用用戶提供的數(shù)據(jù)有關(guān),在使用它和對(duì)它執(zhí)行操作前未曾預(yù)先驗(yàn)證和消毒。您可能聽說(shuō)過稱為 cross-site scripting (XSS) 的漏洞。XSS 通過提供程序不期望的輸入,然后利用程序?qū)o(wú)賴輸入的處理方式發(fā)動(dòng)進(jìn)攻。編寫良好的程序可以避免這些假定。在機(jī)場(chǎng)安全方面,PHP 程序用于檢查旅客的行李。

其他問題是一些細(xì)微的邏輯錯(cuò)誤。例如,檢查一系列參數(shù),看看是否批準(zhǔn)某個(gè)用戶訪問某種資源、是否把括弧放錯(cuò)位置以至于某些用戶進(jìn)入了他們?cè)静辉摰降牡胤。我們希望您的?yīng)用程序組織良好并具有這種集中式邏輯。

識(shí)別用戶輸入
最棘手的一件事情是如何從外部源(如某個(gè)用戶、別的 Web 站點(diǎn)或某些其他資源)和已經(jīng)驗(yàn)證的數(shù)據(jù)中區(qū)分出不受信任的輸入。有人提出了“不相信一切”的觀點(diǎn),即不管來(lái)自何處,對(duì)于所有函數(shù)都要驗(yàn)證其數(shù)據(jù)。這一做法會(huì)牽涉到以下幾件事情:第一,驗(yàn)證在不同的上下文中意味著不同的事情;第二,在應(yīng)用程序的所有級(jí)別上快速執(zhí)行驗(yàn)證是一件枯燥乏味和易于出錯(cuò)的事情;第三,您是在審計(jì)應(yīng)用程序而不是在從頭重新編寫它。您需要通過現(xiàn)有代碼來(lái)跟蹤用戶輸入,而不能用驗(yàn)證函數(shù)包裝您看到的每個(gè)變量。

不期望的用戶輸入
用戶輸入從何而來(lái)?第一個(gè)源是 GET、POSTCOOKIE 數(shù)據(jù)。一般稱為 GPC 數(shù)據(jù)。此數(shù)據(jù)的可識(shí)別程序依賴于一個(gè)有爭(zhēng)議的 php.ini 設(shè)置:register_globals。在 PHP V4.3.0 以后,register_globals 默認(rèn)情況下被設(shè)置為 Off。但是幾年前,在 PHP 中,register_globals 的默認(rèn)值是打開的,所以存在很多需要它的代碼。

register_globals 本身并非安全風(fēng)險(xiǎn)。但是,它為跟蹤用戶輸入和確保應(yīng)用程序安全增加了難度。為什么會(huì)這樣?因?yàn)槿绻蜷_ register_globals,在全局名稱空間和 $_GET、$_POST$_COOKIE 數(shù)組中,將創(chuàng)建 GET、POSTCOOKIE 傳遞到 PHP 腳本的所有變量。

下面是工作方式及其重要性的示例:

清單 1. COOKIE 的安全性
 1 <?php23 // See if the user has the secret cookie.4 if (!empty($_COOKIE['secret'])) {5    $authorized = true;6 }78 // Now let's go through a list of press releases and show them.9 $releases = get_press_releases();10 foreach ($releases as $release) {1112     // Some releases are restricted. Only show them to people who can13     // see secrets.14     if ($release['secret']) {15         if (!$authorized) {16             continue;17         }18     }1920     // We must be allowed to see it.21     showRelease($release);22 }

您應(yīng)該注意幾件事。第一,依靠 cookie 來(lái)判斷用戶是否已通過身份驗(yàn)證不是個(gè)好主意 —— 因?yàn)槿藗兛梢院苋菀椎卦O(shè)置自己的 cookie 值。我們將在另外一篇文章中敘述這一點(diǎn)。無(wú)論如何,此腳本的缺點(diǎn)在于,如果打開 register_globals,它就不具備安全性了。

下面介紹名為 press.php 的腳本。一般來(lái)說(shuō),當(dāng)用戶訪問 press 發(fā)行版的腳本時(shí),其瀏覽器將顯示 http://www.example.com/company/press.php。

現(xiàn)在注意當(dāng)用戶擅自將其更改為 http://www.example.com/company/press.php?authorized=1 時(shí)將發(fā)生什么事?

看看前面的代碼:僅當(dāng)用戶使用 cookie 時(shí)才設(shè)置 $authorized。它永遠(yuǎn)不會(huì)被設(shè)置為假。后來(lái)引入了 register_globals —— 它取代了剛才使用的 $_GET['authorized'],同時(shí)在全局范圍內(nèi)還存在一個(gè)值為 1 的變量 $authorized。因此,即使用戶沒有通過 cookie 檢查,$authorized 后來(lái)在 foreach 循環(huán)中引用時(shí),仍然會(huì)被驗(yàn)證為真。

修復(fù)此缺陷可以使用兩種方式。其一,當(dāng)然是關(guān)閉 register_globals。如果關(guān)閉它對(duì)您的生產(chǎn)站點(diǎn)沒有影響,則這是個(gè)好主意。您需要測(cè)試一下應(yīng)用程序,確保它沒有因此中斷運(yùn)行。

另一種方式有點(diǎn)像“防御性編程”。我們只需要將 cookie 檢查更改為以下形式即可:

清單 2. 使用 COOKIE 提高安全性
1 <?php23 // See if the user has the secret cookie.4 $authorized = false;5 if (!empty($_COOKIE['secret'])) {6    $authorized = true;7 }...

這時(shí),當(dāng)用戶將 ?authorized=1 添加到腳本 URL 時(shí),$authorized 變量仍然被設(shè)置為 1 —— 但是它隨即會(huì)被 $authorized = false 覆蓋,只有那些實(shí)際具有秘密 cookie 的用戶才能看到受限的 press 發(fā)行版。他們?nèi)匀豢梢栽O(shè)計(jì)自己的 cookie。

審計(jì)代碼的教訓(xùn):設(shè)法關(guān)閉 register_globals。如果不打開 register_globals 應(yīng)用程序就不能運(yùn)行,并且您無(wú)法修改它,或者在應(yīng)用程序必須運(yùn)行的地方您無(wú)法控制 PHP 配置,則需要在條件塊中查找所有全局變量設(shè)置,或者通過某些函數(shù)調(diào)用進(jìn)入全局范圍。如果 register_globals 為打開狀態(tài),則這兩種情形都是由用戶將變量設(shè)置為任意值引起的。

找到這些變量的好辦法是將 php.ini 設(shè)置 error_reporting 設(shè)置為 E_ALL,同時(shí)使用 log_errorsdisplay_errors,這樣,所有 PHP 警告和錯(cuò)誤都會(huì)被分別記錄在文件中或顯示在屏幕上。每當(dāng)使用未初始化的變量(假定具有值)時(shí),您將得到一條 E_NOTICE。這像 C 和 Java™ 語(yǔ)言中那樣,仍然與讓 PHP 要求聲明 變量有所不同。結(jié)果,當(dāng)我們的第一個(gè)版本的腳本運(yùn)行時(shí),出現(xiàn)的錯(cuò)誤消息是:

Notice: Undefined variable: authorized in C:\var\www\articles\press.php on line 15

只要用戶沒有權(quán)限,錯(cuò)誤就發(fā)生在第 15 行,而不是起初設(shè)置變量的第 5 行。PHP 在布爾上下文中將不確定的變量解釋為假(參閱 參考資料 中列出的 PHP 手冊(cè)中的“類型強(qiáng)制轉(zhuǎn)換”),這樣代碼無(wú)論如何都會(huì)“正常運(yùn)行”了 —— 除非有人暗中使用別的方式定義 $authorized。

期望的用戶輸入
上面說(shuō)的是不期望的用戶輸入。下一次,我們將談?wù)撃谕脩糨斎,?i xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">不 期望輸入值時(shí)的情況。