原文地址:Web Application Security Checklist
原文作者:Teo Selenius(已授權)
譯者 & 校正:HelloGitHub-小熊熊 & 滷蛋
對於開發者而言,網路安全的重要性不言而喻。任何一處程式碼錯誤、一個依賴項漏洞或是資料庫的埠暴露到公網,都會有可能直接送你上熱搜。
那麼,哪裡可以找到詳細的避雷指引呢?OWASP's top 10 清單太短了,而且它更關注的是漏洞羅列,而非對預防。相比之下,ASVS 是個很好的列表,但還是滿足不了實際需求。
本文這份清單將介紹 72 個實操要點,讓你全方位保護你的 Web 應用程式。各位看官,準備入坑啦!
一、瀏覽器端的威脅防禦
1、用且僅用 HTTPS,防範網路攻擊
眾所周知,一個安全的應用需要對瀏覽器和 Web 伺服器之間的所有連線進行加密。此外,建議禁用一些舊的密碼套件和協議。
使用 HTTPS 時,僅加密網站的“敏感”部分是不夠的。如非這樣,攻擊者可以截獲某個未加密的 HTTP 請求,然後偽造來自伺服器的響應,返回惡意內容。
幸運的是,HTTPS 目前是很容易做到的。我們可以通過 Let's Encrypt 免費獲得證照,加上 CertBot 免費續期。
繼續我們的清單,下一個是 HSTS 它與 HTTPS 密切相關。
2、使用 HSTS 和預載入來保護使用者免受 SSL 剝離攻擊
伺服器可以用 HSTS 或 Strict Transport Security header 來強制進行加密連線。它表示需要一直使用 HTTPS 連線訪問網站。
HSTS 可以防止 SSL 剝離攻擊。所謂的 SSL 剝離攻擊也就是:網路上的攻擊者截獲瀏覽器發出的第一個 HTTP 請求(通常是未加密的),並立即偽造對該請求的回覆,假裝是伺服器並將連線降級為明文 HTTP。
值得注意的是,HSTS 僅在使用者至少成功訪問了一次應用程式的情況下才能生效。為了克服這個限制,可以把我們的網站提交到 https://hstspreload.org ,這樣,各瀏覽器便可以將我們的域名通過硬編碼寫入到 HSTS 列表中。
如下:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
警告:
在實施 HSTS 時,將會強制進出該網站的所有網路請求均被加密,如果網站請求中仍然有純文字,可能無法訪問。所以,先設定一個小的 max-age 引數進行除錯,如果一切正常工作,再加大這個值。除錯成功後再加上預載入 (preload) ,把開啟預載入保留在最後一步,因為關閉它是件很麻煩和痛苦的事情。
3、設定安全 Cookie,保護使用者免受網路攻擊
給 Cookie 加上 Secure 屬性。此屬性將防止 Cookie 在(意外或強制的)未加密的連線中洩漏。
Set-Cookie: foo=bar; ...other options... Secure
4、安全生成 HTML 以避免 XSS 漏洞
要避免 XSS(跨站點指令碼)漏洞,可以採用下面兩種方法:
- 完全靜態的網站(例如 JavaScript SPA + 後端API)。避免生成 HTML 問題的最有效方法是根本不生成HTML,如前述方法,當然,也可以試試很酷的 NexJS。
- 模板引擎。針對傳統的 Web 應用程式,其中的 HTML 大多是在後端伺服器上根據提供引數動態生成的。這種情況下,不要通過字串連線來建立 HTML 。推薦的做法是使用模板引擎,比如 PHP 語言的 Twig、Java 語言的 Thymeleaf、Python 語言的 Jinja2 等等。
此外,務必要正確配置模板引擎,從而可以自動對引數進行編碼,並且不要使用任何可以繞過這種編碼的“不安全”函式。不要把 HTML 放在回撥函式、屬性(不帶引號)或 href/src 等諸如此類的地方。
5、安全使用 JavaScript 以避免 XSS 漏洞
要避免 JavaScript 端的 XSS(跨站點指令碼)漏洞,切忌將不受信任的資料傳遞到可執行程式碼的函式或屬性中。這類常見的函式或屬性包括:
- eval、setTimeout、setInterval 等。
- innerHTML,React's dangerouslySetInnerHTML 等。
- onClick、onMouseEnter、onError 等。
- href、src 等。
- location, location.href 等。
6、沙箱處理不可信內容,避免 XSS 漏洞
最好是能避免不可信的內容,但往往又不能完全避免:例如需要從遠端獲取 HTML 進行展示,或者需要允許使用者用所見即所得的編輯器寫文章,等等。
要避免這些場景中的 XSS(跨站點指令碼)漏洞,請首先使用 DOMPurify 清理內容,然後在沙箱中進行內容呈現。
即使所見即所得的編輯庫聲稱從 HTML 中移除了惡意內容,仍然可以通過重新淨化和沙箱來處理,進一步確保安全。
還有一種常見的情況是,我們想在網頁展示廣告等內容。這種情況下簡單採用 IFrame 是不夠的,因為 same-origin 策略會允許跨域的 frame 將父級 frame (也就是我們的網站)的 URL 修改為一個釣魚網站。因此,要記住使用 IFrame 的沙箱屬性來避免此種情況的發生。
7、採用內容安全策略,避免 XSS 漏洞
內容安全策略(CSP)可以很好地防禦 XSS(跨站點指令碼)攻擊、點選劫持攻擊等。所以,一定要用它!預設情況下,CSP 會阻止幾乎所有的危險操作,所以額外的配置越少越好。如下:
Content-Security-Policy: default-src 'self'; form-action 'self'; object-src 'none'
它允許從 Web 應用程式的原始碼載入指令碼、樣式、影像、字型等,但不允許載入其他內容。最值得注意的是,它將阻止內聯指令碼()的執行,從而更好地預防 XSS 漏洞。
此外,form-action:'self' 指令可防止在網站上建立惡意 HTML 表單(比如“您的會話已過期,請在此處輸入密碼”類似的表單),並將其提交到攻擊者的伺服器。
無論如何,都不要指定 script-src: unsafe inline ,一旦這樣做,CSP 將形同虛設。
最後,如果你擔心 CSP 會影響生產環境,可以先以 Report-Only 模式進行部署:
Content-Security-Policy-Report-Only: default-src 'self'; form-action 'self'
8、設定 HttpOnly 的 Cookie,保護使用者免受 XSS 攻擊
為 Cookie 設定 HttpOnly 屬性,可以防止 Cookie 被 JavaScript 程式碼訪問。 一旦跨指令碼攻擊發生,該設定也會讓黑客更難竊取到 Cookie 資訊。當然,有些需要被 JavaScript 程式碼訪問的 Cookie,就不能做這個設定了。
Set-Cookie: foo=bar; ...other options... HttpOnly
9、針對下載功能,合理設定避免 XSS 漏洞
向使用者提供下載功能時,在 header 中設定 Content-Disposition: attachment,從而避免 XSS 漏洞。該設定將禁止在使用者瀏覽器直接渲染檔案,從而避免 HTML 或 SVG 格式的下載檔案可能引發的漏洞。如下:
Content-Disposition: attachment; filename="document.pdf"
假如我們想允許特定的檔案(如 pdf)能在瀏覽器端開啟,並且也確定這樣是安全的,那麼,可以針對該型別檔案,將 header 省略掉或是將 attachment 換為 inline。
10、針對 API 響應,合理設定避免 XSS 漏洞
反射型檔案下載(RFD)攻擊往往通過構建一個 URL 從 API 下載一個惡意檔案來實現。針對該類漏洞,可採用在 API HTTP 響應中返回帶有安全檔名的 Content-Disposition header來防禦。
11、利用現有平臺的反跨站請求偽造(CSRF)機制,避免 CSRF 漏洞
為避免反跨站請求偽造漏洞,務必確保我們所採用的平臺開啟了反跨站請求偽造功能,並確保該配置發揮了應有的作用。
12、驗證 OAuth 身份認證的 state 引數,避免 CSRF 漏洞
有一類與 OAuth 身份認證相關的跨站請求偽造漏洞是黑客讓使用者不經意間採用其賬戶進行登入。因此,如果有使用 OAuth 身份認證,務必確保對狀態(state)引數的驗證。
13、正確使用 HTTP 協議,避免 CSRF 漏洞
除了 POST、PUT、PATCH、DELETE 以外,不要使用其它 HTTP 方法進行資料更改。GET 請求一般是不包含在反跨站請求偽造機制中的。
14、為 Cookie 設定同源屬性,避免 CSRF、XS-leak、XSS 漏洞
為 Cookie 設定 SameSite 屬性。SameSite 能防止大多數的跨站點請求偽造攻擊,而且還可以防止許多跨站點洩漏的漏洞。
SameSite 屬性有兩種模式:寬鬆(lax)和嚴格(strict)。
寬鬆模式可以防止大多數跨站點計時和跨站點請求偽造攻擊,但對基於 Get 請求的跨站點請求偽造漏洞無效。如下:
Set-Cookie: foo=bar; ...other options... SameSite=Lax
嚴格模式則可以防止該類基於 Get 請求的漏洞,以及反射型的跨站點指令碼漏洞。然而,嚴格模式不適合常規的應用程式,因為它會中斷身份驗證連結。如果使用者已登入某個網站,現在要在新的頁面開啟指向該應用程式的連結,則開啟的新頁面將不會為該使用者自動登入。由於嚴格模式的限制,會話 Cookie 也不會隨請求一起傳送。嚴格模式設定如下:
Set-Cookie: foo=bar; ...other options... SameSite=Strict
15、每次登入建立一個新的會話 ID,防止會話固定攻擊
會話固定攻擊一般是在以下情形發生:
-
攻擊者將 Cookie(例如 JSESSIONID=ABC123)注入到使用者的瀏覽器中。不用擔心,攻擊者有很多方法可以做到這一點。
-
使用者使用其憑據登入,並在登入請求中提交攻擊者設定的 JSESSIONID=ABC123 。
-
應用程式對 Cookie 和使用者進行身份驗證。
-
與此同時,擁有該 Cookie 的攻擊者也就可以通過該使用者的身份進行登入了。
為了防止出現這種情況,程式中需要在身份驗證通過後,建立一個新的會話 ID 返回給使用者,而不是驗證可能被動了手腳的 Cookie。
16、合理命名 Cookie,防止會話固定攻擊
難道 Cookie 命名也能影響到網路應用程式的安全性?確實如此!將 Cookie 採用 __Host-** 的形式來命名,瀏覽器將:
- 不能通過非加密的連結訪問該項 Cookie, 從而避免會話固定攻擊以及其它涉及到 Cookie 讀取與寫入的攻擊;
- 不允許子域名重寫該項 Cookie,從而避免來自子域名網站(抑或是被攻陷,抑或本身就是惡意的)的攻擊。
該項設定示例如下:
Set-Cookie: __Host-foo=bar ...other options...
17、設定 Cache-Control header,防止使用者資訊被竊取
快取是將訪問過的網站、下載過的檔案全部儲存在硬碟的某個位置,直到有人手動刪除它們。預設情況下,瀏覽器會對頁面的一切內容進行快取,從而加快訪問速度、節約網路頻寬。
要在公共網路環境保證資訊保安,我們需要將所有 HTTP 響應設定一個合適的 Cache-Control header,特別是針對非公開的和動態的內容。
該項設定示例如下:
Cache-Control: no-store, max-age=0
18、設定 Clear-Site-Data header,防止使用者資訊被竊取
另外一個可以有效保證使用者退出後記錄即被清除的 header 是 Clear-Site-Data 。當使用者退出登入時,可以在 HTTP 請求中攜帶該 header。瀏覽器會清除該域名下的快取、Cookie、儲存以及執行上下文。大部分瀏覽器都支援該 header。
該項設定示例如下:
Clear-Site-Data: "*"
19、妥當地處理“退出”,防止使用者資訊被竊取
使用者退出登入後,務必要對訪問令牌和會話識別碼進行失效處理。這樣,即使攻擊者從訪問歷史/快取/記憶體等地方獲取到這些資訊,它們也不再有效。
此外,如果有單點登入,切記要呼叫單點登入的退出埠。否則,因為單點登入會話仍處於活躍狀態,此時的退出將會無效,只要使用者再次點選“登入”,即可自動登入。
最後,清理掉你可能用到過的 Cookie、HTML5 儲存等。上面提到的 Clear-Site-Data 還未被某些瀏覽器支援,所以最好還是手工清除一下。
20、針對 JavaScript 密碼採用 SessionStorage,防止使用者資訊被後來者竊取
SessionStorage 類似於 LocalStorage,但對每個標籤頁都是獨有的,而且在瀏覽器/標籤頁關閉以後將自動清除。
注意:如果要在系統內開啟的多個標籤頁之間同步使用者的授權資訊,那就需要用事件來同步 sessionStorage 資訊。
21、不要通過 URL 傳輸敏感資訊
URL 設計的初衷就不是為了傳輸敏感資訊。它會被顯示在螢幕上,儲存到瀏覽器歷史記錄,也容易隨 referrer header 而洩漏,被記錄在伺服器日誌等。所以,切忌在 URL 中傳遞敏感資訊。
22、採用 Referrer 策略,防止 URL 地址洩露
預設情況下,當從系統中連結到一個外部網站時,瀏覽器會設定一個 Referrer 的 header 來告訴該網站此次訪問的來源。這個 header 包含了整個 URL 地址,這可能就涉及到一點隱私。
可以在 HTTP 響應中設定一個 Referrer-Policy 的 header 來禁止該預設行為:
Referrer-Policy: no-referrer
23、為應用設定獨立域名,防止同源應用相互干擾
如果我們這樣設定應用域名:https://www.example.com/app1/ 和 https://www.example.com/app2/,是非常危險的。因為瀏覽器會認為它們是同源應用,也就是同樣的服務主機、埠和模式。正因為是同源應用,它們將對彼此有完全的訪問許可權。任何影響其中一個的漏洞都會同樣影響到另外一個。
因此,我們需要給每個應用一個獨立的域名。所以,這種情況下應該設定為:https://app1.example.com/
和 https://app2.example.com/
注意:位於同一個域名下的子域名是可以為整個域名設定 Cookie 的。例如 app1.example.com 可以為 example.com 設定 Cookie,而這個 Cookie 也將適用於 app2.example.com。允許為一個站點設定 Cookie 有時會給會話固定等型別的漏洞以可乘之機。公共字尾列表可以用來應對該問題。此外,也可以通過將 Cookie 命名為 __Host-
來防止其被子域名所覆蓋。
24、謹慎採用 CORS(跨域資源共享)
瀏覽器的安全模型大部分是依賴於同源策略,它可以防止應用的跨域讀取。而 CORS(跨域資源共享)則是一種允許網站進行跨域資源訪問的手段。所以,決定使用它之前,最好先搞清楚自己是否真的需要。
25、限制請求來源
如果你在 api.example.com
的服務需要被來自 www.example.com
的 GET 請求訪問,那麼可以在 api.example.com
服務上指定如下header:
Access-Control-Allow-Origin: https://www.example.com
如果你有個公開的服務介面(比如說一個提供給網際網路上 JavaScript 客戶端使用的計算器服務),那麼你可以指定一個隨機的來源:
Access-Control-Allow-Origin: *
如果你只想讓有限的幾個域名訪問它,那麼可以在程式中讀取請求的 Origin header,進行比對後處理。不過,建議使用現成的庫來操作,不要徒手擼,很容易出錯。
26、謹慎使用 allow credentials 選項
預設情況下,跨域資源共享是不帶使用者憑證的。但如果在 Web 伺服器端指定如下 header,則將允許攜帶:
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Credentials: true
這對 header 組合相當危險。因為它會使跨域訪問具備已登入使用者的許可權,並使用該許可權來訪問網站資源。所以,如果你不得不使用它,務必小心為上。
27、對 HTTP method 進行驗證
僅允許所需要的 HTTP 方法,從而最小化攻擊面。
Access-Control-Allow-Methods: GET
28、合理使用 WebSockets, 避免反跨站請求偽造等漏洞
WebSockets 迄今還是比較新的技術,技術文件較少使用它難免會有些風險。所以,採用時務必要做到以下幾點:
-
對連線進行加密
就像我們應該用 https:// 而非 http:// 採用 WebSockets 時也要使用 wss:// 而非 ws://
HSTS 也會影響 WebSockets ,它會自動將非加密的 WebSocket 連線升級到 wss://
-
對連線進行鑑權
如果使用的是基於 Cookie 的鑑權機制,且 WebSocket 伺服器與應用伺服器在同一個域名下,那就可以在 WebSocket 中繼續使用已有的會話。不過切記要對請求源進行驗證!
如果不是基於 Cookie ,可以在系統中建立一個單次使用、有時間限制並與使用者 IP 繫結的授權令牌,用該令牌對 WebSocket 進行授權。
-
對連線源進行確認
理解 WebSockets 的一個關鍵點在於要知道同源策略對其是無效的。任何一個能與你的系統建立 WebSocket 連線的網站,在使用 Cookie 鑑權的時候,都是可以直接獲得使用者資訊的。因此,在 WebSocket 握手時,必須要確認連線源。可以通過驗證請求頭中的 Origin 引數來確認。
如果想要做到雙重保險,可以採用反跨站請求偽造令牌作為 URL 引數。但針對每個任務則需要建立一次性的獨立令牌,而不要直接使用反跨站請求偽造令牌,因為後者主要是用來為應用的其它部分提供安全保障的。
29、採用 U2F 令牌或客戶端證照,保護系統關鍵使用者免受釣魚攻擊
如果系統可能會面臨釣魚攻擊的威脅,說人話也就是,“如果存在這樣的可能性:攻擊者建立一個假的網站,騙取管理員/CEO 或其它使用者的信任,從而盜取其使用者名稱、密碼和驗證碼”,那麼就應該使用 U2F 令牌或客戶端證照來防止這種攻擊,這樣的話即使攻擊者有了使用者名稱、密碼和驗證碼也無法得逞。
備註: 強調釣魚防護對於一般使用者而言往往會帶來不必要的麻煩。然而,提供多一種可選項對終端使用者而言也非壞事。此外,向使用者提前告知釣魚攻擊的危險也是非常必要的。
30、針對跨站點洩露進行保護
跨站點洩露是一系列瀏覽器邊通道攻擊。這種攻擊使惡意網站可以從其它 Web 應用程式的使用者中推測出資訊。
這種攻擊存在已有時日,但是瀏覽器端卻是最近才開始新增針對性的預防機制。可以在 這篇文章 中瞭解關於該類攻擊的更多細節以及應該採取的安全控制措施。
二、伺服器端的威脅防禦
其次,是伺服器端的威脅防禦,這裡從應用系統、基礎設施、應用架構、應用監控、事件響應等不同側面,歸納瞭如下建議:
2.1 應用系統
31、對使用者輸入進行合法性驗證
該類別的措施中最關鍵的一點就是儘可能嚴格地對所有使用者輸入進行合法性驗證。適當的驗證會使系統漏洞更難被發現和利用。對不合法的使用者輸入直接拒絕,而不要嘗試去清洗。驗證方面包括如下:
- 採用嚴格的資料型別。針對日期採用 DataTime 型別,數字採用 Integer 型別等等。針對有固定可選項的情況採用列舉型別。儘量避免採用字串型別。
- 如果必須採用字串,至少給一個長度限制。
- 如果必須採用字串,將可輸入的字符集儘可能地減少。
- 如果要處理 JSON,使用 JSON 模式進行驗證。
- 如果要處理 XML,使用 XML 模式進行驗證。
32、異常處理優雅化,避免技術細節洩露
對終端使用者不要顯示堆疊記錄或類似的除錯資訊。採用全域性的異常處理器對異常進行處理,展現給瀏覽器端簡單的錯誤資訊。這樣會使攻擊者更難發現和利用系統中的漏洞。
33、不要自己做鑑權
對使用者進行鑑權時可能會出現各種各樣的問題:要抵禦密碼猜想攻擊、使用者列舉攻擊,要管理密碼重置、儲存使用者憑證,樣樣都不容易。就像密碼處理一樣複雜,我們普通人還是不要嘗試了。
直接使用 auth0 等類似的工具來進行身份驗證,採用一些廣泛使用的、安全的軟體模組來實現通訊協議(常見的為 OpenID connect)。如果不想用 auth0 這類第三方的身份提供商,也可以自己搭建一個類似 KeyCloak 的服務來代替。
34、對一切都進行鑑權,減少攻擊面
應用系統要預設對一切都進行鑑權,除非是一些靜態資源、異常頁面或登出頁面。
35、採用多重身份認證
萬一有人破解了身份認證服務呢?如果存在這種擔憂,直接上多重身份認證(說人話也就是:除了密碼以外,還需要手機驗證碼)。這樣就算身份認證服務被黑、攻擊者可以冒充到任何人,還是無法知道手機收到的驗證碼。
36、通過嚴格的許可權控制,避免對資料或功能的未授權訪問
許可權控制雖不是件容易事,但也有妥善處理的方法:只要時刻記住不要在控制器方法中忘了對使用者許可權進行驗證,從而帶來使用者越權的漏洞,包括:
- 不要預設對所有控制器方法開通訪問許可權。
- 根據使用者角色劃分每個控制器的訪問許可權。
- 採用方法級別的安全控制,限制對服務方法的訪問許可權。
- 採用集中化的許可權管理工具,防止對每條記錄的非授權訪問。
- 採用前端 Web 應用和後臺 API 結合的架構,對每個 App 和 API 均採取許可權控制,而不僅是對與網際網路連線的部分進行控制。
為了進一步澄清許可權管理工具,這裡總結了一些要點:
- 資料記錄要有可以進行許可權控制的欄位,比如
int ownerId
。 - 被授權的使用者要有一個 ID。
- 要有一個類可用來進行許可權評估,在資料記錄的 ownerId 與 使用者的 ID 相匹配時,能判斷出使用者具有對應的訪問許可權。
- 在以上基礎上,可以將許可權評估類整合到應用平臺的許可權控制系統中,比如 Spring Security 產品的 PreAuthorize、PostAuthorize 等等。
- 如果需要更復雜的許可權控制,也可以搭建一個完善的 ACL 系統。
37、採用合適的工具和技術,避免注入漏洞
注入類的漏洞有很多,而且都很相似,包括 SQL 注入、HTML 注入、XML 注入、XPath 注入、命令注入、SMTP 注入、響應 header 注入等等。名稱不同但本質相同,相應地解決方法也類似:
- 問題原因: 使用字串拼接,來構建特定協議下的引數化訊息。
- 解決方案:採用合適的、安全的、現成的工具來實現這項任務。
這裡不會深入太多細節,只要記住:不管你是什麼協議,都謹記上面這點。後面會列舉一些常見的注入類漏洞。
38、建立安全的資料庫查詢語句,避免 SQL 注入漏洞
如果要避免 SQL 注入漏洞,那就記住絕不要自己用字串拼接 SQL 查詢語句。採用一個物件關係對映框架(ORM)來實現,可以讓開發更高效、應用更安全。
如果想要構建更細粒度的查詢,可以使用更底層一點的 ORM。
如果不能使用 ORM,那就嘗試預處理語句,但也要小心這類語句會比 ORM 更容易出現錯誤。
警告:
ORM 框架也不是萬能的,體現在兩方面:一是,它對原生的 SQL 查詢還是支援的,最好不要使用這類查詢;二是,像其它任何軟體一樣,ORM 框架也會時不時被曝出漏洞。所以,還是遵循我們一而再再而三強調的策略:對所有輸入進行驗證,採用網路應用程式防火牆(WAF),並保持軟體包的更新,這樣基本就可以放心了。
39、謹慎使用作業系統的命令列,防止命令注入的相關漏洞
如果可以避免,最好不要執行作業系統命令。如果不能避免,那最好遵循以下準則:
-
採用合適的庫/方法來構建命令及其引數。引數必須是 list 型別。不要用單獨字串來建立命令。
-
不用使用 shell 來呼叫命令。
-
預定義好命令引數。比如 curl,如果允許使用者通過 -o 來指定引數,那麼攻擊者就有機會寫入到本地檔案系統。
-
瞭解程式如何執行,並相應地對引數進行驗證。再比如 curl,你可能只是想讓使用者可以拉取某個網站的內容,但如果他拉取了 file:///etc/passwd,那就危險了。
-
想清楚再行動。在上面的例子中,就算驗證了訪問地址是以 http:// 或 https:// 開頭,攻擊者也可以發起以這兩類協議開頭的攻擊,如:http://192.168.0.1/internal_sensitive_service/admin。
-
再強調一遍:真得要想清楚了再行動。就算你對 DNS 進行驗證,確保命令中不含敏感內網地址,你有去禁止將特定 DNS 記錄對映到 192.168.0.1 嗎?如果答案是否,那就危險了。
40、合理配置 XML 解析器,避免 XML 漏洞
作為一種標記語言,XML 的危險性體現在它可以訪問系統資源。XSLT 的一些實現甚至支援嵌入程式碼。因此,在處理時必須非常謹慎。
-
如果可以,避免接受來自不受信任源的 XML/XSLT。
-
如果要向 XML、XSLT 或 XPath 傳參,記住要使用安全的軟體元件,而不要使用字串連線/格式化的方式。
-
使用主流、安全的軟體元件來解析 XML/XSLT。不要使用錯誤的庫或程式碼來處理 XML。此外,在任何情況下,都不要試圖去徒手擼一個解析器(比如 SAML),非常容易出錯。
-
正確配置解析器:禁用 XSLT 文件、禁用 xinclude、禁用文件型別定義、禁用外部實體,啟用 DOS 保護。具體配置在實現時會有所不同,但務必對所選擇的解析器進行深入的研究。
41、採用合適的類構建URL,避免 URL 注入漏洞
URL 注入經常會在以下情況發生:
flavour = request.getParam("flavour");
url = "https:/api.local/pizzas/" + flavour + "/";
return get(url).json();
如果 flavour 被設定為:
../admin/all-the-sensitive-things/
那麼這個 API 請求將會變為 https://api.local/admin/all-the-sensitive-things/
,是不是很凶險?
解決方案依然是採用合適的 URL 構建庫來為 URL 傳參,從而能正確地對引數進行編碼。
42、採用合適的類構建路徑,避免路徑遍歷漏洞
就像 URL 地址一樣,如果攻擊者設法在路徑中的某個地方偷偷地插入 ../../../
,檔案路徑可能最終指向意料之外的位置。要避免這種情況,請建立一個類,採用這個類安全地構造路徑,並驗證最終路徑是否在預期目錄中。避免在檔案路徑中使用不受信任的資料,或者更好的是,完全避免使用檔案系統,直接採用雲端儲存。
43、謹慎採用檔案系統,接收不受信任的內容
如果允許使用者寫入伺服器的檔案系統,可能會出現各種各樣的問題。改用雲端儲存,或者在資料庫中使用二進位制 blob。
如果您必須訪問磁碟,則應遵循以下指導原則:
-
不要讓不受信任的資料影響內部檔案路徑。
-
將檔案儲存在遠離 webroot 的隔離目錄中。
-
在寫入磁碟之前,請驗證檔案內容是否與預期格式匹配。
-
正確設定檔案系統許可權以防止寫入不需要的位置。
-
不要提取壓縮包(例如 ZIP),因為它們可以包含任何檔案,包括指向系統任意地方的連結和路徑。
44、不要動態執行程式碼,避免遠端程式碼執行漏洞
不要使用 eval 或等效函式。找到一種其它的方法來實現程式碼執行。否則,不受信任的資料將有可能進行函式呼叫,從而在有機會在伺服器上執行惡意程式碼。
45、合理採用序列化,避免反序列化漏洞
對不受信任的資料進行反序列化是很危險的,很容易導致遠端程式碼執行。
-
如果可以避免,不要使用序列化。
-
如果可以在伺服器端序列化物件,則對其進行數字簽名。當需要再次反序列化它們時,請在繼續反序列化之前驗證簽名。
-
使用一些主流的軟體元件,並保持更新。許多反序列化庫會一直被發現漏洞。GSon 是個不錯的選擇。
-
使用簡單的文字格式,如 JSON,而不是二進位制格式。此外,應該避免像XML這樣有問題的格式,因為這樣除了反序列化之外,還需要擔心 XML 漏洞。
-
在處理序列化物件之前驗證它。例如:對於 JSON,在繼續反序列化之前,根據嚴格的 JSON 模式驗證 JSON 文件。
2.2 基礎設施
46、採用網路應用程式防火牆(WAF)
安裝防火牆,會減少很多風險。ModSecurity 就是一個很好的開源選擇。
47、配置 Web 伺服器,避免 HTTP desync 攻擊
HTTP desync,也稱 HTTP 請求走私攻擊,是指攻擊者劫持隨機使用者向系統發出的 HTTP 請求。這類攻擊一般在以下情況下發生:
-
前端伺服器,比如負載均衡器或反向代理伺服器,接受攜帶有 Content-length、Transfer-Encoding 等頭部引數的請求時,將請求未經處理隨即傳遞到後臺;
-
後臺接受該請求的伺服器(通常是應用伺服器),採用(或被欺騙採用)一個不同於前端伺服器的機制來確定 HTTP 請求從何處開始、何處結束,比如前端伺服器使用 Content-Length,而應用伺服器採用 Transfer-Encoding;
-
前端伺服器重複利用與後端伺服器的連線;
-
前端伺服器在與後臺伺服器連線時採用 HTTP/1(而非 HTTP/2)。
那麼該如何進行防範呢?一般是根據所採用的產品:
-
諮詢所採用的反向代理產品供應商,確保該產品具備主動防範攻擊的能力;
-
配置前端伺服器,在與後臺連線時採用 HTTP/2;
-
配置前端伺服器,防止利用同一個連線傳送多個客戶端的 HTTP 請求;
-
採用網路應用程式防火牆(WAF),並確保其具備防止請求走私的模組。
48、採用容器
讓目標應用隔離其他應用來執行。這樣,即使發生了攻擊事件,攻擊者也不會有許可權去訪問未經許可的檔案、系統或網路資源。因此,最好使用 Kubernetes 或一個雲端環境來部署你的應用。如果因為某種原因必須使用一臺伺服器,那麼可以手動採用 Docker 來約束應用。
49、使用 SELinux/AppArmor
即使通過容器來執行應用,也還是需要進一步採用 SELinux 或 AppArmor 策略來進一步地對應用做出約束,從而減少容器漏洞引發的威脅。
50、採用最少許可權的服務賬戶
這種方法帶來的好處是即使發生了被攻擊事件,也能減少被攻擊造成的損失。再次重申,列出所有的情形是不可能的,這裡僅列舉一些例子幫助大家理解:
- 即使使用了 Docker,甚至是使用了 SELinux/AppArmor,不要用 root 賬戶來執行你的應用。為你的應用單獨建立一個具備儘可能少的許可權的賬戶,從而降低攻擊者利用容器或核心漏洞等進行攻擊的可能性;
- 如果有使用資料庫,確保應用程式中的資料庫使用者在訪問資料庫時具備儘可能少的許可權;
- 如果應用中整合了 API,確保應用訪問 API 時具備儘可能少的許可權。
51、限制外部網路連線
攻擊者通常需要建立一定的反向通訊渠道來建立操控渠道或竊取資料。此外,一些漏洞也是需要外部網路連線才會被發現、被利用。
因此,不能讓應用隨便訪問外部網路,包括 DNS。試下在伺服器執行命令 nslookup www.example.com
,如果執行成功,則說明你沒有對外部網路連線做出適當的限制。如何處理此類問題,一般則取決於基礎設施。
針對外部的 TCP/UDP/ICMP 連線,一般可以通過以下方式禁用:
- 閘道器防火牆,如果有的話;
- 如果是老式伺服器,可以採用本地的防火牆(例如 iptables 或 Windows 防火牆);
- 如果伺服器端採用 Docker,可以使用 iptables;
- 如果使用了 Kubernetes,可採用網路策略定義。
DNS 處理起來稍微麻煩一點,我們通常需要允許對一些 hosts 的訪問。
- 如果有本地的 hosts 檔案,那就很簡單,可以採取上面的任何一種方式來將 DNS 徹底禁用;
- 如果沒有,那麼你需要在你上游的 DNS 中配置一個私有的區域,在網路層限制僅能訪問該指定的 DNS 伺服器。這個私有區域內只允許對一些預先指定的 hosts 的訪問。
52、跟蹤 DNS 記錄,防止子域名劫持
子域名劫持發生場景舉例如下:
-
假如我們擁有一個域名 example.com;
-
針對一次促銷活動,我們買了另一個域名 www.my-cool-campaign.com ,然後建立了一個別名從 campaign.example.com 對映到 www.my-cool-campaign.com;
-
這次促銷活動結束後,www.my-cool-campaign.com 域名也到期了;
-
但是,從 campaign.example.com 到 www.my-cool-campaign.com 的別名對映仍存在;
-
如果有人購買了這個到期的域名,那麼 campaign.example.com 便可以直接指向該域名;
-
如果攻擊者在 www.my-cool-campaign.com 域名下提供一些惡意內容,那麼便可以通過 https://campaign.example.com 域名直接訪問到;
因此,需要隨時留意你的 DNS 記錄。如果需要處理的類似情況較多,強烈建議你做一個自動監控方案。
2.3 架構
53、建立內部 API 用來訪問資料來源
對連線網際網路的網路應用程式不應該太過於信任。例如,不應允許它進行資料庫直連。否則,當有人攻破應用程式時,整個資料庫都將面臨威脅。
相反,我們應該搭建多元件組成的架構,例如:
-
我們域名為 www.example.com 的應用程式使用 auth0 進行鑑權。
-
該應用程式訪問內部 API 服務 api.example.local 時,攜帶被授權使用者的 token,放在請求頭部的 Authorization 中。
-
位於 api.example.local 的 API 服務根據使用者的 token 進行訪問限制,進而根據被授予的許可權讀寫資料庫。
假如現在有黑客想要攻破我們的應用程式,即使成功,他也沒有許可權訪問整個資料庫,而只是利用某個使用者的 token,進而訪問該 token 所允許訪問的那部分資料。
54、內部連線也需加密和驗證
不要盲目相信內網的安全性,有很多方法可以攻破它。對於系統間的訪問,全部採用 TLS(也就是 HTTPS)進行加密,最好在網路和系統兩個層次對連線進行鑑權。
55、對敏感資訊集中管理
如果沒有采用合適的敏感資訊管理方案,就很難保持授權的短期性化、可審計性和祕密性。因此,建議採用 HashiCorp Vault 一類的工具來集中管理密碼、加密 key 等類似資訊。
2.4 監控
56、收集,分析,報警
集中收集日誌到一個獨立系統,比如 SIEM(安全資訊和事件監控系統)。在這個系統中,可以在一些表徵脆弱性、攻擊的事件發生時進行報警。當嚴重威脅發生時,可以立即通知相關人員。
57、收集系統安全事件
最重要的日誌來源可能就是系統自身了。當有可疑行為發生時,系統應能引發異常,記錄事件,可能的話,甚至可以自動封鎖可能帶來問題的使用者或IP地址。常見可疑行為包括:
- 輸入值的合法性驗證錯誤(例如,試圖輸入 UI 中不可能提供的值)
- 訪問控制錯誤 (例如,嘗試訪問一條在 UI 中不可能出現的記錄)
- 資料庫語法錯誤表示某個人發現了一處 SQL 注入的脆弱性,這時候可要動作快點採取行動了
- XML 錯誤表示某個人發現了一處 XML 注入的脆弱性,或者正嘗試利用 XXE(XML 外部實體)脆弱性進行攻擊
- 錯誤請求表示使用者可能傳送了被應用拒絕的請求。Spring 框架的 RequstRejectedException 就是一個例子
- 反跨站請求偽造令牌驗證錯誤一般表示有人正嘗試尋找系統中存在的脆弱性
58、收集執行時安全日誌
使用執行時安全監控工具如 Falco 來對異常系統訪問進行檢測。如果採用了 Kubernetes,那麼 Falco 就特別有用。遠端也可以對日誌進行收集和監控。
59、收集 SELinux/AppArmor 日誌
假如我們制定了 SELinux 策略防止向外部的連線,但系統忽然向外部某個網站(例如 burpcollaborator.net)發起 HTTP 請求,那就需要立刻引起關注。又或者你的系統嘗試訪問 /etc/passwd。這兩種情況都表示有人已經發現了我們系統中的漏洞。
60、收集 Web 伺服器事件
對 Web 伺服器軟體,至少要對訪問日誌和錯誤日誌進行收集,收集後傳送到集中式的日誌伺服器。在突發事件響應時,這將輔助我們快速理清時間線。
61、收集網路應用程式防火牆(WAF)日誌
如果你像上文推薦使用了網路應用程式防火牆(WAF),那麼也對這個日誌進行收集。但不用針對這個日誌設定報警,因為它基本上會收到來自網際網路各種各樣的問題,而且不部分是你不用擔心的。
2.5 事件響應
62、制定應對計劃
一旦對我們的系統進行了監控和加固,攻擊者將難以快速定位系統漏洞,即使最終發現,我們也能快速瞭解情況。
但僅瞭解情況是不夠的,還需要做出如下準備:
- 快速分析系統日誌,瞭解當前狀況和需採取的對應措施
- 在應用防火牆等產品中,快速對個別 url 地址和引數做出限制
- 如有需要,快速關停系統
六、開發管理
63、威脅模型
系統地考慮一下“可能會出現哪些問題”並據此做出調整。設計一個新的系統時,越早開始這一步越好。當對系統發生改變時,再重新梳理一遍這個過程。
例如:
小王:如果攻擊者攻破了我們連線了網際網路的伺服器,怎麼辦?
小陳:那可就完蛋了!
小王:好吧!這就說明我們在這裡存在著一個信任關係,我們認為連線了網際網路的伺服器是不會被攻破的。我們可以信任這一點嗎?
小陳:未必吧!有一百種可能導致我們的伺服器被黑掉,例如我們程式碼中存在的脆弱性,或者依賴中存在的脆弱性,或者是我們 Web 伺服器所安裝軟體的脆弱性。
小王:好吧!那就讓我們打破這層信任關係。接下來該做些什麼呢?
小陳:我們這樣來分解一下系統:建立一些內部的介面用來實際訪問資料庫,由此以來,前端的 Web 伺服器就不能直接訪問後臺的所有東西。
小王:這是個好辦法!除此以外,還有其它什麼可能出問題呢?
小陳:嗯,如果黑客攻破了我們的內網呢?
小王:那所有東西都要丟失了,因為內網裡伺服器之間的連線都是未加密的。
小陳:……
這就是威脅模型,它不需要多麼複雜。使用這種方式,來找出系統中可能存在的威脅。
64、原始碼強制審查
通過技術控制手段,防止程式碼未經他人稽核便提交入庫。這是構建安全開發環境的基礎,因為它可以做到:
-
如果攻擊者攻陷了一個開發人員的電腦,或者是開發人員自身企圖發起攻擊,將不能直接將惡意程式碼遷入程式碼庫;
-
如果開發人員的錯誤導致引入了有漏洞的程式碼,很可能在被其他人檢查時及時發現。
65、自動化持續整合管道,僅允許簡單訪問
開發人員應該有許可權觸發 Jenkins 構建,且 Jenkins 許可權配置也僅該如此,不要再允許其它許可權。單個開發人員應該不能在構建階段引入任意程式碼。當然,如果像上文推薦的強制性地採用了程式碼審查,Jenkinsfile 也可以儲存在版本管理工具中。
66、對 artifacts 進行簽名
如果是構建容器映象,可以把對映象簽名作為構建的一步。將簽名金鑰儲存在安全的地方。構建階段需要訪問金鑰,但是杜絕將金鑰與 Jenkinsfile 一起儲存在版本管理工具中。更好的方式是將金鑰儲存在 HashiCorp Vault 之類的地方,然後在構建時再進行拉取。
67、持續整合管道中加入靜態應用程式掃描器
在持續整合管道中使用 SpotBugs 和 Find-Sec-Bugs(或者根據你所採用的技術棧進行選擇)之類的工具。它們可以幫你在部署程式碼之前發現已知的漏洞。
此外,也可以作為 IDE 的外掛安裝在開發人員的電腦上,在程式碼遷入之前就執行這些工具進行檢查。
68、構建時對依賴進行檢查,保證最小的依賴集
應用程式中依賴的每個軟體包都是一個風險來源。通過依賴,我們拉取了第三人的程式碼並在我們的應用伺服器上執行,所以,必須要搞清楚我們依賴的這個軟體包是什麼,為什麼會依賴它?
-
保持最小的依賴集;
-
僅使用我們所信任的依賴。它們必須是廣泛使用和廣為人知的;
-
採用構建框架,對依賴進行確認。
此外,嚴格控制應用伺服器的對外連線,從而避免後門的存在。
69、對依賴進行安全掃描
使用 OWASP 依賴檢查工具對依賴中常見的安全問題進行掃描。除了在持續整合管道中,也可以在開發人員的開發環境執行這些工具。
70、持續整合管道對映象進行安全掃描
如果採用了容器化技術,可以使用 Trivy 等工具對容器映象進行一些常規漏洞的掃描。
71、自動化部署和簽名驗證
開發人員可以有許可權到生產環境中部署,但是許可權範圍應該控制在前階段已經構建和簽名過的特定映象,而不是直接訪問生產伺服器。如果是使用 Kubernetes,可以通過 Notary 或開放策略代理來驗證待部署映象的簽名。
72、設定一個安全人員
一個人的精力是有限的。我們不能期望每個開發人員都精通滲透測試或是安全工程師。正如你不能期望所有的安全專家都是優秀的開發人員一樣。因此,可以在團隊中設定一個專門關注安全的人員,主要與開發人員、架構師進行交流,幫助保護我們的應用程式並在團隊中傳播安全意識。
三、結論
保證應用程式的安全性,光靠避免漏洞時不夠的,必須全面通盤考慮,主動進行防禦。這裡對一些主要方法進行了總結:
-
使用最新版本的的軟體元件來執行危險的操作,如身份驗證、訪問控制、加密、訪問資料庫或解析 XML,並確保正確配置了這些元件,例如 XML 解析時禁用外部實體。
-
使用平臺提供的安全控制,例如反跨站請求偽造保護。
-
使用 Web 瀏覽器提供的安全控制元件,如 HSTS、SameSite Cookie 和內容安全策略。
-
對安全控制進行集中化處理,特別是身份驗證和訪問控制,從而避免一些遺漏,如在某些控制器方法上忘記對安全進行控制。
-
使用 Web 應用程式防火牆,防止應用程式漏洞被發現和被利用。
-
通過限制對檔案、網路和系統資源的訪問來對應用程式進行限制。
-
利用威脅模型發現架構中的威脅,並相應進行處理。既包括在原始碼層面對每個開發人員的原始碼進行安全控制,也包括在架構層面對前端 Web 伺服器的安全控制。
-
對系統進行監控,制定異常處理預案。
-
在開發環境和持續整合環境中使用漏洞掃描程式對程式碼、映象、依賴進行掃描。
-
對開發人員、架構師等開展安全培訓,並在團隊中配備一名安全人員。
如果你看到最後一定是收穫頗豐,這裡還有一個收穫更多知識的方法,但比讀完這篇文章要難得多,加入我們一起變強!
變強之路充滿荊棘,所以強者才受人尊敬