本博客Nginx 配置之安全篇

之前有細心的朋友問我,為什麼你的博客副標題是「專注WEB 端開發」,是不是少了「前端」的「前」。我想說的是,儘管我從畢業到現在七年左右的時間一直都在專業前端團隊從事前端相關工作,但這並不意味著我的知識體係就必須局限於前端這個範疇內。現在比較流行「全棧工程師」的概念,我覺得全棧意味著一個項目中,各個崗位所需要的技能你都具備,但並不一定意味著你什麼都需要做。你需要做什麼,更多是由能力、人員配比以及成本等各個因素所決定。儘管我現在的工作職責是在WEB 前端領域,但是我的關注點在整個WEB 端。

我接觸過的有些前端朋友,從一開始就把自己局限在一個很小的範圍之中,這在大公司到也無所謂,大公司分工明確,基礎設施齊全,你只要做好自己擅長的那部分就可以了。但是當他們進入創業公司之後,會發現一下子來了好多之前完全沒有接觸過的東西,十分被動。

去年我用Lua + OpenResty替換了線上千萬級的PHP + Nginx服務,至今穩定運行,算是前端之外的一點嘗試。我一直認為學習任何知識很重要的一點是實踐,所以我一直都在折騰我的VPS,進行各種WEB安全、優化相關的嘗試。我打算從安全和性能兩方面介紹一下本博客所用Nginx的相關配置,今天先寫安全相關的。

隱藏不必要的信息

大家可以看一下我的博客請求響應頭,有這麼一行server: nginx,說明我用的是Nginx服務器,但並沒有具體的版本號。由於某些Nginx漏洞只存在於特定的版本,隱藏版本號可以提高安全性。這只需要在配置裡加上這個就可以了:

server_tokens   off;

如果想要更徹底隱藏所用Web Server,可以修改Nginx源碼,把Server Name改掉再編譯,具體步驟可以自己搜索。需要提醒的是:如果你的網站支持SPDY,只改動網上那些文章寫到的地方還不夠,跟SPDY有關的代碼也要改。更簡單的做法是改用Tengine這個Nginx的增強版,並指定server_tag為off或者任何想要的值就可以了。另外,既然想要徹底隱藏Nginx,404、500等各種出錯頁也需要自定義。

同樣,一些WEB語言或框架默認輸出的x-powered-by也會洩露網站信息,他們一般都提供了修改或移除的方法,可以自行查看手冊。如果部署上用到了Nginx的反向代理,也可以通過proxy_hide_header指令隱藏它:

proxy_hide_header        X-Powered-By;

禁用非必要的方法

由於我的博客只處理了GET、POST 兩種請求方法,而HTTP/1 協議還規定了TRACE 這樣的方法用於網絡診斷,這也可能會暴露一些信息。所以我針對GET、POST 以及HEAD 之外的請求,直接返回了444 狀態碼(444 是Nginx 定義的響應狀態碼,會立即斷開連接,沒有響應正文)。具體配置是這樣的:

NGINXif ($request_method !~ ^(GET|HEAD|POST)$ ) {
    return    444;
}

合理配置響應頭

我的博客是由自己用ThinkJS 寫的Node 程序提供服務,Nginx 通過proxy_pass 把請求反向代理給Node 綁定的IP 和端口。在最終輸出時,我給響應增加了以下頭部:

NGINXadd_header  Strict-Transport-Security  "max-age=31536000";
add_header  X-Frame-Options  deny;
add_header  X-Content-Type-Options  nosniff;
add_header  Content-Security-Policy  "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://a.disquscdn.com; img-src 'self' data: https://www.google-analytics.com; style-src 'self' 'unsafe-inline'; frame-src https://disqus.com";

Strict-Transport-Security(簡稱為HSTS)可以告訴瀏覽器,在指定的max-age內,始終通過HTTPS訪問我的博客。即使用戶自己輸入HTTP的地址,或者點擊了HTTP鏈接,瀏覽器也會在本地替換為HTTPS再發送請求。另外由於我的證書不支持多域名,我沒有加上includeSubDomains。關於HSTS更多信息,可以查看我之前的介紹

X-Frame-Options用來指定此網頁是否允許被iframe嵌套,deny就是不允許任何嵌套發生。關於這個響應頭的更多介紹可以看這裡

X-Content-Type-Options用來指定瀏覽器對未指定或錯誤指定Content-Type資源真正類型的猜測行為,nosniff表示不允許任何猜測。這部分內容更多介紹見這裡

Content-Security-Policy(簡稱為CSP)用來指定頁面可以加載哪些資源,主要目的是減少XSS的發生。我允許了來自本站、disquscdn的外鏈JS,還允許內聯JS,以及在JS中使用eval;允許來自本站和google統計的圖片,以及內聯圖片(Data URI形式);允許本站外鏈CSS以及內聯CSS;允許iframe加載來自disqus的頁面。對於其他未指定的資源,都會走默認規則self,也就是只允許加載本站的。關於CSP的詳細介紹請看這裡

之前的博客中,我還介紹過X-XSS-Protection這個響應頭,也可以用來防範XSS。不過由於有了CSP,所以我沒配置它。

需要注意的是,以上這些響應頭現代瀏覽器才支持,所以並不是說加上他們,網站就可以不管XSS,萬事大吉了。但是鑑於低廉的成本,還是都配上。

HTTPS 安全配置

啟用HTTPS 並正確配置了證書,意味著數據傳輸過程中無法被第三者解密或修改。有了HTTPS,也得合理配置好Web Server,才能發揮最大價值。我的博客關於HTTPS 這一塊有以下配置:

NGINXssl_certificate      /home/jerry/ssl/server.crt;
ssl_certificate_key  /home/jerry/ssl/server.key;
ssl_dhparam          /home/jerry/ssl/dhparams.pem;

ssl_ciphers          ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:DES-CBC3-SHA;

ssl_prefer_server_ciphers  on;

ssl_protocols        TLSv1 TLSv1.1 TLSv1.2;

最終效果是我的博客在ssllabs的測試中達到了A+,如下圖:

ssllabs test

如何配置ssl_ciphers可以參考這個網站。需要注意的是,這個網站默認提供的加密方式安全性較高,一些低版本客戶端並不支持,例如IE9-、Android2.2-和Java6-。如果需要支持這些老舊的客戶端,需要點一下網站上的「Yes, give me a ciphersuite that works with legacy / old software」鏈接。

另外,我在ssl_ciphers最開始加上了CHACHA20,這是因為我的Nginx支持了CHACHA20_POLY1305加密算法,這是由Google開發的新一代加密方式,它有兩方面優勢:更好的安全性和更好的性能(尤其是在移動和可穿戴設備上)。下面有一張移動平台上它與AES-GCM的加密速度對比圖(via):

chacha20 poly1305

啟用CHACHA20_POLY1305最簡單的方法是在編譯Nginx時,使用LibreSSL代替OpenSSL。下面是用Chrome訪問我的博客時,點擊地址欄小鎖顯示的信息,可以看到加密方式使用的就是CHACHA20_POLY1305:

imququ.com

關於CHACHA20_POLY1305安全性和性能的詳細介紹可以查看本文

補充:使用CHACHA20_POLY1305的最佳實踐是「僅針對不支持AES-NI的終端使用CHACHA20算法,否則使用AES-GCM」。關於這個話題的詳細解釋和配置方法,請參考我的這篇文章:使用BoringSSL優化HTTPS加密算法選擇

關於ssl_dhparam的配置,可以參考這篇文章:Guide to Deploying Diffie-Hellman for TLS

SSLv3已被證實不安全,所以在ssl_protocols指令中,我並沒有包含它。

ssl_prefer_server_ciphers配置為on,可以確保在TLSv1握手時,使用服務端的配置項,以增強安全性。

好了,本文先就這樣,後面再寫跟性能有關的配置。

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.