☰
Linux防火墙封禁高危端口实战:445/3389/6379与远程保命
2026/9/29 1:25:22 网站建设 项目流程

一台刚交付的云主机,从拿到公网 IP 到被第一波扫描撞上门,中间通常撑不过几个小时。日志里刷得最多的,往往是 445、3389、6379 这几个熟面孔——有人拿它们跑漏洞利用,有人拿它们试空口令,也有人只是批量扫一遍留着以后用。很多同行把"Linux 防火墙封禁高危端口命令"当成一条条记不住的语法去背,结果真到线上操作时,要么是规则写了没生效,要么是重载那一刻把自己踢出 SSH,要么是发现容器里的端口压根绕过了主机的规则链。这篇就把我在物理机、云主机、容器宿主机上反复用过的那套做法完整摊开,从"为什么要封"讲到"封完怎么验证""怎么保证不把自己关在门外",命令都能直接抄,但更重要的是每一步背后的判断依据。

1. 445、3389、6379 为什么总在第一波扫描名单里

1.1 端口数量与攻击面的关系并不是线性增长

一台默认安装完的 Linux,监听端口通常不超过二十个,但真正对公网开放的往往只有那么三四个。麻烦在于,开放的那几个端口背后挂着的服务,性质差别极大:22 端口是远程管理入口,封了你就上不去;443 端口是业务入口,封了业务就死;而 445、111、2375 这类端口,很多情况下是安装某个软件时被顺手带起来的,管理员自己都不一定知道它在听。

扫描器不关心你的业务逻辑,它只做一件事:把 1 到 65535 挨个敲一遍,把回应的端口记下来,然后拿去比对漏洞库。所以攻击面的大小,本质等于"对外可达的端口数量 × 每个端口背后服务的脆弱程度"。封禁高危端口的价值,就是把第二个乘数直接砍掉——这个端口根本不回应,再多的漏洞库条目也无从下手。

这里有个容易被忽略的细节:风险不只来自"你主动开放",还来自"默认监听"。Redis 默认配置在某些版本里监听 0.0.0.0,MongoDB 早期版本默认不开认证,Docker 的远程 API 一旦以-H tcp://0.0.0.0:2375启动就等于把整台机器交出去。这些都不是管理员决定的,是软件默认行为决定的,而防火墙恰好是唯一能在不改动软件配置的前提下兜住底的那一层。这也是为什么我一直建议:能在服务配置里改监听地址的,优先改配置;改不动的、来不及改的,先用防火墙挡上,两者叠加才踏实。

1.2 常见高危端口清单与它们各自的危险来源

把高危端口简单理解成"别人常扫的端口"是不够的,每个端口背后都有一条具体的风险链路。我按实际遇到过的频率整理了一张表,方便你对照自查。

端口协议对应服务风险来源
21TCPFTP明文传输凭据、匿名登录、历史溢出漏洞
23TCPTelnet全明文,凭据可被直接嗅探
135/137/139TCP/UDPNetBIOS / RPC内网信息泄露、横向移动的跳板
445TCPSMB无需认证即可触发,历史漏洞影响面极大
111TCP/UDPrpcbind暴露 NFS 挂载关系,配合 2049 使用
2375TCPDocker Remote API未授权即可创建特权容器接管宿主机
3306/5432/1433TCPMySQL / PostgreSQL / SQL Server弱口令爆破、公网直接拖库
6379TCPRedis默认无认证,可写文件、可反弹执行
11211UDPMemcached放大反射,也会被拿来做数据泄露
9200TCPElasticsearch早期版本无认证,索引数据全裸
27017TCPMongoDB早期默认无认证,批量被清库
3389TCPRDP爆破强度最高的入口之一
5900TCPVNC弱口令、无认证模式

445 端口被反复点名,原因值得单独说清楚:SMB 是文件共享协议,设计上服务于内网可信环境,认证流程复杂且历史实现中出过大量问题,一旦被触发,攻击者拿到的往往不只是文件读取权限,还可能直接执行代码。更关键的是,它的传播特性——一台机器中招后会自动向同网段其他 445 端口发起连接,这让它成为大规模蠕虫式传播的首选通道。所以哪怕你的服务器根本不提供文件共享,只要 445 在监听,就应该当成零容忍项处理。

1.3 封端口是止损,不是修复

需要说清楚的是态度问题。封禁端口解决的是"外部够不着",但服务本身的漏洞、弱口令、错误配置一点没变。如果有台内网机器已经被控制,攻击者从内网发起连接,你的公网防火墙规则完全拦不住。所以正确的心态是:封端口属于收缩暴露面的第一步,是给后续修复争取时间的止损动作,绝不能替代打补丁、改口令、收紧服务配置这些根本工作。

我自己的处理顺序通常是:先摸清监听、再封高危端口、同时排期修配置,最后复查是否还有必要保留该端口的对外可达性。三步里防火墙是最快能落地的一步,也是唯一能在凌晨两点不重启业务的前提下完成的一步。

2. 动手封之前,先把本机的监听底账摸清楚

2.1 ss 与 netstat 到底该用哪个

老教程里清一色是netstat -tulnp,但 net-tools 包在新版发行版里已经默认不装了,ss是 iproute2 套件的一部分,几乎必然存在,速度也快得多。看监听端口的常用写法是:

ss -lntup

逐个参数解释一下:-l只看监听状态,-n不做域名和服务名解析(直接显示数字端口,避免看到一堆mysqlssh之类的别名反而对不上规则),-t看 TCP,-u看 UDP,-p显示占用端口的进程。加上-p需要有 root 权限,普通用户跑出来进程那一列是空的,这点很多人会误以为是命令有问题。

如果只想看某个具体端口,可以用过滤表达式:

ss -lntp 'sport = :445'

UDP 端口千万别漏掉。Memcached 走 11211/UDP、SNMP 走 161/UDP、有些 DNS 相关服务走 53/UDP,只查 TCP 会让你以为一切干净。

2.2 0.0.0.0 和 127.0.0.1 的区别决定了要不要封

ss输出里最左边那一列是本地地址,这一列是判断暴露面的核心依据:

  • 127.0.0.1:6379:只接受本机回环连接,外部根本连不上,防火墙封不封它意义不大。
  • 0.0.0.0:6379:监听所有网卡的 IPv4 地址,只要主机有公网 IP 且网络可达,外部就能连。
  • [::]:6379:IPv6 的等价写法,同样意味着全监听。
  • 192.168.1.10:6379:只监听指定网卡地址,暴露范围限于该网段。

很多"我明明封了 445 怎么还在被扫"的情况,最后查出来是服务监听在 IPv6 上,而防火墙只写了 IPv4 规则。所以摸底账这一步,看到[::]开头的监听一定要单独记一笔。

顺便给一条查暴露项的快捷命令:

ss -lntup | grep -v -E '127\.0\.0\.1|\[::1\]'

它会过滤掉只监听回环的行,剩下的就是需要你逐个确认是否该对外开放的端口。

2.3 从外部视角做一次真实探活

本机看到的监听只是"我认为的状态",真正的可达性要从外部验证。最轻量的方式是nc:

nc -zv -w 3 目标IP 445

-z表示只探测不发送数据,-v输出详细信息,-w 3是超时 3 秒。返回succeeded说明通了,Connection refused说明端口没监听或被 REJECT,一直卡到超时说明被 DROP 了。这三种结果的差异后面验证章节还会用到。

telnet也能用,虽然它本意不是干这个的:

telnet 目标IP 445

连上后按Ctrl+]再输入quit退出。它的好处是几乎所有系统都有,坏处是连不通时的提示信息不如nc明确。要做批量扫描就用nmap:

nmap -Pn -p 21,23,135,139,445,3389,6379 目标网段/24

-Pn是跳过主机存活探测直接扫端口,避免因为对方不回 ICMP 而漏报。这里必须提醒:扫描只对自己有权限的资产做,扫别人的地址段在很多地方是要担责的。

2.4 把底账整理成一份可复查的清单

摸完一轮之后,我习惯把结果整理成三列:端口、监听地址、是否有必要对外开放。这份清单的价值在半年后体现——那时候你已经忘了当初为什么开这个端口,而清单上写着"临时调试用,7 月 3 日应关闭",一眼就能判断该不该留。

具体做法很简单,把ss的输出重定向留档即可:

ss -lntup > /root/port-audit-$(date +%F).txt

有条件的话把这份文件纳入配置管理,每次变更前后各存一份,对比两次输出的差异,就能看出哪个操作引入了新的监听端口。这比事后翻操作记录快得多。

3. firewalld、iptables、ufw 别在不该纠结的地方浪费时间

3.1 三者的关系是分层而不是并列替代

一个常见误解是"firewalld 和 iptables 二选一"。实际情况是:内核里的包过滤由 netfilter 框架完成,iptables 和 nftables 是操作 netfilter 的用户态工具,firewalld 则是运行在 iptables(新版走 nftables 后端)之上的一层管理服务。firewalld 的 zone、service、rich rule 最终都会被翻译成规则链。所以"我装了 firewalld 还能不能直接敲 iptables"——能敲,但非常不明智,因为 firewalld 重载时会把自己管理的链刷掉,你手写的规则跟着一起消失,而且不会给你任何提示。

ufw 的位置类似,它是 Ubuntu 系上对 iptables 的简化封装,规则最终也落到同一套链上。所以在同一台机器上同时启用 firewalld 和 ufw,基本等于自己给自己挖坑。

3.2 按发行版和场景选工具的判断表

纠结用哪个的时候,我一般按这张表决定,基本不犹豫:

场景推荐工具理由
CentOS 7+ / RHEL / Rocky / Almafirewalld系统默认,与 systemd 集成好,重载不中断连接
Ubuntu / Debian 桌面或轻量服务器ufw语法最短,适合单人维护的少量机器
需要精细控制链、做 NAT、做复杂转发iptables 或 nftables表达能力最强,规则顺序完全可控
容器宿主机iptables(配 DOCKER-USER 链)Docker 直接操作 iptables,用高层工具容易被绕过
已有成熟的 iptables 脚本体系继续用 iptables迁移成本高于收益,没必要为了新而新

判断的核心不是哪个更先进,而是"哪一层能覆盖住你实际需要控制的流量"。容器场景特别典型:ufw 写了 deny,容器发布的端口照样能从外面访问,因为流量走的是 FORWARD 链和 DNAT,压根不经过 ufw 管理的 INPUT 链。

3.3 先确认当前到底是谁在管事

在敲任何规则之前,花十秒确认一下当前状态,能省掉后面半小时的困惑:

systemctl status firewalld systemctl status ufw iptables -L -n | head -20

如果firewalld是 active,就别动 iptables;如果两个都是 inactive,说明当前没有任何系统级过滤(云主机上可能还有云安全组兜着,但不能假设)。还有一种情况是iptables -L能看到 Docker 相关链,说明 Docker 在管一部分规则,这时候修改要格外小心,改错了可能导致所有容器网络不通。

4. firewalld 封禁高危端口:zone 和 rich rule 的正确姿势

4.1 zone 决定了规则挂在哪个网络接口上

firewalld 里最容易让人绕晕的概念是 zone。简单理解:zone 是一组"信任级别 + 绑定的网卡",规则写在 zone 里,只对绑到该 zone 的网卡生效。默认情况下,大多数发行版把主网卡放在publiczone。

先确认当前状态:

firewall-cmd --get-active-zones firewall-cmd --get-default-zone

第一条会告诉你哪个 zone 绑了哪些网卡,第二条告诉你默认 zone。如果--get-active-zones显示的是public,那后面所有命令都可以省略--zone=public;如果显示的是别的名字(有些云镜像会自定义 zone),就必须显式指定,否则规则写到默认 zone 上,跟实际绑定的 zone 对不上,规则就不生效。

看某个 zone 当前放了什么:

firewall-cmd --zone=public --list-all

输出里的ports:和services:两行是关键。services是预定义的服务集合,比如ssh服务对应 22/tcp,samba服务对应 137/138/139/445 等一串端口。这意味着如果你的 zone 里赫然写着services: ssh dhcpv6-client samba,那 445 是被 samba 这个服务条目带开放的,直接--remove-port=445/tcp不会有效果,因为它不是通过--add-port加的。

4.2 端口、端口段、服务的三种写法差异

封禁单个端口和端口段,语法不一样:

# 封单个端口(不持久,重启失效) firewall-cmd --zone=public --remove-port=445/tcp # 封一段端口 firewall-cmd --zone=public --remove-port=8000-8010/tcp # 移除某个服务带来的所有端口 firewall-cmd --zone=public --remove-service=samba

注意--remove-port和--remove-service都只能移除"曾经被 add 过"的内容。如果端口是被某个服务间接带开的,移除端口无效,必须移除服务。判断方法就是看--list-all里它出现在ports还是services那一行。

如果端口压根没被显式开放,但你依然想确保它被丢弃(比如担心后续有人误操作放开了),更稳的做法是用 rich rule 主动 drop,它优先级高于 zone 的通用规则:

firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" port port="445" protocol="tcp" drop'

4.3 permanent 与 reload 这个坑几乎人人踩过

firewall-cmd 有个设计:不加--permanent的修改立刻生效但重启后丢失;加了--permanent的修改写入配置文件但不会立刻生效,必须--reload才应用。这个设计本身合理,但实际用起来经常出问题,典型的是三条:

第一,先加了--permanent又忘了 reload,验证时发现没生效,以为命令写错了,反复试。第二,reload 会重新加载所有规则,理论上 firewalld 的 reload 不断开已建立的连接(这是它相比 iptables restart 的优势),但如果规则里有明显错误,依然可能导致新连接被拒。第三,有些人为了省事把--permanent和--reload串在一条命令里用&&,测试阶段这么做没问题,生产环境上建议分两步执行,中间留出验证窗口。

标准的两步写法:

firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" port port="445" protocol="tcp" drop' firewall-cmd --reload

reload 之后必须重新--list-all确认规则真的在里面,别信命令的回显success——它只表示命令语法通过,不表示规则生效。

4.4 用 rich rule 做源地址级的精细控制

真正体现 firewalld 能力的场景是:端口不能全封,但要限定来源。比如 3306 需要给办公网段访问,其他一律拒绝:

# 先允许指定网段 firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" port port="3306" protocol="tcp" accept' # 再全局丢弃 firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" port port="3306" protocol="tcp" drop' firewall-cmd --reload

rich rule 里的 accept 和 drop 是有顺序优先级的,同一 zone 内更具体的规则优先匹配,但为了避免依赖这套隐式优先级,我习惯把 accept 写在前面、drop 写在后面,并且接受规则里尽量带上 source 限制,这样逻辑上一目了然。

再加上日志记录,就得到了一个可观测的封禁规则:

firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" port port="445" protocol="tcp" log prefix="DROP445 " level="notice" limit value="5/m" drop'

limit value="5/m"是限速,每分钟最多记 5 条,防止被扫描时把日志写爆——这是吃过亏的经验,曾经有台机器一夜之间写满了 4G 日志分区,导致服务起不来。

5. iptables 手写规则链:DROP 还是 REJECT 值得想清楚

5.1 规则顺序决定一条包走哪条路

iptables 是逐条匹配、匹配到就执行动作、不再往下走的模型,所以规则顺序比规则内容更重要。用-A是追加到链尾,用-I是插入到链首(-I INPUT 1表示插到第一条)。封禁场景下基本都该用-I,因为系统里可能已经有若干 ACCEPT 规则,追加到链尾的 DROP 永远轮不到执行。

先看现有规则:

iptables -L INPUT -n -v --line-numbers

--line-numbers会显示每条规则的序号,后面删除规则时要用到;-v显示每条规则匹配的包数和字节数,这是排查"规则到底有没有被命中"的关键——如果一个 DROP 规则跑了三天,packets 计数还是 0,要么是没人扫,要么是前面有规则把它截胡了。

5.2 一次封一批端口的写法

逐个端口写规则既啰嗦又慢,用 multiport 扩展可以一条搞定:

iptables -I INPUT 1 -p tcp -m multiport --dports 21,23,135,139,445,2375,3389,5900 -j DROP

注意 multiport 的端口数量上限是 15 个,超过要拆成多条。端口段也可以用--dports 8000:8010这种冒号写法。

UDP 高危端口单独再来一条:

iptables -I INPUT 2 -p udp -m multiport --dports 111,137,161,11211 -j DROP

如果只想限制来源,加上 source 条件:

iptables -I INPUT 1 -p tcp --dport 3306 ! -s 203.0.113.0/24 -j DROP

!是取反,意思是"来源不在这个网段就丢弃"。这种写法的可读性其实一般,容易看反,我更喜欢写成两条:先 ACCEPT 再 DROP。规则多了以后,可读性比省一行的价值大得多。

还有一点:如果链的默认策略是 ACCEPT,你需要显式写 DROP 规则;如果默认策略是 DROP,则要显式写 ACCEPT 规则。改默认策略风险很高,尤其是远程操作时把 INPUT 策略改成 DROP 而没放行 22,等于当场断线。

5.3 DROP 与 REJECT 的行为差异和选择依据

这是最常被问的问题。两者的区别在于:

  • DROP:包被静默丢弃,不回任何响应。发起方会一直等到超时,通常是几秒到几十秒。
  • REJECT:返回一个错误响应(默认是 ICMP port unreachable,也可以指定--reject-with tcp-reset),发起方立刻知道连接被拒。

安全视角上,DROP 更"沉默",不向扫描者透露端口状态,扫描全端口时会慢很多;REJECT 则更礼貌,能立刻告诉对方"这个端口不通"。

实际选择时要考虑业务侧影响。如果这个端口有内部程序会去连,DROP 会让程序卡在超时上,可能拖慢页面响应甚至耗尽连接池;REJECT 会让程序立刻拿到错误尽快走失败分支。对外部暴露的端口,我一般对外网来源用 DROP,对已知内部网段用 REJECT,写法是:

# 内网来源直接拒绝,让程序快速失败 iptables -I INPUT 1 -s 10.0.0.0/8 -p tcp --dport 445 -j REJECT --reject-with tcp-reset # 其余来源静默丢弃 iptables -I INPUT 2 -p tcp --dport 445 -j DROP

5.4 保存与开机生效:不同发行版差得挺多

iptables 规则默认只存在于内存,重启就没了。持久化方式各发行版不一样:

系统持久化方式
RHEL / CentOS 7 及之前service iptables save,文件在/etc/sysconfig/iptables
Debian / Ubuntu装iptables-persistent,用netfilter-persistent save
通用做法iptables-save > /etc/iptables/rules.v4,配 systemd 单元开机恢复
nftables 后端nft list ruleset > /etc/nftables.conf,启用nftables.service

这里有个细节:CentOS 7 之后默认用 firewalld,/etc/sysconfig/iptables可能不存在也不会被加载。如果你在 CentOS 7+ 上手工写 iptables 规则并保存,结果重启后失效,八成是这个原因——要么停掉 firewalld 改用纯 iptables,要么老老实实用 firewalld 的 rich rule。

6. Ubuntu 上的 ufw 三行就能搞定,但默认策略要看清

6.1 默认 deny 与规则顺序之间的陷阱

ufw 的核心理念是"默认拒绝入站",正确启用顺序是这样的:

ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp ufw enable

顺序不能反。如果先ufw enable再ufw allow 22/tcp,中间那几秒你的 SSH 连接会直接被切断,只能去控制台救场。这是我带过的实习生踩过的第一个坑,现在我把这条写进了团队的检查清单。

ufw enable会提示是否继续,因为操作可能中断现有 SSH 连接。看到这个提示时先别急着按 y,确认 22 端口已经放行。

6.2 封端口、封网段、加限速的写法

封禁单个或一批端口:

ufw deny 445/tcp ufw deny 21,23,135,139/tcp ufw deny 11211/udp

ufw deny底层用的是 DROP,如果想要 REJECT 的行为,直接写ufw reject 445/tcp。批量封禁的逗号写法其实不是 ufw 原生支持的,稳妥写法是分多条,或者用 application profile。为避免语法不支持,我更建议一条一条写,反正也不多。

封指定来源网段:

ufw deny from 198.51.100.0/24 to any port 3306 proto tcp

限速防爆破(对 SSH 很实用):

ufw limit 22/tcp

limit默认是 30 秒内超过 6 次连接就临时拒绝,能显著降低爆破效率。

查看和删除规则:

ufw status numbered ufw delete 3

加了numbered才能按序号删除,这个设计比 iptables 友好。

6.3 ufw 和 Docker 的冲突必须提前知道

这是 Ubuntu 服务器上最隐蔽的问题之一:装了 Docker 之后,容器发布的端口会绕过 ufw 规则。原因在于 Docker 把转发规则插到了 nat 表的 PREROUTING 和 FORWARD 链上,外部流量做 DNAT 直接进容器,根本不经过 ufw 管理的 INPUT 链。

表现就是:ufw status显示 6379 是 deny,但外部nc -zv依然能连上容器里的 Redis。

解决办法有几种,按推荐顺序:

  1. 最优解:发布端口时绑定到回环地址,-p 127.0.0.1:6379:6379,从源头限制可达性。
  2. 次优解:在DOCKER-USER链里加规则,这条链是 Docker 预留给你自定义的,不会被容器操作覆盖:
    iptables -I DOCKER-USER -p tcp --dport 6379 -j DROP
  3. 下策:在daemon.json里设置"iptables": false并自己管理全部规则,工作量陡增,容易出错。

7. 封完之后怎么验证:规则、连接、日志三条线对账

7.1 规则层面确认规则真的加载了

不同工具的命令不一样,我把常用的几条列在一起备用:

firewall-cmd --zone=public --list-all firewall-cmd --zone=public --list-rich-rules iptables -L INPUT -n -v --line-numbers nft list ruleset ufw status verbose

firewall-cmd --list-rich-rules值得单独用,因为--list-all输出较长,rich rule 容易看漏。iptables 那边重点看-v的包计数,确认规则位置在前面而不是被别的规则挡住。

7.2 连接层面验证三种结果的含义

前面提过nc -zv -w 3 IP 端口的三种结果,这里把它们的对应关系说清楚,这是排查时最重要的判断依据:

返回结果含义对应规则
succeeded端口可达无拦截,或命中 ACCEPT
Connection refused被明确拒绝命中 REJECT,或服务未监听
卡住直到超时无任何响应命中 DROP
No route to host网络层不通路由或云安全组拦截

有个容易混淆的点:Connection refused既可能是 REJECT 造成的,也可能是服务根本没监听。区分方法是本地跑ss -lntp,如果本地有监听而远端是 refused,那就是 REJECT 生效了;如果本地也没监听,那和服务无关,是端口没起。

7.3 日志层面把被丢弃的连接留下证据

规则生效但没日志,等于没法审计。firewalld 的 rich rule 加 log 参数前面演示过;iptables 的写法是:

iptables -I INPUT 1 -p tcp --dport 445 -j LOG --log-prefix "DROP-445: " --log-level 4 iptables -I INPUT 2 -p tcp --dport 445 -j DROP

LOG 是"记录后继续匹配",所以必须紧跟一条真正的 DROP 规则,顺序不能反。日志会写到内核日志,用dmesg | tail或journalctl -k -f查看。

这里必须提醒限速问题。日志规则不限速的话,一次全端口扫描就能产生上万条记录,日志分区写满会导致整个系统出问题。iptables 可以用-m limit --limit 5/m --limit-burst 10来限速:

iptables -I INPUT 1 -p tcp --dport 445 -m limit --limit 5/m -j LOG --log-prefix "DROP-445: "

7.4 把验证结果和预期做成对照表

我习惯在变更结束后填一张这样的表,一是给自己确认,二是留给后面接手的人:

端口预期状态本机验证结果外网验证结果结论
22放行可达可达正常
445封禁本地仍监听超时符合预期
6379封禁本地仍监听超时符合预期
3306限指定网段本地仍监听办公网可达、其他超时符合预期

注意"本地仍监听"是正常的——防火墙只拦外部流量,不会让服务停止监听。如果本地都连不上,那不是防火墙的问题,是服务自己挂了。

8. 最怕把自己关在门外:远程改防火墙的保命流程

8.1 先放行后封禁的顺序不能颠倒

这条规矩听起来像废话,但每年都有人栽在上面。核心操作原则就一条:在远程会话里修改防火墙,任何时刻都必须保证当前会话所需的端口是放行的。

具体到操作上,就是所有封禁类命令都在放行规则之后执行。而且封禁时不要用"清空规则再重写"的思路,iptables -F在远程场景下是极度危险的操作,因为它会瞬间清掉所有规则,如果默认策略是 DROP,你的会话当场断开,且重启也救不回来。真需要重建规则,正确做法是先写一份完整的新规则文件,然后一次性应用。

8.2 定时回滚脚本:几十秒换一条命的保险

这个技巧我推荐给每一个需要远程操作防火墙的人,成本几乎为零:

# 写法一:用 at 在 5 分钟后执行回滚 echo "iptables -P INPUT ACCEPT; iptables -F" | at now + 5 minutes # 写法二:systemd-run 定时任务(适合所有 systemd 系统) systemd-run --on-active=300 /usr/local/bin/firewall-rollback.sh # 写法三:后台 sleep 加回滚(最简陋但随处可用) nohup bash -c 'sleep 300; iptables -P INPUT ACCEPT; iptables -F' >/dev/null 2>&1 &

思路很简单:在动手改规则之前,先挂一个"5 分钟后自动恢复"的任务。改完规则后立刻开一个新终端验证 SSH 还能连;确认没问题,再手动取消这个定时任务。如果改崩了,等 5 分钟自动恢复,你还能连回去。这 5 分钟比任何应急预案都实用。

取消方法:at用atq查任务号、atrm 编号删除;systemd-run用systemctl list-timers找到对应单元后systemctl stop掉。

回滚脚本的内容要写清楚,比如 firewalld 场景可以是:

#!/bin/bash firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" port port="445" protocol="tcp" drop' firewall-cmd --reload

8.3 云安全组和系统防火墙是两层,别只改一层

公网云主机上,安全组(有的平台叫网络 ACL、有的叫防火墙规则)通常作用在虚拟网络层,先于主机防火墙生效。这意味着:安全组没放开,主机防火墙放行了也没用;安全组放开了,主机防火墙没配好,端口依然可达。两层得都看。

这也是排查"端口到底通不通"时最容易绕圈的地方。我的排查顺序是:

  1. 先确认云控制台安全组入方向规则里该端口是否放行;
  2. 再确认主机ss -lntp有监听;
  3. 再确认主机防火墙规则;
  4. 最后从外部探活。

按这个顺序走,基本不会出现查了两小时最后发现是安全组的问题这种尴尬。

8.4 容器和转发链会绕过你写的规则

前面提过 Docker 的情况,这里补充几个同类问题。开启了 IP 转发(net.ipv4.ip_forward=1)的机器上,经过本机转发的流量走的是 FORWARD 链而不是 INPUT 链,只在 INPUT 上封端口对这类流量无效。判断方法是看iptables -L FORWARD -n -v是否有大量匹配。

另外,如果主机跑着 Kubernetes,kube-proxy 会往 nat 和 filter 表里塞大量规则,手工维护 iptables 基本不可行,这时候应该通过 CNI 的网络策略或者在节点外侧做网络层限制,而不是在节点上敲 iptables。

9. 把一次性封禁做成能长期维护的端口治理

9.1 用白名单思路替代到处加黑名单

加黑名单是最直观的做法——想到一个端口封一个。但维护久了会发现两个问题:一是规则越加越多,半年后自己也看不懂哪条还需要;二是总有想不到的端口被新装的软件带进来。

更省心的思路是反过来做:默认拒绝所有入站,只放行确实需要对外的那几个端口。这样新装的软件即使默认监听 0.0.0.0,也不会自动暴露。firewalld 里可以这样设置:

firewall-cmd --permanent --zone=public --set-target=DROP firewall-cmd --permanent --zone=public --add-service=ssh firewall-cmd --reload

iptables 里则是把 INPUT 链默认策略设为 DROP,然后按需 ACCEPT。注意改默认策略前务必确认放行规则已经生效,并且配合 8.2 节的定时回滚,否则极易断线。

9.2 变更记录比规则本身更值钱

我维护过的机器里,出问题最多的不是规则写错,而是没人知道某条规则为什么存在。所以每次改动我都会在配置目录里留一行注释或一条记录,说清楚时间、原因、影响范围。firewalld 的 rich rule 支持在规则里带描述(通过--add-rich-rule配合 zone 配置文件的注释行),iptables 则可以直接在规则上挂 comment:

iptables -I INPUT 1 -p tcp --dport 445 -m comment --comment "2024-06-15 封SMB,审计要求" -j DROP

-m comment --comment的内容在iptables -L -n加--line-numbers时不一定显示,需要用iptables -L INPUT -n --line-numbers -v配合iptables-save查看,iptables-save的输出里注释是完整保留的。养成这个习惯,一年后接手的人会感谢你。

9.3 防火墙挡掉的东西比你想的多

最后说一个常被问到的问题:防火墙关掉有什么影响吗。从技术上说,关掉之后所有入站连接不再被过滤,原本被丢弃的扫描、爆破、漏洞利用尝试会全部打到服务上。如果你的服务本身有认证、有 WAF、有入侵检测,也许还能撑住;但绝大多数情况下,端口一旦暴露,日志量和异常请求量会在几分钟内明显上升。

反过来讲,防火墙也不是万能的。它挡得住从外部发起的连接,挡不住已经进入内网的横向移动,也挡不住应用层的攻击——比如你的 Web 服务本身有漏洞,443 端口放行了,攻击照样能打进来。所以我在实际维护中一直把它当成多层防护里成本最低、见效最快的那一层:先在防火墙把不该露的端口露不出去,再去处理服务配置、补丁、口令这些更花时间的事情。

这套命令我用过很多次,最深的体会是那句老话——真正容易出错的不是语法,而是顺序和验证。记住"先放行后封禁、改前挂回滚、改后必验证"这三条,剩下的语法查手册就够了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询