標籤:
如果你還能記起早期Web應用開發中使用C開發CGI程式的話,一定會對繁瑣的表單處理深有體會。當PHP的register_globals配置選項開啟時,複雜的原始表單處理不複存在,公用變數會自動建立。它讓PHP編程變得容易和方便,但同時也帶來了安全隱患。
使用者輸入從何而來?第一個源是 GET、POST 和 COOKIE 資料。一般稱為 GPC 資料。此資料的可識別程式依賴於一個有爭議的 php.ini設定:register_globals。在 PHP V4.3.0 以後,register_globals 預設情況下被設定為 Off。但是幾年前,在 PHP 中,register_globals 的預設值是開啟的,所以存在很多需要它的代碼。
事實上,register_globals是無辜的,它並不會產生漏洞,同時還要開發人員犯錯才行。可是,有兩個主要原因導致了您必須在開發和布署應用時關閉register_globals:
- 第一,它會增加安全性漏洞的數量;
- 第二,隱藏了資料的來源,與開發人員需要隨時跟蹤資料的責任相違背。
register_globals?本身並非安全風險。但是,它為跟蹤使用者輸入和確保應用程式安全增加了難度。為什麼會這樣?因為如果開啟register_globals,在全域名稱空間和 $_GET、$_POST 或 $_COOKIE 數組中,將建立 GET、POST 和 COOKIE 傳遞到 PHP 指令碼的所有變數。
下面是工作方式及其重要性的樣本:
1 <?php 2 3 // See if the user has the secret cookie. 4 if (!empty($_COOKIE[‘secret‘])) { 5 $authorized = true; 6 } 7 8 // Now let‘s go through a list of press releases and show them. 9 $releases = get_press_releases();10 foreach ($releases as $release) {11 12 // Some releases are restricted. Only show them to people who can13 // see secrets.14 if ($release[‘secret‘]) {15 if (!$authorized) {16 continue;17 }18 }19 20 // We must be allowed to see it.21 showRelease($release);22 }23 ?>
您應該注意幾件事。第一,依靠 cookie 來判斷使用者是否已通過身分識別驗證不是個好主意 —— 因為人們可以很容易地設定自己的 cookie 值。我們將在另外一篇文章中敘述這一點。無論如何,此指令碼的缺點在於,如果開啟 register_globals,它就不具備安全性了。
下面介紹名為 press.php 的指令碼。一般來說,當使用者訪問 press 發行版的指令碼時,其瀏覽器將顯示 http://www.example.com/company/press.php。
現在注意當使用者擅自將其更改為 http://www.example.com/company/press.php?authorized=1 時將發生什麼事?
看看前面的代碼:僅當使用者使用 cookie 時才設定 $authorized。它永遠不會被設定為假。後來引入了 register_globals —— 它取代了剛才使用的 $_GET[‘authorized‘],同時在全域範圍內還存在一個值為 1 的變數 $authorized。因此,即使使用者沒有通過 cookie 檢查,$authorized 後來在 foreach 迴圈中引用時,仍然會被驗證為真。
修複此缺陷可以使用兩種方式。其一,當然是關閉 register_globals。如果關閉它對您的生產網站沒有影響,則這是個好主意。您需要測試一下應用程式,確保它沒有因此中斷運行。
另一種方式有點像“防禦性編程”。我們只需要將 cookie 檢查更改為以下形式即可:
1 <?php2 3 // See if the user has the secret cookie.4 $authorized = false;5 if (!empty($_COOKIE[‘secret‘])) {6 $authorized = true;7 }8 ?>
這時,當使用者將 ?authorized=1 添加到指令碼 URL 時,$authorized 變數仍然被設定為 1 —— 但是它隨即會被 $authorized = false 覆蓋,只有那些實際具有秘密 cookie 的使用者才能看到受限的 press 發行版。他們仍然可以設計自己的 cookie。
審計代碼的教訓:設法關閉 register_globals。如果不開啟 register_globals 應用程式就不能運行,並且您無法修改它,或者在應用程式必須啟動並執行地方您無法控制 PHP 配置,則需要在條件塊中尋找所有全域變數設定,或者通過某些函數調用進入全域範圍。如果 register_globals 為開啟狀態,則這兩種情形都是由使用者將變數設定為任意值引起的。
找到這些變數的好辦法是將 php.ini 設定 error_reporting 設定為 E_ALL,同時使用 log_errors 或 display_errors,這樣,所有 PHP 警告和錯誤都會被分別記錄在檔案中或顯示在螢幕上。每當使用未初始化的變數(假定具有值)時,您將得到一條 E_NOTICE。這像 C 和 Java? 語言中那樣,仍然與讓 PHP 要求聲明 變數有所不同。結果,當我們的第一個版本的指令碼運行時,出現的錯誤訊息是:
1 Notice: Undefined variable: authorized in C:\var\www\articles\press.php 2 on line 15
只要使用者沒有許可權,錯誤就發生在第 15 行,而不是起初設定變數的第 5 行。PHP 在布爾上下文中將不確定的變數解釋為假(參閱 參考資料 中列出的 PHP 手冊中的“類型強制轉換”),這樣代碼無論如何都會“正常運行”了 —— 除非有人暗中使用別的方式定義 $authorized。
如果您必須要開發一個在register_globals開啟的環境中布署的應用時,很重要的一點是您必須要初始化所有變數並且把error_reporting 設為 E_ALL(或 E_ALL | E_STRICT)以對未初始設定變數進行警告。當register_globals開啟時,任何使用未初始設定變數的行為幾乎就意味著安全性漏洞。
PHP安全編程:register_globals的安全性