适用场景
NFS 服务安全加固是内网文件共享场景的基础工作:数据库备份盘、日志归档目录、Kubernetes 的 NFS StorageClass、媒体文件共享都会用到 NFS。由于 NFS 依赖 RPC 端口且默认信任客户端 IP,一旦 exports 配置过宽或防火墙未收敛,内网任意主机都能挂载共享目录并读写数据,甚至通过 UID 映射获得 root 级别的文件权限。本文适用于需要自查和加固 Linux 文件共享服务、并希望用可复制配置完成收敛的运维工程师。
出现以下任一情况,应优先执行本文的加固流程:showmount -e 从非授权主机能列出共享目录;exports 中使用 (rw,no_root_squash) 或通配网段;2049、111、20048 等端口对办公网段或公网开放;共享目录中存在网站根目录、数据库目录或 SSH 密钥。
前置条件
- 服务端为 CentOS/RHEL 8+(nfs-utils)或 Ubuntu/Debian(nfs-kernel-server),具备 root 权限
- 已规划受信任客户端网段,例如 10.20.30.0/24,业务侧不使用动态 IP
- 服务端与客户端时区、UID/GID 规划一致;如需强认证,内网已有 Kerberos KDC
- 加固前记录现有挂载关系,避免切断业务写入路径
原理说明
NFS 的安全模型建立在”客户端 IP 可信 + UID 一致”两个假设上,这决定了它的风险点。第一,RPC 服务天然分散:NFSv3 依赖 rpcbind(111/tcp、111/udp)、rpc.mountd(默认动态端口)与 rpc.statd,端口漂移会让防火墙策略难以收口,也容易被扫描器定位。第二,UID 映射是权限的核心:默认的 root_squash 会把客户端的 root(UID 0)映射为匿名用户,从而阻止其以 root 身份写入共享目录;一旦改为 no_root_squash,客户端 root 即可在共享目录内创建 SUID 程序,配合客户端其他漏洞完成提权。第三,鉴权只到 IP 粒度:exports 中的 IP 或网段即全部授权范围,同一网段内任意主机均可挂载,无额外身份校验。
因此加固的四个方向是明确的:把端口固定下来以便防火墙收口、把授权范围收缩到具体主机或最小网段、把 UID 映射设为匿名并叠加 noexec/nosuid 挂载选项、对高敏场景引入 Kerberos 的 sec=krb5p 强认证与加密。
操作步骤
1. 固定 RPC 端口,为防火墙收口创造条件
在服务端编辑 /etc/nfs.conf,为 mountd、statd 等组件指定固定端口,服务重启后不再漂移。
cat >> /etc/nfs.conf <<'EOF'
[mountd]
port=20048
[statd]
port=32765
outgoing-port=32766
[lockd]
port=32767
udp-port=32767
EOF
# 仅保留 NFSv4(单端口 2049),彻底停用 v3 与 rpcbind 暴露
sed -i 's/^#\?vers3=.*/vers3=n/' /etc/nfs.conf
grep -q '^vers3=' /etc/nfs.conf || echo 'vers3=n' >> /etc/nfs.conf
systemctl restart nfs-server rpcbind
rpcinfo -p localhost
若业务必须保留 NFSv3,则至少要保证三件事:上述端口固定生效、rpcbind 只监听内网地址、防火墙对 111 端口做来源限制。
2. 重写 exports,按最小权限授权
替换 /etc/exports,把通配网段改为具体主机或最小网段,并强制匿名映射。
cp /etc/exports /etc/exports.bak.$(date +%F)
cat > /etc/exports <<'EOF'
# 语法:共享目录 授权客户端(选项)
/data/backup 10.20.30.11(rw,sync,no_subtree_check,root_squash,all_squash,anonuid=65534,anongid=65534)
/data/media 10.20.30.0/29(ro,sync,no_subtree_check,root_squash)
/srv/k8s-pv 10.20.30.21(rw,sync,no_subtree_check,root_squash,all_squash,anonuid=100000,anongid=100000)
EOF
exportfs -ra
exportfs -v
关键选项含义:rw/ro 控制读写权限;sync 保证写入落盘后再返回,避免异常断电丢数据;no_subtree_check 关闭子树检查,提升性能并减少文件名不一致问题;root_squash 把客户端 root 映射为匿名;all_squash 更进一步,把全部客户端用户统一映射为匿名,适用于只按目录共享、不区分用户的场景;anonuid/anongid 指定匿名映射后的实际 UID/GID,需与目录属主一致,否则会出现写入被拒。
绝对不要在生产 exports 中出现的写法:*(rw,no_root_squash)、0.0.0.0/0(rw)、/data(rw,no_root_squash)(不带客户端限制)。这三种写法等同于把文件系统交给任意可达主机。
3. 目录权限与文件系统加固
# 目录属主与匿名 UID 对齐,权限按需最小化
mkdir -p /data/backup /data/media
chown -R 65534:65534 /data/backup
chmod 0750 /data/backup
chmod 0755 /data/media
# 独立分区挂载时叠加 noexec/nosuid/nodev,阻止通过共享目录执行程序
# /etc/fstab 示例:
# /dev/mapper/vg-data /data ext4 defaults,noexec,nosuid,nodev 0 2
mount -o remount,noexec,nosuid,nodev /data
noexec 与 nosuid 是纵深防御的关键:即便攻击者通过其他途径获得了共享目录写权限,也无法在这里投放可执行文件或 SUID 程序完成提权。
4. 防火墙只放行必要来源
# firewalld:仅允许受信任网段访问 NFS 相关端口
firewall-cmd --permanent --zone=trusted --add-source=10.20.30.0/24
for p in 2049/tcp 20048/tcp 32765/tcp 32767/tcp; do
firewall-cmd --permanent --zone=trusted --add-port=$p
done
firewall-cmd --permanent --zone=public --remove-service=nfs 2>/dev/null
firewall-cmd --permanent --zone=public --remove-service=mountd 2>/dev/null
firewall-cmd --permanent --zone=public --remove-service=rpc-bind 2>/dev/null
firewall-cmd --reload
firewall-cmd --list-all --zone=trusted
使用 nftables 的环境,等价思路是:drop 所有 111/2049 入向流量,只为受信任网段单独放行。核心原则是”默认拒绝、显式放行”,不要图省事把 NFS 服务挂到 public 区域。
5. 高敏场景启用 Kerberos 强认证
若共享目录涉及源码、财务或个人信息,仅靠 IP 授权不足,应引入 sec=krb5p(认证 + 完整性 + 加密):
# 服务端 /etc/exports 追加安全标签
echo '/data/secure 10.20.30.11(rw,sync,no_subtree_check,sec=krb5p)' >> /etc/exports
exportfs -ra
# 客户端挂载方式
mkdir -p /mnt/secure
mount -t nfs4 -o sec=krb5p,hard,noexec,nosuid 10.20.30.10:/data/secure /mnt/secure
mount | grep secure
sec=krb5p 需要服务端与客户端均已加入同一 Kerberos 域、且已签发 nfs 主机主体(nfs/host@REALM)。它同时解决两个问题:身份不再只靠 IP 判断,数据传输全程加密,抓包无法直接还原文件内容。
6. 监控与审计
# 定期快照当前挂载与授权状态,便于发现异常变化
showmount -e localhost > /var/log/nfs-exports-$(date +%F).txt
nfsstat -s | head -20
# 审计共享目录内的关键变更(需 auditd 已启用)
auditctl -w /data/backup -p wa -k nfs_backup_write
ausearch -k nfs_backup_write -ts today | tail -20
配置验证
- 授权范围:从非受信任主机执行
showmount -e 10.20.30.10,应超时或被拒绝。 - 端口收敛:
ss -lntp | grep -E '2049|20048|32765'确认监听地址仅为内网,且ss -lunp | grep 111不再对外。 - 匿名映射生效:在客户端以 root 身份在共享目录创建文件,服务端执行
ls -ln应看到属主为 65534 而非 0。 - 只读共享:对 /data/media 尝试写入,应返回 Read-only file system。
- 业务无损:客户端重新挂载后执行一次读写演练,确认备份任务与 K8s PV 正常。
常见问题
Q1:客户端挂载成功但无法写入,提示 Permission denied,如何排查?
按顺序核查四点:exports 中的 anonuid/anongid 是否与目录实际属主一致(ls -ld 对比 ls -ln);目录权限位是否给到该匿名 UID 写权限;是否误用了 ro 选项;服务端是否开启了 SELinux 且未给共享目录打标(setsebool -P nfs_export_all_rw on 并确认 semanage fcontext 已配置)。匿名映射场景下,权限判断依据是映射后的 UID,而不是客户端的用户名。
Q2:重启后挂载丢失或客户端报 stale file handle,怎么处理?
stale file handle 通常是因为共享目录在服务端被重建、inode 变化,或导出被临时删除后客户端仍持有旧句柄。处理方式是在客户端执行 umount -f /mnt/data 后重新挂载,并在 /etc/fstab 中加入自动挂载条目与 _netdev 选项,保证网络就绪后再挂载。若服务端使用了动态端口,客户端在做 failover 时还会因端口漂移失败,这就是必须把 mountd 端口固定的原因。
Q3:完全关闭 rpcbind 会不会影响现有业务?
只有当所有客户端都使用 NFSv4 时才可以安全停用 rpcbind——NFSv4 是单端口协议,不需要 rpcbind。若客户端仍以 NFSv3 挂载,停用后会立即出现 mount 失败。稳妥做法是先在测试环境验证客户端的挂载方式(mount | grep nfs 查看是否带 vers=3),确认全部为 v4 后再执行停用。
总结
NFS 加固的核心是把两个隐含假设补上:用固定端口加防火墙白名单替代”端口不确定”,用 root_squash/all_squash 加匿名 UID 对齐 替代”信任客户端 root”,再用独立分区上的 noexec/nosuid/nodev 做纵深防御。高敏数据改用 sec=krb5p,让授权从 IP 粒度升级到身份粒度并加密传输。最后把 showmount 快照与 auditd 审计纳入例行检查,才能在授权范围被意外放宽时第一时间发现。