SSH 跳板机端口穿透:当 ProxyJump、ssh -L 和 SOCKS5 都不可用时
记录一种在服务器禁止 SSH TCP forwarding 时仍可通过跳板机访问目标服务的实用方案:ProxyCommand + nc 登录,socat + SSH ControlMaster 做固定端口穿透。
SSH 跳板机端口穿透:当 ProxyJump、ssh -L 和 SOCKS5 都不可用时
在内网运维场景里,经常会遇到这种链路:
本机 -> 跳板机 -> 目标机
本机不能直接访问目标机,只能先登录跳板机,再从跳板机访问目标机。通常我们会优先使用 ProxyJump、ssh -L 或 ssh -D SOCKS5 代理。但如果跳板机或目标机的 sshd 禁止 TCP forwarding,这些标准方案会直接失败。
这篇文章记录一个更朴素但实用的方案:用 ProxyCommand + nc 完成 SSH 登录,再用 socat + ssh + nc 穿透少量固定端口。它不需要修改服务器 sshd 配置,适合权限受限但可以正常 SSH 登录的环境。
示例里的 IP、用户和别名均为脱敏占位,不对应真实环境。
1. 基础 SSH 链路
先给跳板机配置普通 SSH Host:
Host bastion-demo
HostName 192.0.2.10
User bastion-user
IdentityFile ~/.ssh/id_ed25519
目标机通过跳板机上的 nc 建立 TCP 连接:
Host target-demo
HostName 198.51.100.20
User app-user
IdentityFile ~/.ssh/id_ed25519
ProxyCommand ssh bastion-demo nc %h %p
验证登录:
ssh target-demo
这条链路的关键点是:ProxyCommand 不走 SSH 的 direct-tcpip channel,而是在跳板机上执行 nc <target-host> <target-port>。因此它可以绕过一部分禁止 ProxyJump 的环境。
要求很简单:
- 本机私钥能登录跳板机。
- 本机私钥或本机公钥授权能登录目标机。
- 跳板机上存在
nc、ncat或socat之一。 - SSH Host 别名建议只用 ASCII。
2. 常见失败现象
2.1 中文或特殊字符别名导致 hostname 报错
如果执行:
ssh 某个中文别名
可能会报:
hostname contains invalid characters
处理方式很直接:SSH Host 别名使用 ASCII,中文写到注释或文档里。
project-bastion
project-target
2.2 ProxyJump 被服务端拒绝
ProxyJump 失败时常见报错:
channel 0: open failed: administratively prohibited: open failed
stdio forwarding failed
原因是 ProxyJump 依赖 SSH direct-tcpip channel。如果跳板机 sshd 禁止 TCP forwarding,就会被服务端拒绝。
替代配置:
ProxyCommand ssh bastion-demo nc %h %p
2.3 SOCKS5 或 ssh -L 被目标机拒绝
如果尝试:
ssh -N -D 127.0.0.1:1080 target-demo
或:
ssh -N -L 18080:127.0.0.1:8080 target-demo
遇到:
channel 1: open failed: administratively prohibited: open failed
通常是目标机 sshd 禁止 TCP forwarding。
可以在目标机检查:
sshd -T 2>/dev/null | egrep '^(allowtcpforwarding|disableforwarding|permitopen|permitlisten)'
如果看到:
allowtcpforwarding no
disableforwarding no
permitopen any
permitlisten any
那么 ssh -D SOCKS5 和 ssh -L 基本都不能用。
3. 固定端口穿透方案
当 SSH forwarding 被禁时,可以在本机用 socat 监听一个本地端口。每次有浏览器连接进来,就执行一次:
ssh target-demo nc 127.0.0.1 <remote-port>
通用模板:
socat TCP-LISTEN:<local-port>,bind=127.0.0.1,reuseaddr,fork EXEC:'ssh <target-alias> nc <remote-host> <remote-port>'
安装 socat:
brew install socat
示例:把本机 18080 转到目标机本地 8080:
socat TCP-LISTEN:18080,bind=127.0.0.1,reuseaddr,fork EXEC:'ssh target-demo nc 127.0.0.1 8080'
浏览器访问:
http://127.0.0.1:18080
如果目标服务监听的是内网 IP 而不是 127.0.0.1,把命令里的 127.0.0.1 换成对应地址即可。
4. 稳定性增强:复用 SSH 连接
上面的原始命令有一个问题:浏览器的每个 TCP 连接都会新建一次 SSH 登录。页面资源多时,连接会明显抖动。
可以先建立 SSH master 连接:
mkdir -p ~/.ssh/cm
chmod 700 ~/.ssh/cm
ssh -MNf \
-S ~/.ssh/cm/target-demo \
-o ControlMaster=yes \
-o ControlPersist=2h \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
target-demo
再让 socat 复用这个 master:
socat TCP-LISTEN:18080,bind=127.0.0.1,reuseaddr,fork,keepalive EXEC:'ssh -T -S ~/.ssh/cm/target-demo -o BatchMode=yes -o LogLevel=ERROR target-demo nc 127.0.0.1 8080'
检查 master 状态:
ssh -S ~/.ssh/cm/target-demo -O check target-demo
关闭 master:
ssh -S ~/.ssh/cm/target-demo -O exit target-demo
这个版本仍然不是完整 VPN,也不是 SOCKS5,但对少量固定 Web 服务已经足够稳定。
5. 什么时候不该用这个方案
这个方案适合:
- 只需要访问少量固定端口。
- 不能修改 sshd 配置。
ProxyJump、ssh -L、ssh -D被服务端策略禁止。- 跳板机能访问目标机,且跳板机上有
nc。
不适合:
- 需要访问大量动态端口。
- 需要全系统透明代理。
- 需要长期高并发访问。
- 能让管理员开启
AllowTcpForwarding yes。
如果管理员允许 TCP forwarding,优先使用标准方案:
ssh -N -L 18080:127.0.0.1:8080 target-demo
或者:
ssh -N -D 127.0.0.1:1080 target-demo
标准方案更简单,也更稳定。
6. 总结
当 SSH TCP forwarding 被禁时,不必一上来就改服务器配置或搭 VPN。对少量固定端口,可以用:
ProxyCommand + nc
socat + ssh + nc
ControlMaster 复用连接
这套组合足够小,也足够实用。它的边界也很清楚:适合临时访问固定端口,不适合替代正式 VPN 或标准 SSH forwarding。