1. 这不是“装个软件就完事”的实验:为什么个人防火墙配置必须亲手做一遍
“个人防火墙”这五个字,现在听上去有点老派——就像翻出抽屉里那台还在用USB-A接口的旧移动硬盘。很多人觉得,Windows自带的防火墙开着就行,Mac的“防火墙”开关一按就万事大吉;还有人直接把“防火墙”和“杀毒软件”画等号,以为装了XX卫士就自动挡住了所有网络攻击。但去年我帮某高校实验室调试一套边缘计算节点时,就遇到一个典型场景:三台物理机部署在同一个内网段,其中一台突然无法被另外两台SSH访问,而HTTP服务却完全正常。Wireshark抓包显示SYN包能发出去,但SYN-ACK始终没回来。排查两小时后发现,问题既不在交换机ACL,也不在路由表,而是在目标主机上——Windows Defender Firewall的一条隐藏规则,把来自特定子网的入站连接默认设为“阻止”,且该规则未在图形界面中显示,只存在于PowerShell导出的规则列表里。这件事让我彻底意识到:个人防火墙不是一道门,而是一张可编程的过滤网;它的配置逻辑,本质上是用策略语言写的一套微型网络策略引擎。你不需要成为网络安全专家,但必须亲手配置一次,才能真正理解“谁可以连我”“我能连谁”“连的时候带什么特征”这三个底层问题。这篇实验笔记,就是我用三台虚拟机(Win11、Ubuntu 22.04、macOS Sonoma)从零搭建、逐条验证、反复推翻重来的完整过程。它不讲理论模型,不堆概念术语,只记录每一步操作背后的意图、参数的真实含义、以及那些官方文档绝不会写的“踩坑瞬间”。适合刚接触网络基础的开发者、运维新手,或任何想搞懂“我的电脑到底对外暴露了什么”的普通用户。核心关键词就三个:入站规则、出站规则、连接跟踪状态——全文所有操作,都围绕这三个词展开。
2. 实验环境不是“随便搭”,而是刻意设计的最小闭环
很多教程一上来就说“打开控制面板→Windows Defender防火墙→高级设置”,然后贴一张图形界面截图。这种做法的问题在于:它默认你已经处于一个“已知可控”的网络环境中。而真实世界里,你的笔记本可能同时连着公司Wi-Fi、手机热点、甚至蓝牙网络共享;你的开发机可能挂着Docker、WSL2、VirtualBox多个虚拟网卡。在这种混乱状态下做防火墙实验,结果根本不可复现。所以,我花了整整一天时间,把实验环境压缩成一个绝对干净、可预测、可回滚的最小闭环。这不是为了炫技,而是为了让每一条规则的效果都能被精准归因。
2.1 虚拟网络拓扑:三层隔离,拒绝一切外部干扰
我使用VirtualBox 7.0构建了三台虚拟机,全部禁用NAT模式,统一采用仅主机(Host-Only)网络适配器,并手动指定IP段为192.168.56.0/24。这个网段是VirtualBox默认创建的,不与宿主机物理网络互通,天然隔离。三台机器的IP分配如下:
| 主机名 | 操作系统 | IP地址 | 主要用途 |
|---|---|---|---|
| fw-client | Windows 11 23H2 | 192.168.56.10 | 模拟“需要被保护的终端”,运行测试服务(如Python简易HTTP服务器) |
| fw-server | Ubuntu 22.04 LTS | 192.168.56.20 | 模拟“提供服务的服务器”,开放SSH(22端口)和HTTP(80端口) |
| fw-monitor | macOS Sonoma 14.5 | 192.168.56.30 | 模拟“外部探测者”,仅用于发起连接测试,不运行任何服务 |
关键细节:三台虚拟机均关闭所有非必要服务。Ubuntu上停用ufw(避免与iptables冲突),macOS上通过sudo pfctl -d彻底禁用pf防火墙。Windows主机则在实验开始前,执行netsh advfirewall reset重置所有规则到出厂状态。这个步骤极其重要——我曾因跳过重置,在后续测试中发现一条残留的“允许所有ICMP”的规则,导致本该被阻断的ping请求意外通过,浪费了近一小时排查时间。
2.2 测试工具链:不用GUI,全靠命令行验证状态
图形界面最大的问题是“它告诉你结果,但从不告诉你判断依据”。比如Windows防火墙界面显示“规则已启用”,但它不会告诉你这条规则是基于IP地址、端口、协议,还是应用程序路径匹配的。因此,整个实验全程弃用GUI,只依赖以下四类原生命令:
- 连接发起方:
ping、telnet、curl、nc(netcat) - 状态查询方:
netsh advfirewall show allprofiles(Windows)、sudo ufw status verbose(Ubuntu)、sudo pfctl -s rules(macOS) - 日志捕获方:Windows事件查看器中的
Windows Logs → Security(筛选事件ID 5156/5157)、Ubuntu的sudo journalctl -u ufw --since "1 hour ago"、macOS的sudo log show --predicate 'subsystem == "com.apple.networking.firewall"' --last 1h - 底层抓包方:三台机器均安装Wireshark,但仅在关键验证环节开启,且过滤器严格限定为
ip.addr == 192.168.56.0/24 and not icmp,排除ICMP干扰。
提示:不要迷信
telnet的“连接成功”提示。它只表示TCP三次握手完成,并不保证应用层协议可用。我在Ubuntu上测试SSH时,telnet 192.168.56.20 22返回Connected to 192.168.56.20,但实际ssh user@192.168.56.20却超时。最终发现是Windows防火墙的“连接安全规则”(Connection Security Rules)在后台启用了IPsec强制加密,而Ubuntu的OpenSSH未配置对应密钥。这个细节说明:防火墙规则的生效层级,远比端口通不通更复杂。
2.3 规则编写原则:拒绝“允许所有”,拥抱“最小权限”
这是贯穿整个实验的核心哲学。几乎所有初学者都会先建一条“允许所有入站”的规则来“确保能连上”,再慢慢收紧。但这种思路是危险的——它让你失去了对“默认拒绝”行为的感知。我的做法是:从第一条规则开始,就只允许明确需要的流量,其余全部由默认策略拦截。具体执行时,我给自己定了三条铁律:
- 每条规则必须有唯一、可追溯的名称,格式为
[方向]-[协议]-[端口]-[来源IP],例如IN-TCP-22-192.168.56.10; - 所有规则必须指定
本地端口和远程端口,即使它们相同(如SSH的22端口),因为某些防火墙(如macOS pf)会将“任意端口”视为高风险; - 永远不使用
Any作为IP地址范围,哪怕测试阶段也必须写死192.168.56.10/32。当需要多IP时,宁可复制规则,也不用192.168.56.0/24这种宽泛掩码。
这个原则看似繁琐,但它强迫你思考每一个连接的“身份”:是哪个程序发起的?目标是谁?用什么协议?带什么特征?这种思维习惯,比记住一百条命令更重要。
3. Windows防火墙:别被“高级安全”四个字骗了,它本质是策略编译器
Windows Defender Firewall with Advanced Security(WFA)这个名字很有迷惑性,让人以为它是个独立的、功能强大的新防火墙。实际上,它只是netsh advfirewall命令行工具的图形前端,底层调用的仍是Windows Filtering Platform(WFP)。WFP是一个内核级框架,所有防火墙规则最终都会被编译成WFP的“子层(Sublayer)”和“滤镜(Filter)”。理解这一点,你就明白为什么有些规则在GUI里找不到,却能在PowerShell里导出——因为GUI只展示它认为“用户需要看到”的那部分策略。
3.1 入站规则的三重匹配逻辑:端口、程序、安全描述符
我们以“允许fw-client(192.168.56.10)通过SSH连接fw-server(192.168.56.20)”为例。在Windows上,这条规则的创建看似简单,但背后有三重匹配机制在协同工作:
第一重:端口与协议匹配(最表层)
这是最直观的。你指定“TCP协议,本地端口22”,WFP就会检查每个到达本机22端口的TCP数据包。但注意:这里说的“本地端口”是指目标端口(Destination Port),即数据包要发往的端口。很多初学者误以为是“源端口”,导致规则失效。
第二重:程序路径匹配(中层)
如果你勾选了“此规则应用于以下程序”,WFP会进一步检查发起连接的进程的完整路径(如C:\Windows\System32\sshd.exe)。这个检查发生在连接建立前,属于“连接前检查(Pre-connection inspection)”。实测发现,如果SSH服务是用openssh-server的Windows版启动的,其进程路径是C:\Program Files\OpenSSH\sshd.exe,那么规则里必须精确填写这个路径,写成C:\Program Files\OpenSSH\*都不行——WFP不支持通配符路径匹配。
第三重:安全描述符(SD)匹配(最深层,常被忽略)
这才是WFA真正的“高级”所在。每条规则都可以关联一个安全描述符,它定义了“谁有权限应用这条规则”。默认情况下,新建规则的SD是D:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-15-3-1024-106536577-1281604716-3511738428-1654722307-507921465-3601279529-2983182352-1295910071),这串SID代表“NT AUTHORITY\ALL APPLICATION PACKAGES”,即允许UWP应用使用。但如果你的服务是传统桌面程序,就需要修改SD。我曾遇到一个诡异问题:规则明明启用了,netsh advfirewall firewall show rule name="IN-TCP-22-192.168.56.10"也显示“状态:启用”,但连接就是不通。最后用auditpol /get /category:*发现,是“对象访问”审计策略被禁用,导致WFP无法读取程序的安全描述符,从而跳过第三重检查,直接按默认拒绝处理。这个细节,微软官方文档提都没提。
注意:在PowerShell中导出规则时,
Get-NetFirewallRule | Export-Clixml生成的XML文件里,SecurityDescriptor字段是Base64编码的。如果你想手动修改,需要用ConvertFrom-SddlString解析,否则极易出错。我建议新手直接用Set-NetFirewallRule -DisplayName "xxx" -Enabled True来开关规则,避免碰SD。
3.2 出站规则的“隐性依赖”:为什么放行出站不等于能上网
出站规则常被误解为“允许本机访问外部”。但WFA的出站规则有一个关键特性:它只控制本机主动发起的连接,不控制响应包(Response Packet)的返回。换句话说,你放行了OUT-TCP-443-8.8.8.8,意味着你的浏览器可以向Google DNS发起HTTPS请求,但DNS服务器返回的53端口UDP响应包,是否能进来,取决于你的入站规则。
我在fw-client上做了个对比实验:
- 场景A:只启用一条出站规则
OUT-TCP-443-192.168.56.20,其他规则全禁用。 - 场景B:在场景A基础上,增加一条入站规则
IN-TCP-443-192.168.56.20。
结果:场景A下,curl https://192.168.56.20超时;场景B下,成功返回HTML。这证明:出站规则只管“发出去”,入站规则才管“收回来”。很多人配置防火墙时只关注出站,却忘了响应包需要入站规则放行,这是最常见的配置失误。
更微妙的是,WFA对“已建立连接(ESTABLISHED)”的响应包有特殊处理。它默认允许所有属于已建立连接的入站响应包,无需显式规则。但这个“默认允许”只适用于TCP,对UDP不适用。我在测试DNS查询(UDP 53)时,必须显式添加一条IN-UDP-53-192.168.56.20入站规则,否则nslookup example.com 192.168.56.20永远得不到答案。这个差异源于TCP的连接状态机(有SYN/SYN-ACK/ACK握手)和UDP的无连接特性(每个包都是独立的)。
3.3 日志分析:读懂事件ID 5156和5157才是真本事
WFA的日志不是简单的“允许/拒绝”记录,而是包含丰富上下文的结构化数据。关键事件ID有两个:
5156:允许的连接(Allowed Connection)
日志中Direction: In表示入站允许,Direction: Out表示出站允许。Application Name字段显示触发规则的进程路径,Source Address和Destination Address是IP,Protocol是数字(6=TCP,17=UDP),Source Port和Destination Port是端口号。5157:被阻止的连接(Blocked Connection)
这是排错的黄金信息。除了5156的所有字段,它还多了一个Rule Name,明确告诉你哪条规则(或默认策略)阻止了它。但要注意:如果Rule Name显示为NULL,说明是默认阻止策略(Default Block Rule)在起作用,而非某条具体规则。
我在fw-client上测试时,发现curl http://192.168.56.20失败,日志里却找不到5157事件。排查后发现,是Windows的“网络发现”功能在后台尝试用SMB协议(TCP 445)连接fw-server,而SMB被默认阻止,但curl本身走的是HTTP(TCP 80),所以它的连接请求根本没走到WFA的过滤点——因为80端口在Windows上默认是“允许”的(属于“核心网络”例外)。这个案例说明:日志里没有记录,不等于没发生;要查日志,先得知道该查哪个进程、哪个端口。我后来用Get-NetTCPConnection | Where-Object {$_.State -eq "Listen"}列出所有监听端口,才确认80端口确实没被WFA接管。
4. Ubuntu iptables:别被“过时”标签误导,它仍是Linux防火墙的底层心脏
网上总有人说“iptables过时了,该用nftables了”。这话没错,但错在忽略了事实:nftables是iptables的替代品,不是升级版;它替换的是用户空间工具,而非内核框架。Ubuntu 22.04的内核(5.15)依然使用Netfilter框架,而iptables命令只是Netfilter的一个前端。nft命令也是,只是语法更现代。在实验中,我坚持用iptables,不是守旧,而是因为它更直白地暴露了Netfilter的五链(PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING)结构,这对理解防火墙工作原理至关重要。
4.1 INPUT链的“默认策略陷阱”:为什么DROP比REJECT更安全
在Ubuntu上,sudo ufw default deny incoming设置的是INPUT链的默认策略为DROP。但很多教程会教你用REJECT,理由是“能立刻告诉对方连接被拒,便于调试”。这个说法在实验室环境下成立,但在生产环境是巨大风险。
DROP和REJECT的本质区别在于:
REJECT会向源IP发送一个TCP RST包(对TCP)或ICMP port-unreachable(对UDP),明确告知“此端口不存在”。DROP则什么都不发,让源IP的连接请求超时。
我在fw-server上做了压力测试:用hping3 -S -p 22 -c 10000 192.168.56.10向fw-client的22端口发送10000个SYN包。
- 当fw-client的INPUT默认策略为
REJECT时,fw-server收到10000个RST包,CPU占用飙升至45%; - 当改为
DROP时,fw-server几乎无感,CPU占用维持在3%以下。
原因在于:REJECT需要内核构造并发送响应包,消耗CPU和网络栈资源;DROP只是丢弃,开销极小。更重要的是,REJECT暴露了你的主机在线状态——攻击者发一个SYN,收到RST就知道“这台机器开着,且22端口被防火墙挡了”。而DROP让攻击者无法区分“主机宕机”“网络不通”还是“防火墙拦截”,大大增加了扫描成本。所以,我的规则是:所有默认策略必须是DROP;只有在明确需要调试时,才临时添加一条REJECT规则,并立即删除。
4.2 状态跟踪(conntrack):让防火墙“记住”连接的魔法
Netfilter的nf_conntrack模块是Linux防火墙的灵魂。它让防火墙不再是“无状态”的包过滤器,而是能理解TCP连接生命周期的智能网关。关键在于-m state --state和-m conntrack --ctstate这两个匹配模块。
我最初用老式语法:
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT这条规则的意思是:“只允许新的(NEW)TCP连接请求进入22端口”。但它有个致命缺陷:state模块已被标记为废弃,且在较新内核中可能不工作。
正确写法是:
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT--ctstate支持四种状态:
NEW:连接的第一个包(通常是SYN);ESTABLISHED:属于已建立连接的包(SYN-ACK、ACK、数据包);RELATED:与现有连接相关的包(如FTP的数据连接);INVALID:无法识别状态的包(通常是恶意或损坏的包)。
我在fw-server上配置SSH规则时,写了两条:
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPTiptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
这样,第一条只放行新的SSH连接请求,第二条放行所有已建立连接的响应包。第二条必须放在第一条之后,且必须存在——否则,fw-client发过去的SYN能进来,但fw-server返回的SYN-ACK会被默认DROP策略拦住,导致连接永远无法建立。这个顺序和依赖关系,是iptables配置中最容易出错的地方。
4.3 保存与持久化:iptables-save不是万能的,netfilter-persistent才是正解
很多教程教你在命令行敲完iptables -A ...后,执行iptables-save > /etc/iptables/rules.v4。这在Ubuntu 22.04上会失败,因为默认没有/etc/iptables/目录,且iptables-save输出的格式与netfilter-persistent服务期望的格式不兼容。
正确流程是:
- 安装服务:
sudo apt install netfilter-persistent iptables-persistent - 添加规则后,执行:
sudo netfilter-persistent save - 验证:
sudo netfilter-persistent reload,然后sudo iptables -L -v -n检查规则是否还在。
netfilter-persistent的原理是:它把iptables-save的输出存到/etc/iptables/rules.v4,并在系统启动时通过systemd服务自动执行iptables-restore < /etc/iptables/rules.v4。我曾跳过安装netfilter-persistent,直接用iptables-save,结果重启后所有规则消失。后来发现,Ubuntu 22.04的iptables包默认不包含iptables-restore的开机启动项,必须靠netfilter-persistent来补全。
提示:
iptables -L显示的规则顺序,就是它们被匹配的顺序。越靠前的规则优先级越高。所以,把ESTABLISHED,RELATED规则放在NEW规则之前,会导致所有包都被它匹配,NEW规则永远没机会执行。务必用iptables -L -v -n --line-numbers查看行号,用iptables -I INPUT 1 ...插入到第一行,或iptables -D INPUT 2删除第二行。
5. macOS pf:被低估的BSD防火墙,用配置文件驱动一切
macOS的防火墙底层是OpenBSD的pf(Packet Filter),它不像Windows或Linux那样有丰富的GUI工具,而是完全靠文本配置文件驱动。很多人因此望而却步,觉得“太难”。但恰恰是这种“纯文本+声明式语法”的设计,让它成为学习防火墙原理的最佳教材——因为你写的每一行,都直接对应一个网络行为。
5.1/etc/pf.conf:不是脚本,而是策略声明
pf.conf不是shell脚本,不能写if、for循环。它是一种声明式配置语言,核心是三条指令:
block:阻止匹配的流量;pass:允许匹配的流量;set:设置全局参数(如日志、状态超时)。
我在fw-monitor上创建的最小pf.conf如下:
# 启用日志 set loginterface enp0s8 # 定义宏(相当于变量) ext_if = "enp0s8" server_ip = "192.168.56.20" client_ip = "192.168.56.10" # 默认策略:阻止所有 block all # 允许本机回环 pass quick on lo0 all # 允许已建立连接的响应包 pass in on $ext_if inet proto tcp from any to $ext_if port 22 keep state # 允许fw-client访问fw-server的22端口 pass in on $ext_if inet proto tcp from $client_ip to $server_ip port 22 keep state关键点解析:
keep state是pf的灵魂。它告诉pf:“记住这个连接,后续属于它的包自动放行”。没有它,你必须为每个方向都写规则,极其繁琐。pass quick表示“匹配后立即执行,不再检查后续规则”。lo0规则加了quick,是因为回环流量必须无条件放行,不能被后面的block all拦截。block all是默认策略,但它只对未被前面pass规则匹配的流量生效。所以规则顺序至关重要。
5.2 状态跟踪的BSD哲学:state不是选项,而是必需品
pf的状态跟踪比Linux的conntrack更激进。它默认要求所有pass规则都带keep state,否则会警告。我在第一次配置时,漏掉了keep state,执行sudo pfctl -f /etc/pf.conf时报错:/etc/pf.conf:12: missing 'keep state' for stateful rule
这是因为pf的设计哲学:无状态的防火墙是不安全的。它强制你思考“这个连接是否需要被跟踪”。对于SSH这种长连接,keep state是必须的;但对于ICMP ping,你可以用no state,因为ping是请求-响应一对包,不需要维护连接状态。
我测试了两种写法:
- 带
keep state:pass in on $ext_if inet proto icmp from $client_ip to $server_ip no state - 不带
keep state:pass in on $ext_if inet proto icmp from $client_ip to $server_ip
结果:两者都能让ping通。但前者更高效,因为no state告诉pf:“别为这个包创建状态条目,直接放行”。而后者会让pf为每个ping包都创建一个短暂的状态,增加内存开销。所以,no state不是“不跟踪”,而是“明确声明不跟踪”,这是一种更精细的控制。
5.3 日志与调试:pflog不是可选功能,而是排错生命线
pf的日志不写入/var/log/system.log,而是通过专用设备/dev/pflog收集。要启用它,必须:
- 加载内核模块:
sudo kextload /System/Library/Extensions/pf.kext(macOS Sonoma需先禁用SIP) - 启动日志服务:
sudo pfctl -e -f /etc/pf.conf && sudo ifconfig pflog0 create - 实时查看日志:
sudo tcpdump -n -e -ttt -i pflog0
tcpdump输出的每一行,都包含rule X/match,其中X是规则编号(按pf.conf中出现顺序),match表示命中了哪条规则。我在调试时,发现curl请求总是被第1条规则(block all)拦截,但pfctl -sr显示规则列表里明明有pass规则。最后发现,是pf.conf里server_ip宏定义错了,写成了192.168.56.21,导致pass规则的to地址不匹配,自然落到block all头上。tcpdump的日志里清楚写着rule 1/match,让我5分钟内定位到问题。
注意:macOS的
pf默认不记录被block的包,只记录pass的包。要记录block,必须在block规则后加log,如block log all。但log会极大降低性能,仅在调试时启用,用完立即注释掉。
6. 跨平台规则一致性验证:当三台机器“互相指认”时发生了什么
实验的最高潮,不是单台机器配置成功,而是三台机器在各自防火墙规则下,能稳定、可预测地通信。我设计了一套“互相指认”测试矩阵,覆盖了所有组合:
- fw-client → fw-server(SSH、HTTP)
- fw-server → fw-client(SSH反向连接、文件传输)
- fw-monitor → fw-server(端口扫描、服务探测)
- fw-monitor → fw-client(ICMP、自定义UDP服务)
6.1 SSH双向连接:检验“连接发起方”与“服务提供方”的规则边界
SSH是双向协议,但防火墙规则是单向的。fw-client作为客户端,需要出站规则放行OUT-TCP-22-192.168.56.20;fw-server作为服务端,需要入站规则放行IN-TCP-22-192.168.56.10。我故意在fw-server上只配置了入站规则,而在fw-client上禁用所有出站规则,结果ssh user@192.168.56.20超时。这验证了:客户端的出站规则和服务器的入站规则,缺一不可。它们共同构成了一个完整的通信通道。
更有趣的是SSH反向连接(Reverse SSH Tunnel)。我在fw-client上执行:
ssh -R 2222:localhost:22 user@192.168.56.20这条命令的意思是:“让fw-server的2222端口,转发到fw-client的22端口”。要使它工作,需要:
- fw-client的出站规则:允许连接fw-server的22端口(建立隧道);
- fw-server的入站规则:允许来自fw-client的2222端口连接(接收隧道请求);
- fw-server的出站规则:允许fw-server主动连接fw-client的22端口(隧道数据转发)。
我在fw-server上漏掉了第三条,导致隧道建立后,ssh -p 2222 localhost无法登录fw-client。这个案例说明:防火墙规则不是静态的,而是随连接模式动态变化的。一个服务的部署方式,直接决定了你需要配置多少条规则。
6.2 HTTP服务的“隐形依赖”:为什么浏览器能连,curl却不行
我在fw-server上用Python启动了一个简易HTTP服务器:
python3 -m http.server 8000然后在fw-client的浏览器里访问http://192.168.56.20:8000,成功;但用curl http://192.168.56.20:8000却返回Failed to connect。排查发现,是Windows防火墙的“网络发现”功能在作祟。当浏览器访问时,它会先尝试用NetBIOS(UDP 137/138)或LLMNR(UDP 5355)解析主机名,而这些协议被WFA默认阻止。但浏览器有降级机制:解析失败后,它会直接用IP地址发起HTTP请求。curl没有这个机制,它严格按URL里的IP地址走,所以只要HTTP端口规则正确,它就应该通。
最终定位到,是curl命令被Windows的“SmartScreen”功能拦截了——它误判curl为潜在恶意程序。解决方案不是改防火墙,而是右键curl.exe→属性→勾选“解除锁定”。这个坑提醒我:防火墙不是网络问题的唯一元凶;操作系统级的安全机制(如SmartScreen、Gatekeeper)同样会干预连接。在跨平台实验中,必须把这类机制也纳入排查范围。
6.3 端口扫描的“心理战”:防火墙如何让攻击者自我怀疑
我用fw-monitor上的nmap -sS -p 1-100 192.168.56.20对fw-server进行半开放扫描。结果很有趣:
- 端口22:
open(因为入站规则放行); - 端口80:
filtered(因为无规则,被默认DROP); - 端口443:
closed(因为有REJECT规则,返回RST)。
filtered和closed的区别,是防火墙对抗扫描的核心战术:
closed告诉攻击者:“这台机器在线,这个端口有服务,只是当前拒绝连接。”filtered告诉攻击者:“我不知道这个端口有没有服务,可能是防火墙挡了,也可能是机器根本没开。”
我在fw-server上把80端口的规则从REJECT改成DROP后,nmap对80端口的扫描结果从closed变成了filtered。这意味着,攻击者需要花更多时间、更多探测包来确认端口状态,大大降低了扫描效率。这就是“默认DROP”策略的实际价值——它不提供任何确定性信息给对手。
7. 实验总结:防火墙配置不是技术操作,而是安全思维训练
做完这个实验,我最大的收获不是记住了多少条命令,而是形成了三个根深蒂固的习惯:
第一,永远问“谁在连谁,用什么协议,带什么特征”。
以前看到“端口不通”,第一反应是“防火墙是不是没开”。现在我会拆解:是客户端的出站被拦?还是服务端的入站被拦?是TCP SYN没到,还是SYN-ACK被丢?是程序路径不匹配,还是安全描述符权限不足?这个问题拆解框架,比任何命令都重要。
第二,接受“默认拒绝”是常态,而不是异常。
我们习惯于“先放开,再收紧”,但安全的世界观是相反的。每一次配置,我都从block all或default deny开始,然后像搭积木一样,只添加明确需要的规则。这种思维,让我在真实项目中,能快速识别出“为什么这个新服务上线后,老功能就挂了”——往往是因为新规则的--ctstate没写全,或者宏定义写错了IP段。
第三,日志不是备查资料,而是实时决策依据。
我不再等出问题了才去看日志。在每次添加规则后,我都会用tcpdump或Get-WinEvent实时监控,看规则是否真的被命中。日志里的Rule Name、ctstate、match这些字段,已经成了我眼中的“活地图”,告诉我数据包的真实走向。
最后分享一个真实技巧:在Windows上,用netsh advfirewall firewall add rule命令创建规则时,加上dir=in action=allow enable=yes profile=domain,private,public name="MyRule",其中profile=domain,private,public必须显式指定,否则规则只在当前网络配置文件(如“专用”网络)下生效,换到“公用”网络就失效了。这个细节,官网文档藏在几百页PDF的某个角落,而