在Ubuntu服务器的安全加固清单中,临时目录(通常是/tmp和/dev/shm)的保护往往被忽视。很多人以为只要设置了正确的文件权限(1777粘滞位)就万事大吉,但实际上,攻击者一旦通过Web漏洞或服务漏洞获得了低权限的Shell,临时目录就是他们发起进一步攻击的跳板。最直接的威胁就是下载和执行恶意脚本。要斩断这条攻击链,最有效的手段不是复杂的入侵检测系统,而是利用Linux内核的文件系统挂载选项:noexec。这个选项直接禁止在指定分区上执行任何二进制文件或脚本,从根源上废掉了临时目录的可执行能力。
noexec选项的底层逻辑要理解noexec为什么有效,需要先明白Linux内核如何执行一个文件。当你在Shell中输入./script.sh或./payload时,内核会检查文件的权限位中的执行位(x)。如果执行位被置位,内核才会尝试加载并运行它。而noexec挂载选项在内核的虚拟文件系统层(VFS)直接拦截了这一请求。即使文件权限是0777(所有人可读可写可执行),只要它所在的文件系统是以noexec挂载的,execve()系统调用就会返回“Permission denied”。这是一种强制性的、无法绕过的限制,除非攻击者有能力重新挂载分区(这需要root权限)。
这里有一个常见的误区需要澄清:noexec阻止的是直接执行二进制可执行文件(ELF文件)和带有Shebang(如#!/bin/bash)的脚本。但它并不阻止解释器直接调用脚本。例如,如果你执行/bin/bash /tmp/script.sh,这依然可以运行。因为此时内核执行的是/bin/bash,而/tmp/script.sh只是作为bash的一个参数被读取。尽管如此,noexec极大地增加了攻击的复杂度和成本。攻击者无法再直接运行编译好的提权工具,也无法直接执行.sh脚本,他们必须寻找系统上已有的解释器(如perl、python、php)来间接执行代码。如果这些解释器也被严格限制或未安装,攻击就会陷入僵局。
识别当前系统的临时目录挂载状态在动手修改之前,先诊断一下你的系统。运行mount命令或查看/proc/mounts,检查/tmp和/dev/shm的挂载选项。
mount | grep -E '/tmp|/dev/shm'
典型的输出可能是:
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noexec,relatime) tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev,noexec)
如果你看到括号里没有noexec,或者/tmp根本不是一个独立的分区(而是根分区/下的一个普通目录),那就意味着你的临时目录目前是可执行的,存在安全隐患。对于/dev/shm,它通常默认就是tmpfs,且很多发行版默认没有加noexec,这是一个更大的盲区,因为攻击者特别喜欢利用共享内存文件系统来存放和执行恶意代码,它的读写速度极快,且往往被监控工具忽略。
通过/etc/fstab实现永久挂载保护最稳妥的方式是通过/etc/fstab文件来固化挂载选项。这样每次系统重启,保护都会自动生效。编辑/etc/fstab需要root权限,使用你熟悉的编辑器打开它:
sudo vim /etc/fstab
如果你的/tmp目前不是独立挂载点,你需要为它创建一个tmpfs条目。tmpfs是基于内存的文件系统,非常适合存放临时文件,系统重启后内容自动清空。添加或修改以下行:
tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev 0 0
对于/dev/shm,它通常已经存在一行,你需要做的是在挂载选项字段中确保包含noexec。默认的条目可能像这样:
none /dev/shm tmpfs defaults 0 0
将其修改为:
none /dev/shm tmpfs defaults,noexec,nosuid,nodev 0 0
这里解释一下这三个关键挂载选项的组合拳:noexec禁止执行任何二进制文件;nosuid禁止SUID位生效,防止攻击者放入带SUID位的shell副本;nodev禁止解释设备文件或块文件,防止攻击者创建设备节点来直接访问磁盘。这三个选项通常一起使用,构建一个最小权限的临时文件系统。
修改完/etc/fstab后,不要直接重启服务器,尤其是在生产环境中。你可以先手动重新挂载来验证配置是否正确,避免因为拼写错误导致系统启动失败。
无需重启立即生效的重新挂载方法对于/tmp目录,如果它已经是独立的挂载点,直接使用mount命令的remount选项:
sudo mount -o remount,noexec,nosuid,nodev /tmp
如果/tmp原本不是独立挂载点,而是根目录下的一个普通文件夹,上述命令会失败。你需要先将其挂载为tmpfs。但要注意,挂载操作会暂时“遮盖”原有的/tmp目录内容。最佳实践是切换到单用户模式或确保没有服务正在使用/tmp中的文件,然后执行:
sudo mount -t tmpfs -o noexec,nosuid,nodev tmpfs /tmp
挂载完成后,务必恢复/tmp目录的权限:
sudo chmod 1777 /tmp
对于/dev/shm,操作类似:
sudo mount -o remount,noexec,nosuid,nodev /dev/shm
验证是否生效,可以做一个简单的测试。在/tmp下复制一个无害的二进制文件,比如/bin/ls,然后尝试执行它:
cp /bin/ls /tmp/ /tmp/ls
系统应该会提示“Permission denied”。测试完成后记得删除这个测试文件。这个测试证明了noexec已经成功拦截了二进制文件的执行。
处理某些软件对可执行临时目录的依赖在实际运维中,强行开启noexec可能会踩坑。部分软件,特别是一些打包工具、安装脚本或Java应用,会在运行时将原生库或临时可执行文件解压到临时目录并尝试执行。最典型的例子是某些版本的Java虚拟机(JVM)在启动时会将JIT编译器的共享库写入/tmp,或者一些安装程序(如NVIDIA驱动安装脚本)会在/tmp下编译内核模块。
遇到这种情况,不要立刻放弃noexec保护。更精细的解决方案是:为这些特定应用重新定向临时目录。绝大多数标准应用都遵循TMPDIR环境变量。你可以在启动这些应用的Systemd服务单元文件中,或者在其启动脚本里,设置环境变量:
export TMPDIR=/var/tmp
/var/tmp通常用于存放那些需要持久化、或者需要可执行权限的临时文件。你可以保持/tmp的严格限制,而将/var/tmp作为一个折中的区域,但也要注意/var/tmp同样应该被监控和定期清理。另一种高级玩法是使用内核的命名空间(namespaces)和绑定挂载(bind mount),为特定的进程创建独立的/tmp视图,但这超出了常规加固的范畴,属于容器化隔离的思路。
对于Java应用,还可以通过JVM参数来指定临时目录:
java -Djava.io.tmpdir=/var/tmp -jar your-app.jar
排查具体是哪个程序在尝试执行/tmp下的文件,可以使用auditd审计子系统。添加一条审计规则,监控所有对/tmp下文件的执行尝试:
sudo auditctl -a always,exit -F dir=/tmp -F perm=x -k tmp_exec_attempt
然后通过ausearch命令查看日志,就能精确定位到是哪个进程、哪个用户违反了规则。这为排错提供了确凿的证据,而不是盲目猜测。
进阶:结合systemd的挂载单元进行管理在现代的Ubuntu系统中,systemd不仅管理服务,也管理挂载点。直接编辑/etc/fstab依然是有效且传统的方法,但systemd会自动将fstab条目转换为挂载单元(.mount文件)。你可以通过systemctl list-units --type=mount来查看所有挂载单元。更精细的控制方式是直接创建systemd挂载单元覆盖文件。例如,为/tmp创建一个覆盖配置,确保即使fstab被误修改,noexec选项依然强制生效。
创建目录和覆盖文件:
sudo mkdir -p /etc/systemd/system/tmp.mount.d/ sudo vim /etc/systemd/system/tmp.mount.d/override.conf
在override.conf中写入:
[Mount] Options=noexec,nosuid,nodev
然后重新加载systemd配置并重启挂载单元:
sudo systemctl daemon-reload sudo systemctl restart tmp.mount
这种方法的好处是,它不直接修改/etc/fstab,降低了人为误操作的风险,并且配置管理工具(如Ansible、Puppet)更容易通过下发一个文件来统一管理所有服务器的挂载策略。对于/dev/shm,可以类似地操作dev-shm.mount单元。
noexec不是万能药:纵深防御的视角虽然noexec是临时目录防护的基石,但必须清醒地认识到它的局限性。如前所述,它无法防御通过解释器执行的脚本。如果系统上存在PHP、Python、Perl等解释器,攻击者依然可以运行/tmp/evil.php或者执行python /tmp/exploit.py。因此,noexec必须与其他安全措施配合使用。
首先,严格控制系统上安装的解释器。遵循最小安装原则,Web服务器上不要安装不必要的开发工具和脚本语言。如果必须安装,使用AppArmor或SELinux对Web应用进程进行强制访问控制(MAC),限制其能够调用的解释器路径。例如,为Nginx或Apache配置AppArmor策略,禁止它们执行/bin/bash、/usr/bin/python等,即使通过解释器间接调用也会被内核拦截。
其次,定期清理临时目录。noexec并不能阻止攻击者将恶意文件写入/tmp,它只是阻止执行。大量的恶意文件堆积可能被用于其他目的,比如通过文件包含漏洞来利用。使用systemd-tmpfiles机制可以自动清理临时目录。创建或编辑/etc/tmpfiles.d/tmp.conf:
# 清理/tmp下超过1天未修改的文件 D /tmp 1777 root root 1d
这行配置告诉systemd-tmpfiles,每天清理一次/tmp目录,删除那些修改时间超过1天的文件和目录。这能有效减少攻击者的驻留时间窗口。
最后,监控和告警是安全闭环的关键。除了前面提到的auditd,还可以使用文件完整性监控工具(如AIDE或Tripwire)监控/tmp目录下是否出现了异常的二进制文件或脚本。结合集中式日志分析平台,对“Permission denied”的审计日志设置告警,当短时间内出现大量来自/tmp的执行失败尝试时,这很可能是一次正在进行的入侵尝试。
Ubuntu系统挂载选项noexec保护临时目录,本质上是一种性价比极高的安全基线措施。它配置简单,对性能几乎无影响,却能有效阻断一大类攻击手法。在云原生和容器化盛行的今天,这一传统的主机加固技术依然在保护宿主机安全方面发挥着不可替代的作用。无论是裸金属服务器、虚拟机还是运行Docker的宿主机,锁定/tmp和/dev/shm的执行权限,都应该是系统初始化安全脚本中的必选动作。
