在给一批NFS服务器做安全体检时,我用showmount -e扫了一遍内网,结果让我后背发凉——几乎一半的机器把完整导出列表敞开给了任何能摸到端口的人。这不是个新问题,它的编号叫 CVE-1999-0554,二十多年前就被登记在案,但到今天依然有大把管理员忽略它。如果你手头也在维护 NFS 服务,或者只是负责运维但没仔细抠过这一层,我建议你花点时间看看这篇文章。它不分析某一次具体攻击,而是把问题链路拆开,告诉你为什么showmount -e会泄露信息,以及我实际在服务器上做的加固步骤和验证过程。1. 先从漏洞本身说起:showmount -e 到底泄露了什么1.1 CVE-1999-0554 的来龙去脉CVE-1999-0554 对应的是 NFS 服务默认对 MOUNT 协议查询不加限制,导致的导出信息泄露问题。早期 NFS 设计时默认网络环境是可信内网,协议层面几乎没有身份验证概念,rpcbind 和 mountd 把“任何能连到端口的调用者”都当作合法请求来源来看待。showmount -e恰恰就是利用这种信任关系,向目标服务器的 mountd 服务发起一次导出列表查询,然后把结果直接打印出来。这个漏洞之所以到今天还在被各类安全工具反复探测,是因为它不像缓冲区溢出那样需要构造复杂数据包,一个默认安装的 nfs-utils 客户端工具就能完成侦察。攻击者不需要任何凭据,只要网络层能到达目标,就能在几秒内拿到服务器的 NFS 导出清单,获知哪些目录被共享、共享给了哪些网段或主机。这类信息对后续的定向攻击来说,相当于提前拿到了一张家底清单。这里要注意,千万别把 CVE-1999-0554 和常见的“NFS 弱权限导致匿名挂载”混为一谈。前者是信息泄露,后者通常是/etc/exports配置不当导致的未授权访问。信息泄露本身不直接让你拿到文件内容,但它是攻击链里非常关键的“踩点”环节,很多自动化扫描器会把它当作 NFS 服务暴露的标记。1.2 从攻击者视角看到的信息有多“值钱”我在一次内部演练中,用一个干净的虚拟机模拟攻击者,对目标执行了下面的命令:showmount -e 192.168.20.15返回结果非常直白:Export list for 192.168.20.15: /data/backup 172.16.0.0/16 /home/share 192.168.20.0/24 /opt/app 10.10.10.5这三行信息能说明什么?目标机器上有/data/backup、/home/share、/opt/app三个目录被共享;/data/backup允许的客户端来自172.16.0.0/16,意味着这台机器很可能是备份服务器,或者至少参与了备份链路;/home/share限定在192.168.20.0/24,说明它在办公网或业务网内承担文件共享角色;/opt/app只允许10.10.10.5挂载,指向一个具体应用服务器。攻击者拿到这些信息后,会立刻把目标机器标记为“高价值存储节点”,然后围绕/data/backup或/opt/app这样的路径去猜应用结构。如果再结合端口扫描发现目标同时开着 Web 服务,就可能利用路径猜测、目录遍历或备份文件的已知命名规则进行下一步利用。很多管理员觉得“我 exports 里限制得很严,只有特定 IP 能挂载,所以无所谓”。这个想法我在踩坑里也犯过,后面会专门说。showmount -e泄露的只是“共享清单”,不是“谁能挂载的访问控制表”,攻击者即使无法挂载,也能从目录名推算业务用途和网络拓扑,这种情报价值在攻防场景里不容小觑。2. 理解 NFS 的 RPC 机制,才能对症下药2.1 查询链路:showmount 是如何拿到导出列表的要彻底解决这个问题,绕不开 NFS 底层的 RPC 调用机制。很多人只会敲命令,不知道这条链路到底经过哪几步,结果加固的时候漏掉环节。showmount -e的完整查询路径大致如下:客户端向目标主机的111端口发送请求,询问 rpcbind 服务:mountd 对应哪个端口;rpcbind 查询自身的端口映射表,返回 mountd 当前监听的端口号;客户端向 mountd 的端口发起MOUNT_PROC_EXPORT调用;mountd 读取/etc/exports解析后的导出列表,把结果返回给客户端。其中,第 1 步依赖 rpcbind,第 3 步依赖 rpc.mountd。rpcbind 默认监听 TCP/UDP 的 111 端口,mountd 端口则往往不固定。在老一些的系统上,mountd 会由一个随机高端口提供服务,一般落在 32767 附近,具体取决于系统启动时的分配情况。可以这样理解:rpcbind 像一个总机接线员,showmount 就像打总机电话说“我要找负责导出列表查询的那个分机”,总机告诉它分机号码,它再直接拨分机去问问题。整个过程没有身份核验、没有门禁,任何一个能打进总机的人都能顺藤摸瓜拿到分机上的答案。2.2 攻击面分析:NFS 链路里的每个环节都能被利用很多人在 NFS 加固时只想着把 111 端口封掉,觉得这样showmount -e就废了。但实际操作中,NFS 服务涉及的不只是 rpcbind 一个端口,完整链路包含多个组件:组件默认端口主要作用如果被利用rpcbind111RPC 服务端口注册与查询暴露 mountd、nfsd 等服务的端口位置nfsd2049提供文件读写如果目录权限配置不当,可能被直接挂载读写rpc.mountd随机高端口处理挂载请求、导出列表查询泄露/etc/exports共享列表rpc.lockd随机高端口管理文件锁可能被用于端口扫描与 RPC 枚举rpc.quotad随机高端口磁盘配额查询泄露用户配额信息这里的关键点是:如果只封了 111 端口,但攻击者通过其他手段已经知道 mountd 的固定端口,showmount -e是可以跳过第 1、2 步,直接向 mountd 端口发起查询的。我在测试环境里干过这件事,在服务端把 mountd 端口固定为 30001 后,客户端哪怕把 111 端口完全屏蔽,只要直接连 30001 端口,导出列表照样拉得出来。2.3 治标还是治本:不同方案对风险面的削减效果常见的加固手段,按效果从弱到强排一下:只修改/etc/exports里的网段限制:这个办法完全影响不了showmount -e,只要 mountd 可达,导出列表就会原样返回,属于典型的“治标都没治到”;用防火墙封掉 111 端口:能阻止 rpcbind 查询,但如果 mountd 端口没一起控制,或者 mountd 的端口是随机变化的,防火墙规则会很难写,封堵效果有限;固定 mountd 端口,同时用防火墙把 111、2049、mountd 端口限定到可信网段:这是我能稳定复现的“治本”方案,既保证业务可正常挂载,又让外部网段拿不到任何信息;在 mountd 层增加 tcp_wrappers 或底层访问控制:相当于在服务进程自身加了一层门禁,即使防火墙规则被绕过或端口被直接访问,mountd 也会根据来源地址拒绝响应。从投入产出比来看,固定端口加防火墙限定是最稳妥也最容易维护的做法。tcp_wrappers 在新版本系统上支持情况不一,得先做检查,我后面会给出检测命令和配置方法。3. 我实际采用的加固步骤(以 Red Hat 系系统为例)3.1 第一步:先自查,把风险看得明明白白动手加固前,先把现状摸清楚。我在一套 Rocky Linux 9 环境上做过完整复现,下面的操作同样适用于 CentOS 7/8、AlmaLinux、部分 Ubuntu 场景。先确认服务状态:systemctl status nfs-server然后找一台和该服务器不在同一个信任网段的机器,模拟攻击者视角执行:showmount -e <目标服务器IP>如果返回了导出列表,说明风险确实存在;如果没有返回,也可能是 rpcbind 访问受限,需要进一步确认是防火墙拦截还是服务本身未启动。这一步的数据要记录下来,方便加固完成后做前后对比。顺便再用rpcinfo -p <目标服务器IP>看一下当前对外暴露的 RPC 服务全貌。它会把 rpcbind 注册的所有程序端口列出来,mounted、nfs、rquotad、lockd 都会在这里现身。这个命令输出的端口范围,就是你写防火墙规则的“标靶范围”。3.2 第二步:把 mountd 从“随机端口”变成“固定端口”随机端口是运维的噩梦,因为你无法在防火墙上精准地开一条规则。所以要做的第一件事,就是给 mountd 固定一个端口。在 Red Hat 系系统上,编辑/etc/sysconfig/nfs(老版本)或/etc/nfs.conf(新版本),找到或者新增以下配置项:MOUNTD_PORT=30001 LOCKD_TCPPORT=30002 LOCKD_UDPPORT=30002 RQUOTAD_PORT=30003这里我选30001到30003作为示例,你可以根据自己的网段规划换成不容易被扫描猜测的高位端口。关键是这几个端口必须避开系统已占用端口,并且不能落在常见端口扫描工具的默认范围内。设置完成后,重启相关服务:systemctl restart rpcbind systemctl restart nfs-server这里有个顺序问题必须注意:先重启 rpcbind 让新的端口映射关系被注册,再重启 nfs-server,因为 nfs-server 的启动过程会调用 rpc.mountd,需要确保 rpcbind 已经准备好了。如果反过来,mountd 虽然起来了,但端口映射可能没被正确记录,后续客户端挂载会出现奇怪的超时。重启之后,再用rpcinfo -p查看一次,确认 mountd 对应的端口变成了30001:rpcinfo -p <目标服务器IP>看到类似这样的输出就说明固定成功:100005 1 tcp 30001 mountd 100005 2 tcp 30001 mountd 100005 3 tcp 30001 mountd3.3 第三步:用防火墙把 NFS 相关端口限定到可信网段端口固定之后,接下来的规则就好写了。在 Red Hat 系系统上,我一般用 firewalld 的 rich rule 来做精细控制,因为它比直接改 zone 的 service 更灵活,能精确匹配 source 和端口。假设这台 NFS 服务器只允许192.168.10.0/24访问,其他网段一律不允许访问 111、2049、30001 到 30003 端口,那么可以这样执行:firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port protocol="tcp" port="111" accept' firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port protocol="udp" port="111" accept' firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port protocol="tcp" port="2049" accept' firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port protocol="udp" port="2049" accept' firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port protocol="tcp" port="30001-30003" accept' firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port protocol="udp" port="30001-30003" accept' firewall-cmd --reload这段配置里我故意只写了“允许可信网段”的规则,没有写拒绝规则。只要默认 zone 的策略不是 ACCEPT ALL,firewalld 会对未匹配到的流量做拒绝处理。如果你不清楚当前默认策略,可以用下面命令查:firewall-cmd --get-default-zone firewall-cmd --zone=public --query-target=default如果默认 zone 的 target 是default,那么未命中的流量会被按 zone 默认策略处理;如果是ACCEPT,则需要在最后补一条 drop 规则。我在实际环境里遇到过 zone 配置混乱导致规则“白加了”的情况,必须把这条养成习惯——写规则前先确认 base 策略。如果用的是 nftables 或系统管理要求不能用 firewalld,在底层做等价限制也很直接,例如:nft add rule inet filter input ip saddr 192.168.10.0/24 tcp dport { 111, 2049, 30001-30003 } accept nft add rule inet filter input ip saddr 192.168.10.0/24 udp dport { 111, 2049, 30001-30003 } accept nft add rule inet filter input tcp dport { 111, 2049, 30001-30003 } drop nft add rule inet filter input udp dport { 111, 2049, 30001-30003 } drop3.4 第四步:在 mountd 层加一道访问控制防火墙属于网络层防护,如果想把安全纵深再往前推一步,可以在 mountd 进程自身增加来源限制。老版本系统上,rpc.mountd 通常会链接 libwrap,也就是支持 tcp_wrappers 规则,可以在/etc/hosts.allow和/etc/hosts.deny里控制访问。首先要确认你的 rpc.mountd 是否支持:ldd /sbin/rpc.mountd | grep libwrap如果输出里有libwrap.so,那就说明支持。配置方式很简单,编辑/etc/hosts.deny:rpc.mountd: ALL再编辑/etc/hosts.allow,把真正需要挂载的网段或者客户端 IP 放进去:rpc.mountd: 192.168.10.0/24然后重启 nfs-server:systemctl restart nfs-server这样即使防火墙规则被绕过了,攻击者直接连到 30001 端口,mountd 也会根据来源地址拒绝。不过要特别提醒一点:现代版本的红帽系系统(尤其是 RHEL 9 之后)默认编译 nfs-utils 时可能不再启用 libwrap,ldd检查会发现没有输出。这时候不要硬配 hosts.allow,因为配置了也不生效,反而会给人造成“我已经加了访问控制”的假象。遇到这种情况,防火墙的精细化规则就是你最重要的一道防线。还有一个方案容易被忽略:在/etc/exports里把共享目录配置成只对确切目标主机开放,而不是用通配符*。虽然前面说过 exports 限制不影响showmount -e的枚举结果,但它能让后续“即使拿到导出列表也没法挂载”的概率大幅上升,相当于把信息泄露的危害给弱化了。3.5 第五步:针对 NFSv4 的补充配置如果你的业务允许,建议只保留 NFSv4,把 NFSv2 和 NFSv3 关掉。NFSv4 协议本身在负载均衡、端口使用上比旧版本更收敛,而且很多企业对旧协议本身就有合规要求。在配置文件/etc/nfs.conf中,可以找到 nfsd 段,加上参数:[nfsd] vers=4.0,4.1,4.2或者直接修改启动参数:RPCNFSDARGS="-N 2 -N 3"确保重启后服务不再监听 v2/v3 协议。这里要留个心眼:即使只开 NFSv4,rpcbind 和 rpc.mountd 进程依然会存在,前面防火墙里关于 111 与 30001 端口的限制依然要做,不能因为开了 v4 就觉得万事大吉。我曾经遇到过同事误以为 NFSv4-only 就不需要管 mountd 端口,结果安全扫描器照样扫到大片高危险端口。另外,在内网客户端比较多、且对密码劫持风险比较敏感的环境里,可以考虑启用 Kerberos 认证(sec=krb5p)。这是另一套相对复杂的体系,需要额外部署 Kerberos 服务,不建议刚接触 NFS 加固的人在短期内直接上手。先把网络层和服务层的基础访问控制做到位,是性价比最高的路径。3.6 顺带的审计与监控每次改完配置后,最好把当前状态留一份记录,后续巡检要有据可查。我习惯把以下几项写入日常脚本或安扫计划:rpcinfo -p:检查当前对外开放的 RPC 服务与端口列表;ss -lntnp | grep -E '111|2049|30001':看监听地址和端口是否符合预期;定期查看 NFS 相关日志,例如/var/log/messages里 mountd 的启动与拒绝记录;周期性模拟攻击者执行一次showmount -e做回归测试。这些动作看起来基础,但很多安全事故恰恰就隐藏在这种例行的“看一眼”里。4. 实施效果验证与踩坑记录4.1 验证脚本:从攻击者视角再测一遍加固做完,最关键的一步就是回到最开始的自查操作,确认问题真的消失了。我从外部非信任网段重新执行:showmount -e <目标服务器IP>此时预期结果是超时或者rpc: Rpcbind failure: Connection refused,说明防火墙把 rpcbind 以及 mountd 端口都挡在了外面。再从可信网段验证业务可用性:showmount -e <目标服务器IP>这一次应当正常返回导出列表,因为来自192.168.10.0/24的请求仍然被允许。如果条件允许,还可以直接用nc探测具体端口是否对非信任网段开放:nc -vz -w 3 <目标服务器IP> 30001返回Connection refused或者超时,说明最关键的 mountd 端口没有暴露。这几种验证方式合在一起,才算对整个链路做了覆盖。光测一个端口或者只看命令输出,都可能漏掉某条访问路径。4.2 常见问题与排查经验在实际操作中,我遇到过几个比较典型的坑,整理成速查表供参考:现象可能原因处理方法改了防火墙规则但 showmount 依然能查到firewalld 的默认 zone target 是 ACCEPT,或规则没 reload先查firewall-cmd --get-default-zone和 target,再确认 reload 是否成功固定端口后重启 nfs-server 失败端口被其他进程占用,或重启顺序不对`ss -lntnphosts.deny 配置后客户端挂载 NFS 失败tcp_wrappers 把挂载请求也当成了未授权来源检查 hosts.allow 是否包含正确客户端网段,并确认 rpc.mountd 真的支持 libwrapshowmount -e 被拒,但正常业务挂载也失败防火墙漏放了 2049 或 111 端口给客户端核对完整端口列表,不要只放 mountd 端口,2049 和 rpcbind 必须一起放行修改 exports 后客户端挂载的还是旧配置没有刷新导出表执行exportfs -ra或重启 nfs-server其中第一个坑最隐蔽,也最容易让人白忙一场。我遇到过一台机器明明按相同步骤配了 rich rule,外部依然能showmount -e,排查到最后发现它的默认 zone 被改成了trusted,一切都通了。所以我在前面反复提醒,先确认默认策略再写规则。还有一个常被忽略的点:修改/etc/exports后,一定要执行exportfs -ra让它真正生效。这个命令会重新解析导出表,同时也会触发 mountd 重新加载配置。如果没有这一步,就算你改得再正确,客户端挂载的依旧可能是旧规则。4.3 还有两个值得说的小细节第一个细节,是mount命令与 showmount 的关系。有些同事看到showmount -e被拒后,会下意识以为 NFS 服务全挂了,直接慌了。实际上,如果客户端使用mount -t nfs4 服务器IP:/共享目录 /本地目录,是可以绕过 rpcbind 查询直接连 2049 端口完成挂载的。所以 showmount 不通并不等于业务中断,这也是“限制 showmount 信息泄露”不同于“停掉 NFS 服务”的关键差异。第二个细节,是安全扫描器对 NFS 的判断逻辑。很多扫描器把showmount -e能枚举出导出目录当作一个明确的漏洞标记,无论你 exports 配置得有多严,它都会照报不误。所以如果你在等保或内部安全巡检里看到了类似“NFS 导出目录可枚举”的报告项,不要急着骂扫描器误报,先按这篇文章的操作走一遍,把实际风险面控制住,再跟审计那边解释清楚最终状态。我在配完这套方案之后,又用showmount -e从外部扫了一遍,返回的是rpc: Rpcbind failure: Connection refused。对一个二十多年前就暴露的老漏洞来说,这算是比较体面的结局。后来我在另一个项目里遇到一台只改了 exports 权限、却没管 mountd 访问控制的机器,内部安全演练一上来就又把它列了出来,可见这个坑有多容易被忽略。如果你手头也有 NFS 服务,建议把showmount -e这条链路完整走一遍:先封端口,再限来源,最后用攻击者视角常态化验证。别让一个老问题,成为你安全报告里的常客。
在给一批NFS服务器做安全体检时,我用showmount -e扫了一遍内网,结果让我后背发凉——几乎一半的机器把完整导出列表敞开给了任何能摸到端口的人。这不是个新问题,它的编号叫 CVE-1999-0554,二十多年前就被登记在案,但到今天依然有大把管理员忽略它。如果你手头也在维护 NFS 服务,或者只是负责运维但没仔细抠过这一层,我建议你花点时间看看这篇文章。它不分析某一次具体攻击,而是把问题链路拆开,告诉你为什么showmount -e会泄露信息,以及我实际在服务器上做的加固步骤和验证过程。1. 先从漏洞本身说起:showmount -e 到底泄露了什么1.1 CVE-1999-0554 的来龙去脉CVE-1999-0554 对应的是 NFS 服务默认对 MOUNT 协议查询不加限制,导致的导出信息泄露问题。早期 NFS 设计时默认网络环境是可信内网,协议层面几乎没有身份验证概念,rpcbind 和 mountd 把“任何能连到端口的调用者”都当作合法请求来源来看待。showmount -e恰恰就是利用这种信任关系,向目标服务器的 mountd 服务发起一次导出列表查询,然后把结果直接打印出来。这个漏洞之所以到今天还在被各类安全工具反复探测,是因为它不像缓冲区溢出那样需要构造复杂数据包,一个默认安装的 nfs-utils 客户端工具就能完成侦察。攻击者不需要任何凭据,只要网络层能到达目标,就能在几秒内拿到服务器的 NFS 导出清单,获知哪些目录被共享、共享给了哪些网段或主机。这类信息对后续的定向攻击来说,相当于提前拿到了一张家底清单。这里要注意,千万别把 CVE-1999-0554 和常见的“NFS 弱权限导致匿名挂载”混为一谈。前者是信息泄露,后者通常是/etc/exports配置不当导致的未授权访问。信息泄露本身不直接让你拿到文件内容,但它是攻击链里非常关键的“踩点”环节,很多自动化扫描器会把它当作 NFS 服务暴露的标记。1.2 从攻击者视角看到的信息有多“值钱”我在一次内部演练中,用一个干净的虚拟机模拟攻击者,对目标执行了下面的命令:showmount -e 192.168.20.15返回结果非常直白:Export list for 192.168.20.15: /data/backup 172.16.0.0/16 /home/share 192.168.20.0/24 /opt/app 10.10.10.5这三行信息能说明什么?目标机器上有/data/backup、/home/share、/opt/app三个目录被共享;/data/backup允许的客户端来自172.16.0.0/16,意味着这台机器很可能是备份服务器,或者至少参与了备份链路;/home/share限定在192.168.20.0/24,说明它在办公网或业务网内承担文件共享角色;/opt/app只允许10.10.10.5挂载,指向一个具体应用服务器。攻击者拿到这些信息后,会立刻把目标机器标记为“高价值存储节点”,然后围绕/data/backup或/opt/app这样的路径去猜应用结构。如果再结合端口扫描发现目标同时开着 Web 服务,就可能利用路径猜测、目录遍历或备份文件的已知命名规则进行下一步利用。很多管理员觉得“我 exports 里限制得很严,只有特定 IP 能挂载,所以无所谓”。这个想法我在踩坑里也犯过,后面会专门说。showmount -e泄露的只是“共享清单”,不是“谁能挂载的访问控制表”,攻击者即使无法挂载,也能从目录名推算业务用途和网络拓扑,这种情报价值在攻防场景里不容小觑。2. 理解 NFS 的 RPC 机制,才能对症下药2.1 查询链路:showmount 是如何拿到导出列表的要彻底解决这个问题,绕不开 NFS 底层的 RPC 调用机制。很多人只会敲命令,不知道这条链路到底经过哪几步,结果加固的时候漏掉环节。showmount -e的完整查询路径大致如下:客户端向目标主机的111端口发送请求,询问 rpcbind 服务:mountd 对应哪个端口;rpcbind 查询自身的端口映射表,返回 mountd 当前监听的端口号;客户端向 mountd 的端口发起MOUNT_PROC_EXPORT调用;mountd 读取/etc/exports解析后的导出列表,把结果返回给客户端。其中,第 1 步依赖 rpcbind,第 3 步依赖 rpc.mountd。rpcbind 默认监听 TCP/UDP 的 111 端口,mountd 端口则往往不固定。在老一些的系统上,mountd 会由一个随机高端口提供服务,一般落在 32767 附近,具体取决于系统启动时的分配情况。可以这样理解:rpcbind 像一个总机接线员,showmount 就像打总机电话说“我要找负责导出列表查询的那个分机”,总机告诉它分机号码,它再直接拨分机去问问题。整个过程没有身份核验、没有门禁,任何一个能打进总机的人都能顺藤摸瓜拿到分机上的答案。2.2 攻击面分析:NFS 链路里的每个环节都能被利用很多人在 NFS 加固时只想着把 111 端口封掉,觉得这样showmount -e就废了。但实际操作中,NFS 服务涉及的不只是 rpcbind 一个端口,完整链路包含多个组件:组件默认端口主要作用如果被利用rpcbind111RPC 服务端口注册与查询暴露 mountd、nfsd 等服务的端口位置nfsd2049提供文件读写如果目录权限配置不当,可能被直接挂载读写rpc.mountd随机高端口处理挂载请求、导出列表查询泄露/etc/exports共享列表rpc.lockd随机高端口管理文件锁可能被用于端口扫描与 RPC 枚举rpc.quotad随机高端口磁盘配额查询泄露用户配额信息这里的关键点是:如果只封了 111 端口,但攻击者通过其他手段已经知道 mountd 的固定端口,showmount -e是可以跳过第 1、2 步,直接向 mountd 端口发起查询的。我在测试环境里干过这件事,在服务端把 mountd 端口固定为 30001 后,客户端哪怕把 111 端口完全屏蔽,只要直接连 30001 端口,导出列表照样拉得出来。2.3 治标还是治本:不同方案对风险面的削减效果常见的加固手段,按效果从弱到强排一下:只修改/etc/exports里的网段限制:这个办法完全影响不了showmount -e,只要 mountd 可达,导出列表就会原样返回,属于典型的“治标都没治到”;用防火墙封掉 111 端口:能阻止 rpcbind 查询,但如果 mountd 端口没一起控制,或者 mountd 的端口是随机变化的,防火墙规则会很难写,封堵效果有限;固定 mountd 端口,同时用防火墙把 111、2049、mountd 端口限定到可信网段:这是我能稳定复现的“治本”方案,既保证业务可正常挂载,又让外部网段拿不到任何信息;在 mountd 层增加 tcp_wrappers 或底层访问控制:相当于在服务进程自身加了一层门禁,即使防火墙规则被绕过或端口被直接访问,mountd 也会根据来源地址拒绝响应。从投入产出比来看,固定端口加防火墙限定是最稳妥也最容易维护的做法。tcp_wrappers 在新版本系统上支持情况不一,得先做检查,我后面会给出检测命令和配置方法。3. 我实际采用的加固步骤(以 Red Hat 系系统为例)3.1 第一步:先自查,把风险看得明明白白动手加固前,先把现状摸清楚。我在一套 Rocky Linux 9 环境上做过完整复现,下面的操作同样适用于 CentOS 7/8、AlmaLinux、部分 Ubuntu 场景。先确认服务状态:systemctl status nfs-server然后找一台和该服务器不在同一个信任网段的机器,模拟攻击者视角执行:showmount -e <目标服务器IP>如果返回了导出列表,说明风险确实存在;如果没有返回,也可能是 rpcbind 访问受限,需要进一步确认是防火墙拦截还是服务本身未启动。这一步的数据要记录下来,方便加固完成后做前后对比。顺便再用rpcinfo -p <目标服务器IP>看一下当前对外暴露的 RPC 服务全貌。它会把 rpcbind 注册的所有程序端口列出来,mounted、nfs、rquotad、lockd 都会在这里现身。这个命令输出的端口范围,就是你写防火墙规则的“标靶范围”。3.2 第二步:把 mountd 从“随机端口”变成“固定端口”随机端口是运维的噩梦,因为你无法在防火墙上精准地开一条规则。所以要做的第一件事,就是给 mountd 固定一个端口。在 Red Hat 系系统上,编辑/etc/sysconfig/nfs(老版本)或/etc/nfs.conf(新版本),找到或者新增以下配置项:MOUNTD_PORT=30001 LOCKD_TCPPORT=30002 LOCKD_UDPPORT=30002 RQUOTAD_PORT=30003这里我选30001到30003作为示例,你可以根据自己的网段规划换成不容易被扫描猜测的高位端口。关键是这几个端口必须避开系统已占用端口,并且不能落在常见端口扫描工具的默认范围内。设置完成后,重启相关服务:systemctl restart rpcbind systemctl restart nfs-server这里有个顺序问题必须注意:先重启 rpcbind 让新的端口映射关系被注册,再重启 nfs-server,因为 nfs-server 的启动过程会调用 rpc.mountd,需要确保 rpcbind 已经准备好了。如果反过来,mountd 虽然起来了,但端口映射可能没被正确记录,后续客户端挂载会出现奇怪的超时。重启之后,再用rpcinfo -p查看一次,确认 mountd 对应的端口变成了30001:rpcinfo -p <目标服务器IP>看到类似这样的输出就说明固定成功:100005 1 tcp 30001 mountd 100005 2 tcp 30001 mountd 100005 3 tcp 30001 mountd3.3 第三步:用防火墙把 NFS 相关端口限定到可信网段端口固定之后,接下来的规则就好写了。在 Red Hat 系系统上,我一般用 firewalld 的 rich rule 来做精细控制,因为它比直接改 zone 的 service 更灵活,能精确匹配 source 和端口。假设这台 NFS 服务器只允许192.168.10.0/24访问,其他网段一律不允许访问 111、2049、30001 到 30003 端口,那么可以这样执行:firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port protocol="tcp" port="111" accept' firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port protocol="udp" port="111" accept' firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port protocol="tcp" port="2049" accept' firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port protocol="udp" port="2049" accept' firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port protocol="tcp" port="30001-30003" accept' firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port protocol="udp" port="30001-30003" accept' firewall-cmd --reload这段配置里我故意只写了“允许可信网段”的规则,没有写拒绝规则。只要默认 zone 的策略不是 ACCEPT ALL,firewalld 会对未匹配到的流量做拒绝处理。如果你不清楚当前默认策略,可以用下面命令查:firewall-cmd --get-default-zone firewall-cmd --zone=public --query-target=default如果默认 zone 的 target 是default,那么未命中的流量会被按 zone 默认策略处理;如果是ACCEPT,则需要在最后补一条 drop 规则。我在实际环境里遇到过 zone 配置混乱导致规则“白加了”的情况,必须把这条养成习惯——写规则前先确认 base 策略。如果用的是 nftables 或系统管理要求不能用 firewalld,在底层做等价限制也很直接,例如:nft add rule inet filter input ip saddr 192.168.10.0/24 tcp dport { 111, 2049, 30001-30003 } accept nft add rule inet filter input ip saddr 192.168.10.0/24 udp dport { 111, 2049, 30001-30003 } accept nft add rule inet filter input tcp dport { 111, 2049, 30001-30003 } drop nft add rule inet filter input udp dport { 111, 2049, 30001-30003 } drop3.4 第四步:在 mountd 层加一道访问控制防火墙属于网络层防护,如果想把安全纵深再往前推一步,可以在 mountd 进程自身增加来源限制。老版本系统上,rpc.mountd 通常会链接 libwrap,也就是支持 tcp_wrappers 规则,可以在/etc/hosts.allow和/etc/hosts.deny里控制访问。首先要确认你的 rpc.mountd 是否支持:ldd /sbin/rpc.mountd | grep libwrap如果输出里有libwrap.so,那就说明支持。配置方式很简单,编辑/etc/hosts.deny:rpc.mountd: ALL再编辑/etc/hosts.allow,把真正需要挂载的网段或者客户端 IP 放进去:rpc.mountd: 192.168.10.0/24然后重启 nfs-server:systemctl restart nfs-server这样即使防火墙规则被绕过了,攻击者直接连到 30001 端口,mountd 也会根据来源地址拒绝。不过要特别提醒一点:现代版本的红帽系系统(尤其是 RHEL 9 之后)默认编译 nfs-utils 时可能不再启用 libwrap,ldd检查会发现没有输出。这时候不要硬配 hosts.allow,因为配置了也不生效,反而会给人造成“我已经加了访问控制”的假象。遇到这种情况,防火墙的精细化规则就是你最重要的一道防线。还有一个方案容易被忽略:在/etc/exports里把共享目录配置成只对确切目标主机开放,而不是用通配符*。虽然前面说过 exports 限制不影响showmount -e的枚举结果,但它能让后续“即使拿到导出列表也没法挂载”的概率大幅上升,相当于把信息泄露的危害给弱化了。3.5 第五步:针对 NFSv4 的补充配置如果你的业务允许,建议只保留 NFSv4,把 NFSv2 和 NFSv3 关掉。NFSv4 协议本身在负载均衡、端口使用上比旧版本更收敛,而且很多企业对旧协议本身就有合规要求。在配置文件/etc/nfs.conf中,可以找到 nfsd 段,加上参数:[nfsd] vers=4.0,4.1,4.2或者直接修改启动参数:RPCNFSDARGS="-N 2 -N 3"确保重启后服务不再监听 v2/v3 协议。这里要留个心眼:即使只开 NFSv4,rpcbind 和 rpc.mountd 进程依然会存在,前面防火墙里关于 111 与 30001 端口的限制依然要做,不能因为开了 v4 就觉得万事大吉。我曾经遇到过同事误以为 NFSv4-only 就不需要管 mountd 端口,结果安全扫描器照样扫到大片高危险端口。另外,在内网客户端比较多、且对密码劫持风险比较敏感的环境里,可以考虑启用 Kerberos 认证(sec=krb5p)。这是另一套相对复杂的体系,需要额外部署 Kerberos 服务,不建议刚接触 NFS 加固的人在短期内直接上手。先把网络层和服务层的基础访问控制做到位,是性价比最高的路径。3.6 顺带的审计与监控每次改完配置后,最好把当前状态留一份记录,后续巡检要有据可查。我习惯把以下几项写入日常脚本或安扫计划:rpcinfo -p:检查当前对外开放的 RPC 服务与端口列表;ss -lntnp | grep -E '111|2049|30001':看监听地址和端口是否符合预期;定期查看 NFS 相关日志,例如/var/log/messages里 mountd 的启动与拒绝记录;周期性模拟攻击者执行一次showmount -e做回归测试。这些动作看起来基础,但很多安全事故恰恰就隐藏在这种例行的“看一眼”里。4. 实施效果验证与踩坑记录4.1 验证脚本:从攻击者视角再测一遍加固做完,最关键的一步就是回到最开始的自查操作,确认问题真的消失了。我从外部非信任网段重新执行:showmount -e <目标服务器IP>此时预期结果是超时或者rpc: Rpcbind failure: Connection refused,说明防火墙把 rpcbind 以及 mountd 端口都挡在了外面。再从可信网段验证业务可用性:showmount -e <目标服务器IP>这一次应当正常返回导出列表,因为来自192.168.10.0/24的请求仍然被允许。如果条件允许,还可以直接用nc探测具体端口是否对非信任网段开放:nc -vz -w 3 <目标服务器IP> 30001返回Connection refused或者超时,说明最关键的 mountd 端口没有暴露。这几种验证方式合在一起,才算对整个链路做了覆盖。光测一个端口或者只看命令输出,都可能漏掉某条访问路径。4.2 常见问题与排查经验在实际操作中,我遇到过几个比较典型的坑,整理成速查表供参考:现象可能原因处理方法改了防火墙规则但 showmount 依然能查到firewalld 的默认 zone target 是 ACCEPT,或规则没 reload先查firewall-cmd --get-default-zone和 target,再确认 reload 是否成功固定端口后重启 nfs-server 失败端口被其他进程占用,或重启顺序不对`ss -lntnphosts.deny 配置后客户端挂载 NFS 失败tcp_wrappers 把挂载请求也当成了未授权来源检查 hosts.allow 是否包含正确客户端网段,并确认 rpc.mountd 真的支持 libwrapshowmount -e 被拒,但正常业务挂载也失败防火墙漏放了 2049 或 111 端口给客户端核对完整端口列表,不要只放 mountd 端口,2049 和 rpcbind 必须一起放行修改 exports 后客户端挂载的还是旧配置没有刷新导出表执行exportfs -ra或重启 nfs-server其中第一个坑最隐蔽,也最容易让人白忙一场。我遇到过一台机器明明按相同步骤配了 rich rule,外部依然能showmount -e,排查到最后发现它的默认 zone 被改成了trusted,一切都通了。所以我在前面反复提醒,先确认默认策略再写规则。还有一个常被忽略的点:修改/etc/exports后,一定要执行exportfs -ra让它真正生效。这个命令会重新解析导出表,同时也会触发 mountd 重新加载配置。如果没有这一步,就算你改得再正确,客户端挂载的依旧可能是旧规则。4.3 还有两个值得说的小细节第一个细节,是mount命令与 showmount 的关系。有些同事看到showmount -e被拒后,会下意识以为 NFS 服务全挂了,直接慌了。实际上,如果客户端使用mount -t nfs4 服务器IP:/共享目录 /本地目录,是可以绕过 rpcbind 查询直接连 2049 端口完成挂载的。所以 showmount 不通并不等于业务中断,这也是“限制 showmount 信息泄露”不同于“停掉 NFS 服务”的关键差异。第二个细节,是安全扫描器对 NFS 的判断逻辑。很多扫描器把showmount -e能枚举出导出目录当作一个明确的漏洞标记,无论你 exports 配置得有多严,它都会照报不误。所以如果你在等保或内部安全巡检里看到了类似“NFS 导出目录可枚举”的报告项,不要急着骂扫描器误报,先按这篇文章的操作走一遍,把实际风险面控制住,再跟审计那边解释清楚最终状态。我在配完这套方案之后,又用showmount -e从外部扫了一遍,返回的是rpc: Rpcbind failure: Connection refused。对一个二十多年前就暴露的老漏洞来说,这算是比较体面的结局。后来我在另一个项目里遇到一台只改了 exports 权限、却没管 mountd 访问控制的机器,内部安全演练一上来就又把它列了出来,可见这个坑有多容易被忽略。如果你手头也有 NFS 服务,建议把showmount -e这条链路完整走一遍:先封端口,再限来源,最后用攻击者视角常态化验证。别让一个老问题,成为你安全报告里的常客。