简体中文
ssh -L/-R/-D 是怎么打穿防火墙的?SSH 端口转发原理与滥用审计
SSH 端口转发可能是运维工具箱里最有用、也最危险的一个功能。ssh -L、ssh -R、ssh -D 三个参数可以在两台机器之间凭空架起一条加密隧道,把本来不通的网络打通;同样的机制,也常被用来在防火墙的只允许出、不允许进的规则上开一个"后门"。安全团队在巡检时最常撞见的异常之一,就是某台服务器上多了一条没有登记来源的 SSH 隧道。
本文把这条隧道拆开看:三种转发模式各自在做什么、为什么它能绕过防火墙、如何从进程和连接里发现异常隧道,以及从 sshd_config 到堡垒机集中入口的管控思路。
三种转发模式:本地、远程、动态
先明确一点:SSH 端口转发利用的是 SSH 连接本身这条已经加密、已经通过认证的通道,在上面复用一个新的数据流。它不需要目标主机开放额外端口,也不依赖目标网络的路由可达性——只要发起端和 SSH 服务器之间有一条能建立的 SSH 连接,隧道就能搭起来。这是它强大、也是它危险的根源。
从协议层面看,转发并不在系统里新建监听型服务,而是复用 SSH 的"通道(channel)"机制:客户端发起一条 direct-tcpip(用于 -L/-D)或 forwarded-tcpip(用于 -R)的通道打开请求,对端收到后替它去连目标地址,再把 TCP 数据流按原样塞回这条已加密的通道。整个过程对操作系统而言,只是 SSH 进程上的一个 socket,没有新端口被真正"开放"给防火墙去识别——这正是很多边界设备对它视而不见的原因。
三种模式的区别在于"监听端口开在哪一侧、流量从哪一侧出去":
| 参数 | 名称 | 监听在哪 | 流量出口 | 典型用途 |
|---|---|---|---|---|
-L | 本地转发 | 客户端本地 | SSH 服务器侧 | 通过跳板机访问只有跳板机才能到达的内网服务 |
-R | 远程转发 | SSH 服务器侧 | 客户端本地 | 把内网服务反向暴露给外面的机器 |
-D | 动态转发 | 客户端本地 | SSH 服务器侧 | 本地起一个 SOCKS 代理,所有流量走服务器出去 |
本地转发 -L 的写法是:
bash
ssh -L 127.0.0.1:3306:10.0.0.5:3306 user@jump.example.com含义是:在本地监听 3306 端口,把连接经 SSH 隧道送到 jump.example.com,再由跳板机去连内网的 10.0.0.5:3306。于是你在本地 mysql -h 127.0.0.1 -P 3306,实际上访问到的是内网数据库。跳板机成为流量出口,内网服务本身不需要对公网开任何端口。
远程转发 -R 方向相反:
bash
ssh -R 0.0.0.0:8080:127.0.0.1:8080 user@public.example.com这行命令在 SSH 服务器(public.example.com)上监听 8080,把进来的连接经隧道送回客户端,由客户端去连自己的 127.0.0.1:8080。注意这里的关键是连接方向:SSH 是客户端主动连服务器建立的,但数据流是"服务器收到请求 → 送回客户端"。这意味着内网里的一台机器,只要它能主动往外连 SSH,就能把自己的服务"反向"挂到公网服务器的一个端口上——防火墙根本拦不住,因为它看到的是从内到外的一条正常出站 SSH 连接。
动态转发 -D 则是在本地起一个 SOCKS 代理:
bash
ssh -D 1080 user@public.example.com本地 1080 端口变成 SOCKS5 代理,浏览器或任何程序把流量交给它,全部经 SSH 服务器转发出去。它常被用来借用服务器的网络位置访问目标、隐藏真实来源,也常被当作绕过出口审计(网页过滤、出口白名单)的数据通道。
为什么 -R 是"内网后门"的标配
理解了连接方向,就能看出 -R 的危险在哪。传统防火墙的默认策略通常是"入站全拒、出站放行":外网进不来的流量直接丢,内网发出去的连接正常放行。而 -R 恰恰是从内网主动向外建立 SSH 连接,然后在服务器侧开一个监听端口把流量引回来——整条链路对防火墙而言只是一条普通出站连接,入站的那半段被包在隧道里,规则根本看不到。
于是攻击者或内部人员可以做这样一件事:在被控的内网主机上执行一条 ssh -R 0.0.0.0:2222:127.0.0.1:22 user@vps,此后任何能连到 vps:2222 的人,都会被直接送进内网主机的 22 端口。这条隧道一天不断,外网对内的通道就一天存在,而防火墙日志里只有一条看起来毫无问题的 SSH 出站会话。
-L 和 -D 的滥用场景同样常见:一个有跳板机 SSH 权限的员工,用 -L 把本不该直接访问的内网管理后台、数据库端口拉到自己笔记本上;或者用 -D 把跳板机当成匿名出口,规避公司对上网行为的审计。这些都不需要破解任何密码,只需要"有一条合法的 SSH 权限",而隧道把这条权限放大了无数倍。
更麻烦的是,这类隧道很容易做成持久化:用 autossh 或 systemd 把 ssh -R 包成开机自启、断线重连的服务,隧道就会在重启后自动重建,不需要攻击者再次登录。巡检时如果只做一次性排查、没有持续监控,这条"断了又自动接上"的通道会反复漏过。
怎么发现隧道正在跑
巡检的核心思路是:看监听端口、看进程、看授权文件、看连接。
先看服务器上有没有不属于业务的监听端口。SSH 服务器侧的 -R 转发会由 sshd 进程持有监听,而且默认只绑在回环地址上,所以巡检时回环监听尤其不能漏:
bash
# 看谁在监听,以及监听进程(注意 127.0.0.1 开头的回环监听)
ss -tnlp | grep -E 'sshd|127.0.0.1:|:2222|:8080'
# 看所有与 sshd 相关的连接,含建立中的隧道
ss -tnp | grep sshd
# 找出带 -R/-D/-L 标志的长驻 ssh 进程,按运行时长排序
ps -eo pid,user,etime,args | grep -E 'ssh .*(-R|-D|-L)'如果看到 sshd 持有某个既不是 22、也不是业务端口的监听,基本可以判定有远程转发在跑。接着定位到具体进程和用户:
bash
# 找出带 -R/-D/-L 标志的长驻 ssh 进程
ps aux | grep -E 'ssh .*(-R|-D|-L)'
# 查看某进程打开了哪些 socket
lsof -i -P -n -p <PID>
# 看当前有哪些 SSH 登录会话在挂
who -u这里有一个容易被忽略的不对称:服务器侧只能看到 -R。因为 -R 的监听开在服务器上,ss 一看便知;但 -L 的监听开在客户端(员工笔记本)上,-D 的 SOCKS 代理也开在客户端,服务器侧只是多了一条普通 SSH 会话,从监听端口上看不出任何异常。所以纯靠服务器巡检只能逮到 -R,要抓 -L/-D 的滥用,得靠出口流量的监控——出站方向突然出现大量、持续的、去往陌生目标的长连接,往往是有人在用隧道借道。
接着查授权文件。很多隧道是通过 authorized_keys 里的公钥无密码建立的,密钥一旦下发就长期有效,隧道就能静默重建。要重点审计:
~/.ssh/authorized_keys里有没有陌生的公钥、有没有可疑的command=或转发选项;- 密钥是否本应带
no-port-forwarding、restrict等限制却缺失; - 跳板机上所有用户(尤其
root、服务账号)的授权文件是否齐全且已知。
默认情况下 sshd 不会为每条转发写日志,所以光靠日志容易漏。更可靠的是在 sshd_config 里把 LogLevel 提到 VERBOSE,并配合连接层面的监控(ss 的定时快照、netflow 等)来比对"谁在什么时候开了什么监听"。
从 sshd_config 到 authorized_keys 的管控
管控端口转发,靠的是服务端配置加密钥选项两层:
/etc/ssh/sshd_config 里的几个关键项:
| 配置项 | 作用 |
|---|---|
AllowTcpForwarding | no 全禁;local 只允许 -L;remote 只允许 -R;yes 全开 |
GatewayPorts | 是否允许 -R 绑定到非回环地址(0.0.0.0),默认 no 只绑 127.0.0.1 |
PermitOpen | 白名单,只允许 -L 转发到指定的 host:port |
对自动化、脚本场景常用的密钥,在 authorized_keys 里按需加选项是最细粒度的做法:
text
restrict,permitopen="10.0.0.5:3306" ssh-ed25519 AAAA... commentrestrict 默认关掉端口转发、X11 转发、伪终端分配等能力,再按需用 permitopen 精确放行单个目标。运维脚本只允许它转发到数据库端口,就别给它开隧道当跳板的权限。
配完要做一个反向验证,而不是只看配置"写没写对":拿一个不该有转发权限的账号,实际执行一条 ssh -L 或 ssh -R,确认它被拒绝并能在 sshd 的 VERBOSE 日志里看到对应的拒绝记录。只有"配置生效"和"能被审计"同时成立,管控才算真正落地。
不过这些配置只约束"SSH 这一条路"。一台有 shell 权限的机器,不用 SSH 也能用 socat、chisel、frp 等工具起隧道,sshd_config 管不到它们。所以真正要封住 -R/-D 式出站隧道,出口侧的流量控制(出站白名单、只放行必要协议)才是兜底手段,SSH 配置只是第一道、也是最常用的一道闸。
三个常见的误区
误区一:只禁密码登录就够了。 把 PasswordAuthentication 关掉、只留公钥,挡不住隧道——有合法私钥的人照样能 -R。隧道滥用发生在"已认证之后",跟认证方式关系不大。
误区二:GatewayPorts no 默认值等于安全。 默认的 no 只是让 -R 绑在服务器的 127.0.0.1,外网连不进来;但服务器本地的其他用户、其他进程,或者在同一台机器上再用一条 -L 把它转发出去,依然能触及。它限制的是"远程可达",不是"本机可达"。
误区三:端口转发是坏的,要一刀切全禁。 端口转发本身是中性的,正常场景(用跳板机访问内网数据库、跨地域加速)每天都在依赖它。一刀切全禁,代价是逼着团队用更隐蔽、更难审计的方式绕路。要管的是"谁、能不能、转发到哪、有没有留痕",而不是这个功能本身。
把隧道收进集中入口
上面的检测和配置,本质都是在"机器 + 用户"的维度上做加法:每台机器自己配 sshd_config,每个 authorized_keys 单独审。当机器只有几台时够用;机器一多,就散得没法管——不知道谁在哪些机器上有权限、有没有私设隧道,更谈不上统一审计。
这也是堡垒机这类集中入口的用武之地:把所有 SSH 接入收敛到单一入口,隧道是否允许由入口统一决定、逐会话留痕。以开源的 Next Terminal 为例,它的 SSH 代理服务器就提供了独立的"开启隧道"开关,关掉后仍能正常登录、只是不能再做端口转发;配合会话录像与审计(见 合规与审计),谁在什么时候开了哪条隧道都能追到人。对 SSH 接入与资产访问的集中控制方式,可参考 SSH 代理服务器、资产访问 与 资产管理;跨网络环境的统一入口则见 安全网关。
判断该用哪种粒度,可以按团队的规模来:机器个位数、人员单一,sshd_config + 密钥选项足够;一旦机器和人员开始增多、出现外包或跨团队协作,就值得把入口收敛到一处,让"隧道有没有开、开到哪"从靠人肉巡检变成一条可查询的审计记录。