攻擊者無時無刻不在準備對你的 Web 應用程序進行攻擊,因此提高你的 Web 應用程序的安全性是非常有必要的晦攒。幸運的是,來自開放式 Web 應用程序安全項目 (OWASP) 的有心人已經整理了一份包含了已知安全問題和防御方式的全面的清單哟旗。這份清單對于具有安全意識的開發(fā)者來說是必讀的闸餐。由 Padraic Brady 著作的 生存手冊:PHP 安全 也是一份很不錯的 PHP 安全閱讀資料矾芙。
密碼哈希
每個人在建構 PHP 應用時終究都會加入用戶登錄的模塊。用戶的帳號及密碼會被儲存在數據庫中拂铡,在登錄時用來驗證用戶感帅。
在存儲密碼前正確的 哈希密碼 是非常重要的地淀。哈希密碼是單向不可逆的,該哈希值是一段固定長度的字符串且無法逆向推算出原始密碼实苞。這就代表你可以哈希另一串密碼烈疚,來比較兩者是否是同一個密碼,但又無需知道原始的密碼胞得。如果你不將密碼哈希,那么當未授權的第三者進入你的數據庫時跃巡,所有用戶的帳號資料將會一覽無遺牧愁。有些用戶可能(很不幸的)在別的網站也使用相同的密碼猪半。所以務必要重視數據安全的問題偷线。
密碼應該單獨被 加鹽處理 声邦,加鹽值指的是在哈希之前先加入隨機子串摆舟。以此來防范「字典破解」或者「彩虹碰撞」(一個可以保存了通用哈希后的密碼數據庫,可用來逆向推出密碼)媳瞪。
哈希和加鹽非常重要蛇受,因為很多情況下厕鹃,用戶會在不同的服務中選擇使用同一個密碼,密碼的安全性很低。
值得慶幸的是诗赌,在 PHP 中這些很容易做到铭若。
使用 password_hash
來哈希密碼
password_hash
函數在 PHP 5.5 時被引入。 此函數現在使用的是目前 PHP 所支持的最強大的加密算法 BCrypt 瞳腌。 當然镜雨,此函數未來會支持更多的加密算法荚坞。 password_compat
庫的出現是為了提供對 PHP >= 5.3.7 版本的支持。
在下面例子中各淀,我們哈希一個字符串诡挂,然后和新的哈希值對比临谱。因為我們使用的兩個字符串是不同的(’secret-password’ 與 ‘bad-password’)悉默,所以登錄失敗麦牺。
<figure class="highlight" style="box-sizing: border-box; background: rgb(255, 255, 255); margin: 0px 4px; font-size: 0.8em;">
<?php
require 'password.php';
$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);
if (password_verify('bad-password', $passwordHash)) {
// Correct Password
} else {
// Wrong password
}
</figure>
password_hash()
已經幫你處理好了加鹽鞭缭。加進去的隨機子串通過加密算法自動保存著岭辣,成為哈希的一部分。password_verify()
會把隨機子串從中提取仑濒,所以你不必使用另一個數據庫來記錄這些隨機子串偷遗。
數據過濾
永遠不要信任外部輸入氏豌。請在使用外部輸入前進行過濾和驗證。filter_var()
和 filter_input()
函數可以過濾文本并對格式進行校驗(例如 email 地址)泪电。
外部輸入可以是任何東西:$_GET
和 $_POST
等表單輸入數據纪铺,$_SERVER
超全局變量中的某些值,還有通過 fopen('php://input', 'r')
得到的 HTTP 請求體突诬。記住攒霹,外部輸入的定義并不局限于用戶通過表單提交的數據浆洗。上傳和下載的文檔,session 值抠刺,cookie 數據,還有來自第三方 web 服務的數據速妖,這些都是外部輸入罕容。
雖然外部輸入可以被存儲、組合并在以后繼續(xù)使用露泊,但它依舊是外部輸入旅择。每次你處理、輸出沉噩、連結或在代碼中包含時川蒙,請?zhí)嵝炎约簷z查數據是否已經安全地完成了過濾长已。
數據可以根據不同的目的進行不同的 過濾 。比如胶果,當原始的外部輸入被傳入到了 HTML 頁面的輸出當中斤斧,它可以在你的站點上執(zhí)行 HTML 和 JavaScript 腳本霎烙!這屬于跨站腳本攻擊(XSS)悬垃,是一種很有殺傷力的攻擊方式。一種避免 XSS 攻擊的方法是在輸出到頁面前對所有用戶生成的數據進行清理烘豌,使用 strip_tags()
函數來去除 HTML 標簽或者使用 htmlentities()
或是 htmlspecialchars()
函數來對特殊字符分別進行轉義從而得到各自的 HTML 實體看彼。
另一個例子是傳入能夠在命令行中執(zhí)行的選項囚聚。這是非常危險的(同時也是一個不好的做法)顽铸,但是你可以使用自帶的 escapeshellarg()
函數來過濾執(zhí)行命令的參數谓松。
最后的一個例子是接受外部輸入來從文件系統(tǒng)中加載文件践剂。這可以通過將文件名修改為文件路徑來進行利用。你需要過濾掉"/"
, "../"
, null 字符或者其他文件路徑的字符來確保不會去加載隱藏拧簸、私有或者敏感的文件男窟。
數據清理
數據清理是指刪除(或轉義)外部輸入中的非法和不安全的字符歉眷。
例如,你需要在將外部輸入包含在 HTML 中或者插入到原始的 SQL 請求之前對它進行過濾淑际。當你使用 PDO 中的限制參數功能時春缕,它會自動為你完成過濾的工作艘蹋。
有些時候你可能需要允許一些安全的 HTML 標簽輸入進來并被包含在輸出的 HTML 頁面中,但這實現起來并不容易宅荤。盡管有一些像 HTML Purifier 的白名單類庫為了這個原因而出現浸策,實際上更多的人通過使用其他更加嚴格的格式限制方式例如使用 Markdown 或 BBCode 來避免出現問題。
反序列化 Unserialization
使用 unserialize()
從用戶或者其他不可信的渠道中提取數據是非常危險的事情惫确。這樣做會觸發(fā)惡意實例化對象(包含用戶定義的屬性)改化,即使對象沒用被使用,也會觸發(fā)運行對象的析構函數盏档。所以你應該避免從不可信渠道反序列化數據燥爷。
如果你必須這樣做,請你使用 PHP 7 的 allowed_classes
選項來限制反序列化的對象類型稚配。
有效性驗證
驗證是來確保外部輸入的是你所想要的內容道川。比如立宜,你也許需要在處理注冊申請時驗證 email 地址、手機號碼或者年齡等信息的有效性尊流。
配置文件
當你在為你的應用程序創(chuàng)建配置文件時灯帮,最好的選擇時參照以下的做法:
- 推薦你將你的配置信息存儲在無法被直接讀取和上傳的位置上钟哥。
- 如果你一定要存儲配置文件在根目錄下,那么請使用
.php
的擴展名來進行命名吁恍。這將可以確保即使腳本被直接訪問到银受,它也不會被以明文的形式輸出出來鸦采。 - 配置文件中的信息需要被針對性的保護起來,對其進行加密或者設置訪問權限顶霞。
- 建議不要把敏感信息如密碼或者 API 令牌放到版本控制器中选浑。
注冊全局變量
注意: 自 PHP 5.4.0 開始,register_globals
選項已經被移除并不再使用拓提。這是在提醒你如果你正在升級舊的應用程序的話隧膘,你需要注意這一點。
當 register_globals
選項被開啟時蹦疑,它會使許多類型的變量(包括 $_POST
, $_GET
和 $_REQUEST
)被注冊為全局變量歉摧。這將很容易使你的程序無法有效地判斷數據的來源并導致安全問題腔呜。
例如:$_GET['foo']
可以通過 $foo
被訪問到,也就是可以對未聲明的變量進行覆蓋券盅。如果你使用低于 5.4.0 版本的 PHP 的話膛檀,請 確保 register_globals
是被設為 off 的。
錯誤報告
錯誤日志對于發(fā)現程序中的錯誤是非常有幫助的泳炉,但是有些時候它也會將應用程序的結構暴露給外部花鹅。為了有效的保護你的應用程序不受到由此而引發(fā)的問題枫浙。你需要將在你的服務器上使用開發(fā)和生產(線上)兩套不同的配置。
開發(fā)環(huán)境
為了在 開發(fā) 環(huán)境中顯示所有可能的錯誤真友,將你的 php.ini
進行如下配置:
<figure class="highlight" style="box-sizing: border-box; background: rgb(255, 255, 255); margin: 0px 4px; font-size: 0.8em;">
display_errors = On
display_startup_errors = On
error_reporting = -1
log_errors = On
</figure>
將值設為
-1
將會顯示出所有的錯誤盔然,甚至包括在未來的 PHP 版本中新增加的類型和參數。 和 PHP 5.4 起開始使用的E_ALL
是相同的愈案。- php.net
E_STRICT
類型的錯誤是在 5.3.0 中被引入的,并沒有被包含在 E_ALL
中遭铺。然而從 5.4.0 開始恢准,它被包含在了 E_ALL
中。這意味著什么锰蓬?這表示如果你想要在 5.3 中顯示所有的錯誤信息芹扭,你需要使用 -1
或者 E_ALL | E_STRICT
赦抖。
不同 PHP 版本下開啟全部錯誤顯示
- < 5.3
-1
或E_ALL
- 5.3
-1
或E_ALL | E_STRICT
5.3
-1
或E_ALL
生產環(huán)境
為了在 生產 環(huán)境中隱藏錯誤顯示,將你的 php.ini
進行如下配置:
<figure class="highlight" style="box-sizing: border-box; background: rgb(255, 255, 255); margin: 0px 4px; font-size: 0.8em;">
display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL
log_errors = On
</figure>
當在生產環(huán)境中使用這個配置時轮锥,錯誤信息依舊會被照常存儲在 web 服務器的錯誤日志中舍杜,唯一不同的是將不再顯示給用戶赵辕。更多關于設置的信息,請參考 PHP 手冊: