白泽图

  • 文章
    • Unity渲染
    • Unity项目开发
    • 工具
    • 数学
    • 算法
    • 网站搭建
    • 网络&操作系统
个人知识库
互联网技术研究与经验总结
  1. 首页
  2. 网络&操作系统
  3. 正文

对外端口被扫码攻击:从"被轰炸到崩溃"到"低频放养、高频禁闭"

2026-09-28 23点热度 0人点赞 0条评论

一份可执行运维手册:任何一台公网出口用 frp 转发、且 frps 有落盘日志的机器,都能照着 Step 0 → Step 4 独立解决同类问题。
结构:先给 30 秒快速路径(给复读这个文档的我),再给完整分析,最后给可复制的命令序列。
记录日期:2026-09-28(示例环境:frps + Ubuntu,家庭服务器经 frp 隧道暴露 SSH/邮件)。


零、30 秒快速路径(复读本手册时先看这里)

症状:自建隧道(SSH/邮件)经公网端口转发,出现"明明隧道通、却连不上、连接反复超时"。
证据:frp 服务端日志(/var/log/frps.log 或 frps.toml 的 log_file 指定路径)出现大量:

[ssh] get a user connection [某公网IP:随机端口]

解题顺序(细节见各 Step):

  1. Step 1 定量:统计日志里源 IP 连接数排行 + 单 IP 节奏,画出"攻击频率"和"自己使用频率"。
  2. Step 2 定红线:红线 = 卡在"攻击者高频"与"我们极限"之间的空档。maxretry = 红线(次/分钟) × findtime(分钟)。
  3. Step 3 检查单 → 落地:先过"防误封自己"检查单(重点:确认封的不是自己/NAT 同池),再写 filter、配 jail、reload。
  4. Step 4 验证回滚:fail2ban-client status 看封禁、iptables -L 验证、unbanip 解封、ufw 固化手动规则。

红线公式(本手册核心):

红线次/分钟 = 通常取"攻击者高频频率"以下、"我们使用上限"以上的中间值
maxretry     = 红线次/分钟 × findtime(分钟)
示例:findtime=1 分钟、红线 72 次/分钟 → maxretry=72

最优先的一条防翻车铁律:

封 IP 之前,先确认那个 IP 不是你自己的出口 IP。验证法:从受保护机子上 curl 外部、或查 nginx/SSH 日志取合法源,与"想封的 IP"比对。宁可漏封,不可错封。


一、起因(参考案例:这问题长什么样)

自建家庭邮件服务器:
- 家里跑 sshd + frpc,服务经 frp 隧道暴露到公网一台服务器;
- 公网服务器用 frps 监听一个对外代理端口,把连接原样转发回家(连北京某个对端口 = 进家里 sshd);
- 平时管理服务器、收发邮件全走这条隧道。

浮出水面的问题:连着连着就断,SSH 连接反复失败(连续重试 12 次全部 connection failed)。一度怀疑隧道、frp、家里 sshd 配置,排查无果——最后盯住公网侧日志,真凶是该端口被外部 IP 高频敲门:

2026-09-28 11:25:07 [ssh] get a user connection [45.148.10.66:59106]
2026-09-28 11:25:17 [ssh] get a user connection [45.148.10.66:44746]
2026-09-28 11:25:26 [ssh] get a user connection [45.148.10.66:49328]

攻击现场数据(本案例 frps.log 统计)

攻击者 频率 间距
高频洪水 bot 约 84 次/分钟 每 ~0.7 秒一条
低频持续 bot 约 6 次/分钟 每 ~10 秒一条
当日该端口连接总量 单日 5438 条 —

高频洪水打爆 frps 入站队列 → 挤占家里 sshd 的 pre-auth 等待队列 → 表现成"隧道是通的、人却连不进"。


二、分析:难点在哪

1. frp 纯转发,天生无"认证防线"

frps 只做:对外端口收到连接 → 原样打包转发回家。不校验用户、不区分是否自己人,公网任何人都能连,"进来的一视同仁"。

2. 我们和低频 bot 在日志里"行为重叠"

fail2ban 靠行为频率区分人机。实测:

来源 频率
低频 bot 约 6 次/分钟
我们正常 SSH 操作 约 3–12 次/分钟

两个都在每分钟几次的量级,不可用频率区分。任何"按频率封"要么误伤自己、要么漏掉低频 bot。

3. "IP 白名单"方案是死路(重点教训)

一度想用 fail2ban 的 ignoreip(豁免名单)加我们出口 IP,发现不可行:

  • 家宽/公司出口 IP 几乎都是动态的(PPPoE 重启就换、NAT 池轮换);
  • 白名单今天有效明天失效,反而把真自己关门外;
  • ignoreip 只能当"纵深保险",不能当防线依赖。

4. 差点误伤自己的"假 bot"乌龙(案例)

日志里一个高频"bot" 103.91.179.102,看着极像扫描器。对账才知是自己:
- tcpdump 抓包:我们直连该出口端口的时刻,103.91.179.102 正好在完成完整 SSH 握手并传输上百 KB;
- 本机 curl 该服务器,nginx access.log 记下它:

103.91.179.102 - - [28/Sep/2026:12:06:25 +0800] "GET / HTTP/1.1" 200 62164 "-" "curl/8.13.0"

"高频 bot"就是我们自己。教训:确认源 IP 真相前动手封禁,第一批误伤往往是自己人。排查链:frps 日志对不上 → tcpdump 抓包 → 应用日志(nginx access.log)实锤出口 IP。

5. fail2ban 工作原理

攻击者每连一次对外端口
  → frps 写一行日志
  → fail2ban filter(正则)抠出源 IP(<HOST>)
  → 该 IP 犯规 +1
  → 判定:findtime 窗口内达到 maxretry → 执行 iptables DROP
  → 关押 bantime 秒;到期自动解封;解封后还高频就再封

fail2ban 不看人,只看"一个源 IP 的行为频率"。①加豁免名单只管自己身份的 GAP;②阈值就是核心决策——详见 Step 2。


三、解法:四步操作法

Step 0 · 环境清点

逐项确认(打勾):
- [ ] 哪台机器是 frps(frp 服务端)?有没有 sudo?
- [ ] frps 日志在哪?读配置:sudo grep -E 'log_file' /etc/frp/frps.toml(或 frps.ini;默认 /var/log/frps.log)。
- [ ] 日志里"连接行"是什么样?先跑:sudo tail -n 20 <frps日志>,找 get a user connection 这类行。
- [ ] 有没有一台"合法管理机"(你日常操作用的机器),并知道它的出口 IP?(不知道怎么查→Step 1-C)
- [ ] fail2ban 装了吗:fail2ban-client version(没装→Step 3 安装)。

后续所有 <frps日志> 都表示 Step 0 命中的实际路径;所有 <port> 表示你的对外代理端口。

Step 1 · 定量:测两组频率(别猜)

1-A 看谁在打(源 IP 连接数排行榜)

sudo tail -n 2000 <frps日志> | grep 'get a user connection' 
  | sed -E 's/.*[([0-9.]+):[0-9]+].*/1/' | sort | uniq -c | sort -rn | head -15

输出形如 115 45.148.10.66 —— 前几名就是疑似攻击源。

1-B 看单个 IP 的节奏(这是高频洪水还是慢性骚扰)

sudo grep 'get a user connection' <frps日志> | grep '45.148.10.66' | tail -20
  • 间隔稳定 ~0.5–2 秒 → 高频洪水(会打瘫服务,重点封)
  • 间隔稳定 ~10 秒及以上 → 低频骚扰(无害,放养)

1-C 测"自己"的使用频率(防误封的基准)

① 先拿到你的出口 IP:从受保护机器(frps 那台)访问外部 echo 并用应用日志记录,例如:

# 受保护机器上起一个临时记录,或直接查 nginx access.log 最近一条 GET
# 然后从你的管理机 curl 受保护机器,回看它记的源 IP 是不是"你"
curl -s http://<受保护机器>/ && ssh <受保护机器> "sudo tail -3 /var/log/nginx/access.log"

② 统计自己正常使用频率:从管理机连续执行固定次数连接并观察记录密度:

# 管理机上:
for i in $(seq 1 10); do ssh -p <port> 用户@主机 "true"; done
# 受保护机器上,看这 10 次在日志里落在多宽的窗口、隔了多久
sudo grep 'get a user connection' <frps日志> | tail -20

估算出:正常操作大概每分钟几条(本案例 3–12 条)。
另注意环境差异:如果你用自动化循环跑批量命令,请把那也测一次,取上限计入"我们使用上限"。

1-D 判别口诀:连接规律(间距稳定、全天不断、次数进入 Top)且 不是你自己的出口 IP → 视为攻击源。

Step 2 · 定红线 → 换算 params

红线选择:压在「攻击者高频频率」之下、与「我们使用上限」之间。本案例:攻击高频 84 次/分、我们上限 ~60 次/分、正常 3–12 次/分,红线取 72 次/分钟(比我们上限高 1.2 倍、比洪水低 15%)。原则:偏离任何一边过近都危险——离自己近误封,离洪水近漏防。

换算公式(唯一要记住的):

findtime(分钟) 选窗口长度(fail2ban 里以秒写,60s=1 分钟)
maxretry = 红线(次/分钟) × findtime(分钟)

本环境示例:findtime=60s(1分钟) → maxretry = 72×1 = 72。
如果你想要更宽窗口防"换 IP 慢速炮":findtime=600s(10分钟) → maxretry = 72×10 = 720(语义:10 分钟内超过 720 条才封)。

判定效果(本案例红线下):

来源 频率 判定
高频洪水 bot 84 次/分钟 封(越线)
低频 bot 6 次/分钟 放养(差 12 倍,无害)
我们正常操作 3–12 次/分钟 放养(差 6–24 倍)
我们批量循环 ~60 次/分钟 仍低于红线

策略一句话:低频骚扰不封(无危害),高频爆发必封;且零依赖 IP。

Step 3 · 动手前检查单 + 落地配置

动手前检查单(每条必过,防自锁):

  • [ ] ① 被封目标是攻击者(确认过 1-A/1-B,且不是你的出口 IP—见 1-C)。
  • [ ] ② 不会误封 NAT 同池:若你公司与攻击源同运营商/同出口池,慎封(宁可改用窄 action)。
  • [ ] ③ 你不是正通过"目标机器 + 该 IP"在远程操作(否则先保证自己有一条逃生通道,或只封目标端口不封全端口)。
  • [ ] ④ 动作面先窄后宽:action 用 iptables(只封指定端口)比 iptables-allports(封全端口)更稳。
  • [ ] ⑤ 先备份:sudo cp /etc/fail2ban/jail.local /etc/fail2ban/jail.local.bak。

3-1 安装

sudo apt install -y fail2ban tcpdump   # tcpdump 用于 Step1-C/验证

3-2 写 filter("日志探查→写正则"通用套路)

套路四步:
1. sudo tail -n 20 <frps日志> 看真实行长啥样;
2. 找到不变的特征片段(如 [ssh] get a user connection);
3. 写出把 IP 放到 <HOST> 的正则:^S+s+S+s+[ssh] get a user connection [<HOST>:[0-9]+](或宽松版 ^.*[ssh] get a user connection [<HOST>:[0-9]+]);
4. 立刻验证匹配率:sudo fail2ban-regex <frps日志> '<你的正则>',结果应"大量匹配且命中 <HOST> 能解出源 IP"。

写好文件 /etc/fail2ban/filter.d/frps-ssh.conf:

[Definition]
failregex = ^.*[ssh] get a user connection [<HOST>:[0-9]+]

⚠️ <HOST> 前不要加反斜杠转义,否则抓不到 IP。
⚠️ 如果你们 frps 版本/日志格式不同(关键是连行里有没有源 IP),按上面套路 1–4 重写。若日志完全走 journald 而无落盘文件,则 jail 改用 backend = systemd(并相应指定 journal 匹配),本手册默认落盘文件场景。

验证:

sudo fail2ban-regex <frps日志> /etc/fail2ban/filter.d/frps-ssh.conf
# 期待:失败行大量匹配;例:5680 行日志,4345 匹配

3-3 配 jail.local(红线填进去)

/etc/fail2ban/jail.local:

[DEFAULT]
bantime = 1800
findtime = 60

[sshd]
enabled = true
maxretry = 5

[frps-ssh]
backend = pyinotify
enabled = true
filter = frps-ssh
logpath = <frps日志>
maxretry = 72          # = 红线72次/分钟 × findtime(1分钟),按 Step2 公式填
findtime = 60
bantime = 1800         # 封半小时
action = iptables[name=frps-ssh, port="<port>"]   # 先窄后宽:只封目标端口

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1   # 纵深保险;如需加你的出口 IP 可以,但不要依赖它

重载生效:

sudo fail2ban-client reload
sudo fail2ban-client status frps-ssh

3-4 应急:fail2ban 就位前的高频洪水先手动压

sudo iptables -I INPUT 1 -s <攻击IP1> -j DROP
sudo iptables -I INPUT 1 -s <攻击IP2> -j DROP

⚠️ 手动规则是内存态,重启丢。要固存用 ufw(写到 /etc/ufw/,开机由服务拉起):

sudo ufw deny from <攻击IP>

fail2ban 的封禁则自动持久化(记在数据库,重启自动重新下发),无需处理。

Step 4 · 验证与回滚

验证封禁生效:

sudo fail2ban-client status frps-ssh          # 看 Currently banned 与 Banned IP list
sudo iptables -L INPUT -n --line-numbers | head   # 看 DROP 规则在不在、行号多少
# 对端试连通(DROP 生效应该是超时无响应):
timeout 3 curl -s -o /dev/null --connect-timeout 3 http://<攻击IP>/ ; echo "exit=$?"   # exit 124 = 超时 = 生效

手动加封 / 解封(fail2ban 管理的):

sudo fail2ban-client set frps-ssh banip <IP>     # 想立刻封某个 IP
sudo fail2ban-client set frps-ssh unbanip <IP>   # 解封(回滚)

删手动 iptables 规则(回滚):

sudo iptables -L INPUT -n --line-numbers         # 找到要删的行号 N
sudo iptables -D INPUT N

重启后:fail2ban 通过数据库自动续封;手动 ufw 规则自动恢复;手动 iptables 规则(未走 ufw)丢失——记得用 ufw 固化。


四、结果(本案例)

  • 高频洪水(每分钟 84 次)被压灭,对外端口恢复直连、SSH 秒连(0.4–0.6s);
  • 低频 bot 仍在刷存在感,但始终在红线之下、永不触发、且无害;
  • 我们正常连接与红线差 6–24 倍,出口 IP 怎么变都不影响;
  • 防线零依赖固定 IP,可持续运行。

五、经验总结

  1. 动手封禁前先确认源 IP 真相:tcpdump + 应用日志交叉验证,否则第一批误伤自己(本手册就是反面教材)。
  2. 白名单死于"出口 IP 固定"假设:动态公网下不可依赖;频率阈值才是无惧地址变化的硬防线。
  3. 频率取量必须把攻击者与正常使用放同一张表:红线压在两者之间的空档。
  4. 低频骚扰无害不必封:封禁资源要用在能打瘫服务的高频爆发上。
  5. fail2ban 是"不看人、看频率"的自动安保:高频自动 DROP、定时解封、自动续封,人不用盯屏。
  6. 动作面先窄后宽:新环境先用只封目标端口的 action(iptables)跑通,确认无误后可视情况加固。

完

标签: 暂无
最后更新:2026-09-28

蒋程

这个人很懒,什么都没留下

点赞
< 上一篇

文章评论

您需要 登录 之后才可以评论
Table of ContentsToggle Table of ContentToggle
  • 零、30 秒快速路径(复读本手册时先看这里)
  • 一、起因(参考案例:这问题长什么样)
    • 攻击现场数据(本案例 frps.log 统计)
  • 二、分析:难点在哪
    • 1. frp 纯转发,天生无"认证防线"
    • 2. 我们和低频 bot 在日志里"行为重叠"
    • 3. "IP 白名单"方案是死路(重点教训)
    • 4. 差点误伤自己的"假 bot"乌龙(案例)
    • 5. fail2ban 工作原理
  • 三、解法:四步操作法
    • Step 0 · 环境清点
    • Step 1 · 定量:测两组频率(别猜)
    • Step 2 · 定红线 → 换算 params
    • Step 3 · 动手前检查单 + 落地配置
    • Step 4 · 验证与回滚
  • 四、结果(本案例)
  • 五、经验总结

COPYRIGHT © 2026 白泽图. ALL RIGHTS RESERVED.

京ICP备2026064017号-1

登录
注册|忘记密码?