[ 翻譯信息 ]
翻譯人員:heiyeluren
翻譯時間:2006-3-15
翻譯章節:《Lesson 24. PHP Security》
中文名稱:PHP安全
PHP勿庸置疑是非常強大的服務器端腳本語言,但是強大的功能總是伴隨著重大的危險,在這章里,你將學習到使用PHP的安全模式來阻止一些PHP潛在的危險因素。
【 安全模式 】
PHP的安全模式提供一個基本安全的共享環境,在一個有多個用戶帳戶存在的PHP開放的Web服務器上。當一個Web服務器上運行的PHP打開了安全模式,那么一些函數將被完全的禁止,并且會限制一些可用的功能。
[ 使用安全模式來強制限制 ]
在安全模式下,一些嘗試訪問文件系統的函數功能將被限制。運行Web服務器用戶ID,如果想要操作某個文件,則必須擁有該文件讀取或者寫入的訪問權限,實現這個限制功能對于PHP來說是沒有問題的。
在安全模式開啟的時候,嘗試讀取或者寫入一個本地文件的時候,PHP將檢查當前訪問用戶是否是該目標文件的所有者。如果不是所有者,則該操作會被禁止。(寫入權限:在較低級別的文件訪問權限下,可能會允許讀取或者寫入系統操作系統的文件,通過PHP的安全模式實現了防止你操作另外一個用戶文件的操作。當然,一個Web服務器可能能夠訪問一個具有全局寫入權限的任意文件。)
當安全模式打開的時候,以下函數列表的功能將會受到限制:
chdir , move_uploaded_file, chgrp, parse_ini_file, chown, rmdir, copy, rename, fopen, require, highlight_file, show_source, include, symlink, link, touch, mkdir, unlink
同樣的,一些PHP擴展中的函數也將會受到影響。(加載模塊:在安全模式下dl函數將被禁止,如果要加載擴展的話,只能修改php.ini中的擴展選項,在PHP啟動的時候加載)
在PHP安全模式打開的時候,需要執行操作系統程序的時候,必須是在safe_mode_exec_dir選項指定目錄的程序,否則執行將失敗。即使允許執行,那么也會自動的傳遞給escapeshellcmd函數進行過濾。
以下執行命令的函數列表將會受到影響:
exec, shell_exec, passthru, system, popen
另外,背部標記操作符(`)也將被關閉。
當運行在安全模式下,雖然不會引起錯誤,但是 putenv 函數將無效。同樣的,其他一些嘗試改變PHP環境變量的函數set_time_limit, set_include_path 也將被忽略。
[ 打開安全模式 ]
打開或者關閉PHP的安全模式是利用php.ini中的safe_mode選項。如果要激活安全模式給當前所有共享Web服務器的用戶,只要設置配置選項為:
safe_mode = On
當函數在訪問文件系統的時候將進行文件所有者的檢查。缺省情況下,會檢查該文件所有者的用戶ID,當你能夠修改文件所有者的組ID(GID)為 safe_mode_gid 選項所指定的。
如果你有一個共享庫文件在你的系統上,當你碰到需要include或require的時候,那么你可以使用 safe_mode_include_dir 選項來設置你的路徑,保證你的代碼正常工作。(包含路徑: 如果你想要使用 safe_mode_include_dir 選項包含更多的包含路徑,那么你可以象 include_path 選項一樣,在Unix/Linux系統下使用冒號進行分割,在Windows下使用分號進行分割)
比如你想要在安全模式下包含 /usr/local/include/php 下的文件,那么你可以設置選項為:
safe_mode_include_dir = /usr/local/include/php
如果你的包含的文件是需要執行的,那么你可以設置 safe_mode_exec_dir 選項。比如你需要 /usr/local/php-bin 路徑下的文件是可以執行的,那么可以設置選項為:
safe_mode_exec_dir = /usr/local/php-bin
(可執行:如果你執行的程序在 /usr/bin 目錄下,那么你可以把這些的二進制文件,連接到你指定選項下能夠執行的路徑)
如果你想設置某些環境變量,那么可以使用 safe_mode_allowed_env_vars 選項。這個選項的值是一個環境變量的前綴,缺省是允許 PHP_ 開頭的環境變量,如果你想要改變,可以設置該選項的值,多個環境變量前綴之間使用逗號進行分割。
比如下面允許時區的環境變量 TZ ,那么修改該選項的值為:
safe_mode_allowed_env_vars = PHP_,TZ
【 其他的安全特征 】
除了安全模式以外,PHP還提供了許多其他許多特征來保證PHP的安全。
[ 隱藏PHP ]
你能夠在php.ini里使用 expose_php 選項來防止Web服務器泄露PHP的報告信息。如下:
expose_php = On
利用整個設置,你能夠阻礙一些來自自動腳本針對Web服務器的攻擊。通常情況下,HTTP的頭信息里面包含了如下信息:
Server: Apache/1.3.33 (Unix) PHP/5.0.3 mod_ssl/2.8.16
OpenSSL/0.9.7c
在 expose_php 選項打開以后,PHP的版本信息將不包含在上面的頭信息里。
當然,用戶訪問網站的時候同樣能夠看到 .php 的文件擴展名。如果你想整個的使用不同的文件擴展名,你需要在 httpd.conf 中找到如下這行:
AddType application/x-httpd .php
你就可以修改 .php 為任何你喜歡的文件擴展名。你能夠指定任意多個的文件擴展名,中間使用空格進行分割。如果你想在服務器端使用PHP來解析 .html 和 .htm 文件的時候,那么你設置選項如下:
AddType application/x-httpd .html .htm
(解析HTML:配置你的Web服務器使用PHP去解析所有的HTML文件,但是如果非服務器端代碼也需要PHP去解析,會影響服務器的性能。靜態頁面你可以使用不同的擴展名,這樣能夠消除對PHP腳本引擎的依賴,增強性能。)
[ 文件系統安全 ]
安全模式限制了腳本所有者只能訪問屬于自己的文件,但是你可以使用 open_basedir 選現來指定一個你必須訪問的目錄。如果你指定了一個目錄,PHP將拒絕訪問除了該目錄和該目錄子目錄的其他目錄。open_basedir 選項能夠工作在安全模式之外。
限制文件系統只能訪問 /tmp 目錄,那么設置選項為:
open_basedir = /tmp
[ 函數訪問控制 ]
你能夠在 disable_functions 選項中使用逗號分割來設定函數名,那么這些函數將在PHP腳本中被關閉。這個設置能夠工作在安全模式之外。
disable_functions = dl
當然,同樣的你能夠使用 disable_classes 選項來關閉對一些類的訪問。
[ 數據庫安全 ]
假設你的PHP腳本中包含一個基于表單值來執行的Mysql查詢:
$sql = "UPDATE mytable SET col1 = " . $_POST["value"] . "
WHERE col2 = 'somevalue'";
$res = mysql_query($sql, $db);
你希望 $_POST["value"] 包含一個整數值來更新你的列 col1??墒?,一個惡意用戶能夠輸入一個分號在表單字段里,接著,是一段他/她想被任意執行的SQL語句。
舉例,假設下面是 $_POST["value"] 提交的值:
0; INSERT INTO admin_users (username, password)
VALUES ('me', 'mypassword');
那么當這個查詢發送給Mysql查詢的時候,那么就變成了下面這條SQL:
UPDATE mytable SET col1 = 0;
INSERT INTO admin_users (username, password)
VALUES ('me', 'mypassword');
WHERE col2 = 'somevalue';
這明顯是一個有害的查詢!首先這個查詢會在 mytable 表里更新 col1。這個并沒有什么麻煩的,但是第二個表達式,它將執行 INSERT 表達式來插入一個能登陸的新管理員。第三個表達式就廢棄了,但同時SQL解析器將拋出一個錯誤,這個有害的查詢才完成。這個攻擊就是大家常說的 SQL injection(注:SQL注入)。
當然,SQL injection 存在一個問題,對方必須了解你的數據庫結構。在這個例子中,攻擊者是知道你有一個表 admin_users,并且知道包含 username 和 password字段,同時,存儲的密碼是沒有加密的。
除了你自己,一般的網站訪問者是不知道這些關于數據庫的信息??墒?,如果你使用了一個開發源代碼的在線電子商務程序,或者使用一個自由的討論版程序,這些數據表的定義都是已知的,或者有一些用戶能夠訪問到你的數據庫。
此外,你的腳本輸出會提示一個查詢錯誤,這些信息里包含了很多關于數據庫結構的重要信息。在一個正常工作的網站上,你應該考慮設置 display_errors 選項為 off,并且使用 log_errors 來代替 display_errors ,把警告和錯誤信息插入到文件中。
(數據庫權限:它是一個非常重要的東西,你只有正確的權限,才能通過腳本正確的連接數據庫。你應該不要在腳本中使用管理員去連接數據庫。如果你這么做,那么一個攻擊者將可能獲取全部的數據庫權限,并且包括其他相同服務器的權限。攻擊者將可能運行 GRANT 或 CREATE USER 命令來獲取更多的訪問權限。 )
如果你要防止 SQL injection 攻擊,你必須保證用戶表單里提交的內容不是一個能夠執行的SQL表達式。
前一個例子中,我們使用一個整型值來進行更新。如果在單引號后面跟上一個字符串,這個攻擊者在分號之前必須提交一個閉合的引用在整個SQL表達式中??墒?,當 magic_quotes_gpc 選項是開啟的時候,在Web表單中提交的引號將自動被轉義。
為了防止被惡意的攻擊者進行 SQL injection攻擊,你應該總是確認提交的數據是合法的。如果你需要的是一個整數值,那么你可以使用 is_numeric 函數來測試這個表達值,或者使用 settype 函數來轉換為一個數字,清除任何一個傻傻的SQL語句。
如果你開發的程序需要幾個提交的值在一個SQL表達式里,你能夠使用 sprintf 函數來構建一個SQL字符串,使用格式化字符來指示數據類型的每個值??聪旅娴睦樱?/p>
$sql = sprintf("UPDATE mytable SET col1 = %d
WHERE col2 = '%s'",
$_POST["number"],
mysql_escape_string($_POST["string"]));
在上一個例子中,整個Mysql的數據已經被使用,所以這個字符串已經通過 mysql_escape_string 函數進行過濾。對于其他數據庫,你可以使用 addslashes 函數進行轉義,或者使用其他方法。
原文轉自:http://www.anti-gravitydesign.com