2018年2月2日 星期五

連鑰匙孔都藏起來的 ssh 完全防禦: SPA

如果說 ssh 服務是進入伺服器的鎖頭, 那麼帳號密碼就是開鎖的鑰匙。 你可以用 密不透水的設定 來保護鎖頭, 或是用 fail2ban 把企圖開鎖但履試履敗的可疑份子擋在庭院之外一陣子, 讓他連試鑰匙的機會都沒有。 但其實另外還有一個中文世界鮮少人知道的終極絕招: 把鎖頭/鑰匙孔整個藏起來, 只當懂門路的人 (你自己) 用特定暗號敲門時, 才讓鎖頭/鑰匙孔短暫出現一兩分鐘。 今天要介紹的伺服器完全防禦機制叫做 Single Packet Authorization (SPA)

一、 概念解說

這要先從 port knocking 說起。 這個機制的運作方式是: 先用防火牆預設把所有的 ports (或至少重要的 ports, 例如 22) 通通封掉, 這就像是在門鎖之外再蓋上一層 沒有鎖頭、 沒有鑰匙孔的門 一樣。 然後跟客戶端約好按照某個特定順序敲幾個 ports (例如先敲 23815、 次敲 62408、 再敲 59174) 伺服器偵測到這個特定順序, 外門才會像芝麻開門一樣地短暫打開一兩分鐘, 這時才看得見鑰匙孔 (port 22, ssh)。 客戶端必須趁這短短的空隙下 ssh 指令連線、 輸入帳號密碼。 過了這個時間, 外門又關上、 任何人又看不見 port 22, 也就是只看見一片銅牆鐵壁, 連鑰匙孔都看不見; 但已連線的工作階段則可維持連線不受影響。 詳見 中文版 port knocking 觀念解說, 也大力推薦圖片搜尋 「port knocking」。 至於 SPA 則是改良版、 更強大的 port knocking。

Port knocking 有幾個缺點:

  1. 有點浪費: 每個封包 (packet) 只貢獻 2 bytes 的資訊 (port nunber); 它的內容 (可能多達 1500 bytes; 視 MTU 而定) 全部沒用到。
  2. 為了避免一大堆封包擠在一起、 先發後到, 兩個封包之間必須隔上一段時間。 (大約半秒鐘)。
  3. MitM 攻擊者可以嘗試 replay -- 重播他所觀察到的敲門序列。
  4. 就像 e-mail 的寄件人欄位可以造假一樣, 封包的來源地址也可以造假。 所以攻擊者就算沒有看見完整的敲門順序, 只看見其中一個封包, 他還是可以故意傳送亂七八糟的封包來破壞你的敲門順序, 讓你無法連線。 這也就很輕易地達到了 DoS 的效果。

2005 年時, Michael Rash 發明了 SPA 並以 fwknop 實作。 簡單地說, 它只用一個封包來敲門。 這個封包的內容包含了一串亂碼 (以便防止 replay 攻擊)、 用戶 id、 時間戳記、 fwknop 版本、 欲開啟的埠號資訊或是 shell 指令、 以及上述所有資訊的 digest。 這所有的內容並非直接放入封包, 而是經過對稱或非對稱方式加密。 除了解決了上述傳統 port forwarding 的問題之外, fwknop 比一般的 port knocking 軟體還多了其他很多優點, 詳見 github 的 fwknop 官網 2005 年的解說文章

以下設定方式主要參考 Patrik Šíma 的文章, 但並不完全相同。

二、 客戶端

我的客戶端是 lubuntu 16.04。 首先要安裝 fwknop-client 套件: sudo apt-get install fwknop-client

再來執行: fwknop -A tcp/22 -D my.server.com --key-gen --use-hmac --save-rc-stanza 意思是: 產生金鑰以便 「連線到 my.server.com 伺服器的 port 22」, 並且把結果存到設定檔而不是印在螢幕上。 新產生的 ~/.fwknoprc 裡面有一段 [my.server.com] 裡面包含這條連線的相關設定。 如果連到同一部伺服器的好幾個 ports 都想採用相同的設定, 可以把 -A 那一段改成: -A tcp/22,tcp/80,tcp/443,tcp/8006。 如果有好幾部伺服器, 也可以為每部伺服器重複下一次這樣的指令, 那麼 ~/.fwknoprc 每次會多出一個段落 (stanza)。 這些段落名稱 [my.server.com] 就像是用戶代號或是變數名稱一樣, 可以任意自取, 不需要是網址格式。 主要用於等一下要連線時的 -n 選項。

另外, 我下指令時 fwknop 經常會抱怨找不到 wget 指令。 可以在命令列上加上 -w /usr/bin/wget 或是一勞永逸地在 ~/.fwknoprc 裡面的 [default] 段落加上一句: WGET_CMD /usr/bin/wget

三、 伺服器端

我的伺服器是 proxmox 4.*, 基本上是 debian 8 (jessie)。

  1. 安裝 fwknop-server iptables-persistent 兩個套件。
  2. 編輯 /etc/fwknop/access.conf 找到 KEY_BASE64 跟 HMAC_KEY_BASE64 那兩句, 把客戶端的 ~/.fwknoprc 裡面對應的那兩句 copy 過去。 這就是對稱式加密的金鑰。
  3. 編輯 /etc/fwknop/fwknopd.conf 找到 PCAP_INTF 那一句, 把 eth0 改成伺服器對外網卡名稱。 (以我的 proxmox 而言是 vmbr0。)
  4. 因為 fwknop 服務還沒有被納入官方的 systemd, 所以必須要自己加一個設定檔。 還好 mrash 大大已幫我們寫好了, 只需要: wget -O /etc/systemd/system/fwknopd.service https://raw.githubusercontent.com/mrash/fwknop/master/extras/systemd/fwknopd.service
  5. 設定每次開機自動啟動: systemctl enable fwknopd
  6. 現在就啟動: systemctl start fwknopd
  7. 查看狀態: systemctl status fwknopd 應該要看到 「active (running)」 而不是 「active (exited)」。

現在還沒有到最危險的那一步; 現在只是啟用服務, 還沒有關門, 所以 ssh 還可以正常連線。

四、 測試、 關門正式啟用

  1. 在伺服器端下: fwknopd --fw-list 應該看到沒有任何 fwknop 所做的 iptables 設定。
  2. 在客戶端下: fwknop -n my.server.com --verbose -R 印出來的最後一句應該要長得類似這樣: send_spa_packet: bytes sent: xxx
  3. 回到伺服器端重下: fwknopd --fw-list 應該要看到多出一句 ACCEPT , 表示暫時允許連線。
  4. 三十秒後再下相同的指令, 那句 ACCEPT 又不見了, 表示門又關上。 暫時開門的時間長度可在 /etc/fwknop/access.conf 裡面 設定 FW_ACCESS_TIMEOUT。
  5. 接下來是危險動作! 請先多開幾個 ssh 連線視窗, 以免等一下玩壞了就不去伺服器!
  6. 設定 iptables, 只允許已建立的連線繼續連線、 封鎖其他所有新的 ssh 連線:
    iptables -A INPUT -i eth0 -p tcp --dport 22 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
    iptables -A INPUT -i eth0 -p tcp --dport 22 -j DROP
    
    如果是用 -I 而不是 -A, 那麼這兩句的順序就要顛倒過來, 因為 iptables 必須先看到 ESTABLISHED 那一句。
  7. 現在再試著從客戶端 ssh, 會覺得主機完全沒有回應, 直接當在那邊。 但下了 fwknop -n my.server.com -R 之後, 又有 30 秒的時間會看到鑰匙孔。
  8. 成功之後, 就把目前的 iptables 設定存起來:
    iptables-save > /etc/iptables/rules.v4
    ip6tables-save > /etc/iptables/rules.v6
    

當然要記得把 ~/.fwknoprc 備份起來, 因為裡面有芝麻開門的口令。 以後如果想要更改金鑰 (KEY_BASE64 跟 HMAC_KEY_BASE64) 等等, 一樣要很小心地先開幾個 ssh 連線才開始更改, 以免改壞了連鑰匙孔都看不見! 又, 如果想改採 非對稱式加解密, 請參考 gpg 版的 fwknop 設定

五、 在 ubuntu 18.04 虛擬機上面的實驗

[2020/6/20] 今天我開一部 kvm 虛擬機, 啟動 (基於 lubuntu bionic 版的) 貴哥實驗室 來當伺服器。 systemctl start fwknopd 時會出現 timeout 錯誤。 fwknopd 明明有啟動, 但是 systemd 不知道是在等 PID 檔還是在等什麼.. 總之沒有找到好的解答。

乾脆 完全略過 systemd, 直接手動執行 fwknopd , 再用 ps ax | grep fwknopd 確認它有成功啟動。 當然, 沒有了 systemd 所需要的 service 檔, 若想要每次開機都自動啟動, 就必須先 在 systemd 底下啟用 rc.local 機制, 再把 fwknopd 這一句寫進 /etc/rc.local 。

另外, 因為我位於區網裡面, 所以客戶端如果執行 fwknop -R ... 那麼伺服器端 (kvm 虛擬機) 所看到且允許放行的, 會是 NAT 外面所看到的實體 IP, 結果客戶端還是無法登入。 所以敲門的指令應該要變成: fwknop -a 192.168.區網.客戶 -n 192.168.區網.伺服器 。 詳見手冊。

六、 躲在防火牆 NAT 後面的機器變得很容易管理

一部 「防火牆兼 fwknop 伺服器」 的背後可能藏著一拖拉庫沒有公共 IP、 只能靠著 NAT 上網的機器。 例如在我的 proxmox 伺服器裡面有一個 192.168.29.0/24 的區網, 上面有幾部只有我在用的虛擬機。 以前如果想要從外面連進這些機器, 可能需要設定 通訊埠轉發 反向 ssh 隧道。 現在防火牆上面有了 fwknop 伺服器, 這兩個機制就可以省下來了。 只需要在 fwknop 的敲門命令列上多幾個參數, 便可以用 fwknop 直接打通一個臨時隧道直通區網內的機器, 完全不需要要其他額外設定,

fwknop -n my.server.com -R -N 192.168.29.132,22 -A tcp/47285
ssh -X -p 47285 my.server.com

第一句叫 fwknop 伺服器打開 port 47285 並且把它跟躲在伺服器後方區網的 192.168.29.132 的 port 22 接線。 第二句話連到伺服器的 port 47285, 其實也就是區網 29.132 那部機器的 ssh 服務。 這時若在伺服器上面下 fwknopd --fw-list, 會在 Chain FWKNOP_PREROUTING 底下短暫地看到 DNAT ... tcp dpt:47285 /* _exp_1517484447 */ to:192.168.29.132:22 之類的 iptables 規則, 過一下就消失了。 呵呵 iptables 我不熟, 不太了解為什麼, 雖然沒看到 --ctstate ESTABLISHED,RELATED 那一句, 但總之在那短短的空檔當中成功 ssh 連線的階段不會斷線, 可以繼續工作。

省略了固定的 port forwarding, 不僅設定變得更簡單, 而且安全性提高, 連短暫開啟的轉發埠號都是臨時挑的, 潰客完全沒有入侵的機會。 早在 2008 年 fwknop 就已經支援這個方向的 NAT; 不過實際下指令時, 語法要看現在的手冊才準。

2015 年 fwknop 開始支援另一個方向的 NAT: 「防火牆兼 fwknop 伺服器」 可以預設禁止區網內的機器上網, 只有在區網機敲門時才短暫開放。 效果類似 阻止手機內賊上傳隱私個資的防火牆, 也許適用於 隱私被 windows telemetry 偷窺 的用戶? 以後有需要用時再來研究吧!

對了, f-droid 市集 上面還有手機客戶端的 app fwknop2 哦!

2 則留言:

因為垃圾留言太多,現在改為審核後才發佈,請耐心等候一兩天。