使用docker container建立SSH反向通道穿透內網連接內部裝置
前言
之前一直很想寫反向通道(reverse tunnel)的流程,但過程很難描述
剛好最近在 docker 上看到好用的 openssh-server 的 image
結果一試就上手,於是就有這篇文章了
步驟說明
首先,你需要一個
位於外網且可以透過SSH連線的裝置
如果沒有,請按上一頁
我今天看到一個 image linuxserver/openssh-server
它把 openssh-server 的部分都做好了
所以我們可以直接用這個 image 去 run 一個 container
也就是寫一份 docker-compose 的文件就搞定了
接下來,我們會分別討論三個裝置上的設定
- 外網裝置 A
- 內網跳板裝置 B
- 透過跳板 B 連至內網裝置 C
步驟簡略來說會長成下面這個樣子:
- 內網的跳板裝置 B 會透過反向去連接在外網的 A(所以 B 在內網只要可以訪問外網就行了),接著 A 會使用另外一個 port 去 forward 裝置 B 的 SSH port
A(SSH port) <— B
這邊有點不太好懂,舉例來說就是假設 A 的 SSH port 是 1984,除了 1984 外,我們在路由器上設定開放 A 裝置的另一個 port 1994
那麼,B 反向到 A 的時候,我們可以利用 1994 這個 port 去讓別人連接 B 的 SSH port
- 我們透過某一可連外網的裝置(隨便一個都行)去連接 A 的反向專用 port 即可到達在內網的裝置 B
某裝置 —> A(反向用 port) —> B
- 因爲裝置 B 在內網,所以我們可以使用 B 來訪問所有在內網的裝置,例如 C
某裝置 —> A(反向用 port) —> B —> C
外網裝置 A
首先,我們先在外網的機器 A 先佈置能能夠進行被反向連接的 server
我們基於 docker hub 上面官方的文件做修改
1 | version: "3" |
主要比較有意義的改動是volumes
跟port
有些版本的 docker 會出現無法 binding volumes 的問題,可以先自己用
mkdir
建立資料夾
因爲它的 image 包好的 SSH port 是2222
,那麼我們把外部1984
對應進去
另外,我們需要多開一個 port 讓內網裏面做反向的裝置能夠連到這臺 server
所以多開了一個 port 1994
這邊記得在路由器幫這兩個 port 做 forwarding,建議內外相同 port 就行了
接着,我們就執行
1 | docker-compose up |
記得,這邊沒有使用-d
去做背景執行是因爲等等 debug 比較方便
我們需要修改 openssh-server 的設定檔sshd_config
打開剛剛指定目錄中的./ssh_setting/ssh_host_keys/sshd_config
去把這三個設定做更改
1 | AllowTCPForwarding yes # 有些教學沒說要開這個,但不開它就不讓你forwarding了 |
重新執行
1 | docker-compose up --force-recreate |
完成這個步驟後
我們就可以放著不管,用背景執行這個 container 了
Extra Step
這邊是額外的連線測試,不做也不影響後面的流程
可以使用 local 做連線測試
1 | ❯ ssh natlee@localhost -p 1984 |
於是,失敗了,因爲我們只能用 key 進行連線!
那麼要如何測試連線呢?
我們先找一對金鑰(如果沒有的話,可以用ssh-keygen
生成)
再把剛找的公鑰(通常預設會在~/.ssh/id_rsa.pub
)放到./ssh_setting/.ssh/authorized_keys
內
內容類似長這樣的東西:
1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDs13dOFtSQ8OJjIIZiVKWTIUbEvy+ZIcswOiXCb4q4nIK80yNyuDOVcbzQyyS0Q0z0UUTpf+TR5Rs4Ox0B8baffTC0CvFHQhwqpgvz/8d179fmUCyDb9yrdgdhSlN4zkMdw8A2P8SQ+3cc+gIQBvj/jo414kynz4zaoGseFxBOpVKc+dW1Y8m3G4wfBB0QpcyxYZ9vkAhrIYyPrIK9EP7LXTlJyQ7gMadJ4eUkMiBSnqfJYxPcYDIeK/uHYyGgpsJQUaxZ8JIqkD2kdmBPX8NHGA1O2VF2UyiJKuRVJEg/oW7YoCJHs81Gj+bl9HdnBC5CEMWckLPLRtfFkW3u9F6PNto9fs48L1dU8MGm6KAirX2GPydmwRS6yh9i32NT5J70izi28cX1IhJn2PtY75aZAPuU2NspsJ1cy4cbd35tJLB0H0= root@test |
再用 SSH 連線一次!
1 | ❯ ssh natlee@localhost -p 1984 |
到這邊算是外網機器佈置成功了!
執行中的時候,資料夾內的內容物長這樣
1 | . |
內網跳板裝置 B
內網裝置需要的東西就比較多了
但還是老樣子,我們在內網裝置上起一個 openssh-server
基於 docker hub 上面官方的文件做修改
1 | version: "3" |
改完之後直接 run 一波
有些版本的 docker 會出現無法 binding volumes 的問題,可以先自己用
mkdir
建立資料夾
內網的裝置就不用給 ports,給了也只有內網電腦能連進去
1 | docker-compose up |
run 完會看到它的啓動畫面,然後就跟剛剛一樣生一堆文件出來
我們接下來要配置一些文件讓它能夠連到 server 且斷線後能夠重連
建立安裝 autossh 的腳本
使用
sudo nano ./ssh_setting/custom-cont-init.d/install.sh
將以下內容貼上1
apk add --no-cache autossh
建立啓動 autossh 的腳本
使用
sudo nano ./ssh_setting/custom-services.d/autossh.sh
將以下內容貼上1
2
3
4
5
6
7
8
9
10
11
12
13
echo "Start AutoSSH"
/usr/bin/autossh \
-M 6767 \
-o "StrictHostKeyChecking=no" \
-o "ServerAliveInterval 15" \
-o "ServerAliveCountMax 3" \
-p 1984 \
-NR '*:1994:localhost:2222' \
natlee@<YOUR_DOMAIN> \
-i ~/.ssh/id_rsa這邊請把
<EXAMPLE_DOMAIN>
改成你自己連接外網裝置的 domain 或 IP
其中,StrictHostKeyChecking
用來自動新增known_host
,避免要手動連接一次 A
接着,我們直接再 run 一次
1 | docker-compose up |
然後就噴錯了
1 | reverse-tunnel-inside-bridge | Warning: Identity file /root/.ssh/id_rsa not accessible: No such file or directory. |
爲什麼它會說無法存取/root/.ssh/id_rsa
?
因爲它必須要靠 key 來連到 server,但它並沒有 key pairs
所以我們得幫他產生一組
我們先別暫停 container,而是直接使用這個指令去生成 key pairs
1 | docker exec -it reverse-tunnel-inside-bridge ssh-keygen |
然後輸出會長成下面這樣
1 | Generating public/private rsa key pair. |
但這樣是不夠的,因爲我們外網的裝置只能使用 key 登入
這時候,我們就必須把公鑰檔案~/.ssh/id_rsa.pub
的內容複製出來(直接進去用cat
指令就行了)
1 | docker exec -it reverse-tunnel-inside-bridge /bin/bash |
這串長長的公鑰就可以貼到我們外網 server A 的./ssh_setting/.ssh/authorized_keys
中
這時候看回去 container 輸出就沒有再跳錯誤了!
再來是安全問題,我們可以再打開目錄中的./ssh_setting/ssh_host_keys/sshd_config
去把這個設定做更改
1 | ChallengeResponseAuthentication no # 安全需求,只開放public key登入 |
到這邊內網裝置 B 已經算是設定完成了
接下來,我們把自己第三方位置的 PC 公鑰複製到內網裝置 B 的ssh_setting/.ssh/authorized_keys
後
我們從 PC 端使用 SSH 連接一開始有提到外網裝置多開的另外一個 port 1994
去做測試
1 | ❯ ssh natlee@<EXAMPLE_DOMAIN> -p 1994 |
成功連到裝置 B,也就是完美穿透防火牆,直接成功打洞進到內網了!
透過跳板 B 連至內網裝置 C
這邊的裝置 C,我使用樹莓派當範例
我們可以利用~/.ssh/config
去設定 SSH 的快速連線方式,如下面的例子
1 | Host reverse-tunnel-outside-server |
第一個 host,我們可以連到外網 server A,也就是 port 爲1984
的那位
第二個 host,我們可以連到內網裝置 B,也就是經過反向後,port 爲1994
的那位
最重要的是第三個 host,裏面這句ProxyCommand ssh -W %h:%p reverse-tunnel-inside-bridge
是精髓
意思是透過第二個 host reverse-tunnel-inside-bridge
進行 proxy 連到區網的位置 192.168.1.87
有了這個快速連線法,我們就可以直接來體驗一下
1 | ❯ ssh pi-reversed |
這樣配置之後,我們很簡單就可以直接從外網穿透到其他內部機器上了!
結語
這篇實驗做滿久的,不過從頭跑一次整個流程之後,我也對打洞有更深一步的認識
打洞的好處很多,像是 vscode 可以使用 remote-ssh 的套件
我們就可以直接選擇./ssh/config
內的機器進行連線
而且 SFTP 也是走 SSH 通道,我們可以直接在外網做內網檔案的傳輸
甚至我們也可以直接用內網做 proxy 來瀏覽內部才能看的網站
但是,這樣做其實會有些資安風險
從上面的流程,我們可以看出來反向
這件事是從內部機器主動連到外網機器
也就是如果有心人士拿到內部機器的存取權限的話,外部機器自然不保了
這邊能做的也只有保管好金鑰了
2024-08-13 更新
雖說內網可以反向連到外網機器很危險,但我們只要在外網機器SSH的設定檔做一些限制就可以了
1 | # /etc/ssh/sshd_config |
我們在sshd_config
中加入這段設定,就可以限制只有telepy
這個使用者
然後,我們的外網機器只有這個使用者且這個使用者只能進行tunneling,其他的操作都會被拒絕
這樣就可以避免內網機器拿到外網機器的命令執行權限
這邊可以參考後來我做的一個新專案Telepy
這個專案提供SSH通道在Web UI上的管理界面以及基本的使用者管理
甚至還有Web Terminal的功能,可以直接在瀏覽器上操作遠端機器
有興趣的話可以去看看!😎
Reference
使用 SSH 反向隧道进行内网穿透
SSH TUNNELLING FOR FUN AND PROFIT: AUTOSSH
這篇文章同步發表於 Medium ,歡迎留言討論!
Medium 文章連結