前阵子研究了一下用 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 。
SSH Port Forwarding 有下列三种模式:
- 本地端口转发
- 远程端口转发
- 动态端口转发
接下来会一一说明各种模式。 先来看看在 SSH Port Forwarding 当中参与的角色有哪些。
Port Fowarding 里的角色定义
对 Local 和 Remote Port Forwarding 来说,都会有下面这三个角色:
客户
- 任何你可以敲 ssh 指令来启动 Port Forwarding 的机器
SSH 服务器
- 可以被 Client 用 SSH 连进去的机器
目标服务器
- 某一台你想建立连线的机器,通常是为了对外开放这台机器上的服务
- 注意 , Client 与 SSH Server 本身都可以是 Target Server ,不是真的要有三台机器才可以进行 Port Forwarding!
而 Dynamic Port Forwarding 比较不一样,在于 Target Server 不会只有一台,而是可以被动态决定的。
了解了这三个角色,那就先来看看 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,但你又很想连到他…
这时候只要你能够 SSH 到那台服务器,就可以利用 Local Port Forwarding 来开启你电脑上的某个 Port(假设为 9090),将对它发送的数据转送到服务器的 Port 8080。 这样一来, 连上你的电脑的 Port 9090 就等于连上了防火墙后的服务器的 Port 8080 ,也就绕过了防火墙的限制。
客户
- 你的电脑
SSH 服务器
- 防火墙后的服务器
- SSH Destination: johnliu@my-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 进到提供服务的服务器,那该怎么办呢?
没问题! 只要你在防火墙后有任何一台你可以 SSH 的机器,接着修改一下指令里的 host 设定,你就可以利用这台机器进行数据转送:
客户
- 你的电脑
SSH 服务器
- 防火墙后你的机器
- SSH 目标: johnliu@my-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 地址。
远程端口转发
指令语法
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,所以无法让客户连进来:
这时候只要利用 SSH Remote Forwarding,就可以借由一台有 Internet IP 的对外机器,开启上面的某个 Port(假设为 9090)来转送数据到你的电脑上的 Port 8080。 这样子,客户只要连上对外机器的 Port 9090 就等于是连上了你电脑的 Port 8080。
客户
- 你的电脑
SSH 服务器
- 对外机器
- SSH Destination: johnliu@external-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 Forwarding 和一台对外机器, 可以让你从任何地方连回这个服务:
客户
- 你的电脑
SSH 服务器
- 对外机器
- SSH Destination: johnliu@external-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 设定好,如果你到家后才想到,那就请你再跑一趟吧…
动态端口转发
指令语法
ssh -D [bind_address:]<port> <SSH Server>
在 SSH Server 上启动一个 SOCKS 代理服务器,同时在 Client 上开启 bind_address:port 等待连接,当有人连上时,将所有数据转送到这个 SOCKS 代理服务器上,启动相对应的连接请求。
使用情境:建立一个 HTTP 代理服务器连到内网的所有 HTTP(S) 服务
只要有一台位于内网且 具有外部 IP 的机器,你就可以利用这个方法建立一个 HTTP 代理服务器,让你能够从外面连回内网里的所有 HTTP(S) 服务:
客户
- 你的电脑
SSH 服务器
- 内网里具有外部 IP 的机器
目标服务器
- 不适用
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 )一起用。
引用
- man ssh
- 袜子 (Wiki)
- SSH 端口转发示例
- SSH 端口转发/隧道指南