The 2018 Guide to Building Secure PHP Software!
目錄
- 前言
- PHP 版本 / PHP Versions
- Composer 依賴管理 / Dependency Management with Composer
- HTTPS 和瀏覽器安全 / HTTPS and Browser Security
- 開發安全的 PHP 程式 / Developing Secure PHP Software
- 資料庫注入 / Database Interaction
- 檔案上傳 / File Uploads
- 跨站指令碼 / Cross-Site Scripting (XSS)
- 跨站請求偽造 / Cross-Site Request Forgery (CSRF)
- XML 攻擊 / XML attacks (XXE, XPath Injection)
- 反序列化和 PHP 物件注入 / Deserialization and PHP Object Injection
- 密碼雜湊 / Password Hashing
- 通用加密 / General-Purpose Cryptography
- 隨機性 / Randomness
- 伺服器端 HTTPS 請求 / Server-Side HTTPS Requests
- 避免的事情 / Things to Avoid
- 專業用法 / Specialized Use-Cases
- 作者的一些話 / A Word From the Author
- 資源 / Resources
前言
2018 年將至,一般程式設計師(特別是 Web 開發程式設計師)應當拋棄過去開發PHP程式的很多不好的習慣和觀念了。雖然部分人不以為意,但是這確實是事實。
這個指南應該以重點部分作為 PHP: The Right Way 安全章節的補充,而不是以一般的 PHP 程式設計話題。
正文
PHP 版本
請在 2018 年使用 PHP 7.2, 並且計劃 2019 年初切換到 PHP 7.3。
PHP 7.2 已於 2017 年 11 月 30 日釋出。
寫這篇文章的時候,只有 7.1 和 7.2 版本還在被 PHP 官方積極維護,而 5.6 和 7.0 只在大概1年內提供安全補丁更新。
對於其他官方不維護的 PHP 版本,雖然某些作業系統會提供長期支援和維護,但這其實通常是有害的。尤其是他們提供安全支援補丁卻沒有版本號,這使得很難解釋系統的安全性(僅僅知道 PHP 版本)。
因此,無論其他供應商提出了什麼承諾,如果可以,你就應該在任何時候都堅決地使用官方提供支援的 PHP 版本。這樣,儘管最終是一個短暫的安全版本,但一個不斷致力於升級的版本,總會讓你收穫一些意外的驚喜。
依賴管理
人生苦短,我用 Composer
在 PHP 生態中,Composer 是最先進的依賴管理方案。我們推薦 PHP: The Right Way 中關於依賴管理的完整章節。
如果你沒有使用 Composer 來管理應用的依賴,最終(hopefully later but most likely sooner)會導致應用裡某個依賴會嚴重過時,然後老舊版本中的漏洞會被利用於計算機犯罪。
重要: 開發軟體時,時常記得保持依賴的更新。幸運地,這隻需一行命令:
composer update
如果你正在使用某些專業的,需要使用 PHP 擴充套件(C 語言編寫),那你不能使用 Composer 管理,而需要 PECL 。
推薦擴充套件
不管你正在編寫什麼,你總會受益於這些依賴。這是除了大多數 PHP 程式設計師的推薦(PHPUnit, PHP-CS-Fixer, ...)外的補充。
roave/security-advisories
Roave's security-advisories 使用 Friends of PHP repository 確保你的專案沒有依賴一些已知易受攻擊的依賴。
composer require roave/security-advisories:dev-master
或者,你可以上傳你的composer.lock
檔案到 Sensio Labs ,作為例行自動化漏洞評估工作流的一部分,以提醒發現任何過時的軟體包。
vimeo/psalm
Psalm 是一個幫助你識別程式碼裡可能存在 bugs 的靜態分析工具。還有其他很好的靜態分析工具(例如 Phan 和 PHPStan 都很棒),但當你發現你需要支援 PHP 5,Psalm 將是 PHP 5.4+ 的首選。
使用 Psalm 挺簡單:
# Version 1 doesn't exist yet, but it will one day:
composer require --dev vimeo/psalm:^0
# Only do this once:
vendor/bin/psalm --init
# Do this as often as you need:
vendor/bin/psalm
如果你是第一次在現有程式碼庫執行,可能會看到很多紅色錯誤。但除非你在構建像 WordPress 那麼大的程式,否則努力透過所有測試絕不是艱鉅的。
無論使用哪種靜態分析工具,我們都推薦你能將他加入到持續整合工作流(Continuous Integration workflow)中,以便在每次更改程式碼中執行。
HTTPS 和瀏覽器安全
HTTPS, which should be tested, and security headers .
2018 年,不安全的 HTTP 網站將不再被接受。幸運的是,由於 ACME 協議 和 Let's Encrypt certificate authority,免費的 TLS 證照成為了可能。
將 ACME 整合到你的伺服器,小菜一碟。
你也許會想,“好,我已經有 TLS 證照了,為了網站變得安全和快速,得花些時間折騰配置資訊。”
不!Mozilla做了件好事情!。你可以根據網站的目標受眾,使用配置生成器生成推薦套件。
如果你希望網站安全,HTTPS ( HTTP over TLS ) 是絕對不能妥協的。使用 HTTPS 立刻就能消除多種攻擊(中間人攻擊、竊聽、重放攻擊以及若干允許使用者模仿的會話形式的攻擊)。
安全頭
在伺服器使用 HTTPS 確實為使用者提供了許多安全性和效能方面的好處,但也還能透過利用某些瀏覽器的安全功能來進一步提升安全性。而這大部分會涉及到響應內容的安全頭。
Content-Security-Policy
- 你需要該 Header ,因為它提供了對於瀏覽器是否允許載入內部和外部資源的細化控制,從而為跨域指令碼攻擊漏洞提供了有效防禦層。
- 參閱 CSP-Builder,以便快速簡便地部署/管理內容安全策略(Content Security Policies)。
- 為了更加深入的分析, Scott Helme's introduction to Content-Security-Policy headers,會是一個很好的引導。
Expect-CT
- 你需要該 Header ,因為它能透過強制某些不良行為者將其錯誤證照的證據頒發到可公開驗證的僅可追加的資料結構,從而針對流氓/受損的證照頒發機構增加一層防護。
- 優先設定為
enforce,max-age=30
。只要你有足夠的自信該 Header 不會造成服務中斷,增加max-age
吧。
Referrer-Policy
- 你需要該 Header ,因為它允許你控制使用者的行為資訊是否洩露給第三方。
- 同樣地,Scott Helme 提供了一篇關於Referrer-Policy Header 介紹好文。
- 除非有理由允許更加寬鬆的設定,否則請設定為
same-origin
或no-referrer
。
Strict-Transport-Security
- 你需要該 Header ,因為它告訴瀏覽器透過 HTTPS 而不是不安全的 HTTP ,將 future requests 設為同源。
- 在第一次部署時,將其設定為
max-age = 30
,然後當你確信沒有任何內容會中斷時,將此值增加到某個較大的值(例如 31536000)。
X-Content-Type-Options
- 你需要該 Header ,因為 MIME 型別的混淆可能會導致不可預知的結果,包括奇怪的允許 XSS 漏洞的邊緣情況。這最好伴隨著一個標準的 Content-Type Header 。
- 除非需要預設的行為(例如檔案的下載),否則請設定為
nosniff
。
X-Frame-Options
- 你需要該 Header ,因為它允許你防止點選劫持。
- 設定為
DENY
(或者SAMEORIGIN
, 但僅僅當你使用<frame>
元素的時候)。
X-XSS-Protection
- 你需要該 Header ,因為它啟用了一些預設情況下未啟用的瀏覽器反 XSS 功能。
- 設定為
1; mode=block
。
同樣,如果你使用 PHP 的內建會話管理功能(建議使用),則可能需要這樣呼叫session_start()
:
<?php
session_start([
'cookie_httponly' => true,
'cookie_secure' => true
]);
這會強制你的應用在傳送會話識別符號時使用 HTTP-Only 和 Secure 標誌,從而防止 XSS 攻擊竊取使用者的 Cookie ,並強制它們分別透過 HTTPS 傳送。 我們之前在 2015 年的部落格文章中介紹了安全的 PHP 會話。
子資源完整性
在將來的某個時候,你也許會使用 CDN 來載入網站的公共 JavaScript/CSS 庫。安全工程師已經遇見了這存在一個明顯的風險,如果很多網站使用 CDN 提供內容,Hack 和替換 CDN(獲得了 CDN 的控制權)就可以注入(惡意)程式碼到成千上萬的網站。
查閱子資源完整性吧。
子資源完整性(SRI,Subresource integrity)允許你將希望 CDN 服務的檔案的內容進行雜湊處理。目前實行的 SRI 只允許使用安全的密碼雜湊函式,這意味著攻擊者不可能生成與原始檔案雜湊相同的惡意版本資源。
一個真例項子: Bootstrap v4-alpha uses SRI in their CDN example snippet
<link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css"
integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ"
crossorigin="anonymous"
/>
<script
src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"
integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn"
crossorigin="anonymous"
></script>
文件關係
Web 開發人員經常在超連結上設定目標屬性(例如,target ="_ blank"
在新視窗中開啟連結)。但是,如果你沒有傳遞rel ="noopener"
標籤,則可以允許目標頁面控制當前頁面。
不要這樣做:
<a href="http://example.com" target="_blank">Click here</a>
這會讓http://example.com
頁面能控制當前頁面。
而應該這樣做:
<a href="https://example.com" target="_blank" rel="noopener noreferrer">Click here</a>
透過這樣在新視窗開啟https://example.com
,當前視窗的控制權也不會授予可能的惡意第三方。
可以更加深入研究。
開發安全的 PHP 程式
如果應用程式安全性對你來說是一個新話題,請從應用程式安全性簡介開始吧。
大多數安全專家指出,開發者可以使用 OWASP Top 10 等資源開始著手。
但是,大多數常見的漏洞也可以是相同高等級的安全問題(例如程式碼和資料沒有完全分離、邏輯不嚴謹和健全、操作環境不安全或是可破譯的密碼協議等)。
我們的假設是,應該授予安全新手知道一些更簡單、基礎的安全知識和問題,並如何解決這些問題,應該是一個更好的、長遠的安全工程。
因此,我們避免推薦十大或二十大安全清單。
資料庫注入
如果你是自己編寫 SQL 程式碼,請確保使用prepared
語句,並且從網路或檔案系統提供的資訊都作為引數傳遞,而不是字串拼接的形式。此外,確保你沒有使用模擬的prepared語句。
為了達到好的效果,可以使用 EasyDB 。
不要這樣做:
<?php
/* Insecure code: */
$query = $pdo->query("SELECT * FROM users WHERE username = '" . $_GET['username'] . "'");
應該這樣做:
<?php
/* Secure against SQL injection: */
$results = $easydb->row("SELECT * FROM users WHERE username = ?", $_GET['username']);
還有其他資料庫抽象層提供了相同的安全性(EasyDB實際上是在使用 PDO ,但在實際的prepare
語句前避免了prepared
語句模擬)。 只要使用者輸入不會影響查詢的結構,就很安全(包括儲存過程)。
檔案上傳
接受檔案上傳是一個冒險的提議,但只要採取一些基本的預防措施,是能保證安全的。也就是說,允許檔案直接上傳的話,這些檔案可能會被意外的允許執行或解釋。上傳的檔案應該是隻讀(read-only)或讀寫(read-write)的,永遠不應該可執行(executable)。
如果你的網站根目錄是/var/www/example.com
,請不要儲存上傳檔案在/var/www/example.com/uploaded_files
。
而應該儲存到一個不能直接訪問的目錄(例如:/var/www/example.com-uploaded/
),以免意外地將其作為伺服器端指令碼執行,並獲得執行遠端程式碼的後門。
一個更加簡潔的方法是將網站根目錄往下移動一個層級(即:/var/www/example.com/public
)。
如何安全地下載這些上傳檔案也是一個問題。
- 直接訪問 SVG 影像型別時,將在使用者瀏覽器執行 JavaScript 程式碼。儘管它的MIME型別中的
image/
字首具有誤導性,但是這是正確的。 - 正如前面提及的,MIME 型別嗅探可能導致型別混淆攻擊。請參閱X-Content-Type-Options。
- 如果你放棄前面關於如何安全地儲存上傳檔案的建議,攻擊者就會透過上傳 .php 或 .phtml 檔案,直接在瀏覽器中訪問檔案來執行任意程式碼,從而完全控制伺服器。
跨站指令碼
同樣地,預防 XSS 和 SQL 注入是一樣簡單的。我們有簡單而易用的 API 來分離文件結構(structure of a document)和填充的資料。
然而,實際上還有很多 Web 開發程式設計師仍是透過生成一大串 HTML 程式碼作為響應的形式開發。並且,這不是 PHP 獨有的現實,這是所有 Web 開發程式設計師都應該重視的。
減少 XSS 漏洞不失為一個好方法。總之,前面談及的瀏覽器安全的章節就顯得十分相關了。簡言之:
- 儘量避免輸出和輸入(
Always escape on output, never on input
)。如果你把已清洗的資料(sanitized data)儲存在資料庫,然後在其它地方被發現了 SQL 注入漏洞,攻擊者將透過惡意程式汙染這些受信任的已清洗資料(trusted-to-be-sanitized record),從而繞開 XSS 保護。 - 如果你的框架有一個提供自動上下文過濾的模板引擎,那就使用它吧。這些工作可由框架安全地做到。
echo htmlentities($ string,ENT_QUOTES | ENT_HTML5,'UTF-8')
是一種安全、有效的方法阻止UTF-8編碼的網頁上的所有 XSS 攻擊,但不是任何 HTML 都有效。- 如果你的環境要求你使用 Markdown 而不是 HTML ,那就不要使用 HTML 了。
- 如果你需要使用原生 HTML(沒有使用模板引擎),參閱第一點,並且使用 HTML Purifier 吧。HTML Purifier 不適合轉義為 HTML 屬性上下文(HTML attribute context)。
跨站請求偽造
跨站請求偽造(CSRF)是一種混淆的代理攻擊,透過誘導使用者的瀏覽器代表攻擊者執行惡意的 HTTP 請求(使用的是該使用者的許可權)。
這在一般情況下是很容易解決的,只需兩步:
- 使用 HTTPS 。這是先決條件。沒有 HTTPS 的話,任何保護措施都是脆弱的,雖然 HTTPS 本身並不防禦 CSRF 。
- 增加基本的 Challenge-response authentication。
- 為每個表單新增一個隱藏的表單屬性。
- 填充一個密碼安全的隨機值(稱為令牌)。
- 驗證是否提供了隱藏的表單屬性,以及是否匹配上期望值。
我們寫了一個名為 Anti-CSRF 的庫,並且:
- 你可以使每個令牌只能使用一次,以防止重放攻擊。
- 多個令牌儲存在後端。
- 一旦令牌獲取完,令牌會迴圈使用。
- 每個令牌可以繫結特定的 URL 。
- 如果某個令牌洩露了,它不能在不同的上下文使用。
- 令牌可以繫結特定的 IP 地址。
- v2.1 後,令牌可以重複使用(例如供 Ajax 使用)。
如果你沒有使用防止 CSRF 漏洞的框架,請將 Anti-CSRF 放在一邊。在不久的將來,SameSite cookies將允許我們更簡單地避免CSRF攻擊。
XML 攻擊 (XXE, XPath Injection)
在處理大量 XML 的應用程式中存在兩個主要的漏洞:
- XML External Entities (XXE)
- XPath 注入
除此之外, XXE 攻擊可用作包含攻擊程式碼的本地/遠端檔案的啟動器。
早期版本的 Google Docs 被著名於 XXE ,但除了在很大程度上使用 XML 的商業應用程式之外,基本聞所未聞。
針對 XXE 襲擊的主要緩解措施:
<?php
libxml_disable_entity_loader(true);
除 XML 文件外,XPath注入與 SQL 注入非常相似。
幸運的是,將使用者輸入傳遞給 XPath 查詢的情況在 PHP 生態中非常罕見。
而不幸的是,這也意味著 PHP 生態中不存在可用的最佳避免措施(預編譯和引數化 XPath 查詢)。最好的辦法是在任何涉及 XPath 查詢的資料上設定允許使用的字元白名單。
<?php
declare(strict_types=1);
class SafeXPathEscaper
{
/**
* @param string $input
* @return string
*/
public static function allowAlphaNumeric(string $input): string
{
return \preg_replace('#[^A-Za-z0-9]#', '', $input);
}
/**
* @param string $input
* @return string
*/
public static function allowNumeric(string $input): string
{
return \preg_replace('#[^0-9]#', '', $input);
}
}
// Usage:
$selected = $xml->xpath(
"/user/username/" . SafeXPathEscaper::allowAlphaNumeric(
$_GET['username']
)
);
白名單總會比黑名單更安全。
反序列化和 PHP 物件注入
深入: 在PHP中安全地實現(反)序列化
如果你將不可信的資料傳遞給unserialize()
,則通常是這兩個結果之一:
- PHP 物件注入,它能用於啟動 POP 鏈(POP chain)並觸發其他誤用物件的漏洞。
- PHP 直譯器本身的記憶體損壞。
大多數開發人員更喜歡使用JSON序列化,這是對其軟體安全狀況的顯著改進。但請記住,json_decode()
容易受到雜湊衝突拒絕服務(Hash-DoS)攻擊。不幸的是,PHP的Hash-DOS問題還沒有得到徹底解決。
從djb33
遷移到Siphash
,對於字串輸入,雜湊輸出的最高位設定為 1 ,對於整數輸入設定為 0 ,使用CSPRNG
提供的請求金鑰,將完全解決這些攻擊。
不幸的是, PHP 團隊還沒有準備好放棄他們已經在 PHP 7 系列中取得的效能提升,所以很難說服他們放棄 djb33 (這是非常快但不安全的) 贊成 SipHash (這也是快速的,但不像 djb33 那麼快,但更安全)。 如果效能受到重大影響,可能會阻礙未來版本的採用,但也影響了安全性。
因此,最好的辦法是:
- 使用
JSON
,因為它比unserialize()
更安全。 - 在任何可能的地方,確保輸入在反序列化之前被認證。
- 對於提供給使用者的資料,透過一個只有伺服器知道的秘鑰使用
sodium_crypto_auth()
和sodium_crypto_auth_verify()
驗證。 - 對於第三方提供的資料,讓他們使用
sodium_crypto_sign()
簽名他們的 JSON 訊息,然後使用sodium_crypto_sign_open()
和第三方公鑰驗證訊息。 - 如果你需要對傳輸的簽名進行十六進位制或 Base64 位編碼,也可以使用分離的簽名 API 。
- 對於提供給使用者的資料,透過一個只有伺服器知道的秘鑰使用
- 如果你無法驗證 JSON 字串,請嚴格限制速度並阻止 IP 地址,以減輕重複的違規者。
密碼雜湊
安全的密碼儲存曾經是一個激烈爭論的話題,但現在實現起來相當微不足道,特別是在 PHP 中:
<?php
$hash = \password_hash($password, PASSWORD_DEFAULT);
if (\password_verify($password, $hash)) {
// Authenticated.
if (\password_needs_rehash($hash, PASSWORD_DEFAULT)) {
// Rehash, update database.
}
}
你甚至不需要知道在後臺使用什麼演算法,因為如果你使用最新版本的 PHP ,你也將使用當前最新的技術,使用者的密碼將會自動進行升級(只要有新的預設演算法可用)。
無論你做什麼,都不要做 WordPress 所做的事情。
從 PHP 5.5 到 7.2 ,預設演算法都是 Bcrypt 。在未來,它可能會切換到獲得密碼雜湊大賽冠軍的 Argon2 。
如果你以前沒有使用password_*
API ,那需要遷移遺留雜湊,請確保以這種方式進行。很多公司搞錯了, 最有名的是雅虎。 最近,錯誤地實施傳統雜湊升級似乎導致了蘋果的iamroot錯誤。
通用加密
這是一些我們詳細寫了的話題:
- Using Encryption and Authentication Correctly (2015)
- Recommended: Choosing the Right Cryptography Library for your PHP Project: A Guide (2015)
- Recommended: You Wouldn't Base64 a Password - Cryptography Decoded (2015)
- Cryptographically Secure PHP Development (2017)
- Recommended: Libsodium Quick Reference: Similarly-Named Functions and Their Use-Cases (2017)
一般來說,你總是希望使用 Sodium cryptography library(libsodium)進行應用層加密。如果你需要支援早於 7.2 的 PHP 版本(像 5.2.4),你可以使用sodium_compat,基本上可以假設你的使用者也是 7.2 。
在特定情況下,由於嚴格的演算法選擇和互操作性,你可能需要不同的庫。如有疑問,請諮詢密碼專家和密碼工程師,瞭解密碼選擇是否安全(這是我們提供的服務之一)。
隨機性
如果你需要隨機數字,請使用random_int()
。如果你需要隨機位元組字串,請使用random_bytes()
。不要使用mt_rand()
,rand()
或uniqid()
。
如果你需要從秘密種子(secret seed)生成偽隨機數(pseudorandom),請使用SeedSpring,而不是srand()
或mt_srand()
。
<?php
use ParagonIE\SeedSpring\SeedSpring;
$seed = random_bytes(16);
$rng = new SeedSpring($seed);
$data = $rng->getBytes(1024);
$int = $rng->getInt(1, 100);
伺服器端 HTTPS 請求
確保 TLS 證照驗證沒有被禁用
隨意使用你已經熟悉的任何相容 PSR-7 的 HTTP 客戶端。 我們喜歡 Guzzle ,有些人喜歡直接使用 cURL 。
無論你最終使用什麼,請確保使用的確定性,以確保始終可以擁有最新的 CACert 軟體包,從而允許啟用最嚴格的 TLS 證照驗證設定並保護伺服器的出站 HTTPS 請求。
安裝 Certainty 很簡單:
composer require paragonie/certainty:^1
使用 Certainty 也很簡單:
<?php
use ParagonIE\Certainty\RemoteFetch;
$latestCACertBundle = (new RemoteFetch())->getLatestBundle();
# cURL users:
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_CAINFO, $latestCACertBundle->getFilePath());
# Guzzle users:
/** @var \GuzzleHttp\Client $http */
$repsonse = $http->get(
'https://example.com',
[
'verify' => $latestCACertBundle->getFilePath()
]
);
這樣可以保護你免受網路伺服器與整合的任何第三方 API 之間的中間人攻擊。
我們真的需要 Certainty 嗎?
保護你的系統, Certainty 並不是嚴格的要求。缺少它並不是什麼漏洞。但如果沒有 Certainty ,開源軟體必須猜測作業系統的 CACert 軟體包的存在位置,如果猜測錯誤,它往往會失敗並導致可用性問題。從歷史上看,這激勵了許多開發人員只是禁用證照驗證,以便他們的程式碼“正常工作”,卻沒有意識到他們只是將應用程式變成主動攻擊。 Certainty 透過將 CACert 捆綁在最新的可預測位置來消除這種激勵。 Certainty 還為希望執行自己的內部 CA 為企業提供大量的工具。
誰禁用了證照驗證?
流行的內容管理系統(WordPress,Magento 等 CMS)的外掛/擴充套件開發者!這是我們試圖在生態系統層面上解決的一個巨大的問題。 它不是孤立的任何特定的 CMS ,你會發現這些不安全的外掛等都是類似的。
如果使用了類似的 CMS ,請在外掛中搜尋CURLOPT_SSL_VERIFYPEER
和CURLOPT_SSL_VERIFYHOST
,你可能會發現有幾個將這些值設定為FALSE
。
避免的事情
- 不要使用
mcrypt
。這是一個十多年來沒有開發出來的密碼學庫。如果你遵循我們的 PHP 版本建議,這應該是一個容易避免的錯誤,因為mcrypt
不再被 PHP 7.2 和更新的版本支援。 - 配置驅動的安全建議應該大部分地忽略。如果你正在閱讀 PHP 安全性指南,並告訴你更改 php.ini 設定而不是編寫更好的程式碼,那麼你可能正在閱讀過時的建議。關閉視窗並轉到一些和
register_globals
無關的文章上吧。 - 不要使用 JOSE(JWT,JWS,JWE),這是一套網際網路標準,它編纂了一系列容易出錯的密碼設計。儘管由於某種原因,被寫入了標準,也吸引了很多傳道人。
- 加密 URL 引數是公司常用來模糊後設資料的反模式(例如,我們有多少使用者?)。 它帶來了實施錯誤的高風險,也造成了錯誤的安全感。我們在連結的文章中提出了一個更安全的選擇。
- 除非迫不得已,否則不要提供“我忘記了我的密碼”的功能。
不要諱言:密碼重置功能是一個後門。 有一些方法可以實施以抵禦合理的威脅模型,但高風險使用者應該不被考慮。 - 避免使用 RSA,改用 libsodium 。如果你必須使用 RSA ,請確保指定 OAEP 填充。
<?php
openssl_private_decrypt(
$ciphertext,
$decrypted, // Plaintext gets written to this variable upon success,
$privateKey,
OPENSSL_PKCS1_OAEP_PADDING // Important: DO NOT OMIT THIS!
);
如果你不得不使用 PKCS#1 v1.5 填充,那麼無論你與哪個整合在一起,幾乎肯定會受到 ROBOT 的影響,請以允許明文洩露和簽名偽造的漏洞將其報告給相應的供應商(或 US-CERT )。
專業用法
現在你已經掌握了在 2018 年及以後構建安全 PHP 應用程式的基礎知識,接下來我們來看一些更專業的用法。
可搜尋的加密
可搜尋的加密資料庫是可取的,但被廣泛認為是不太可能實現的。上面連結的部落格文章試圖透過改進我們解決方案來實現,但本質上是這樣的:
- 設計你的架構,以便資料庫(database compromise)不會讓攻擊者訪問你的加密金鑰。
- 用一個金鑰加密資料。
- 基於 HMAC 或具有靜態鹽的安全 KDF (secure KDF with a static salt)建立多個索引(具有自己獨特的金鑰)
- 可選:截斷步驟3的輸出,將其用作布隆過濾器(Bloom filter)
- 在 SELECT 查詢中使用步驟3或4的輸出
- 解密結果。
在這個過程中的任何一步,你都可以根據實際使用情況進行不同的權衡。
沒有 Side-Channels 的基於令牌的身份驗證
深入: Split Tokens: Token-Based Authentication Protocols without Side-Channels
說到資料庫(上一節),你是否知道 SELECT 查詢理論上可能是定時資訊洩漏的來源?
簡單的緩解措施:
- 把你的認證令牌分為兩半
- 一半在 SELECT 查詢中使用
- 後一半在恆定的時間(constant-time)驗證
- 可以選擇將後半部分的雜湊儲存在資料庫中。這對於只能使用一次的令牌是有意義的,例如 密碼重置或“在此計算機上記住我”的令牌
即使可以使用定時洩漏來竊取一半的令牌,剩下的也需要暴力破解才能成功。
開發安全的API
我們寫了 SAPIENT (the Secure API ENgineering Toolkit),讓伺服器到伺服器驗證的訊息傳遞變得簡單易行。除了 HTTPS 提供的安全性之外,Sapient
允許你使用共享金鑰或公鑰來加密和驗證訊息。 這使得即使存在中間攻擊者,並設有流氓證照頒發機構,你也可以使用Ed25519
對 API 請求和響應進行身份驗證,或者將訊息加密到只能由接收方伺服器的金鑰解密的目標伺服器。 由於每個 HTTP 訊息體都透過安全密碼進行身份驗證,所以可以安全地使用它來代替stateful token juggling protocols
(例如 OAuth)。但是,在密碼學方面,在做任何不規範的事情之前,總要確保他們的實現是由專家研究的。
所有Sapient
使用的密碼演算法都由Sodium cryptography library
提供。
進一步閱讀:
Paragon Initiative Enterprises
已經在其許多產品(包括許多開源軟體專案)中使用了Sapient
,
並將繼續新增軟體專案到Sapient
使用者群中。
使用Chronicle記錄安全事件
深入: Chronicle Will Make You Question the Need for Blockchain Technology
Chronicle是一個基於雜湊鏈資料結構的僅追加密碼分類賬(append-only cryptographic ledger),具有很多吸引公司“區塊鏈”技術的屬性,而不會過分矯枉過正。
除了僅追加密碼分類賬(append-only cryptographic ledger)這個具有創造性的用例之外,Chronicle
整合到SIEM中時,也可以十分有亮點,因為你可以將安全關鍵事件傳送到私人Chronicle
中,並且它們是不能被改變的。
如果你的Chronicle
設定為將其摘要雜湊交叉簽名到其他Chronicle
例項,或者如果有其他例項配置為複製你的Chronicle
內容,攻擊者就很難篡改你的安全事件日誌。
在Chronicle
的幫助下,你可以獲得區塊鏈所承諾的彈性特性(resilience),而沒有任何隱私,效能或可伸縮性問題。
要將資料釋出到本地Chronicle
,你可以使用任何與Sapient-compatible API,但最簡單的解決方案稱為Quill。
作者的一些話
一些聰明的讀者可能注意到我們引用了很多我們自己的工作,包括部落格文章和開源軟體。(當然也不僅僅引用了我們自己的工作)
這絕不是偶然的。
自從我們在 2015 年初成立以來,一直在編寫安全庫並參與提高 PHP 生態系統安全性的工作。我們已經涉足了很多領域,而且我們的安全工程師(他們最近推動了更安全的加密技術加入 PHP 核心,就在最近的 PHP 7.2 中)自我擔保地說,並不擅長自我炒作,或是對已經做過的工作持續熱情。但你很可能沒有聽說我們多年來開發的工具或庫。對於這個,深感抱歉。
不論如何,我們也不可能成為各方面的先行者,所以我們儘可能地選擇與重視公共利益而不是貪圖小利的行業專家工作。
這也是為什麼瀏覽器安全的許多章節都參考了 Scott Helme 和他公司的工作,他們在為開發人員提供這些新的安全功能方面具有可訪問性和可理解性。
本指南當然不會是詳盡的。編寫不安全程式碼的方法幾乎和編寫程式碼的方法一樣多。 安全是一種心態,而不是目的地。 隨著上面所寫的一切,以及後面涉及的資源,我們希望這將有助於全世界的開發人員,從今天開始用 PHP 編寫安全的軟體。
資源
如果你已經按照本頁上的所有內容進行了操作,並且需要更多內容,則可能會對我們策劃的閱讀列表感興趣,以便學習應用程式安全性。
如果你認為自己編寫的程式碼足夠安全,並希望我們從安全工程師的角度對其進行評判,這也是我們為客戶提供的服務。
你如果為一家要進行合規性測試(PCI-DSS,ISO 27001等)的公司工作,可能還想聘請我們公司來稽核你的原始碼。我們的流程比其他安全諮詢公司更適合開發者。
接下來是 PHP 和資訊保安社群提供的資源列表,這些資源幫助網際網路更加安全。
-
PHP: The Right Way:現代 PHP 開發的實用指南,免費線上。
-
Let's Encrypt:證照頒發機構,透過提供免費 TLS 證照,為建立更安全的 Internet 做了很多。
-
Qualys SSL Labs:為 TLS 配置提供了一個快速而簡單的測試套件。幾乎每個人都使用這個來解決他們的密碼組和證照問題,理由很充分:It does its job well.
-
Security Headers:可以檢驗你的網站在使用瀏覽器安全功能來保護使用者方面的表現如何。
-
Report-URI:一個很好的免費資源,提供監控 CSP/HPKP 等安全策略的實時安全報告服務。他們給你一個 Report-URI,你可以傳遞給你的使用者的瀏覽器,如果有什麼事情發生或有人發現 XSS 攻擊媒介,他們會投訴Report-URI。 Report-URI 會彙總這些錯誤,並允許你更好地對這些報告進行疑難解答和分類。
-
PHP Security Advent Calenda:RIPSTech旗下的團隊負責。
-
Snuffleupagus:一個面向安全的 PHP 模組(Suhosin的精神繼承者,似乎在很大程度上會被放棄)
-
PHP Delusions:一個致力於更好地使用 PHP 的網站。大部分的口吻是非常有見地的,作者對技術的準確性和清晰度的奉獻使得值得一讀,特別是對於那些不太喜歡 PDO 功能的人來說。
-
Have I Been Pwned?:幫助使用者發現他們的資料是否屬於過時資料洩露。
結尾
原文地址:The 2018 Guide to Building Secure PHP Software - P.I.E. Staff
最早是在社群裡帖子 - The 2018 Guide to Building Secure PHP Software 看到,一位同學只發了原連結,由於是全英,文章也比較長,就沒有深讀,但可以知道這是一篇很好的文章,值得學習,這幾天花了時間翻譯了全文。
為避免歧義,部分專業名詞和語句保留了原文。翻譯過程藉助了 Google 和 Google 翻譯,本人英文和相關專業水平有限,如有錯誤感謝指出修正。
本作品採用《CC 協議》,轉載必須註明作者和本文連結