書名:構建安全的 PHP 應用
做者:(美) Ben Edmunds
譯者:張慶龍php
如下記錄這本 PHP Web 安全小書的大體內容,對書中的知識點進行備忘。css
這是一個老生常談的話題:咱們能夠利用 SQL 語句自己的做用方式,使用簡單的字符串拼接,就能使其執行結果偏離預期,甚至形成毀滅性後果。好比:html
'UPDATE User SET name = "'. $name .'" WHERE id = 123'
若此時 $name
的值爲 "; DROP DATABASE User; --
,則拼接以後,實際執行的 SQL 語句爲:前端
UPDATE User SET name = ""; DROP DATABASE User; -- " WHERE id = 123
此時,災難就發生了。算法
注:--
表示註釋後續的語句。注意 --
後有一個空格。這裏使用 #
也能夠達到註釋的效果。shell
在執行前進行敏感字符的過濾(不能只經過 JavaScript);數據庫
爲不一樣的業務模塊分配細顆粒度權限的數據庫鏈接;segmentfault
使用預處理和佔位符,如 $db->prepare()
以及 $db->execute()
;瀏覽器
使用存儲過程。但這種作法將部分業務邏輯轉移到了數據庫層,增長了測試和版本控制的難度。安全
使用 $_POST
中的全部字段直接做爲數據庫操做的數據,可能會使得攻擊者經過修改表單的提交項,從而實現意外數據的修改,如:
----- xxx.html <form action="action.php" method="post"> <input name="username"> <input name="password"> <!-- 加入新表單項 --> <input name="role" value="admin" /> <input type="submit"> </form> ----- action.php $user = User::create(Input::all());
如上,若是攻擊者在前端頁面中加入了一條新的表單項,在後臺不加區分的狀況下,直接把所有數據用於數據庫修改或增長,可能會形成新數據的插入或原數據的修改不如預期。如上例中,本應按照 role
的默認值建立的普通用戶,此時變爲了 admin
身份。
字段映射。數據庫字段、數據庫視圖字段、API 接口字段不徹底相同,使得攻擊者難以知曉數據庫字段的真實名;
給能夠被安全賦值的字段加上白名單、或給危險字段加黑名單。如 Laravel 中的 $fillable
和 $guard
。
PHP 的弱類型在必定程度上提高了開發效率,但也留下了安全隱患。不一樣類型間(包括數據庫自己)的隱式轉換有可能會使得數據表中的數據與預期不符。
於是咱們必定要關注輸入數據的類型,還包括那些在 JavaScript 處理階段被轉換的數據類型。
使用 htmlspecialchars()
或 htmlentities()
對如 <, >, &
等特殊字符進行轉移,使得存儲於數據庫中的功能性 HTML 標籤不會直接輸出到瀏覽器(實際上,這一過程在數據輸入的時候也須要進行)。
使用 escapeshellcmd()
和 escapeshellarg()
轉移命令和參數,以確保命令執行的安全可控性。
HTTPS 指 HTTP Secure 或 HTTP on SSL。HTTPS 能夠保證內容的安全性,使得只有最終傳遞到的、具備有效證書的接收者才能獲得這一內容。採用 HTTPS 能夠有效地預防中間人攻擊和會話劫持。關於 HTTPS 的原理科普能夠參考 「也許,這樣理解HTTPS更容易」。
普通的虛擬主機配置不能使用在 SSL 上。使用託管主機或在一個服務器上運行多個站點都會存在問題。這時須要更換爲專用服務器。
此外,HTTPS 在鏈接階段包含 SSL 握手用於創建鏈接,所以速度會變慢。但在鏈接創建完成以後,這個問題就不明顯了。
想要使用 HTTPS,你須要完成如下步驟:
子站點較多時,使用通配 SSL 證書。反之使用標準版便可。
首先須要生成私鑰:
openssl genrsa -out yourApp.key 1024
而後使用私鑰生成簽名:
openssl req -new -key yourApp.key -out yourApp.csr
以後須要在證書頒發機構中獲取證書,一般須要使用 yourApp.csr
文件,這一步獲取的證書爲 yourAppSigned.crt
。
最後就是根據服務器的類型(Apache、Nginx 或其餘)進行對應的配置。在 Apache 中爲:
<VirtualHost *.443> # ... SSLEngine on SSLCertificateFile /your/path/to/yourAppSigned.crt SSLCertificateKeyFile /your/path/to/yourApp.key # ... </VirtualHost>
在 Nginx 中爲:
server { listen 443; # ... ssl on; ssl_certificate /your/path/to/yourAppSigned.crt ssl_certificate_key /your/path/to/yourApp.key # ... }
你可使用如下方式用以正確的適配協議,如:
<link href="//assets/xx.css">
這時,當訪問的 URL 爲 http://xxx.com
時,該引用也會是 HTTP 協議;當訪問的 URL 爲 https://xxx.com
時,引用會變爲 HTTPS 協議。
不要存儲密碼或可逆加密結果,要存儲不可逆的哈希串。
雖然哈希方式使得密碼存儲變爲密碼值的不可逆串,消除了反向破解的可能。但仍有不少安全隱患。
雖然從哈希後的字符串反向解析是不可能的,但經過枚舉的方式一個個試探仍然能夠獲得出正確的密碼。固然,枚舉是不現實的,一般的作法是存儲一個查找表,表項爲 密碼 - 哈希串
。而後經過查找的方式暴力試探和破解。
這一方式能夠經過對哈希過程加「鹽」進行預防,如在密碼進行哈希前,於密碼中插入一些字符,混合後一塊兒哈希。
彩虹表在技術上與查找表相似,但其使用了數學方法用較小的內存實現了查找表。關於彩虹表能夠參考 維基百科。
碰撞攻擊,即不一樣的字符串的哈希值相同。在離散數學中,此攻擊又能夠稱爲「生日攻擊」,如下引用 維基百科:
生日問題是指,若是一個房間裏有 23 個或 23 個以上的人,那麼至少有兩我的的生日相同的機率要大於 50% 。這就意味着在一個典型的標準小學班級(30 人)中,存在兩人生日相同的可能性更高。對於 60 或者更多的人,這種機率要大於 99% 。從引發邏輯矛盾的角度來講生日悖論並非一種悖論,從這個數學事實與通常直覺相抵觸的意義上,它才稱得上是一個悖論。大多數人會認爲,23 人中有 2 人生日相同的機率應該遠遠小於 50% 。計算與此相關的機率被稱爲生日問題,在這個問題以後的數學理論已被用於設計著名的密碼攻擊方法:生日攻擊。
鹽是爲了使哈希惟一而附加在其上的東西。這意味着即便有了哈希密碼錶,攻擊者也不能正確地匹配上密碼。由此可知,鹽的隨機性是密碼安全的一部分。
雖然 PHP 的內置函數 rand()
和 mt_rand()
能夠生成隨機數,但這是使用算法生成的數字,於是沒有足夠的外部數據使其真正惟一。這意味着採用這兩種函數生成的隨機數能夠被攻擊者猜想。事實上,只須要知道 rand()
函數的 624 個值就能夠預判以後的全部值了。
使用 /dev/random
在大多數系統中是真正隨機的好方法。它會收集系統熵和環境數據,如鍵盤輸入、硬件數據等。但這一過程會致使阻塞,使得效率極低。在這一狀況下,咱們可使用 /dev/urandom
,該方法在真正隨機上並不夠強壯,但它做爲鹽卻足夠安全。
爲了使用隨機而又不存儲鹽的具體值,對應的哈希方法中,如
crypt()
,返回的結果會包括咱們採用的算法、密碼的哈希值,以及鹽。
MD5 早已被數學方法證實其並不安全。它很容易在現代硬件上產生衝突。但 MD5 也不是一無可取,配合合適的鹽也能夠保證哈希結果的安全。
同 MD5 同樣,SHA-1 算法也被證實能夠經過不到 2^69
次哈希產生衝突,於是是不安全的。
兩者採用的核心算法幾乎是同樣的,但 SHA-256 使用 32 位字符,而 SHA-512 採用 64 位,兩者的循環次數也不相同。
BCrypt 是 Blowfish 密碼的衍生方法。該算法是迭代的,因爲開銷的關係,使其能夠防止暴力破解。BCrypt 在加密純文本密碼時有 72 字符的限制,但這一算法長期以來仍沒有漏洞公佈,於是被認爲是密碼安全的。
SCrypt 是一個在內存方面增強的衍生算法。理論上來講,該算法在高內存消耗之下是一個更爲安全的算法。
在 PHP 5.5 版本以後引入了新的密碼哈希函數 password_hash()
和 password_verify()
,極大程度簡化了密碼操做流程,該函數會自動獲取隨機鹽並進行哈希。
只要時間足夠,暴力破解和嘗試總會獲得一個正確的結果。對於此,咱們能夠限制嘗試的頻率和次數,或者封鎖敏感 IP。
對於那些使用明文或採用不安全的哈希方法存儲密碼的遺留系統,升級它們的方式大體分爲如下兩種。
若是用戶該次登陸的密碼匹配於數據庫的密碼,則能夠用當前密碼值從新哈希,並替換掉數據庫的密碼。但這種被動的替換方式可能會持續很長時間(須要用戶自行觸發),因此數據庫須要有一個標識字段,用以表示該密碼是否已經置換成功。但給數據表添加字段並不容易,尤爲是對於運行中的大型應用。
採用這種方式,能夠選擇一個時機,統一對用戶密碼字段進行遍歷更新。看上去是一勞永逸的方法,但會使得密碼驗證機制效率變低。並且,現有系統會一直被早先的機制所拖累。
確保訪問的頁面、參與的業務請求都必須被身份驗證和權限控制模塊所覆蓋。警戒重定向致使的權限穿透。
不少數據表中使用自增主鍵做爲記錄的惟一標識。而且在 Cookie 和 API 中使用這些整形值。這會形成一些隱患,建議的作法是將這些值混淆到一些字符串中,使得它們被模糊處理。
一些框架中會使用某個路徑做爲公開文件夾,好比 /public
,這也就意味着咱們能夠經過對該文件夾的相對路徑直接訪問到其中的文件,而無視權限和身份的限制。建議的作法是將敏感的、須要安全防禦的文件放置在其餘路徑中,使得經過 URL 沒法直接訪問。
咱們應該爲驗證邏輯提供缺省值,以保證在沒有考慮全面之時不會引起大型漏洞。
此外,不要相信動態類型,尤爲是在判斷語句中,整形返回值和布爾值的隱式轉換可能會形成嚴重的後果。
XSS(跨站腳本攻擊)和 CSRF(跨站請求僞造)分別是用戶過度信任網站與網站過度信任瀏覽器所產生的安全隱患。前者的解決方案一般是在輸入和輸出時進行檢測和過濾,然後者一般是在提交表單中添加 token
令牌。關於這兩種攻擊的細節能夠參見 參考連接。
這裏涉及到 API 中的冪等性問題,指的是一次和屢次對某一個資源的請求應該具備一樣的反作用。基於此,建立數據的請求是不符合冪等性的。好比因爲網絡延遲問題,用戶屢次點擊建立按鈕,發送的合法建立請求前後抵達服務器,從而致使建立行爲產生屢次。這一問題在轉帳等業務上也比較廣泛。具體能夠參考 「理解HTTP冪等性」。使用以前提到的一次性的 token
令牌能夠預防這一問題的出現。
應對併發狀況,須要考慮對文件、數據庫等資源的併發處理策略。必要時須要對操做的文件加鎖,以及對數據庫使用 ... for update
以添加悲觀鎖或經過版本字段實現樂觀鎖。能夠參見 參考連接。