1. 先还原现场:从一次“诡异断网”说起
那天下午的体感非常典型:办公网突然开始丢包,ping网关一会儿延迟正常,一会儿飙到几千毫秒,网页时不时被插进一个假的“安全认证”页面,输入账号密码后毫无反应。登录机房里的Linux服务器一看,arp -a里的网关MAC已经变成一个眼生的地址,而它在几分钟前还是我亲手记录的官方地址。这就是标准的ARP欺骗攻击,属于局域网内最容易发生、也最容易被低估的安全事件。
这篇文章想把我在Linux上防御ARP欺骗的整套思路和配置讲清楚:从ARP协议为什么会被人利用,到静态绑定、arptables过滤、sysctl加固、arpwatch监控这些手段分别怎么用、为什么这么用,最后再补上我在实际环境里踩过的坑。适合Linux运维、网络管理员,以及第一次接触内网安全的新手——尤其是那些负责固定服务器、机房设备的同学,这套方案可以直接抄作业。
1.1 ARP协议为什么天生就“不设防”
ARP(Address Resolution Protocol,地址解析协议)的作用很单纯:在局域网里,把IP地址翻译成MAC地址。IP是逻辑地址,真正在以太网上传输数据靠的是MAC地址。当主机A想和192.168.10.1通信时,它先查自己的ARP缓存表;查不到就在整个广播域发一条广播:“谁是192.168.10.1?请把你的MAC地址告诉我。”目标主机收到后回一句:“我是192.168.10.1,我的MAC是xx:xx:xx:xx:xx:xx。”A就把这个映射记下来,之后直接按这个MAC发包。
问题出在两个地方。第一,ARP请求和应答都默认“无条件信任”,任何人都可以发一条ARP回复说“192.168.10.1的MAC是我”,而且很多操作系统即使收到非请求的ARP应答,也会直接更新缓存,也就是攻击者不需要先问就能投毒。第二,ARP处于以太网和IP之间,不是完整的网络层协议,传统防火墙默认只认IP层,ARP报文在iptables里基本是“透明”的。这两点叠加,ARP欺骗才能在局域网里屡试不爽。
1.2 攻击者的三种常见套路
攻击者通常在同一个广播域内动手,手段大同小异,但目标分三类,理解它们有助于防御时对症下药。
一是中间人攻击。攻击者伪造网关MAC发给受害者,让受害者的流量全部送到攻击者机器上;同时再伪造受害者IP发给网关,让网关的回包也送到攻击者机器。攻击者只要打开IP转发,就能坐在中间完整地观看流量,顺便篡改数据——网页挂马、账号密码嗅探、DNS劫持都靠这个套路。
二是断网型攻击。攻击者根本不想看内容,只向受害者或全网乱发假的ARP应答,让ARP缓存彻底混乱,受害者的流量被导向不存在的MAC,表现就是频繁丢包、找不到网关,整个网段濒临瘫痪。这类攻击成本极低,甚至一个App就能做到。
三是针对特定服务器的欺骗。攻击者只伪造某台数据库或办公系统的IP,把指向它的流量全部劫持到自己的机器上,骗登录、骗敏感数据,隐蔽性更强,受害主机往往毫无察觉。
不管哪种套路,本质都是同一件事:攻击者让别人的ARP缓存相信了错误的IP-MAC映射。所以Linux下的防御,重点也集中在“别让错误映射写进缓存”和“及时察觉异常映射”这两件事上。
2. 防御设计的整体思路:这个战场在链路层
2.1 为什么常规防火墙拦不住它
很多第一次接触这个问题的人会先问:iptables不是可以配INPUT、OUTPUT规则吗,把非网关MAC的流量都DROP掉不行吗?行,但拦不住ARP。iptables工作在网络层,它看到的是IPv4报文头;ARP帧的以太网类型是0x0806,没有IP头,iptables默认根本不处理这类帧。要过滤ARP,必须用arptables(针对ARP协议层)或者ebtables(针对以太网桥接层),这才是链路层的“防火墙”。
这解释了为什么“在网关服务器上开防火墙”这种常规操作对ARP欺骗几乎无效。攻击者发的是二层广播或单播帧,三层防火墙连看都看不到,更别说拦截。想防住它,就必须到二层去想办法。
2.2 三管齐下的设防模型
我在实际环境里最终采用的思路是“防来源、过滤过程、盯结果”三层配合,而不是迷信某一个工具。
防来源:把关键的IP-MAC映射做成静态绑定,让攻击者的假应答根本没有机会写进缓存。这是最底层、最硬的一招。
过滤过程:用arptables在ARP协议层做白名单,对声称是网关的ARP报文进行合法性校验,不合法的一律丢弃。这一招防的是那些不直接改缓存、而是靠大量伪造报文扰乱网络的攻击。
盯结果:用arpwatch加自写脚本监控ARP流量的变化,一旦发现网关MAC变更或异常的大量ARP广播,立刻告警。防不住的时候,至少要第一时间知道。
如果网络里有可管理的交换机,还可以在上游开启DHCP Snooping和DAI(Dynamic ARP Inspection),在交换机层面就把伪造ARP报文拦掉,主机侧的配置作为纵深防御的补充。没有交换机权限的小环境,Linux主机的这套加固就是主力。
3. 实操配置:Linux主机上的完整防御步骤
下面我以一个具体网络为例子,把完整步骤走一遍。假设场景是:内网192.168.10.0/24,本机IP是192.168.10.50,网卡eth0,网关是192.168.10.1,网关真实MAC是00:11:22:33:44:55。所有命令都需要root权限,系统为Debian系(Ubuntu同样适用,RHEL系把安装命令换成yum/dnf即可)。
3.1 获取“标准答案”:先记录正确的IP-MAC对照表
防御的前提是知道什么是“对的”。在配置任何绑定之前,先拿到一份可信的IP-MAC映射表。我推荐在攻击尚未发生时、或者确认网络正常的早期做这件事,把它当作主机初始化的一部分。
# 查看当前ARP缓存 ip neigh show dev eth0 # 主动向网关发ARP探测,确认它的真实MAC arping -I eth0 -c 4 192.168.10.1看到返回的MAC后,把它记录到/etc/ethers里,格式是“MAC地址 IP地址”:
00:11:22:33:44:55 192.168.10.1如果局域网里还有需要重点保护的服务器(比如数据库、业务网关),也一并记录。这一步花不了两分钟,但等出问题了你就会知道它有多值钱——没有这份记录,攻击发生时你连“什么叫异常”都不知道,只能对着抓包结果发呆。
3.2 静态绑定:把网关IP钉死在正确的MAC上
拿到了标准答案,最直接的做法就是用ip neigh把网关绑定为永久表项:
ip neigh replace 192.168.10.1 lladdr 00:11:22:33:44:55 dev eth0 nud permanentnud permanent表示这条表项永远有效、不会过期,也不会被任何新的ARP应答更新。这个操作的原理很简单:既然攻击的核心是诱导主机更新ARP缓存,那我就把这个缓存的口子直接焊死。绑定后用ip neigh show dev eth0可以看到状态为PERMANENT的条目。
如果想做成开机自动执行,我建议写一个小的systemd服务,而不是把命令塞进rc.local。新建/etc/systemd/system/gateway-arp-binding.service:
[Unit] Description=Bind gateway MAC permanently After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/sbin/ip neigh replace 192.168.10.1 lladdr 00:11:22:33:44:55 dev eth0 nud permanent RemainAfterExit=yes [Install] WantedBy=multi-user.target然后systemctl enable --now gateway-arp-binding即可。这里有个必须提醒的坑:如果网关侧做了主备切换或链路聚合的MAC漂移,永久绑定会让本机找不到新MAC而直接断网。所以静态绑定的对象一定要是“MAC长期稳定”的网关或服务器,不能盲目绑所有主机,尤其是别碰移动设备。
3.3 arptables:在ARP协议层做白名单过滤
静态绑定保护的是本机缓存,但局域网里其他主机可能还在被欺骗;而且攻击者用大量伪造ARP报文做干扰时,本机也会收到一堆错误的二层流量。这时候需要arptables在协议层做白名单。
安装并启用:
apt install arptables下面的规则对input方向的ARP报文做校验:凡是声称“我是192.168.10.1(网关)”的ARP请求或应答,源MAC必须是真正的网关MAC,否则直接丢弃;其余ARP报文正常放行。
# 1. 先确认当前规则为空 arptables -L -v # 2. 设置INPUT默认策略为DROP arptables -P INPUT DROP # 3. 放行网关发出的ARP请求(opcode=1,包含免费ARP) arptables -A INPUT -i eth0 --opcode 1 --source-ip 192.168.10.1 --source-mac 00:11:22:33:44:55 -j ACCEPT # 4. 放行网关产生的ARP应答(opcode=2) arptables -A INPUT -i eth0 --opcode 2 --source-ip 192.168.10.1 --source-mac 00:11:22:33:44:55 -j ACCEPT # 5. 放行其他所有ARP报文 arptables -A INPUT -i eth0 -j ACCEPT很多人会疑惑:第5步不是把其他所有ARP都放行了吗,那DROP还有什么意义?关键在于规则是顺序匹配的。一个伪造的ARP应答,它的IP是192.168.10.1但MAC不是00:11:22:33:44:55,它过不了第4步,而第5步要求的是“其他所有”,它同样不属于“其他”——因为它也声称是网关,只是身份不合法。匹配完前5条规则后,它落到INPUT的默认策略上,被DROP。换句话说,第5步确保的是“和网关无关的正常ARP不受影响”,默认DROP专门兜底“冒充网关的非法帧”。
如果还要保护局域网里其他关键服务器,按同样的模式追加成对规则即可,注意把IP和MAC替换成目标服务器的正确值。规则配置完成后查看:
arptables -L -v规则需要持久化。Debian系可以安装arptables-persistent,也可以手动保存:
arptables-save > /etc/arptables.rules再写一个对应的restore服务,或者把restore动作放到/etc/network/if-pre-up.d/下让网络启动时自动恢复。RHEL系没有现成的helper,我建议直接写systemd unit,跟上面网关绑定的写法一致。
3.4 sysctl参数加固:让协议栈更“冷静”
静态绑定管的是指定表项,arptables管的是网关IP,但攻击者还可以用大量假冒本机IP或广播IP的请求骚扰整个网段。Linux内核本身就提供了几个抗ARP异常的参数,虽然单独用效果有限,配合起来很香。
编辑/etc/sysctl.d/99-arp-hardening.conf:
net.ipv4.conf.all.arp_ignore = 1 net.ipv4.conf.all.arp_announce = 2 net.ipv4.conf.all.arp_filter = 1 net.ipv4.conf.default.arp_ignore = 1 net.ipv4.conf.default.arp_announce = 2 net.ipv4.conf.default.arp_filter = 1 net.ipv4.conf.all.drop_gratuitous_arp = 1生效:
sysctl -p /etc/sysctl.d/99-arp-hardening.conf简单说这几个参数的作用。arp_ignore=1要求本机只对“到达本网卡的、目标IP是本网卡地址”的ARP请求进行应答,避免被伪装的请求牵着鼻子走;arp_announce=2要求发起ARP请求时使用出接口自身的最优IP地址,减少多网卡场景下“报错门牌号”的概率;arp_filter=1则规定,只有当源IP能通过该接口正常路由时才应答,防止跨接口的ARP串扰。这三个参数主要解决主机本身的ARP行为异常,对多网卡服务器尤其重要。
drop_gratuitous_arp=1则比较猛,它让内核忽略所有“主动宣告”的ARP报文。前面说过,免费ARP(gratuitous ARP)是攻击者最常用来投毒的手段,这个参数等于一棍子把这类投毒全挡住。但要注意:如果网络里用了keepalived这类靠免费ARP做虚拟IP漂移的方案,打开这个参数会把漂移打断,所以只建议在业务确实不需要免费ARP的普通服务器上开启。
3.5 arpwatch:部署一双盯着二层的眼睛
硬防御做了,还得有监控。arpwatch是一个老牌工具,专门监听网卡上的ARP流量,学习并记录IP-MAC对应关系,发现变化或异常就报警。
apt install arpwatch # 前台调试模式跑起来看效果 arpwatch -i eth0 -d确认正常后转后台运行。arpwatch会把学到的映射关系存到/var/lib/arpwatch/arp.dat,报警默认发到root邮箱,同时写syslog。如果服务器没配邮件,就盯日志:
tail -f /var/log/syslog | grep -i arpwatch看到这类日志就意味着有人或某个程序在宣告新的MAC:
arpwatch: changed ethernet address 192.168.10.1 00:11:22:33:44:55 (aa:bb:cc:dd:ee:ff)我个人的使用心得是:arpwatch不适合当实时护栏,它更像事后情报源。因为默认报警会随攻击流量数量爆炸,几分钟内邮箱就被刷满;而且发现异常时攻击可能已经开始。所以我的用法是“arpwatch留痕,脚本告警”,脚本见下一节。
3.6 应急自动恢复脚本:快速把缓存掰回来
有些环境不允许长期静态绑定(比如动态分配IP、主机经常迁移),这时候可以写一个轻量脚本,周期性检查网关ARP缓存,发现异常立刻修正。我把这类脚本定位为“应急止血”,不是长期方案。
#!/bin/bash # /usr/local/sbin/arp-guard.sh GW_IP="192.168.10.1" GW_MAC="00:11:22:33:44:55" DEV="eth0" LOG="/var/log/arp-guard.log" while true; do CUR_MAC=$(ip neigh show "$GW_IP" dev "$DEV" | awk '{print $5}' | head -n1) if [ -z "$CUR_MAC" ]; then # 表项被清空,重新绑定 ip neigh replace "$GW_IP" lladdr "$GW_MAC" dev "$DEV" nud permanent elif [ "$CUR_MAC" != "$GW_MAC" ]; then # 缓存里出现了陌生MAC,说明很可能被投毒,立刻纠正并记录 echo "$(date '+%F %T') WARNING: gateway MAC changed to $CUR_MAC, restore to $GW_MAC" >> "$LOG" ip neigh replace "$GW_IP" lladdr "$GW_MAC" dev "$DEV" nud permanent fi sleep 5 done用systemd或nohup把它挂起来即可。原理很直白:攻击者要得手,必须先改变缓存里的MAC;脚本一旦看到缓存里的MAC不是标准答案,就把它掰回来。这算“反应式防御”,速度比人类手工处理快得多。当然它有个先天局限——只能纠正本机缓存,改不了局域网里其他主机的缓存,所以能不能根治,还得看攻击规模和上游设备的配合。
4. 常见问题与排查实录
防御配置本身不复杂,复杂的是部署之后的各种副作用。下面这几个问题我基本都现场遇到过,写出来帮大家少走弯路。
4.1 网关绑定了,外网还是断?
这是最让我头疼过的一个坑:明明本机已经nud permanent绑定网关,而且ip neigh看表项也正常,但外网就是不通。后来才发现问题根本不在本机,而是网关侧也中了招——攻击者给网关发送假的ARP应答,让网关以为本机IP对应的MAC是攻击者的机器。于是网关把回包全发给攻击者,攻击者再决定丢还是转发。
这说明一个关键问题:ARP欺骗是双向的。防守方只在受害主机上绑定,只能保护自己;攻击者同时污染网关缓存时,你还是会被断。解决办法有两个:一是在能登录的网关设备上也做静态绑定或开DAI;二是走交换机的端口安全,把可信主机的MAC绑定在端口上,让攻击者的机器连入时直接被拒。主机上的防御从来不是孤岛。
4.2 静态绑定和动态环境打架
永久绑定最大的问题就是“太硬”。有一次我帮同事的笔记本也配了网关永久绑定,结果他带着笔记本回家,换了Wi-Fi和网关之后,那台笔记本就再也上不了网了——因为缓存里还钉着公司网关的MAC。所以静态绑定只建议用在固定不动的服务器上,笔记本、手机这类移动设备一律不要碰。如果确实需要给移动设备做保护,优先靠交换机的DHCP Snooping和DAI,而不是主机绑定。
4.3 怎么把藏在暗处的攻击者揪出来
防御做到位后,下一个自然需求是定位攻击者。最实用的工具是tcpdump,直接看二层头:
tcpdump -ne -i eth0 arp输出里会连续出现某个固定的MAC反复宣告“192.168.10.1 is-at xx:xx:xx:xx:xx:xx”,这个MAC就是攻击机的网卡地址。拿到MAC之后,登录交换机查MAC地址表,定位到具体端口,顺着网线就能找到物理机器。如果攻击MAC做了随机化,那就配合arptables的规则计数:统计DROP规则被命中的次数,再看来源MAC的规律,多看一会儿总能找到破绽。
4.4 规则重启后全部消失
arptables写好的规则重启就没了,这是最常见的“假死”现场。检查两件事:第一,有没有把规则用arptables-save > /etc/arptables.rules持久化;第二,有没有配置开机恢复。我见过很多人在线配置得很漂亮,忘了持久化,一重启全部失效,然后误以为“防御没用”。另外注意,如果网卡被ip link set down再up,部分系统会清空ARP缓存,绑定条目也可能不见,所以我把网关绑定做成了独立的systemd服务,跟网卡状态解耦,能减少这类问题。
还有一个容易踩的配置坑:arptables规则里出现了一台内网服务器的MAC,恰巧这台服务器做过高可用漂移,MAC换了,结果所有声称该IP的ARP都被当非法帧丢掉,相关业务全部异常。排查方法是用tcpdump -ne -i eth0 arp对比看到的真实流量,再看arptables计数,两者一对照就能定位是规则误伤还是真有攻击。
我把这些问题整理成了一张速查表,方便运维时快速对照:
| 现象 | 可能原因 | 处理方向 |
|---|---|---|
| 静态绑定后仍断网 | 网关侧缓存被污染,回包被劫持 | 网关也做绑定/DAI,或交换机端口绑定 |
| 重启后绑定消失 | 未持久化,或网卡重置清空缓存 | 做systemd unit,绑定与网卡状态解耦 |
| arptables规则命中过多 | 真有攻击,或规则误伤正常主机 | tcpdump抓包对比,检查是否有MAC漂移 |
| 移动设备绑定后无法上网 | 环境变更,缓存锁死旧网关 | 移动设备不要用永久绑定 |
| 邮件报警刷屏 | arpwatch过度敏感 | 改日志留痕,脚本按条件告警 |
5. 最后再分享几个现场才总结得出的经验
第一个体会是“永远提前留底”。我一开始是出了事才去抓包、才去对MAC,结果发现平时根本没记录过正确映射,抓到假包都不知道拿什么对比。后来我把所有要保护的服务器IP-MAC做成了一张表,存到wiki和/etc/ethers,攻击发生时对照时间几乎为零。这个习惯比任何工具都重要。
第二个体会是“别迷信单一手段”。永久绑定确实硬,但它扛不住网关侧被污染;arptables确实能过滤,但攻击者换个IP接着发也能造成干扰;arpwatch能告警,可它拦不住。真正让我把问题控制住的是三层配合:绑定保底、过滤拦路、监控留痕,有条件再把交换机DAI开上。少任何一层,防御都是有洞的。
第三个体会是“防御要做减法”。刚接手时我恨不得把能开的参数全开上,结果drop_gratuitous_arp把内网的虚拟IP漂移打断,arp_announce又让多网卡业务出现路由异常。后来我养成了一个习惯:每次只改一个参数,观察两三天再决定要不要保留。网络世界最怕拍脑袋上一堆配置,回头自己都说不清是哪条规则把业务搞挂了。
如果局域网里也被ARP欺骗折磨得不轻,照这个思路先把网关和关键服务器绑起来,配上arptables的网关白名单,再挂一个arpwatch留痕。三步走完,能挡住绝大部分攻击。剩下的还是要靠平时积累:留底、分层、做减法,这句话在网络上哪里都适用。