談談 Web 安全

攻擊者無時無刻不在準備對你的 Web 應用程序進行攻擊,所以提升你的 Web 應用程序的安全性是很是有必要的。幸運的是,來自開放式 Web 應用程序安全項目 (OWASP) 的有心人已經整理了一份包含了已知安全問題和防護方式的全面的清單。這份清單對於具備安全意識的開發者來講是必讀的。由 Padraic Brady 著做的 生存手冊:PHP 安全 也是一份很不錯的 PHP 安全閱讀資料。php

密碼哈希

每一個人在建構 PHP 應用時終究都會加入用戶登陸的模塊。用戶的賬號及密碼會被儲存在數據庫中,在登陸時用來驗證用戶。html

在存儲密碼前正確的 哈希密碼 是很是重要的。哈希密碼是單向不可逆的,該哈希值是一段固定長度的字符串且沒法逆向推算出原始密碼。這就表明你能夠哈希另外一串密碼,來比較二者是不是同一個密碼,但又無需知道原始的密碼。若是你不將密碼哈希,那麼當未受權的第三者進入你的數據庫時,全部用戶的賬號資料將會一覽無遺。有些用戶可能(很不幸的)在別的網站也使用相同的密碼。因此務必要重視數據安全的問題。laravel

密碼應該單獨被 加鹽處理 ,加鹽值指的是在哈希以前先加入隨機子串。以此來防範「字典破解」或者「彩虹碰撞」(一個能夠保存了通用哈希後的密碼數據庫,可用來逆向推出密碼)。git

哈希和加鹽很是重要,由於不少狀況下,用戶會在不一樣的服務中選擇使用同一個密碼,密碼的安全性很低。github

值得慶幸的是,在 PHP 中這些很容易作到。web

使用 password_hash 來哈希密碼算法

password_hash 函數在 PHP 5.5 時被引入。 此函數如今使用的是目前 PHP 所支持的最強大的加密算法 BCrypt 。 固然,此函數將來會支持更多的加密算法。 password_compat 庫的出現是爲了提供對 PHP >= 5.3.7 版本的支持。shell

在下面例子中,咱們哈希一個字符串,而後和新的哈希值對比。由於咱們使用的兩個字符串是不一樣的(’secret-password’ 與 ‘bad-password’),因此登陸失敗。數據庫

<?php
require 'password.php';

$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);

if (password_verify('bad-password', $passwordHash)) {
    // Correct Password
} else {
    // Wrong password
}
複製代碼

password_hash() 已經幫你處理好了加鹽。加進去的隨機子串經過加密算法自動保存着,成爲哈希的一部分。password_verify() 會把隨機子串從中提取,因此你沒必要使用另外一個數據庫來記錄這些隨機子串。安全

數據過濾

永遠不要信任外部輸入。請在使用外部輸入前進行過濾和驗證。filter_var() 和 filter_input() 函數能夠過濾文本並對格式進行校驗(例如 email 地址)。

外部輸入能夠是任何東西:$_GET 和 $_POST 等表單輸入數據,$_SERVER 超全局變量中的某些值,還有經過 fopen('php://input', 'r') 獲得的 HTTP 請求體。記住,外部輸入的定義並不侷限於用戶經過表單提交的數據。上傳和下載的文檔,session 值,cookie 數據,還有來自第三方 web 服務的數據,這些都是外部輸入。

雖然外部輸入能夠被存儲、組合並在之後繼續使用,但它依舊是外部輸入。每次你處理、輸出、連結或在代碼中包含時,請提醒本身檢查數據是否已經安全地完成了過濾。

數據能夠根據不一樣的目的進行不一樣的 過濾 。好比,當原始的外部輸入被傳入到了 HTML 頁面的輸出當中,它能夠在你的站點上執行 HTML 和 JavaScript 腳本!這屬於跨站腳本攻擊(XSS),是一種頗有殺傷力的攻擊方式。一種避免 XSS 攻擊的方法是在輸出到頁面前對全部用戶生成的數據進行清理,使用 strip_tags() 函數來去除 HTML 標籤或者使用 htmlentities() 或是 htmlspecialchars() 函數來對特殊字符分別進行轉義從而獲得各自的 HTML 實體。

另外一個例子是傳入可以在命令行中執行的選項。這是很是危險的(同時也是一個很差的作法),可是你可使用自帶的 escapeshellarg() 函數來過濾執行命令的參數。

最後的一個例子是接受外部輸入來從文件系統中加載文件。這能夠經過將文件名修改成文件路徑來進行利用。你須要過濾掉"/""../"null 字符或者其餘文件路徑的字符來確保不會去加載隱藏、私有或者敏感的文件。

數據清理

數據清理是指刪除(或轉義)外部輸入中的非法和不安全的字符。

例如,你須要在將外部輸入包含在 HTML 中或者插入到原始的 SQL 請求以前對它進行過濾。當你使用 PDO 中的限制參數功能時,它會自動爲你完成過濾的工做。

有些時候你可能須要容許一些安全的 HTML 標籤輸入進來並被包含在輸出的 HTML 頁面中,但這實現起來並不容易。儘管有一些像 HTML Purifier 的白名單類庫爲了這個緣由而出現,實際上更多的人經過使用其餘更加嚴格的格式限制方式例如使用 Markdown 或 BBCode 來避免出現問題。

查看 Sanitization Filters

反序列化 Unserialization

使用 unserialize() 從用戶或者其餘不可信的渠道中提取數據是很是危險的事情。這樣作會觸發惡意實例化對象(包含用戶定義的屬性),即便對象沒用被使用,也會觸發運行對象的析構函數。因此你應該避免從不可信渠道反序列化數據。

若是你必須這樣作,請你使用 PHP 7 的 allowed_classes 選項來限制反序列化的對象類型。

有效性驗證

驗證是來確保外部輸入的是你所想要的內容。好比,你也許須要在處理註冊申請時驗證 email 地址、手機號碼或者年齡等信息的有效性。

查看 Validation Filters

配置文件

當你在爲你的應用程序建立配置文件時,最好的選擇時參照如下的作法:

  • 推薦你將你的配置信息存儲在沒法被直接讀取和上傳的位置上。
  • 若是你必定要存儲配置文件在根目錄下,那麼請使用 .php 的擴展名來進行命名。這將能夠確保即便腳本被直接訪問到,它也不會被以明文的形式輸出出來。
  • 配置文件中的信息須要被針對性的保護起來,對其進行加密或者設置訪問權限。
  • 建議不要把敏感信息如密碼或者 API 令牌放到版本控制器中。

註冊全局變量

注意: 自 PHP 5.4.0 開始,register_globals 選項已經被移除並再也不使用。這是在提醒你若是你正在升級舊的應用程序的話,你須要注意這一點。

當 register_globals 選項被開啓時,它會使許多類型的變量(包括 $_POST$_GET 和 $_REQUEST)被註冊爲全局變量。這將很容易使你的程序沒法有效地判斷數據的來源並致使安全問題。

例如:$_GET['foo'] 能夠經過 $foo 被訪問到,也就是能夠對未聲明的變量進行覆蓋。若是你使用低於 5.4.0 版本的 PHP 的話,請 確保 register_globals 是被設爲 off 的。

錯誤報告

錯誤日誌對於發現程序中的錯誤是很是有幫助的,可是有些時候它也會將應用程序的結構暴露給外部。爲了有效的保護你的應用程序不受到由此而引起的問題。你須要將在你的服務器上使用開發和生產(線上)兩套不一樣的配置。

開發環境

爲了在 開發 環境中顯示全部可能的錯誤,將你的 php.ini 進行以下配置:

display_errors = On
display_startup_errors = On
error_reporting = -1
log_errors = On
複製代碼

將值設爲 -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

生產環境

爲了在 生產 環境中隱藏錯誤顯示,將你的 php.ini 進行以下配置:

display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL
log_errors = On
複製代碼

當在生產環境中使用這個配置時,錯誤信息依舊會被照常存儲在 web 服務器的錯誤日誌中,惟一不一樣的是將再也不顯示給用戶。更多關於設置的信息,請參考 PHP 手冊:

相關文章
相關標籤/搜索