章 29. 使用 Register Globals

可能 PHP 中最具爭議的變化就是從 PHP 4.2.0 版開始配置檔中 register_globals 的預設值從 on 改為 off 了。對此選項的依賴是如此普遍以至於很多人根本不知道它的存在而以為 PHP 本來就是這麼工作的。本節會解釋用這個指令如何寫出不安全的代碼,但要知道這個指令本身沒有不安全的地方,誤用才會。

當 register_globals 開啟以後,各種變量都被注入代碼,例如來自 HTML 表單的請求變量。再加上 PHP 在使用變量之前是無需進行起始化的,這就使得更容易寫出不安全的代碼。這是個很艱難的抉擇,但 PHP 社區還是決定預設關閉此選項。當開啟時,人們使用變量時確實不知道變量是哪裡來的,只能想當然。但是 register_globals 的關閉改變了這種代碼內定變量和用戶端傳送的變量混雜在一起的糟糕情況。下面舉一個錯誤使用 register_globals 的例子:

例子 29-1. 錯誤使用 register_globals = on 的例子

<?php
// 當會員合法的時候,賦值 $authorized = true
if (authenticated_user()) {
    
$authorized true;
}

// 由於並沒有事先把 $authorized 起始化為 false,
// 當 register_globals 開啟時,可能通過GET auth.php?authorized=1 來定義該變量值
// 所以任何人都可以繞過身份驗證
if ($authorized) {
    include 
"/highly/sensitive/data.php";
}
?>

當 register_globals = on 的時候,上面的代碼就會有危險了。若果是 off,$authorized 就不能通過如 URL 請求等模式來改變,這樣就好多了,儘管起始化變量是一個良好的寫程式習慣。比如說,若果在上面的代碼執行之前加入 $authorized = false 的話,無論 register_globals 是 on 還是 off 都可以,因為會員狀態被起始化為未經認證。

另一個例子是關於會話的。當 register_globals = on 的時候,$username 也可以用在下面的代碼中,但要意識到 $username 也可能會從其它途徑進來,比如說通過 URL 的 GET。

例子 29-2. 使用會話時同時相容 register_globals on 和 off 的例子

<?php
// 我們不知道 $username 的來源,但很清楚 $_SESSION 是
// 來源於會話資料
if (isset($_SESSION['username'])) {

    echo 
"Hello <b>{$_SESSION['username']}</b>";

} else {

    echo 
"Hello <b>Guest</b><br />";
    echo 
"Would you like to login?";

}
?>

採取相應的預防措施以便在偽造變量輸入的時候給予警示是完全有可能的。若果事先確切知道變量是哪裡來的,就可以檢查所送出的資料是否是永遠不正當的表單送出而來。不過這不能保證變量未被偽造,這需要攻擊者去猜測應該怎樣去偽造。若果不在乎請求資料來源的話,可以使用 $_REQUEST 陣列,它內含了 GET、POST 和 COOKIE 的所有資料。明細可參見本手冊的來自 PHP 之外的變量

例子 29-3. 探測有害變量

<?php
if (isset($_COOKIE['MAGIC_COOKIE'])) {

    
// MAGIC_COOKIE 來自 cookie
    // 這樣做是確保是來自 cookie 的資料

} 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 {

   
// 這一次請求中並沒有設定 MAGIC_COOKIE 變量

}
?>

當然,單純地關閉 register_globals 並不代表所有的代碼都安全了。對於每一段送出上來的資料,都要對其進行具體的檢查。永遠要驗證會員資料和對變量進行起始化!把 error_reporting() 設為 E_NOTICE 層級可以檢查未起始化的變量。

更多關於模擬 register_globals 為 on 或 off 的訊息,請見此 FAQ

Superglobals 可用性說明: 自 PHP 4.1.0 起,可以使用超全局陣列變量例如 $_GET$_POST$_SERVER 等等。更多訊息請閱讀手冊中的 superglobals