應用開發中的網路安全
最近有個朋友讓我幫忙看看他系統中的一個問題:他給了我一個用戶名密碼,讓我ssh到他的某台伺服器上。那是一台redis server,裡面存放資料庫查詢的緩存和其他的一些業務邏輯。問題斷斷續續花了不少時間才定位出來,期間應我的要求,朋友又給了我其它兩台伺服器的用戶名和密碼。在這個過程中,這個朋友在網路安全意識上的淡薄,可能也是很多web/mobile app開發者共通的問題。去年我有篇文章講安全,主要集中在社工方面,今天,則講講網路安全。
在上面的例子里,朋友犯下了好幾個常見的錯誤:
伺服器直接通過公有IP暴露在互聯網上。
管理埠(ssh port)沒有對源IP做任何限制。
使用用戶名+密碼的方式而非用戶名+密鑰的方式登入。
下面我們講講這些錯誤的應對之策。
不要暴露不必要的服務
網路設計有個著名的原則:最小許可權原則。最小許可權原則要求計算環境中的特定抽象層的每個模塊如進程、用戶或者計算機程序只能訪問當下所必需的信息或者資源。當我們賦予每一個合法動作最小的許可權,就是為了保護數據以及功能避免受到錯誤或者惡意行為的破壞。[1]
理論上講,一個互聯網服務,暴露給任何源IP的只應該是必須對外的服務,如 web server,api server(本質上也是 web server),loadbalancer 等,按埠來說,主要是 80(HTTP)/443(HTTPS)埠。除了對外的伺服器需要 public IP 外,其他任何伺服器應該應該只有 private IP。如果應用部署在AWS,那麼,只需要有 public ELB 即可;如果應用部署在自己(或者第三方)的數據中心,那麼,只需要 nginx / haproxy 等服務所在的伺服器有 public IP。
data base server 和 cache server 等內部服務,因為其只對 web server 提供服務,並不對最終用戶提供服務,一定只能有 private IP。如下圖:
每台伺服器,除必要的對外服務埠外,其他埠一律封死。如 database server 是 mysql,那麼就打開其預設的 3306 埠。打開某個埠要注意其允許訪問的源IP。即便是一台擁有 public IP 的伺服器(如 web server),使用 ssh 訪問的源 IP 也盡量只允許相同子網的內網IP。
打造安全的 ssh 服務
ssh 主要用在三種場合:
伺服器診斷
伺服器配置和軟體安裝
其他基於命令行的伺服器管理任務
如今通過 logstash / reimann 等日誌服務工具,各個伺服器上的日誌和事件都可以被集中到中心節點,方便查詢和診斷。因此,為伺服器診斷之目的而存在的 ssh 登入可以被替換。如果你使用 ssh 主要是此種目的,可以考慮選擇一個合適的日誌服務工具。
伺服器配置和軟體安裝是大多數 ssh 登入的主要舞台。但如今這樣一個高度自動化的時代,伺服器的構建應該通過各種 ops 工具(如ansible/chef等)來完成,而非手工完成。如果大量的伺服器配置和軟體安裝還通過 ssh 登入手工完成,那麼,團隊要考慮學習一種 ops 工具了。
儘管90%的場合下,伺服器診斷和伺服器的配置安裝都可以通過工具完成,但還有少量的一次性任務不得不依賴 ssh。比如服務無法訪問,也沒留下什麼日誌的時候,你可能需要在伺服器上使用 tcpdump 抓包做網路診斷。因此,臨時性的ssh訪問還是必要的。
對此,有兩個方法訪問:1) 在網路的邊界設置一台 ssh server(如上圖),2) 配置 SSL VPN。
在內網和外網的邊界處設置一台 ssh server,管理員可以通過其 public IP ssh 登錄進來,然後以其為跳板,進一步通過 ssh 登錄到內網中的其他伺服器進行管理。
專門的 ssh server,因為其可從互聯網的任意位置進行訪問,需要做一些特殊處理:登錄的用戶只擁有很小的許可權(不能sudo),不能安裝軟體,系統里可運行的軟體最好也最小化成只保留 ssh 和必要的軟體。
除此之外,可以做這些設置:
設置 ssh 登錄的方式為僅允許用戶名 + private key的方式,杜絕密碼在網路上傳輸被破解的可能性。
把 ssh port 換成一個非知名埠。這樣雖然對特定的攻擊(就是針對你的伺服器)不起作用(還是可以針對你的IP一個埠一個埠嗅探出來),但可以防止被泛泛的攻擊者鎖定(有些攻擊會嗅探全網的 22 埠,然後針對打開這一埠的伺服器再進一步攻擊)。
在 iptables 里對能夠訪問的源 IP 進行限制 —— 如無必要,請不要允許任意源 IP。
相對於專門的 SSH 伺服器,更 "professional" 的方法是配置 SSL VPN。SSL VPN的原理是在你的計算機和目標網路中建立一條隧道,使得你的計算機獲得內網IP和路由,當你發起對內網的某台伺服器的訪問時,協議棧生成的 IP 包(Inner packet)會進入 SSL VPN tunnel,封裝公網 IP 頭,並使用 TLS 加密;當數據從 tunnel 出來後,又會被解密,把 inner packet 取出分發到目的地。對於伺服器而言,你的訪問等同於一個來自內網的訪問。
配置SSL VPN的過程比較複雜,這裡就不展開了,可以照著 openvpn 的文檔一步步配置。初服務端需要安裝外,每個需要訪問內網的計算機還需要安裝 vpn 軟體(如 tunnelblick,如果要動態令牌,還需要安裝 google authenticator),並為其生成客戶端證書。整個過程很麻煩,但麻煩帶來的好處是,大部分攻擊者可以被拒之門外。
動態安全
將網路規劃成內網和外網,賦予外界最小的訪問許可權,並在必要的情況下允許用戶通過SSL VPN訪問內網,進行管理任務。這樣做,從網路安全的角度看,安全等級已經比較高了。但有些對安全性要求非常高的場合,還應該應用動態安全。
比如你有一個單獨存放用戶信用卡數據的 database cluster。該 cluster 里的伺服器,只有一個叫 tyr 的管理員才能登錄。tyr 需要通過 SSL VPN 接入到內網,獲得到達伺服器的路由,然後使用自己的私鑰通過ssh訪問該伺服器。這雖然已經足夠安全,但 ssh 的埠畢竟是一直打開的,萬一內網中某個擁有外網 IP 的伺服器(如 web server)被攻陷,攻擊者可以利用該伺服器為跳板進一步攻擊,此時,一直打開 ssh port 就存在著潛在的風險。其實 ssh 埠並不需要一直打開 —— 有沒有一種方式在想要訪問的時候才通過iptables打開該埠,訪問完畢後再關閉該埠呢?
當你想到這樣的問題時,離解決方案也就不遠了:你可以自己撰寫一個服務,比如說叫:knock-knock。在接收到某種特定的命令,如客戶端說:「天王蓋地虎」,knock-knock 服務就在 iptables 里打開這個客戶端對 ssh port 的訪問;當客戶端訪問結束後,說一句「寶塔鎮河妖」,knock-knock 服務就在 iptables 里關閉剛才打開的埠。
這進一步有個小問題:客戶端如何連接 knock-knock 服務?如果說 knock-knock 專門監聽某個埠,豈不是前驅狼後遇虎,關了 ssh port,又開了一個其他有潛在風險的 port?好問題。還記得十年前有個好玩的技術叫 Wakeup-On-LAN 么?只要在區域網內向目標機器發特定的 ethernet frame,目標機器的網卡收到之後就打開機器的電源。我們也可以使用類似的技術,向目標機器發送特定的報文,而目標機器上運行的服務使用 libpcap 監聽所有網路流量,遇到自己識別的特徵便執行相應的操作即可。
這樣的服務其實早有自己的名稱,叫 port-knocking [2],而且有開源的實現,其中一個實現是:knock [3]。
如上所述,knock 可以幫助你動態打開和關閉 ssh port,進一步增強了伺服器的安全性。它可以這樣使用:
$ knock yourserver.yourdomain.com 1111:tcp 2222:udp 3333:tcpn
當網路中依次接受到這樣三個預先定義好的對特定埠訪問的建連報文(對TCP而言,是SYN包)後,knock 會執行某個動作(打開本機的 ssh 埠)。
防火牆
大部分的雲服務商都提供了基本的網路防火牆功能,可以在網路層面限制數據的流入流出,做好了以上的安全措施,再設置合理的 firewall rule(或 ACL)你的網路已經比較安全了。如果需要更多(更高級)的安全手段,可以考慮在網路的邊緣部署軟體或硬體防火牆。這裡就不多說了。
本文所涉及的安全內容僅為網路安全。安全是個一攬子解決方案,在不同的層級上要做好不同的安全,網路再安全,如果應用層的安全沒做好,一樣會被攻擊地體無完膚。
如果您覺得這篇文章不錯,請點贊。多謝!
歡迎訂閱公眾號『程序人生』(搜索微信號 programmer_life)。每篇文章都力求原汁原味,北京時間中午12點左右,美西時間下午8點左右與您相會。
1. 見:https://zh.wikipedia.org/wiki/%E6%9C%80%E5%B0%8F%E6%9D%83%E9%99%90%E5%8E%9F%E5%88%992. 見:Port knocking3. 見:https://github.com/jvinet/knock
推薦閱讀:
※從DOTA至DOTA2中國戰隊到底拿了多少個冠軍?
※你認為Dota2 7.07版本最重要的改動是哪些?
※最近dota2比賽里為什麼發育型三號位最近這麼火?