變更記錄

2020-05-25
加入「補充:小技巧和工具」章節來放各種 SSH Tunneling 的小技巧和工具。

前陣子研究了一下用 SSH Tunneling 來連到內部網路的方法,一開始實在有點難理解 SSH 指令與實際情況的關係,但了解後才發現他超級強大,於是就把細節記錄下來之後可以參考,也希望能幫大家了解一下這東西。

什麼是 SSH Tunneling (Port Forwarding)?

Tunneling 指的是將網路上的 A、B 兩個端點用某種方式連接起來,形成一個「隧道」,讓兩端的通訊能夠穿透某些限制(例如防火牆),或是能將通訊內容加密避免洩漏。而 SSH Tunneling 就是指利用 SSH 協定來建立這個隧道,所以不但能加密你的通訊,如果中間設有防火牆擋掉某些特定 Port 的連線(例如 HTTP/HTTPS 的 80/443)而沒有擋下 SSH 的 Port 22,這個隧道便會讓防火牆認為是只是一般的 SSH 連線,進而放行,也就達到了「穿透防火牆」的效果。

另外,因為 SSH Tunneling 的目標是兩個端點上的 Port,而過程就像是把對 A 點上的某個 Port X 所傳送的資料轉送 (Forward)至 B 點上的 Port Y,所以 SSH Tunneling 又稱為 SSH Port Forwarding

Tunneling 示意圖

SSH Port Forwarding 有下列三種模式:

  • Local Port Forwarding
  • Remote Port Forwarding
  • Dynamic Port Forwarding

接下來會一一說明各種模式。先來看看在 SSH Port Forwarding 當中參與的角色有哪些。

Port Fowarding 裡的角色定義

對 Local 和 Remote Port Forwarding 來說,都會有下面這三個角色:

Client
  • 任何你可以敲 ssh 指令來啟動 Port Forwarding 的機器
SSH Server
  • 可以被 Client 用 SSH 連進去的機器
Target Server
  • 某一台你想建立連線的機器,通常是為了對外開放這台機器上的服務
  • 注意ClientSSH Server 本身都可以是 Target Server ,不是真的要有三台機器才可以進行 Port Forwarding!

而 Dynamic Port Forwarding 比較不一樣,在於 Target Server 不會只有一台,而是可以被動態決定的。

了解了這三個角色,那就先來看看 Local Port Forwarding 是怎麼回事。

Local Port Forwarding

指令語法

ssh -L [bind_address:]<port>:<host>:<host_port> <SSH Server>

Client 上開啟 bind_address:port 等待連線,當有人連上時,將所有資料轉送到 host:host_port 去。 注意host 是相對於 SSH Server 的位址,而不是 Client

使用情境一:連到位在防火牆後的開發伺服器上的服務

你有一台位於防火牆後的開發伺服器, 你在上面架了某個服務在 Port 8080 上,但防火牆只開放 Port 22 的 SSH 連線,讓你無法從你的電腦直接連到 Port 8080,但你又很想連到他…

 情境 1 示意圖

這時候只要你能夠 SSH 到那台伺服器,就可以利用 Local Port Forwarding 來開啟你電腦上的某個 Port(假設為 9090),將對它發送的資料轉送到伺服器的 Port 8080。這樣一來, 連上你的電腦的 Port 9090 就等於連上了防火牆後的伺服器的 Port 8080 ,也就繞過了防火牆的限制。

 情境 1 解法示意圖
Client
  • 你的電腦
SSH Server
  • 防火牆後的伺服器
  • SSH Destination: johnliu@my-server
Target Server
  • 防火牆後的伺服器

SSH 指令:

ssh -L 9090:localhost:8080 johnliu@my-server

這邊的 localhost 是相對於 johnliu@my-server ,指的就是防火牆後的伺服器本身。

註釋

  • 你完全可以在你的電腦上用相同的 Port number 來做 Port Forwarding,這邊用 9090 只是為了避免混淆:

    ssh -L 8080:localhost:8080 johnliu@my-server
    
  • 如果你沒有給 bind_address ,預設會 Bind 在 localhost 上。如果你想把 Port 9090 開放給所有人用:

    ssh -L 0.0.0.0:9090:localhost:8080 johnliu@my-server
    

使用情境二:透過防火牆後的機器,連到防火牆後的特定服務

情境一有用的前提是你能夠 SSH 到提供服務的伺服器裡,但今天如果你沒有權限,無法 SSH 進到提供服務的伺服器,那該怎麼辦呢?

 情境 1 示意圖

沒問題!只要你在防火牆後有任何一台你可以 SSH 的機器,接著修改一下指令裡的 host 設定,你就可以利用這台機器進行資料轉送:

 情境 1 解法示意圖
Client
  • 你的電腦
SSH Server
  • 防火牆後你的機器
  • SSH Destination: johnliu@my-server
Target Server
  • 防火牆後的伺服器
  • 192.168.1.101:8080

SSH 指令:

ssh -L 9090:192.168.1.101:8080 johnliu@my-server

這邊的 192.168.1.101 是相對於 johnliu@my-server ,所以是防火牆後的伺服器的 IP 位址。

Remote Port Forwarding

指令語法

ssh -R [bind_address:]<port>:<host>:<host_port> <SSH Server>

SSH Server 上開啟 bind_address:port 等待連線,當有人連上時,將所有資料轉送到 host:host_port 去。 注意host 是相對於 Client 的位址,而不是 SSH Server

使用情境一:透過對外機器,讓其他人能夠連到你的電腦上的服務

你在你的電腦上開發完了一個服務架在 Port 8080 上,然後你想要 Demo 給客戶看,但你的電腦只有內部 IP,所以無法讓客戶連進來:

Remote 情境 1 示意圖

這時候只要利用 SSH Remote Forwarding,就可以藉由一台有 Internet IP 的對外機器,開啟上面的某個 Port(假設為 9090)來轉送資料到你的電腦上的 Port 8080。這樣子,客戶只要連上對外機器的 Port 9090 就等於是連上了你電腦的 Port 8080。

Remote 情境 1 解法示意圖
Client
  • 你的電腦
SSH Server
  • 對外機器
  • SSH Destination: johnliu@external-server
Target Server
  • 你的電腦

SSH 指令:

ssh -R 0.0.0.0:9090:localhost:8080 johnliu@external-server

這邊的 localhost 是相對於 Client ,指的就是你的電腦本身。

警告

基於安全考量, Remote Forwarding 預設都只能夠 bind 在 SSH Server 的 localhost 上,所以單靠以上指令是無法讓 Port 9090 開放給外部連線的。你必須調整 SSH Server 上的 SSH 服務的設定檔(一般在 /etc/ssh/sshd_config )加入 GatewayPorts 設定,才能讓所有人都連到:

GatewayPorts yes

這邊有三個選項:預設為 no ,也就是唯一指定 localhost;設定為 yes 可以唯一指定為 wildcard( 0.0.0.0 );設定為 clientspecified 可以讓啟動 Remote Forwarding 的 Client 自行指定。

使用情境二:透過對外機器,從外面連回內部網路上的服務

有一個在內網裡的內部服務,你的電腦可以用 IP 192.168.1.100 和 Port 8080 連到這個服務,但因為都在內網所以大家都沒有 Internet IP,所以無法讓你從家裡透過 Internet 連回來:

Remote 情境 2 示意圖

這時候藉由 Remote Forwarding 和一台對外機器, 可以讓你從任何地方連回這個服務:

Remote 情境 2 解法示意圖
Client
  • 你的電腦
SSH Server
  • 對外機器
  • SSH Destination: johnliu@external-server
Target Server
  • 內部服務
  • 192.168.1.100:8080

SSH 指令:

ssh -R 0.0.0.0:9090:192.168.1.100:8080 johnliu@external-server

在這裡, 192.168.1.100 是相對於你的電腦,所以就算外部機器連不到這個位址也沒關係,因為是透過你的電腦做資料轉送。這樣子,只要連到對外機器上的 Port 9090 就等於是連到內部服務上的 Port 8080 了,你就能夠從外部存取內網服務。

這應該是 SSH Port Forwarding 最強大的功能了!只要在網路上租一台最便宜的主機(Linode, Digital Ocean 之類的),就可以拿他來當圖示中的對外機器,來連回內部網路上的服務。不過前提是你得在有內網連線時將 Port Forwarding 設定好,如果你到家後才想到,那就請你再跑一趟吧…

Dynamic Port Forwarding

指令語法

ssh -D [bind_address:]<port> <SSH Server>

在 SSH Server 上啟動一個 SOCKS 代理伺服器,同時在 Client 上開啟 bind_address:port 等待連線,當有人連上時,將所有資料轉送到這個 SOCKS 代理伺服器上,啟動相對應的連線請求。

使用情境:建立一個 HTTP 代理伺服器連到內網的所有 HTTP(S) 服務

只要有一台位於內網且具有外部 IP 的機器,你就可以利用這個方法建立一個 HTTP 代理伺服器,讓你能夠從外面連回內網裡的所有 HTTP(S) 服務:

Dynamic 情境示意圖
Client
  • 你的電腦
SSH Server
  • 內網裡具有外部 IP 的機器
Target Server
  • N/A

SSH 指令:

ssh -D 9090 johnliu@internal-machine

假設你是用 Linux 和 Chrome,你可以在你的電腦上用以下指令讓 Chrome 使用這個代理伺服器:

google-chrome --user-data-dir=~/proxied-chrome --proxy-server=socks5://localhost:9090

註釋

  • 這邊的 google-chrome 只是範例,不同的 Linux 發行版名字可能會不同
  • --user-data-dir 是為了讓 Chrome 能夠開啟一個新的 Chrome session,不加的話 --proxy-server 這個設定就沒用了

一般的 Port Forwarding 只能夠轉送一個 IP 上的一個 Port ,當你有很多 IP 或很多 Port 想轉時就只能一個一個開, 很不方便。相比之下,Dynamic Port Forwarding 能直接架起一個代理伺服器,只要你用的程式有支援 SOCKS 協定,透過這個代理伺服器讓你想怎麼轉就怎麼轉。不過這方式也不是沒缺點,就是那台轉送用的機器一定得要有對外 IP,這樣才能夠從你的電腦連回來。

結論

從圖可以看出來,Local 跟 Remote Forwarding 的差異主要在 Port 開啟的地方:Local Forwarding 是將 Client 上的 Port 打開以供連線;Remote Forwarding 則是將 SSH Server 上的 Port 打開。另外要注意的點是轉送的目的地 host :Local Forwarding 是相對於 SSH Server,而 Remote Forwarding 則是相對於 Client。

雖然 Dynamic Port Forwarding 的彈性更大,但條件就是 SSH Server 就必須要能夠從外面連回來。不過其實也是有 Workaround 啦,搭配一下 Port Forwarding 就行了,但這樣的話你有更好的 Proxy 選擇,像是 Tinyproxy 等等。

就寫到這邊,有問題也歡迎大家討論唷!

補充:小技巧和工具

這邊放一些大家在使用 SSH Tunneling 上的小技巧和工具,但細節就請大家自行 Google 囉。

常用的 SSH 指令參數

-N
不要執行任何遠端指令。沒有加這個參數時,建立 Port Forwarding 的同時也會開啟 Remote Shell,讓你可以對 SSH Server 下指令,而這個參數可以讓 Remote Shell 不要打開。
-f
ssh 指令在背景執行,讓你可以繼續用 Shell 做事情。通常會搭上面的 -N 使用。

常用的 SSH Client 端設定

註釋

設定檔通常在 ~/.ssh/config 或是 /etc/ssh/ssh_config

ServerAliveInterval
設定一段時間,如果 Client 在這段時間內都沒從 SSH Server 收到資料,就發出一段訊息請 SSH Server 回應。這會讓連線不會呈現閒置狀態,避免防火牆或 Router 切斷你的連線。預設為 0 ,不會發出任何訊息。
ServerAliveCountMax
設定在 SSH Server 沒回應的情況下,Client 最多要送幾次請求回應的訊息(上面提到的那個)。達到此次數後,Client 就會切斷與 SSH Server 之間的連線。這個主要是避免在 SSH Server 已經無法連線後,Client 還不斷送出請求回應的情況。預設為 3

autossh:自動重啟 SSH 連線

autossh 是一支可以幫你監控 SSH 連線狀態並自動重連的程式。如果你的網路狀況很糟糕,或是防火牆會三不五時把你斷線,他可以幫你自動重啟連線。

Fail2Ban:阻擋不明連線

Fail2Ban 可以幫你阻擋不明連線,原理就是去監看 SSH 服務的 log 來偵測登入失敗的 IP,然後在這些 IP 的失敗次數達到一定值時,利用防火牆來暫時停止該 IP 的連線請求,過一定時間後再恢復。可以拿來擋掉最基本的暴力攻擊。

如果你租了線上主機來玩,建議最少要裝 Fail2Ban 來保護你的 SSH Server。

Port Knocking:有條件的開啟 SSH Port

Port Knocking 指的是 Client 必須用特殊的順序來對 SSH Server 上的某些 Port 發出連線請求後,SSH Server 才會開放 Client 連線的技巧(比如依序對 Port 1000、2000、3000 發出請求,才會對你開放 Port 22)。這樣的好處是平時 Port 22 就會是關閉的狀態,讓攻擊者以為 SSH 沒有開放,減少被攻擊的機會。我沒用過,但看起來會搭配其他服務(像 knockd )一起用。

Share on: TwitterFacebookEmail

comments powered by Disqus

Published

Last Updated

Category

SSH, Linux

Tags

Contact