FRPS SSH 连接反复断开问题排查记录

时间:2025-10-09 ~ 2025-10-10
服务:公网 FRPS 转发 SSH(Docker 部署)

背景与目标

  • 为了在外出时也能远程控制公司电脑,几年前自建了一套 FRP 转发:公网服务器运行 frps,公司内部电脑运行 frpc,通过端口 6001 暴露 SSH。
  • 方案长期稳定使用,最近一周登录总是失败,需要连续尝试很多次才能成功。
  • 于是逐步排查,发现先是本地 SSH 认证受到影响,修复后又暴露出公网暴力扫描的问题。
  • 目标:先恢复 SSH 登录的稳定性,再压制公网扫描带来的噪音与风险

现象概述与调查思路

  1. 用户感知

    • 近期外网通过 ssh -p 6001 连接公司主机时,经常连续失败,多次重试才勉强连上。
  2. FRPC 侧初检

    • 在公司电脑中查看 docker logs frpc,大量日志成对出现:
      ... start a new work connection ...
      ... join connections ...
      ... join connections closed
      
      说明客户端与 frps 建立了隧道,但宿主侧迅速关闭了本地连接。
  3. FRPS 日志追踪

    • 启用 log.to = "/tmp/frps.log" 后,tail -f tmp/frps.log 显示同一公网地址在数秒内反复发起 get a user connection,例如:
      2025-10-10 13:00:45.933 ... [ssh-office] get a user connection [196.251.72.***:52178]
      2025-10-10 13:00:55.844 ... [ssh-office] get a user connection [193.32.162.***:43492]
      
      远端频繁试探,显然不是正常运维流量。
  4. 宿主 SSH 日志核实

    • 切换到公司电脑中检查sshd服务的日志journalctl -u ssh -n 200 发现日志中中充斥着
      Failed password for invalid user ...
      Connection closed by invalid user ...
      
      源地址统一为 172.25.0.3(FRPC 容器的地址),确定攻击流量是通过 FRP 转发到宿主 sshd
  5. 处理顺序

    • 先调整 SSH 登录策略,使合法用户能稳定登录;随后针对仍在刷日志的陌生公网 IP 部署防护措施。

初始配置快照

FRPS (etc/frp/frps.toml)

bindPort = 7000                        # FRPS 控制面监听端口
auth.token = "***-redacted-token-***"  # 与客户端共享的 Token

log.level = "trace"                    # 提升日志等级便于追踪
log.to = "/tmp/frps.log"               # 将日志写入容器 /tmp,供 Fail2ban 使用

[transport]
tcpMux = false                         # 关闭多路复用,避免交互式会话复位

webServer.addr = "0.0.0.0"             # Dashboard 监听地址
webServer.port = 7500                  # Dashboard 端口
webServer.user = "***-masked-user-***" # Dashboard 账号(已脱敏)
webServer.password = "***-masked-***"  # Dashboard 密码(已脱敏)

vhostHTTPPort = 80                     # HTTP 虚拟主机端口

FRPC (etc/frp/frpc.toml)

serverAddr = "***.***.***.***"         # 公网 FRPS 地址(完全脱敏)
serverPort = 7000                      # 对应 FRPS 控制端口
auth.token = "***-redacted-token-***"  # 与服务器一致的 Token

[transport]
tcpMux = false                         # 关闭 TCP 多路复用
dialServerTimeout = 30                 # 连接服务器超时时间
dialServerKeepalive = 7200             # WorkConn 保活

[[proxies]]
name = "ssh-office"                    # SSH 隧道名称
type = "tcp"                           # 使用 TCP 代理
localIp = "host.docker.internal"       # 仍沿用 Docker 默认别名
localPort = 22                         # 本地 SSH 服务端口
remotePort = 6001                      # 公网映射端口(高风险点)

Docker Compose(问题发生前)

services:
  frps:
    restart: always
    image: snowdreamtech/frps
    container_name: frps
    volumes:
      - ./etc/frp/frps.toml:/etc/frp/frps.toml  # 挂载 FRPS 配置
    ports:
      - 7000:7000                               # 控制端口
      - 6001:6001                               # SSH 代理端口

排查步骤与关键发现

阶段一:先让合法用户稳定登陆

  1. 复现问题

    • 从外网登录时,经常需要尝试 5~6 次才能成功,甚至成功后几秒就掉线。
    • 查看 docker logs frpc,几乎每次尝试都会出现 start a new work connection → join connections → join connections closed,说明 frps 侧握手正常,是宿主 sshd 主动断开。
  2. 排查宿主日志

    • journalctl -u ssh -n 200:查看 SSH 服务最近 200 条记录,方便捕捉异常。
    • 输出中出现大量 Invalid user XXXpam_faillock 提示,用户名随机,源 IP 为 FRPC 所在的 172.25.0.3。可推断外部有人透过 FRP 尝试密码爆破。
  3. 加固 SSH 配置

    • 修改 /etc/ssh/sshd_config,核心调整:
      PasswordAuthentication no   # 禁止密码登录,仅允许密钥
      PermitRootLogin no          # 禁止 root 账号远程登陆
      AllowUsers safeuser         # 只放行实际使用者(示例已脱敏)
      
    • 执行 sudo systemctl restart sshd 重载配置;systemctl 会优雅重启服务,避免会话中断。
    • 再次通过 FRP 隧道登录,确认凭公钥可以稳定连接,第一阶段问题解决。
    • 同时核实 frpc 容器内的 /etc/hosts 已包含 host.docker.internal 映射,确保本地地址解析稳定。
  4. 新的异常

    • 登录恢复后,tmp/frps.log 仍持续刷出连接—断开记录,而合法用户的会话并未受影响。说明表象问题虽解,但公网暴力扫描还在进行,需要进一步治理。

阶段二:定位并缓解公网暴力扫描

  1. 量化恶意流量

    • grep "get a user connection" tmp/frps.log | cut -d'[' -f4 | cut -d']' -f1 | sort | uniq -c | sort -nr | head
      逐步解析:先筛出连接日志,再提取 IP,最后统计频次。结果发现 193.32.162.*196.251.*.* 等地址在一分钟内尝试十几次。
    • 结合 whois 工具验证这些 IP 多来自海外托管网络,进一步确认是非授权访问。
  2. 部署 Fail2ban(第一轮)

    • 自定义 Filter:
      failregex = ^.*get a user connection \[(?P<host><HOST>):\d+\].*$
      
      <HOST> 捕获源 IP,用于统计重复失败。
    • 使用 fail2ban-regex tmp/frps.log config/fail2ban/filter.d/frps-proxy.conf 检验,确认 900+ 次命中,证明规则有效。
    • Jail 参数设置 findtime = 120maxretry = 3bantime = 86400,即 2 分钟内超过 3 次就封禁一天。
  3. 解决 Docker 转发漏封

    • 现实情况:Fail2ban 使用默认 iptables-multiport 时,只在宿主 INPUT 链加规则;而 FRPS 容器的流量经过 Docker 的 DOCKER-USERFORWARD → NAT,攻击包仍然被转发。
    • 方案:编写自定义动作 iptables-docker.conf,在启动时 -I DOCKER-USER 1 -j f2b-frps,把封禁链插在 Docker 用户链首位。
    • 重新启动 Fail2ban 后执行 sudo iptables -L DOCKER-USER -n --line-numbers,看到类似:
      Chain DOCKER-USER (policy ACCEPT)
      num  target     prot opt source               destination
      1    f2b-frps   all  --  0.0.0.0/0            0.0.0.0/0
      2    RETURN     all  --  0.0.0.0/0            0.0.0.0/0
      
      说明所有进入 Docker 的流量都会先经过 Fail2ban 封禁链。
    • 再用 sudo iptables -L f2b-frps -n 查看链内容,可见每个被封 IP 都对应一条 DROP 规则。此后 tmp/frps.log 不再出现这些 IP 的新连接。

最终配置摘要

SSH 服务关键配置(/etc/ssh/sshd_config 节选)

PasswordAuthentication no   # 禁止密码登录,杜绝暴力破解成功可能
PermitRootLogin no          # 禁止 root 直接登录,降低风险
AllowUsers safeuser         # 仅允许授权账号(示例名已脱敏)
PubkeyAuthentication yes    # 启用密钥认证
ClientAliveInterval 60      # 定期发送保活,保持会话稳定

Fail2ban Filter (config/fail2ban/filter.d/frps-proxy.conf)

[Definition]
failregex = ^.*get a user connection \[(?P<host><HOST>):\d+\].*$  # 匹配 FRPS 用户连接日志并抓取源 IP
ignoreregex =                                                       # 暂无忽略规则,可按需追加

Fail2ban Jail (config/fail2ban/jail.d/frps.conf)

[frps]
enabled  = true                              # 启用该 jail
filter   = frps-proxy                        # 引用上文 filter
logpath  = /home/ubuntu/Workspace/WebService/frps/tmp/frps.log  # FRPS 日志路径
port     = 6001                              # 关注的代理端口
maxretry = 3                                 # findtime 内最大容忍次数
findtime = 120                               # 统计时间窗口(秒)
bantime  = 86400                             # 封禁时长(秒)
banaction = iptables-docker                  # 使用 Docker 专用封禁动作

自定义 Action (config/fail2ban/action.d/iptables-docker.conf)

[INCLUDES]
before = iptables-common.conf

[Definition]
actionstart = <iptables> -N f2b-<name>             # 创建自定义链
              <iptables> -I DOCKER-USER 1 -j f2b-<name>  # 在 DOCKER-USER 链首跳转
actionstop  = <iptables> -D DOCKER-USER -j f2b-<name>   # 停止时移除跳转
             <iptables> -F f2b-<name>                 # 清空链规则
             <iptables> -X f2b-<name>                 # 删除自定义链
actioncheck = <iptables> -C DOCKER-USER -j f2b-<name>  # 检查链是否存在
actionban   = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>  # 封禁 IP
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>    # 解封 IP

[Init]
blocktype = DROP                                  # 封禁行为为丢弃

Docker Compose(调整后)

services:
  frps:
    restart: always
    image: snowdreamtech/frps
    container_name: frps
    volumes:
      - ./etc/frp/frps.toml:/etc/frp/frps.toml  # 配置文件
      - ./tmp/frps.log:/tmp/frps.log            # 挂载日志方便 Fail2ban 读取
    networks:
      npm-default:
        aliases:
          - frps
    ports:
      - 7000:7000                               # 控制端口
      - 6001:6001                               # SSH 代理端口

验证与结果

  1. Fail2ban 状态

    sudo fail2ban-client status frps
    

    返回(样例脱敏):

    Status for the jail: frps
    |- Filter
    |  |- Currently failed: 0
    |  |- Total failed:     932
    |  `- File list:        /home/.../frps/tmp/frps.log
    `- Actions
       |- Currently banned: 11
       |- Total banned:     11
       `- Banned IP list:   193.32.162.*** 196.251.*** 80.94.92.***
    
  2. iptables 验证
    sudo iptables -L DOCKER-USER -n --line-numbers 显示 Fail2ban 链位于链首,可拦截所有容器向内的流量。

  3. FRPS 日志复查
    连续 30 分钟未再出现已封禁 IP 的 get a user connection 记录,仅保留健康连接日志。

  4. SSH 可用性
    多次从可信环境通过 ssh -p 6001 user@***.***.***.*** 登录,连接稳定;journalctl -u ssh 中仅剩合法用户成功登陆的记录。

后续建议

  • 最小暴露面:考虑改用 stcp/visitor 方案或在入口前加云防火墙,彻底隐藏固定端口。
  • 密钥管理:已禁用密码登录,仅保留公钥;建议定期轮换密钥,限制跳板访问来源。
  • Fail2ban 自动化:若需对子网累计计数,可扩展自定义 action 或引入 ipset/crowdsec 等工具。
  • 监控告警:结合 fail2ban 邮件/Webhook 通知,及时掌握异常封禁。
  • 日志持久化tmp/frps.log 已挂载到宿主 tmp/,建议定期轮询并归档,或接入日志系统。

附录

常用命令

# 验证正则效果(确认 failregex 是否能命中日志)
fail2ban-regex tmp/frps.log config/fail2ban/filter.d/frps-proxy.conf

# 查看 Fail2ban 日志(实时跟踪封禁动作)
sudo tail -f /var/log/fail2ban.log

# 清理历史封禁记录(停服务后)
sudo iptables -F DOCKER-USER                     # 清空 DOCKER-USER 链
sudo iptables -A DOCKER-USER -j RETURN           # 恢复默认返回规则
sudo rm /var/lib/fail2ban/fail2ban.sqlite3       # 删除 Fail2ban 历史数据库

# 检查 FRPS 日志中新连接频率(找出高频来源)
grep "get a user connection" tmp/frps.log | cut -d'[' -f4 | sort | uniq -c | sort -nr | head

关键日志摘录(脱敏)

Fail2ban 封禁恢复记录

2025-10-10 21:27:05,631 fail2ban.actions [1503039]: NOTICE  [frps] Ban 80.94.92.62
2025-10-10 21:27:09,467 fail2ban.actions [1503039]: NOTICE  [frps] Ban 196.251.115.189
2025-10-10 21:30:36,018 fail2ban.actions [1519768]: NOTICE  [frps] Restore Ban 80.94.92.50

FRPS 攻击流量样例

2025-10-10 13:27:07.234 [I] [proxy/proxy.go:204] [0125a6e14fd416da] [ssh-office] get a user connection [196.251.114.***:35308]
2025-10-10 13:27:09.574 [I] [proxy/proxy.go:204] [0125a6e14fd416da] [ssh-office] get a user connection [92.118.39.***:59268]
2025-10-10 13:27:34.482 [I] [proxy/proxy.go:204] [0125a6e14fd416da] [ssh-office] get a user connection [193.32.162.***:55104]

SSH 登录失败片段

Oct 10 00:18:12 Workstation sshd-session[1474238]: Failed password for invalid user caishaobin from 172.25.0.3 port 43502 ssh2
Oct 10 00:18:13 Workstation sshd-session[1474304]: Failed password for invalid user trlian from 172.25.0.3 port 39242 ssh2
Oct 10 00:18:20 Workstation sshd-session[1474664]: Failed password for root from 172.25.0.3 port 39252 ssh2

结论:问题根因是公网暴力扫描通过 FRP 直达宿主 SSH,导致认证失败后立即断开合法连接。通过禁用密码登陆、自定义 Fail2ban Docker 封禁链、持久化日志等手段,可有效缓解攻击并恢复 SSH 稳定性。后续建议进一步缩小暴露面,例如改用 STCP 或额外的防火墙策略。***

文章作者: 居不正
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 翠南山
计算机 命令行工具 服务器
喜欢就支持一下吧