Skip to content

ssh -L/-R/-D 是怎么打穿防火墙的?SSH 端口转发原理与滥用审计

SSH 端口转发可能是运维工具箱里最有用、也最危险的一个功能。ssh -Lssh -Rssh -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 权限",而隧道把这条权限放大了无数倍。

更麻烦的是,这类隧道很容易做成持久化:用 autosshsystemdssh -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-forwardingrestrict 等限制却缺失;
  • 跳板机上所有用户(尤其 root、服务账号)的授权文件是否齐全且已知。

默认情况下 sshd 不会为每条转发写日志,所以光靠日志容易漏。更可靠的是在 sshd_config 里把 LogLevel 提到 VERBOSE,并配合连接层面的监控(ss 的定时快照、netflow 等)来比对"谁在什么时候开了什么监听"。

从 sshd_config 到 authorized_keys 的管控

管控端口转发,靠的是服务端配置加密钥选项两层:

/etc/ssh/sshd_config 里的几个关键项:

配置项作用
AllowTcpForwardingno 全禁;local 只允许 -Lremote 只允许 -Ryes 全开
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... comment

restrict 默认关掉端口转发、X11 转发、伪终端分配等能力,再按需用 permitopen 精确放行单个目标。运维脚本只允许它转发到数据库端口,就别给它开隧道当跳板的权限。

配完要做一个反向验证,而不是只看配置"写没写对":拿一个不该有转发权限的账号,实际执行一条 ssh -Lssh -R,确认它被拒绝并能在 sshdVERBOSE 日志里看到对应的拒绝记录。只有"配置生效"和"能被审计"同时成立,管控才算真正落地。

不过这些配置只约束"SSH 这一条路"。一台有 shell 权限的机器,不用 SSH 也能用 socatchiselfrp 等工具起隧道,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 + 密钥选项足够;一旦机器和人员开始增多、出现外包或跨团队协作,就值得把入口收敛到一处,让"隧道有没有开、开到哪"从靠人肉巡检变成一条可查询的审计记录。