# Linux (九) - SSH Tunnel 突破防火牆限制 ![Tunnel](https://raw.githubusercontent.com/facerecog/auto-ssh-tunnel/gh-pages/images/auto-ssh-tunnel%20logo.png =200x200) 上一篇有提到 SSH 可以在使用者和客戶端之間建立一條安全的隧道 (tunnel) 來進行資料的傳輸,或者是存取到特定的服務。透過 SSH Tunnel 可以達到傳輸的加密或是網路和防火牆的穿透。 <!-- more --> 舉例來說,我有一台電腦 A 不能對外連線,有一台伺服器 B 可以對外連線,而電腦 A 是可以連線到伺服器 B 的,這時就可以在 A 和 B 之間建立 Tunnel 並將 A 要做的事情轉傳給 B,讓 B 幫忙連到外網完成。這樣的概念類似於 `Port Forwarding` 或是 `Proxy`。 SSH Tunnel 可以分為以下幾種 : * Local Tunnel (Local Forwarding) * Remote Tunnel (Remote Forwarding) * Dynamic Tunnel (Dynamic Forwarding) ## Local Tunnel Local Tunnel 會從 Client 端將特定服務的流量透過 SSH 傳送給 Server 端 (Remote),再透過 Server 端將流量導向到 `特定` 的 Host 上的 Port。 舉例來說,電腦 A 會先打開一個 port,伺服器 B 也會打開一個 port (也就是 SSH 預設的 port 22),電腦 A 會打通一條隧道到伺服器 B 的 port,並且在建立隧道時也會和伺服器 B 約定好當我傳送 Request 過去時,你要幫我把這個 Request 再傳給誰,這個誰有可能是伺服器 B 本身的 port 或是另一個伺服器 C 上的 port。 再說的生活化一點,當我的電腦因為受到防火牆的限制而不能連到 google 時且 SSH 連線並沒有被禁止,我就可以從我的電腦上送出一個 Request 到另一台可以連到 google 的伺服器,這台伺服器就可以幫我連到 google 再回傳結果給我。 <!-- ![image alt](https://www.tunnelsup.com/images/ssh-local1.png) --> 下面我們來看一下建立 Local Tunnel 的指令,這個指令要在自己的電腦上執行 : ```bash= $ ssh -NfL <local ip>:<local port>:<host ip>:<host port> <user>@<remote server ip> ``` * -N : 不要另外開啟一個 Shell。 * -f : 在背景執行。 * -L : 代表要建立 Local Tunnel。 * local ip : 選填,通常是可以不填,就是綁定本機。 * local port : 本機的 port,盡量選大於 5000。 * host ip : 要連接的服務的 ip 或是 domain name,若是遠端 SSH Server 上的服務可以用 `localhost` 或 `127.0.0.1`。 * host port : 要連接的服務的 port。 ### 範例 #### 1. 連線到被防火牆擋下的外部服務 這個範例是本機要連到 `facebook.com`,但是可能是公司網路的防火牆將 `facebook.com` 擋住了,這時候就可以就建立一個 Local Tunnel 來讓外部的一台 Server 幫忙連線。 在建立 tunnel 之前要先在 `外部 Server` 上打開對應的 port 打開,這裡我們用 `6588` port。 ```bash= $ sudo firewall-cmd --add-port=6588/tcp --permanent success $ sudo firewall-cmd --reload success $ sudo firewall-cmd --zone=public --list-all public target: default icmp-block-inversion: no interfaces: sources: services: dhcpv6-client ssh ports: 6588/tcp protocols: masquerade: no forward-ports: source-ports: icmp-blocks: rich rules: ``` 接著要在 `自己的電腦` 上執行以下指令,這個例子是當透過本機的 `6588` port 將 Request 傳送到 server.foo.com 這個 Server 時,Server 會把 Request 導向到 `facebook.com` 的 `80` port,最後將結果回傳到本機的 `6588` port。 ```bash= # 建立 Local Tunnel $ ssh -NfL 6588:facebook.com:80 user@server.foo.com ``` 這時候打開瀏覽器輸入 `http://localhost:6588` 就可以連到 facebook 了。 #### 2. 連線到被防火牆擋下的遠端伺服器上的服務 這個範例是要連到遠端 Server 上的服務,這個概念就比較簡單了。連到了遠端的 Server 上後,Server 會將 Request 導向到對應的 port,接著再回傳結果。 接著要在 `自己的電腦` 上執行以下指令,下面這個例子是當透過本機的 `6588` port 將 Request 傳送到 server.foo.com 這個 Server 時,Server 會把 Request 導向到 `7520` 這個 port。這個 port 可能是對應到一個網頁或是一支 API 等等,接著再將結果回傳。 ```bash= # 建立 Local Tunnel $ ssh -NfL 6588:localhost:7520 user@server.foo.com ``` 這時候同樣打開瀏覽器輸入 `http://localhost:6588` 就可以連到這項服務了。 ## Remote Tunnel Remote Tunnel 會從 Server 端 (Remote) 將特定服務的流量透過 SSH 傳送給 Client 端 (Local)。 舉例來說,我有一台電腦 A 放在家裡沒有固定開放的 IP,但我有一台 AWS 雲端伺服器 B 有固定開放的 IP。如果我想要從外面連回到家裡的電腦,在沒有開放的 IP 的情況下通常是不行的,因為你並沒有辦法明確的指出你要連到誰。 這時候 SSH Tunnel 就可以透過建立 `反向隧道` 的方式來突破這個限制以達到內網穿透。Remote Tunnel 的概念是,既然外網機器不知道內網機器的 IP,那就反過來由內網機器主動連線到外網機器,並且建立這條隧道,所以稱為 `反向隧道`。 透過建立反向隧道,外網機器和內網機器就有一條連通的溝通管道了,這時就可以透過另一台電腦先連線到外網機器,再讓外網機器利用反向隧道將訊息傳送到內網的機器。 <!-- ![Remote Tunnel](https://www.tunnelsup.com/images/ssh-remote.png) --> 下面我們來看一下建立 Remote Tunnel 的指令,這個指令要在內網的機器上執行 : ```bash= $ ssh -NfR <host ip>:<host port>:<local ip>:<local port> <user>@<remote server ip> ``` * -N : 不要另外開啟一個 Shell。 * -f : 在背景執行。 * -R : 代表要建立 Remote Tunnel。 * host ip : 外網機器的 ip 或是 domain name,可以不填,因為 ssh 本身就需要指定連線對象的 ip,也就是指令中的 `remote server ip`。 * host port : 外網機器要開放的 port,也就是當連到外網機器的這個 port,Request 就會透過這條反向隧道送到內網的機器。 * local ip : 可以用 `localhost` 或 `127.0.0.1`,就是綁定內網這台機器。 * local port : 內網這台機器的 port,對應到各項服務,SSH 預設值就是 22。 ### 範例 同樣的在建立 tunnel 前要先在 `外網機器` 上打開對應的 port,這裡我們用 `7020` port。作法請參考 Local Tunnel 的介紹。 接著要在 `內網機器` 上執行以下指令,這個範例是要建立一個 Remote Tunnel 在一台外網機器和一台內網機器之間,可以看到這裡指定的主機是 `server.foo.com`,而外網機器開放了 `7020` 這個 port,所以當連線到 `server.foo.com` 的 `7020` port 時,就會自動將 Request 導向到內網的機器。而這裡導向到的是內網機器的 `22` port,也就是 SSH 的 port。 ```bash= $ ssh -NfR 7020:localhost:22 user@server.foo.com ``` 建立完成後在 `外網機器` 上執行以下指令,可以看到外網機器上的 `7020` port 已經開始監聽了。 ```bash= $ sudo netstat -antp | grep 7020 tcp 0 0 0.0.0.0:7020 0.0.0.0:* LISTEN 19102/sshd: opc tcp6 0 0 :::7020 :::* ``` 如此一來我們就可以使用第三方的電腦,透過 `server.foo.com` 的 `7020` port 來和內網的機器建立 SSH 的連線了。 ```bash= $ ssh user@server.foo.com -p 7020 ``` ## Dynamic Tunnel Dynamic Tunnel 會從 Client 端將特定服務的流量透過 SSH 傳送給 Server 端 (Remote),再透過 Server 端將流量導向到 `非特定` 的 Host 上的 Port。 從前面的定義可以發現 Dynamic Tunnel 和 Local Tunnel 流量導向的方向是一樣的,差別在於 Dynamic Tunnel 會導向到 `非特定` 的 Host 上的 Port,而 Local Tunnel 是導向到 `特定` 的 Host 上的 Port。 首先,什麼是 `特定` 的 Host 上的 Port ? Local Tunnel 會指定當流量通過這條隧道後最終要去的目的地,所以不管傳進來的 Request 是什麼,通通都會導向到一樣的目的地。 而 `非特定` 的意思是 Dynamic Tunnel 並不會指定流量通過隧道後的目的地,這條隧道的用意只是要讓 Request 可以流向到遠端的 Server 上,再讓遠端的 Server 作為一個代理伺服器 (SOCKS proxy) 將 Request 送出去,透過這樣的作法來突破防火牆的限制。 Dynamic Tunnel 的概念類似於 VPN,先連到另一台可以對外連線的機器,再讓這台機器幫我對外傳送和接收訊息。 下面我們來看一下建立 Dynamic Tunnel 的指令,這個指令要在本機上執行 : ```bash= $ ssh -NfD <local ip>:<local port> <user>@<remote server ip> ``` * -N : 不要另外開啟一個 Shell。 * -f : 在背景執行。 * -D : 代表要建立 Dynamic Tunnel。 * local ip : 可以用 `localhost` 或 `127.0.0.1` 或是不填,就是綁定這台機器。 * local port : 本機上的 port,當本機的流量導向到這個 port 時就會通過這條隧道送到 Server。 ### 範例 首先在本機上執行以下指令,這個指令代表當本機上有服務將流量導向到 `8500` port 時,就會將這個流量送進隧道中導向到 `server.foo.com` 這台 Server 上。接著這台 Server 就會再將接收到的 Request 傳送出去,並將結果送回到來。 ```bash= $ ssh -NfD 8500 user@server.foo.com ``` 當 Dynamic Tunnel 建好後,我們就可以透過 SOCKS 代理伺服器來執行了。這裡以 Chrome 舉例。 * Linux/Windows 的用戶在 Chrome 的捷徑上按右鍵選擇內容,接著將以下設定加在目標的後面。 ```bash= --proxy-server="socks5://localhost:2323" ``` * macOS 的用戶使用 terminal 輸入以下指令方式開啟 Chrome。 ```bash= open -a "Google Chrome" --args --proxy-server="socks5://localhost:2323" ``` macOS 的用戶可以詳見參考 8 這篇文章,直接設定將所有流量導向到代理伺服器上。 ## autossh SSH Tunnel 建立一段時間之後可能會突然斷線,這時就可以安裝 `autossh` 來取代 `ssh`。`autossh` 會在斷線時自動重新進行連線,以確保連線正常。 而 `autossh` 的指令承襲了 `ssh`,所以可以直接將指令中的 `ssh` 替換成 `autossh` 就好了。不過 `autossh` 還提供了一個參數 `-M` 來建立讓本機上的一個 port 來監聽連線狀況,如下 : ```bash= $ autossh -M 6020 ... ``` ## Summary SSH 除了上一篇所介紹大家最常拿來連線到伺服器的操作外,Tunnel 才是他最強大的功能,在 Tunnel 中所有的訊息都是加密的。除非真的發生上一篇所介紹的 `中間人攻擊`,不然基本上完全可以不用擔心。 不過上述的前提都是本機上要可以執行 ssh 並且連的出去,有些人想要在公司建 SSH Tunnel 出去來突破公司的防火牆,只要 SSH 的 Port 沒有被禁用就可以,如果被禁用了,那你還是乖乖的使用內網吧。 ## 參考 [1] [SSH Tunnel](https://blog.johnsonlu.org/ssh-tunnel/) [2] [[教學] 透過 SSH Tunnel 將伺服器內部服務綁定到本機電腦上](https://xenby.com/b/269-%E6%95%99%E5%AD%B8-%E9%80%8F%E9%81%8E-ssh-tunnel-%E5%B0%87%E4%BC%BA%E6%9C%8D%E5%99%A8%E5%85%A7%E9%83%A8%E6%9C%8D%E5%8B%99%E7%B6%81%E5%AE%9A%E5%88%B0%E6%9C%AC%E6%A9%9F%E9%9B%BB%E8%85%A6%E4%B8%8A) [3] [ssh-tunnel原理及其使用](https://plu.one/devops/2018/09/22/ssh-tunnel/) [4] [SSH Tunnel介紹](https://pohsienshih.github.io/2019/SSH-Tunnel-Introduction/) [5] [The power of SSH tunnelling – Simple guide with drawings and examples](https://better-coding.com/ssh-tunnelling-the-power-of-simplicity/) [6] [Differences between ssh -L to -D](https://superuser.com/questions/408031/differences-between-ssh-l-to-d) [7] [What is the difference between Local/Remote/Dynamic SSH tunneling?](https://serverfault.com/questions/272754/what-is-the-difference-between-local-remote-dynamic-ssh-tunneling/272762) [8] [利用遠端 SSH 伺服器架設 SOCKS 代理伺服器(Proxy Server)保護網路傳輸內容](https://myapollo.com.tw/zh-tw/ssh-socks-proxy-server/) [9] [鑿一個反向 ssh 隧道, 對朋友或世界展示筆電或家裡的某個服務](https://newtoypia.blogspot.com/2016/08/reverse-ssh-tunneling.html) [10] [Multiple SSH Tunnels](https://www.phase2technology.com/blog/multiple-ssh-tunnels) ###### tags: `Linux`