在開發過程中,除了確保業務邏輯沒有安全隱患外,應該充分了解和利用框架內建的安全機制或者工具來確保應用以及服務器的安全性,下面我們總結下ThinkPHP中涉及到的安全機制。
系統安全
系統安全指ThinkPHP可以配合的服務器的安全部署策略。
應用部署建議
首先,我們建議在條件允許的情況下,把框架目錄和項目目錄都部署在非WEB訪問目錄下面,ThinkPHP的訪問機制完全支持框架和項目的非WEB目錄訪問,你只需要把入口文件和資源(主要是指JS、樣式和圖片文件)目錄放置于WEB目錄下面即可。因此,建議的部署目錄如下:
index.php 項目入口文件
Public/ 項目資源文件目錄
Js/ JS目錄
Css/ 樣式文件目錄
Images/ 圖像文件目錄
protected/ (受保護的目錄,可以部署到非WEB目錄或者設置安全訪問)
ThinkPHP/ 框架系統目錄
App/ 項目目錄
Uploads/ 項目上傳目錄
系統安全設置
如果你已經通過部署策略設置了系統目錄安全訪問的話,則可以跳過這段。如果你無法完全做到上述的服務器目錄安全保護,也無需擔心,ThinkPHP仍然可以通過設置確保你的目錄安全。框架的核心文件本身已經做了訪問判斷,所有核心文件均不能直接在URL中被訪問,因此也不用擔心直接訪問某些文件導致的錯誤暴露你的服務器路徑之類的信息。
對于應用目錄,系統則提供了目錄安全訪問機制,你可以在第一次生成項目目錄結構之前,在入口文件中添加:
define('BUILD_DIR_SECURE', true);
運行項目后會自動給項目的相關目錄生成目錄安全文件(在相關的目錄下面生成空白的htm文件),并且可以自定義安全文件的文件名 DIR_SECURE_FILENAME ,默認是index.html,如果你想給你們的安全文件定義為default.html可以使用
define('DIR_SECURE_FILENAME', 'default.html');
還可以支持多個安全文件寫入,例如你想同時寫入index.html和index.htm 兩個文件,以滿足不同的服務器部署環境,可以這樣定義:
define('DIR_SECURE_FILENAME', 'index.html,index.htm');
默認的安全文件只是寫入一個空白字符串,如果需要寫入其他內容,可以通過DIR_SECURE_CONTENT參數來指定,例如:
define('DIR_SECURE_CONTENT', 'deney Access!');
下面是一個完整的使用目錄安全寫入的例子
define('BUILD_DIR_SECURE',true);
define('DIR_SECURE_FILENAME', 'index.html');
define('DIR_SECURE_CONTENT', 'deney Access!');
除了目錄安全之外,還需要保護模板文件不被直接訪問,因為有可能會在模板文件中暴露數據表的字段信息。解決辦法是配置.htaccess文件(針對Apache服務器,其他服務器參考修改),把以下代碼保存在項目的模板目錄目錄(默認是Tpl)下保存存為.htaccess。
<Files *.html>
Order Allow,Deny
Deny from all
</Files>
如果你的模板文件后綴不是html可以將*.html改成你的模板文件的后綴。
表單安全
自動驗證和自動完成
ThinkPHP內置的自動驗證和自動完成功能可以有效地對表單提交的數據安全加以控制。這兩部分在快速入門系列中已經有過詳細的介紹,就不再描述了。
表單令牌
ThinkPHP內置了表單令牌驗證功能,可以有效防止表單的重復提交等安全防護。
表單令牌驗證相關的配置參數有:
'TOKEN_ON'=>true, // 是否開啟令牌驗證 默認關閉
'TOKEN_NAME'=>'__hash__', // 令牌驗證的表單隱藏字段名稱
'TOKEN_TYPE'=>'md5', //令牌哈希驗證規則 默認為MD5
'TOKEN_RESET'=>true, //令牌驗證出錯后是否重置令牌 默認為true
如果開啟表單令牌驗證功能,系統會自動在帶有表單的模板文件里面自動生成以TOKEN_NAME為名稱的隱藏域,其值則是TOKEN_TYPE方式生成的哈希字符串,用于實現表單的自動令牌驗證。
自動生成的隱藏域位于表單Form結束標志之前,如果希望自己控制隱藏域的位置,可以手動在表單頁面添加{__TOKEN__} 標識,系統會在輸出模板的時候自動替換。
如果頁面中存在多個表單,建議添加{__TOKEN__}標識,并確保只有一個表單需要令牌驗證。
如果個別頁面輸出不希望進行表單令牌驗證,可以在控制器中的輸出方法之前動態關閉表單令牌驗證,例如:
C('TOKEN_ON',false);
$this->display();
模型類在創建數據對象的同時會自動進行表單令牌驗證操作,如果你沒有使用create方法創建數據對象的話,則需要手動調用模型的autoCheckToken方法進行表單令牌驗證。如果返回false,則表示表單令牌驗證錯誤。例如:
$User = M("User"); // 實例化User對象
// 手動進行令牌驗證
if (!$User->autoCheckToken($_POST)){
// 令牌驗證錯誤
}
表單合法性檢測
表單合法性檢測是3.1版本開始增加的表單提交字段檢測機制,你不再需要擔心用戶在提交表單的時候注入非法字段數據了。表單字段合法性檢測需要使用create方法創建數據對象的時候才能生效,有兩種方式:
一、屬性定義
可以給模型配置insertFields 和 updateFields屬性用于新增和編輯表單設置,使用create方法創建數據對象的時候,不在定義范圍內的屬性將直接丟棄,避免表單提交非法數據。
insertFields 和 updateFields屬性的設置采用字符串(逗號分割多個字段)或者數組的方式,例如:
class UserModel extends Model{
protected $insertFields = array('account','password','nickname','email');
protected $updateFields = array('nickname','email');
}
設置的字段應該是實際的數據表字段,而不受字段映射的影響。
在使用的時候,我們調用create方法的時候,會根據提交類型自動識別insertFields和updateFields屬性:
D('User')->create();
使用create方法創建數據對象的時候,新增用戶數據的時候,就會屏蔽'account','password','nickname','email' 之外的字段,編輯的時候就會屏蔽'nickname','email'之外的字段。
下面是采用字符串定義的方式,同樣有效:
class UserModel extends Model{
protected $insertFields = 'account,password,nickname,email';
protected $updateFields = 'nickname,email';
}
二、方法調用
如果不想定義insertFields和updateFields屬性,或者希望可以動態調用,可以在調用create方法之前直接調用field方法,例如,實現和上面的例子同樣的作用:
在新增用戶數據的時候,使用:
$User = M('User');
$User->field('account,password,nickname,email')->create();
$User->add();
而在更新用戶數據的時候,使用:
$User = M('User');
$User->field('nickname,email')->create();
$User->where($map)->save();
這里的字段也是實際的數據表字段。field方法也可以使用數組方式。
如果你的字段比較多,還可以使用field方法的排除功能,例如:
$User->field('status,create_time',true)->create();
變量安全
變量安全獲取
對全局系統變量的獲取建議采用系統提供的變量獲取方法獲取,具體可以參考快速入門系列的變量
全局變量過濾
如果你習慣于直接調用$_GET $_POST變量的話,那么或者可以采用ThinkPHP系統內置的對全局變量的安全過濾方式,只需要設置VAR_FILTERS參數。
例如:
'VAR_FILTERS'=>'htmlspecialchars'
在3.1.2版本開始,安全過濾方法的定義函數需要調整為引用返回的方式,而且能夠實現多維數組的遞歸過濾,例如:
'VAR_FILTERS'=>'filter_vars'
對應的過濾方法定義如下:
function filter_vars(&$value){
$value = htmlspecialchars($value);
}
數據安全
防SQL注入
對于WEB應用來說,SQL注入攻擊無疑是首要防范的安全問題,系統底層對于數據安全方面本身進行了很多的處理和相應的防范機制,例如:
$User = M("User"); // 實例化User對象
$User->find($_GET["id"]);
即便用戶輸入了一些惡意的id參數,系統也會強制轉換成整型,避免惡意注入。這是因為,系統會對數據進行強制的數據類型檢測,并且對數據來源進行數據格式轉換。而且,對于字符串類型的數據,ThinkPHP都會進行escape_string處理。
通常的安全隱患在于你的查詢條件使用了字符串參數,然后其中一些變量又依賴由客戶端的用戶輸入,要有效的防止SQL注入問題,我們建議:
查詢條件盡量使用數組方式,這是更為安全的方式;
如果不得已必須使用字符串查詢條件,使用預處理機制;
查詢條件預處理
where方法使用字符串條件的時候,支持預處理(安全過濾),并支持兩種方式傳入預處理參數,例如:
$Model->where("id=%d and username='%s' and xx='%f'",array($id,$username,$xx))->select();
或者
$Model->where("id=%d and username='%s' and xx='%f'",$id,$username,$xx)->select();
模型的query和execute方法 同樣支持預處理機制,例如:
$model->query('select * from user where id=%d and status=%d',$id,$status);
或者
$model->query('select * from user where id=%d and status=%d',array($id,$status));
execute方法用法同query方法。
防XSS攻擊
XSS(跨站腳本攻擊)可以用于竊取其他用戶的Cookie信息,要避免此類問題,可以采用如下解決方案:
直接過濾所有的JavaScript腳本;
轉義Html元字符,使用htmlentities、htmlspecialchars等函數;
系統的擴展函數庫提供了XSS安全過濾的remove_xss方法;
新版對URL訪問的一些系統變量已經做了XSS處理。
上傳安全
網站的上傳功能也是一個非常容易被攻擊的入口,所以對上傳功能的安全檢查是尤其必要的。
系統提供的上傳擴展類庫(ORG.Net.UploadFile)提供了安全方面的支持,包括對文件后綴、文件類型、文件大小以及上傳圖片文件的合法性檢查,確保你已經在上傳操作中啟用了這些合法性檢查。
其他安全建議
下面的一些安全建議也是非常重要的:
對所有公共的操作方法做必要的安全檢查,防止用戶通過URL直接調用;
不要緩存需要用戶認證的頁面;
對于關鍵操作需要檢查用戶權限;
對用戶的上傳文件,做必要的安全檢查,例如上傳路徑和非法格式。
如非必要,不要開啟服務器的目錄瀏覽權限;
對于項目進行充分的測試,不要生成業務邏輯的安全隱患(這可能是最大的安全問題);