如何一步步構建安全的 HTTPS 站點

通常一個 web 站點開啟 HTTPS ,以 nginx 為例,我們可以這樣進行配置:

server {
listen 443 ssl http2;
server_name www.example.com;
index index.html index.htm;
root /www/www;
ssl on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_certificate /usr/local/nginx/ssl/example.com.rsa.cer;
ssl_certificate_key /usr/local/nginx/ssl/example.com.rsa.key;
ssl_ciphers AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5;;
}

上述 nginx 配置中包含了配置監聽端口、開啟ssl、配置證書、以及支持的加密算法。一般來說用戶訪問域名不可能直接在瀏覽器的地址欄中輸入 https://www.example.com 來進行訪問,而是輸入域名,默認情況下是通過 HTTP 協議來進行訪問的,即 http://www.example.com,因此,在nginx 的配置中我們還需要定義一個server 段來處理 HTTP 的訪問。

server {
listen 80 default_server;
server_name _ www.example.com;
location / {
return 302 https://$host$request_uri;
}
}

上述配置中監聽了 80端口,並且定義了一個 location,將 HTTP 請求 302 跳轉到 HTTPS 的Host 去。這樣就實現了用戶不管怎麼訪問都可以跳轉到 HTTPS 。

但是問題來了,這樣的配置其實是有缺陷的,如果用戶端從瀏覽器手動輸入的是 HTTP 地址,或者從其它地方點擊了網站的 HTTP 鏈接,那麼瀏覽器會依賴於服務端 301/302 跳轉才能使用 HTTPS 服務。而第一次的 HTTP 請求就有可能被劫持,因為中間的數據傳輸是明文的,就有可能會導致請求無法到達服務器,從而構成 HTTPS 降級劫持。

要解決降級劫持,我們可以使用HSTS。

什麼是 HSTS?

HSTS(HTTP Strict Transport Security,HTTP 嚴格傳輸安全),是一套由互聯網工程任務組發佈的互聯網安全策略機制。網站可以通過配置 HSTS,來強制瀏覽器使用 HTTPS 與網站通信,保障網站更加安全。

HSTS的作用是強制客戶端(如瀏覽器)使用HTTPS與服務器創建連接。服務器開啟HSTS的方法是,當客戶端通過HTTPS發出請求時,在服務器返回的超文本傳輸協議響應頭中包含 `Strict-Transport-Security` 字段。非加密傳輸時設置的`HSTS`字段無效。

比如,`https://example.com/`的響應頭含有`Strict-Transport-Security: max-age=31536000; includeSubDomains`。這意味著兩點:

在接下來的一年(即31536000秒)中,瀏覽器只要向`example.com`或其子域名發送HTTP請求時,必須採用`HTTPS`來發起連接。比如,用戶點擊超鏈接或在地址欄輸入 `http://www.example.com/` ,瀏覽器應當自動將 http 轉寫成 `https`,然後直接向 `https://www.example.com/` 發送請求。

在接下來的一年中,如果 `example.com` 服務器發送的`TLS`證書無效,用戶不能忽略瀏覽器警告繼續訪問網站。

如何進行配置?

以 nginx 為例,我們在對應域名的 vhost 中增加響應頭:

server {
....
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
...
}

參數解釋:

  • max-age,單位是秒,用來告訴瀏覽器在指定時間內,這個網站必須通過 HTTPS 協議來訪問。也就是對於這個網站的 HTTP 地址,瀏覽器需要先在本地替換為 HTTPS 之後再發送請求。
  • includeSubDomains,可選參數,如果指定這個參數,表明這個網站所有子域名也必須通過 HTTPS 協議來訪問。
  • preload,可選參數,HSTS 這個響應頭只能用於 HTTPS 響應;網站必須使用默認的 443 端口;必須使用域名,不能是 IP。而且啟用 HSTS 之後,一旦網站證書錯誤,用戶無法選擇忽略。

瀏覽器請求後響應頭中會顯示:

strict-transport-security:max-age=31536000

如圖所示:

如何一步步構建安全的 HTTPS 站點

HSTS 可以很好地解決 HTTPS 降級攻擊,但是對於 HSTS 生效前的首次 HTTP 請求,依然無法避免被劫持。瀏覽器廠商們為了解決這個問題,提出了 HSTS Preload List 方案:內置一份可以定期更新的列表,對於列表中的域名,即使用戶之前沒有訪問過,也會使用 HTTPS 協議。

目前這個 Preload List 由 Google Chrome 維護,Chrome、Firefox、Safari、IE 11 和 Microsoft Edge 都在使用。如果要想把自己的域名加進這個列表,首先需要滿足以下條件:

  • 擁有合法的證書(如果使用 SHA-1 證書,過期時間必須早於 2016 年);
  • 將所有 HTTP 流量重定向到 HTTPS;
  • 確保所有子域名都啟用了 HTTPS;
  • 輸出 HSTS 響應頭:
  • max-age 不能低於 18 周(10886400 秒);
  • 必須指定 includeSubdomains 參數;
  • 必須指定 preload 參數;

但是,即便滿足了上述所有條件,也不一定能進入 HSTS Preload List,更多信息可以看這裡(https://hstspreload.org/)。通過 Chrome 的 chrome://net-internals/#hsts 工具,可以查詢某個網站是否在 Preload List 之中,還可以手動把某個域名加到本機 Preload List。

對於 HSTS 以及 HSTS Preload List,我的建議是隻要你不能確保永遠提供 HTTPS 服務,就不要啟用。因為一旦 HSTS 生效,你再想把網站重定向為 HTTP,之前的老用戶會被無限重定向,唯一的辦法是換新域名。

如果確定要開啟,點擊https://hstspreload.org,輸入你的域名,勾選協議,提交即可。

如何一步步構建安全的 HTTPS 站點

確認後,你就可以將你的域名提交給 HSTS 預加載列表了:

如何一步步構建安全的 HTTPS 站點

提交成功後會給你返回成功的信息,不過你要保證你的配置比如是一直開啟了,否則也會從列表中刪除。

如何一步步構建安全的 HTTPS 站點

再次訪問,查看瀏覽器響應頭:

如何一步步構建安全的 HTTPS 站點

此外,我們要做到讓HTTPS 網站更安全更快速,還應當做到以下幾點:

第一,密鑰要足夠的複雜,以rsa 密鑰對為例,最好超過2048位;

第二,ssl_ciphers 的合理配置,儘量拋棄那些已經被證明不安全的加密算法,使用較新的被證明無安全威脅的算法,例如可以這樣配置:

 ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:TLS-CHACHA20-POLY1305-SHA256:TLS-AES-256-GCM-SHA384:TLS-AES-128-GCM-SHA256:EECDH+CHACHA20:EECDH+AESGCM:EECDH+AES:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!KRB5:!aECDH:!EDH+3DES;

第三,避免使用已經被證明不安全的加密協議,例如 SSLV2和SSLV3 ,而使用 TLSv1.2 TLSv1.3;

ssl_protocols TLSv1.2 TLSv1.3;

一般來說較新的協議都是針對上一個版本進行了很多的優化,比如TLS1.2和TLS1.3協議,可以看下這2個協議的加密過程,首先,我們看下 TLS1.2的加密過程:

如何一步步構建安全的 HTTPS 站點

以 ECDHE 密鑰交換算法為例,TLS1.2協議完整的SSL握手過程如下:

  • 第一步,首先客戶端發送ClientHello消息,該消息中主要包括客戶端支持的協議版本、加密套件列表及握手過程需要用到的ECC擴展信息;
  • 第二步,服務端回覆ServerHello,包含選定的加密套件和ECC擴展;發送證書給客戶端;選用客戶端提供的參數生成ECDH臨時公鑰,同時回覆ServerKeyExchange消息;
  • 第三步,客戶端接收ServerKeyExchange後,使用證書公鑰進行簽名驗證,獲取服務器端的ECDH臨時公鑰,生成會話所需要的共享密鑰;生成ECDH臨時公鑰和ClientKeyExchange消息發送給服務端;
  • 第四步,服務器處理ClientKeyExchange消息,獲取客戶端ECDH臨時公鑰;服務器生成會話所需要的共享密鑰;發送密鑰協商完成消息給客戶端;
  • 第五步,雙方使用生成的共享密鑰對消息加密傳輸,保證消息安全。

可以看到,TLS1.2 協議中需要加密套件協商、密鑰信息交換、ChangeCipherSpec 協議通告等過程,需要消耗 2-RTT 的握手時間,這也是造成 HTTPS 協議慢的一個重要原因之一。

通過抓包分析,我們可以看到他的整個加密過程:

如何一步步構建安全的 HTTPS 站點

接下來,我們看下 TLS 1.3 的的交互過程,如圖所示:

如何一步步構建安全的 HTTPS 站點

抓包得後如圖所示,可以看到客戶端的整個加密過程:

如何一步步構建安全的 HTTPS 站點

在 TLS 1.3 中,客戶端首先不僅發送 ClientHello 支持的密碼列表,而且還猜測服務器將選擇哪種密鑰協商算法,併發送密鑰共享,這可以節省很大一部分的開銷,從而提高了速度。

TLS1.3 提供 1-RTT 的握手機制,還是以 ECDHE 密鑰交換過程為例,握手過程如下。將客戶端發送 ECDH 臨時公鑰的過程提前到 ClientHello ,同時刪除了 ChangeCipherSpec 協議簡化握手過程,使第一次握手時只需要1-RTT,來看具體的流程:

  • 客戶端發送 ClientHello 消息,該消息主要包括客戶端支持的協議版本、DH密鑰交換參數列表KeyShare;
  • 服務端回覆 ServerHello,包含選定的加密套件;發送證書給客戶端;使用證書對應的私鑰對握手消息簽名,將結果發送給客戶端;選用客戶端提供的參數生成 ECDH 臨時公鑰,結合選定的 DH 參數計算出用於加密 HTTP 消息的共享密鑰;服務端生成的臨時公鑰通過 KeyShare 消息發送給客戶端;
  • 客戶端接收到 KeyShare 消息後,使用證書公鑰進行簽名驗證,獲取服務器端的 ECDH 臨時公鑰,生成會話所需要的共享密鑰;
  • 雙方使用生成的共享密鑰對消息加密傳輸,保證消息安全。

如果客戶端之前已經連接,我們有辦法在 1.2 中進行 1-RTT 連接,而在 TLS 1.3 中允許我們執行 0-RTT連接,如圖所示:

如何一步步構建安全的 HTTPS 站點

當然,具體採用 TLS1.2 還是 TLS1.3 需要根據實際的業務場景和用戶群體來決定,在較新版本的瀏覽器一般都支持最新的加密協議,而類似 IE 8 以及Windows xp 這種古老的瀏覽器和操作系統就不支持了。如果說你的用戶是一些政府部門的客戶,那麼就不適合採用這種較新的技術方案了,因為據我所知很多政府部門的操作系統還是xp和 IE 8以下的版本,這會導致新協議無法在他們的操作系統中正常工作。因此你可以講加密算法和加密協議多配置幾個,向下兼容不同客戶端。

第四,證書要從可靠的CA廠商申請,因為不可靠的廠商(比如不被主流瀏覽器信任的證書廠商)會亂修改證書日期,重複簽發證書。此外即使是可靠的 CA 簽發的證書也有可能是偽造的,比如賽門鐵克之前就被曝出醜聞而被火狐和Chrome 懲罰,結果就是這些主流瀏覽器不在信任這些CA 機構簽發的一部分證書。因此一旦發現證書不受信任要儘快替換。

如何一步步構建安全的 HTTPS 站點

第五,使用完整的證書鏈,如果證書鏈不完整,則很有可能在一些版本的瀏覽器上訪問異常。

第六,使用HTTP/2,使用最新的 HTTP 2 可以提升網站的訪問速度以及擁有更好的性能支持。

第七,保護證書私鑰不被外洩。

第八,根據自己的業務需求選擇合適的證書,證書分為自簽證書、 DV、 EV 和OV 證書,一般來說只是需要進行簡單的數據加密,採用 DV 證書即可,這類證書通常都可以免費申請,只需要進行簡單的域名所有者權驗證即可申請,而EV和OV證書一般價格昂貴,適合金融機構或針對數據加密有嚴格要求的單位使用,這類證書籤發手續複雜,一般需要進行企業身份認證後才會簽發。自簽證書一般用戶臨時測試使用,不建議生產環境使用,因為它並不是受信任的CA 機構簽發的,瀏覽器不會信任。

如何一步步構建安全的 HTTPS 站點

當我們配置完後,可以通過https://www.ssllabs.com/ssltest/ ,對你的 HTTPS 站點進行評分,如果是A+則說明你的站點安全性特別高。如圖所示,如果評分不高,你可以查看具體的詳情來針對你的站點進行更具體的優化。

如何一步步構建安全的 HTTPS 站點

最後,附上一份nginx 的配置,作為參考:

server
{
listen 443 ssl http2 default_server;
server_name www.example.com ;
index index.html index.htm index.php;

root /web;
ssl on;
ssl_certificate /nginx/ssl/awen/fullchain.cer;
ssl_certificate_key /nginx/ssl/example/example.com.key;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:TLS-CHACHA20-POLY1305-SHA256:TLS-AES-256-GCM-SHA384:TLS-AES-128-GCM-SHA256:EECDH+CHACHA20:EECDH+AESGCM:EECDH+AES:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!KRB5:!aECDH:!EDH+3DES;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets on;
resolver 114.114.114.114 valid=300s;
resolver_timeout 10s;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
add_header X-Frame-Options deny;
add_header X-Content-Type-Options nosniff;
add_header CIP $http_x_real_ip;
add_header Accept-Ranges bytes;
}
server {
listen 80;
server_name _;
server_name www.example.com ;
return 302 https://$host$request_uri;
}

好了,以上就是我給大家分享的關於 HTTPS 站點的優化建議。


分享到:


相關文章: