使用SSH反向隧道進行內網穿透

CopperDong發表於2017-11-12

使用SSH反向隧道進行內網穿透

這篇文章主要介紹瞭如何利用SSH 反向隧道穿透NAT,並演示瞭如何維持一條穩定的SSH 隧道。

假設有機器A 和B,A 有公網IP,B 位於NAT 之後並無可用的埠轉發,現在想由A 主動向B 發起SSH 連線。由於B 在NAT 後端,無可用公網IP + 埠 這樣一個組合,所以A 無法穿透NAT,這篇文章應對的就是這種情況。

首先有如下約定,因為很重要所以放在前面:

機器代號 機器位置 地址 賬戶 ssh/sshd 埠 是否需要執行sshd
A 位於公網 a.site usera 22
B 位於NAT 之後 localhost userb 22
C 位於NAT 之後 localhost userc 22

這裡預設你的系統init 程式為systemd,如果你使用其他的init 程式,如果沒有特殊理由還是換到一個現代化的GNU/Linux 系統吧……

SSH 反向隧道

這種手段實質上是由B 向A 主動地建立一個SSH 隧道,將A 的6766 埠轉發到B 的22 埠上,只要這條隧道不關閉,這個轉發就是有效的。有了這個埠轉發,只需要訪問A 的6766 埠反向連線B 即可。

首先在B 上建立一個SSH 隧道,將A 的6766 埠轉發到B 的22 埠上:

1
B $ ssh -p 22 -qngfNTR 6766:localhost:22 usera@a.site

然後在A 上利用6766 埠反向SSH 到B:

1
A $ ssh -p 6766 userb@localhost

要做的事情其實就是這麼簡單。

隧道的維持

穩定性維持

然而不幸的是SSH 連線是會超時關閉的,如果連線關閉,隧道無法維持,那麼A 就無法利用反向隧道穿透B 所在的NAT 了,為此我們需要一種方案來提供一條穩定的SSH 反向隧道。

一個最簡單的方法就是autossh,這個軟體會在超時之後自動重新建立SSH 隧道,這樣就解決了隧道的穩定性問題,如果你使用Arch Linux,你可以這樣獲得它:

1
$ sudo pacman -S autossh

下面在B 上做之前類似的事情,不同的是該隧道會由autossh 來維持:

1
B $ autossh -p 22 -M 6777 -NR 6766:localhost:22 usera@a.site

-M 引數指定的埠用來監聽隧道的狀態,與埠轉發無關。

之後你可以在A 上通過6766 埠訪問B 了:

1
A $ ssh -p 6766 userb@localhost

隧道的自動建立

然而這又有了另外一個問題,如果B 重啟隧道就會消失。那麼需要有一種手段在B 每次啟動時使用autossh 來建立SSH 隧道。很自然的一個想法就是做成服務,之後會給出在systemd 下的一種解決方案。

“打洞”

之所以標題這麼起,是因為自己覺得這件事情有點類似於UDP 打洞,即通過一臺在公網的機器,讓兩臺分別位於各自NAT 之後的機器可以建立SSH 連線。

下面演示如何使用SSH 反向隧道,讓C 連線到B。

首先在A 上編輯sshd 的配置檔案/etc/ssh/sshd_config,將GatewayPorts 開關開啟:

1
GatewayPorts yes

然後重啟sshd

1
A $ sudo systemctl restart sshd

然後在B 上對之前用到的autossh 指令略加修改:

1
B $ autossh -p 22 -M 6777 -NR '*:6766:localhost:22' usera@a.site

之後在C 上利用A 的6766 埠SSH 連線到B

1
C $ ssh -p 6766 userb@a.site

至此你已經輕而易舉的穿透了兩層NAT。

最終的解決方案

整合一下前面提到的,最終的解決方案如下:

首先開啟A 上sshd 的GatewayPorts 開關,並重啟sshd(如有需要)。

然後在B 上新建一個使用者autossh,根據許可權最小化思想,B 上的autossh 服務將以autossh 使用者的身份執行,以盡大可能避免出現安全問題:

1
2
B $ sudo useradd -m autossh
B $ sudo passwd autossh

緊接著在B 上為autossh 使用者建立SSH 金鑰,並上傳到A:

1
2
3
B $ su - autossh
B $ ssh-keygen -t 'rsa' -C 'autossh@B'
B $ ssh-copy-id usera@a.site

注意該金鑰不要設定密碼,也就是執行ssh-keygen 指令時儘管一路回車,不要輸入額外的字元。

然後在B 上建立以autossh 使用者許可權呼叫autossh 的service 檔案。將下面文字寫入到檔案/lib/systemd/system/autossh.service,並設定許可權為644:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Unit]
Description=Auto SSH Tunnel
After=network-online.target
[Service]
User=autossh
Type=simple
ExecStart=/bin/autossh -p 22 -M 6777 -NR '*:6766:localhost:22' usera@a.site -i /home/autossh/.ssh/id_rsa
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=always
[Install]
WantedBy=multi-user.target
WantedBy=graphical.target

在B 上讓network-online.target 生效:

1
B $ systemctl enable NetworkManager-wait-online

如果你使用systemd-networkd,你需要啟用的服務則應當是systemd-networkd-wait-online 。

然後設定該服務自動啟動:

1
B $ sudo systemctl enable autossh

如果你願意,在這之後可以立刻啟動它:

1
B $ sudo systemctl start autossh

然後你可以在A 上使用這條反向隧道穿透B 所在的NAT SSH 連線到B:

1
A $ ssh -p 6766 userb@localhost

或者是在C 上直接穿透兩層NAT SSH 連線到B:

1
C $ ssh -p 6766 userb@a.site

如果你對SSH 足夠熟悉,你可以利用這條隧道做更多的事情,例如你可以在反向連線時指定動態埠轉發:

1
C $ ssh -p 6766 -qngfNTD 7677 userb@a.site

假設C 是你家中的電腦,A 是你的VPS,B 是你公司的電腦。如果你這樣做了,那麼為瀏覽器設定埠為7677 的sock4 本地(localhost)代理後,你就可以在家裡的瀏覽器上看到公司內網的網頁。



相關文章