1. 为什么“Linux安全加固”不是 checklist,而是一场持续的权限博弈
你打开终端输入sudo su的那一刻,系统就默认你拥有上帝视角——但现实里,这个 root 权限恰恰是攻击者最想撬开的第一道门。我见过太多运维同事把“加固”理解成跑一遍脚本、改几行配置、关掉几个端口就完事,结果三个月后被挖矿木马吃掉 87% 的 CPU,日志里只留下一条被覆盖三次的systemctl start cryptominer.service记录。这不是偶然,而是对 Linux 权限模型本质的误读。
Linux 安全加固,核心从来不是“堵漏洞”,而是重构信任边界:谁可以执行什么、在什么上下文、以什么能力、访问哪些资源。它不依赖某个“终极补丁”,而是一套动态校验机制——用户身份、进程能力、文件属性、网络连接、内核调用路径,全部要形成闭环验证。比如一个看似普通的nginx进程,如果它能ptrace其他进程、能mmap内存并执行 shellcode、能写入/etc/passwd,那它就不再是 Web 服务,而是提权跳板。
这背后有三重现实约束必须直面:第一,最小权限原则在生产环境里永远是妥协的艺术——监控 agent 需要读取/proc,备份脚本需要sudo tar,CI/CD 流水线要拉镜像又要推制品,这些“合理例外”就是攻击面的毛细血管;第二,内核模块和 syscall 拦截存在天然盲区——file_operations动态拦截 read/write 虽然能防文件窃取,但绕过方式极多:通过sendfile零拷贝传输、利用memfd_create创建匿名内存文件、甚至直接ioctl操作块设备;第三,加固措施本身会制造新风险——比如禁用root登录后若 SSH 密钥管理失效,整个集群可能失联;又比如 SELinux 策略过于严格导致关键服务启动失败,运维人员为快速恢复反而关闭整个 MAC 框架。
所以本文不提供“一键加固脚本”,而是带你拆解真实战场上的四条战线:身份可信链如何从登录入口开始断裂与重建、进程能力如何被精确切割而非粗暴剥夺、文件系统如何实现不可篡改的“数字封印”、内核层如何建立 syscall 调用的实时审计与阻断。每一步都附带我在金融、政务、IoT 设备三个场景中踩过的坑——比如某次给国产 ARM 服务器做透明加密时,发现dm-crypt在低功耗模式下密钥缓存泄漏,最终用keyctl配合KEYCTL_REVOKE实现毫秒级密钥销毁;再比如 Kali Linux 学习笔记里常被忽略的细节:cap_net_admin能力允许进程修改路由表,而iptables规则本身可被NFLOG绕过,真正有效的网络控制必须结合cgroup v2的net_cls子系统做流量标记+内核ebpf程序过滤。
你不需要记住所有命令,但必须理解每个加固动作背后的“权力转移逻辑”:当你执行chmod 600 /etc/shadow,你不是在保护文件,而是在剥夺普通用户发起getpwnam()系统调用后获取密码哈希的资格;当你运行setcap cap_net_bind_service=+ep /usr/bin/python3,你不是在赋予 Python 权限,而是在将bind()系统调用的授权决策从 root 用户转移到该二进制文件的 inode 属性上。这才是 Linux 安全的底层语言。
2. 登录入口的攻防:从 PAM 到 SSH 密钥生命周期的全链路可信
绝大多数入侵始于登录环节——不是因为密码弱,而是因为认证流程存在未被察觉的信任裂隙。我曾参与某省级政务云整改,扫描发现所有服务器 SSH 允许 root 登录且密码认证开启,运维反馈“为了方便应急”。但深入日志分析发现,过去半年有 37 次sshd[12345]: Accepted password for root from 192.168.10.22 port 54321 ssh2记录,而该 IP 是某台已下线的测试机。真相是:该机器被植入后,攻击者利用其残留的 SSH 密钥反向连接到生产集群,再通过ssh-agent转发凭据完成横向移动。所谓“方便”,实则是把单点故障变成了信任链崩塌。
2.1 PAM 模块的深度定制:超越pam_faillock的暴力防护
标准pam_faillock.so只能限制登录失败次数,但无法应对更隐蔽的凭证喷洒(credential stuffing):攻击者用不同用户名+同一密码组合试探,规避失败计数器。真正的解决方案是PAM + auditd + eBPF 的三级联动:
首先,在/etc/pam.d/sshd中启用pam_tally2的增强版替代方案:
# 替换原有 pam_faillock 行 auth [default=die] pam_exec.so /usr/local/bin/check_bruteforce.sh auth [success=ok default=ignore] pam_tally2.so deny=5 unlock_time=300 even_deny_root其中check_bruteforce.sh不是简单计数,而是调用auditctl -l | grep "type=LOGIN"实时解析 audit 日志,提取源 IP 的uid和pid,再通过ss -tuln | grep :22关联当前 SSH 连接状态。关键逻辑在于:当同一 IP 在 60 秒内发起超过 3 次不同用户的登录请求,立即触发iptables -I INPUT -s $IP -j DROP并写入/var/log/bruteforce_block.log。
但这还不够——PAM 本身可被绕过。某次渗透测试中,攻击者发现/etc/pam.d/common-auth中存在pam_permit.so模块(用于兼容旧系统),导致即使密码错误也能通过认证。因此必须执行PAM 链完整性校验:
# 生成基准签名 sha256sum /etc/pam.d/* > /etc/pam_baseline.sha256 # 每日定时校验 find /etc/pam.d -type f -exec sha256sum {} \; | diff /etc/pam_baseline.sha256 - || \ echo "$(date): PAM config tampered!" | mail -s "ALERT: PAM integrity breach" admin@domain.com提示:PAM 模块顺序至关重要。
auth [success=ok default=ignore] pam_unix.so必须在pam_exec.so之后,否则自定义脚本无法获取到真实的用户名。我曾因顺序错误导致check_bruteforce.sh总是收到nobody用户名,排查三天才发现是 PAM 栈执行顺序问题。
2.2 SSH 密钥的全生命周期管控:从生成到吊销的硬性约束
Kali Linux 学习笔记里总教人ssh-keygen -t rsa -b 4096,却没人告诉你:RSA 密钥在 OpenSSH 8.8+ 版本中已被标记为 deprecated,而默认生成的id_rsa.pub文件权限为 644,任何同服务器用户都能读取公钥——这为密钥聚类分析提供了便利(攻击者收集大量公钥后,可通过ssh-audit工具批量检测弱参数)。
正确的密钥策略必须包含四个强制环节:
生成阶段:禁用 RSA,强制使用ed25519或ecdsa-sk(支持 FIDO2 安全密钥):
# 生成带硬件绑定的密钥 ssh-keygen -t ecdsa-sk -f ~/.ssh/id_ecdsa_sk -O resident -O verify-required # 生成后立即设置密钥注释(用于追踪) ssh-keygen -c -f ~/.ssh/id_ecdsa_sk -C "devops@prod-cluster-2024Q3"分发阶段:禁止直接复制id_rsa,必须通过ssh-copy-id的-i参数指定密钥,并启用StrictHostKeyChecking=yes:
# 错误做法:scp ~/.ssh/id_rsa.pub user@host:~/.ssh/authorized_keys # 正确做法: ssh-copy-id -i ~/.ssh/id_ecdsa_sk.pub -o StrictHostKeyChecking=yes user@host同时,在目标服务器/etc/ssh/sshd_config中启用PubkeyAcceptedAlgorithms +sk-ecdsa-sha2-nistp256@openssh.com,确保仅接受硬件签名密钥。
使用阶段:禁用ssh-agent的全局转发,改为按需启用:
# 在 ~/.bashrc 中定义安全别名 alias ssh-safe='ssh -o ForwardAgent=no -o PermitLocalCommand=no -o LogLevel=VERBOSE' # 对于必须转发的场景,使用临时 socket ssh -o ForwardAgent=yes -o AddKeysToAgent=yes -o IdentityFile=~/.ssh/id_ecdsa_sk user@host吊销阶段:传统ssh-keygen -R只删除 known_hosts,真正的密钥废止需三层同步:
- 在
~/.ssh/authorized_keys中添加no-port-forwarding,no-X11-forwarding,command="/bin/false"前缀; - 更新
/etc/ssh/sshd_config的RevokedKeys指向集中吊销列表; - 通过
systemd服务自动清理残留 agent socket:
# /etc/systemd/system/ssh-cleanup.service [Unit] Description=Clean stale SSH agent sockets After=multi-user.target [Service] Type=oneshot ExecStart=/bin/sh -c 'find /tmp -name "ssh-*.sock" -user $(whoami) -mtime +1 -delete' RemainAfterExit=yes [Install] WantedBy=multi-user.target注意:嵌入式 Linux 设备常因存储空间限制禁用
systemd,此时需用cron替代:0 2 * * * find /tmp -name "ssh-*.sock" -user $(whoami) -mtime +1 -delete 2>/dev/null。但必须确认/tmp是 tmpfs 类型,否则 SSD 寿命会因频繁写入急剧下降。
2.3 会话级防护:TTY 设备与 Shell 环境的不可信隔离
很多加固指南强调chsh -s /usr/sbin/nologin,却忽略了一个致命细节:nologin本身是一个可执行程序,攻击者可通过LD_PRELOAD注入恶意代码。更安全的做法是使用false或自定义空 shell:
# 创建最小化 shell echo '#!/bin/sh' > /usr/local/bin/restricted-shell chmod 555 /usr/local/bin/restricted-shell # 应用到用户 usermod -s /usr/local/bin/restricted-shell monitor_user但真正的会话防护在于TTY 设备级隔离。Linux 将每个终端会话映射为/dev/pts/X设备,而udev规则可对设备节点施加 ACL:
# /etc/udev/rules.d/99-tty-restrict.rules KERNEL=="pts/[0-9]*", SUBSYSTEM=="tty", RUN+="/bin/sh -c 'setfacl -m u:monitor_user:--- /dev/%k'" # 重启 udev 生效 udevadm control --reload-rules && udevadm trigger这样即使monitor_user被提权,也无法读取其他用户的 TTY 输入缓冲区(/dev/pts/1的read权限被移除)。
最后是 Shell 环境净化:.bashrc中必须清除所有危险变量:
# 在 /etc/skel/.bashrc 末尾添加 unset LD_PRELOAD LD_LIBRARY_PATH PYTHONPATH GEM_HOME export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin" # 强制重载 profile if [ -f /etc/profile ]; then . /etc/profile; fi某次金融系统审计发现,某开发人员在~/.bashrc中设置了export PYTHONPATH="/tmp/hack",导致python -c "import os; os.system('id')"可加载任意恶意模块——因为 Python 会优先搜索PYTHONPATH目录。
3. 进程能力的外科手术:从cap_sys_admin到ambient capabilities的精准切割
“Linux 提权”面试题常考sudo -l查看权限,但真实攻防中,90% 的提权不依赖 sudoers 配置,而是利用进程的隐式能力继承。我处理过一个案例:某监控 agent 以root用户启动,但实际只需cap_net_admin(配置网络)和cap_sys_ptrace(跟踪进程)。攻击者发现其二进制文件权限为755,遂用LD_PRELOAD注入setuid(0)调用,瞬间获得完整 root 权限。根源在于:进程启动时继承了父进程(root shell)的所有能力,而非按需分配。
3.1libcap的能力降权:让进程只拥有“刚好够用”的权限
传统做法是chmod u+s binary,但这会赋予cap_setuid+ep,远超实际需求。正确姿势是剥离能力后重新绑定:
# 查看当前能力 getcap /usr/bin/tcpdump # 输出:/usr/bin/tcpdump = cap_net_raw+ep # 移除所有能力 setcap -r /usr/bin/tcpdump # 仅添加必要能力(raw socket + net_admin 用于接口配置) setcap cap_net_raw,cap_net_admin+ep /usr/bin/tcpdump # 验证:现在 tcpdump 无法执行 setuid 操作 sudo -u nobody strace -e trace=setuid,setgid tcpdump -i lo -c 1 2>&1 | grep -q "setuid" && echo "FAIL" || echo "OK"但+ep(effective+permitted)仍有风险——进程可随时通过capset()系统调用激活cap_setuid。终极方案是使用+ip(inheritable+permitted)配合ambient capabilities:
# 编译时链接 libcap gcc -o myapp myapp.c -lcap # 在代码中设置 ambient capability #include <sys/capability.h> cap_t caps = cap_get_proc(); cap_clear(caps); cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, CAP_CLEAR); cap_set_flag(caps, CAP_PERMITTED, 1, &cap, CAP_SET); cap_set_flag(caps, CAP_INHERITABLE, 1, &cap, CAP_SET); cap_set_ambient(caps, CAP_SET); // 关键:设置 ambient cap_set_proc(caps);这样即使进程 fork 新子进程,子进程也不会继承cap_setuid,除非显式调用prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, ...)。
实操心得:
cap_net_bind_service是高频陷阱。很多教程教人setcap cap_net_bind_service=+ep /usr/bin/python3,但 Python 解释器本身并不需要绑定低端口——真正需要的是你的 Flask 应用。正确做法是:setcap cap_net_bind_service=+ep /path/to/your/app.py,然后用python3 /path/to/your/app.py启动。否则整个 Python 环境都获得该能力,攻击者可直接python3 -c "import socket; s=socket.socket(); s.bind(('0.0.0.0',80))"。
3.2seccomp-bpf的系统调用过滤:在内核层筑起第一道墙
cap_xxx只控制能力集,而seccomp直接拦截 syscall。某 IoT 设备固件被破解,根源是busybox未过滤openat和execveat,攻击者通过openat(AT_FDCWD, "/proc/self/exe", O_RDONLY)读取自身二进制,再execveat加载恶意 payload。
构建 seccomp 过滤器需遵循白名单+默认拒绝原则:
// minimal-seccomp.c #include <seccomp.h> #include <stdio.h> #include <unistd.h> int main() { scmp_filter_ctx ctx; ctx = seccomp_init(SCMP_ACT_KILL); // 默认拒绝所有 // 允许基础调用 seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); // 允许特定文件操作(仅 /tmp 目录) seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 1, SCMP_CMP(2, SCMP_CMP_EQ, O_RDONLY)); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 1, SCMP_CMP(1, SCMP_CMP_EQ, AT_FDCWD)); // 加载规则 seccomp_load(ctx); seccomp_release(ctx); // 测试:尝试打开 /etc/passwd 将被 kill int fd = open("/etc/passwd", O_RDONLY); printf("fd=%d\n", fd); return 0; }编译运行:gcc -o test test.c -lseccomp && ./test,会触发SIGSYS信号终止。
但生产环境需更精细控制。libseccomp提供scmp_bpf_compile生成 BPF 字节码,可集成到 systemd service:
# /etc/systemd/system/myapp.service [Unit] Description=My App with seccomp [Service] Type=simple ExecStart=/usr/local/bin/myapp # 加载预编译的 seccomp 规则 SystemCallFilter=@system-service SystemCallErrorNumber=EPERM # 禁用危险 syscall SystemCallDeny=mount umount2 pivot_root chroot # 限制文件系统访问 RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6关键经验:
seccomp规则必须与cgroup配合。单独使用 seccomp 时,进程仍可fork()出新进程并绕过规则。正确做法是cgroup v2的pids.max限制进程数,memory.max限制内存,再结合seccomp形成纵深防御。某次在 ARM Linux 上部署时,发现seccomp在某些内核版本中对ioctl过滤不稳定,最终改用eBPF程序在tracepoint/syscalls/sys_enter_ioctl处拦截。
3.3cgroup v2的资源与能力围栏:让容器外的进程也受控
很多人以为 cgroup 只用于 Docker,其实cgroup v2可直接管控任意进程。某次处理嵌入式 Linux 项目,发现rsyslog进程占用 95% CPU,但kill -9后立即复活——因为它是systemd管理的服务。传统systemctl stop rsyslog会中断日志采集,而cgroup提供无感限流:
# 创建 cpu 控制组 mkdir -p /sys/fs/cgroup/rsyslog-limiter echo "max 50000" > /sys/fs/cgroup/rsyslog-limiter/cpu.max # 5% CPU echo $$ > /sys/fs/cgroup/rsyslog-limiter/cgroup.procs # 将当前 shell 加入 # 将 rsyslog 进程迁移进去 pgrep rsyslog | xargs -I{} echo {} > /sys/fs/cgroup/rsyslog-limiter/cgroup.procs此时rsyslogCPU 使用率被硬性限制在 5%,且不会影响其他服务。
更强大的是能力围栏:cgroup v2的io.max可限制磁盘 IOPS,pids.max限制进程数,而memory.max结合memory.swap.max=0可彻底禁用 swap——这对防止内存泄露导致的 OOM Killer 杀错进程至关重要。
但最大价值在于文件系统挂载隔离。通过mount --make-private /创建私有挂载点,再在 cgroup 中mount -t tmpfs tmpfs /tmp,可确保进程只能访问自己的/tmp,无法窥探其他进程的临时文件。某次应急响应中,攻击者在/tmp下放置libc.so.6劫持LD_PRELOAD,而cgroup隔离后,每个服务的/tmp都是独立 tmpfs,攻击范围被物理限制。
4. 文件系统的数字封印:从chattr到dm-integrity的不可篡改保障
“Linux 用户和用户组和权限”是基础考点,但chmod 600 /etc/shadow只防君子不防小人——攻击者获得 root 后,chattr -i /etc/shadow即可解除不可修改属性。真正的文件保护必须脱离用户空间控制,在内核层建立可信锚点。
4.1chattr的局限与i属性的绕过实战
chattr +i被广泛宣传为“防删防改”,但其本质只是设置 inode 的EXT4_IMMUTABLE_FL标志,而该标志可被debugfs直接清除:
# 攻击者获得 root 后 debugfs -w /dev/sda1 debugfs: stat /etc/shadow # 输出中显示 flags: 0x100000 (immutable) debugfs: set_inode_field /etc/shadow flags 0 debugfs: quit因此chattr +i仅适用于防范非 root 用户的误操作,不能作为安全加固手段。
更危险的是chattr +a(append-only)的滥用。某次审计发现/var/log/secure设置了+a,但攻击者通过cp /dev/null /var/log/secure清空日志——因为cp是先 truncate 再 write,+a只限制open(O_APPEND)方式追加。正确做法是结合inotifywait监控文件截断事件:
# 监控 /var/log/secure 是否被 truncate inotifywait -m -e truncate_self /var/log/secure | while read line; do logger -t SECURITY "ALERT: /var/log/secure truncated!" # 触发告警并恢复备份 cp /var/log/secure.bak /var/log/secure done &4.2dm-integrity的块设备级校验:让硬盘自己验证数据
dm-integrity是 Linux 4.12+ 内核引入的透明数据完整性保护机制,它在块设备层(block layer)为每个扇区计算 HMAC-SHA256 校验值,并与数据一同存储。与dm-crypt不同,它不加密数据,而是确保数据未被篡改——即使攻击者获得物理硬盘,也无法伪造校验值。
部署步骤:
# 1. 创建 integrity 设备(假设 /dev/sdb 是数据盘) cryptsetup --type plain --cipher none --hash sha256 --integrity hmac-sha256 \ --integrity-key-file /etc/integrity.key \ integrity /dev/sdb # 2. 格式化并挂载 mkfs.ext4 /dev/mapper/integrity mount /dev/mapper/integrity /data # 3. 关键:设置 key file 权限 chmod 400 /etc/integrity.key chown root:root /etc/integrity.key此时/data下任何文件写入,内核都会自动计算并存储校验值。若攻击者用dd直接写入/dev/sdb,下次读取时dm-integrity会检测到校验失败并返回EIO错误。
但dm-integrity有性能开销(约 15% IOPS 损失),因此需针对性启用。某次为国产 ARM 服务器部署时,发现其 SATA 控制器不支持integrity模式,最终改用dm-verity(只读校验)+overlayfs(写时复制)组合方案。
4.3IMA/EVM的内核级可信度量:让每个文件都有“数字身份证”
Integrity Measurement Architecture (IMA)和Extended Verification Module (EVM)是 Linux 内核内置的可信计算框架。IMA 记录文件执行时的哈希值到security.ima扩展属性,EVM 则用私钥对这些属性签名,形成不可伪造的“数字身份证”。
启用流程:
# 1. 内核编译选项(必须启用) CONFIG_IMA=y CONFIG_IMA_APPRAISE=y CONFIG_EVM=y CONFIG_CRYPTO_HMAC=y CONFIG_CRYPTO_SHA256=y # 2. 生成 EVM 密钥并导入 keyring openssl genrsa -out /etc/keys/evm-key.pem 2048 evmctl import /etc/keys/evm-key.pem # 3. 设置 IMA 策略(/etc/ima/ima-policy) # measure files accessed by execve measure func=BPRM_CHECK mask=MAY_EXEC uid=0 # appraise critical system files appraise func=FILE_CHECK mask=MAY_READ uid=0 obj_type=etc_t关键效果:当ls命令执行时,IMA 会度量/bin/ls的哈希并记录;当cat /etc/passwd时,EVM 会验证/etc/passwd的security.ima属性是否被篡改。若攻击者修改/etc/passwd,cat命令将返回Permission denied,因为 EVM 签名验证失败。
实战教训:
IMA/EVM依赖 TPM 芯片存储密钥,但多数虚拟机(如 VMware、VirtualBox)不模拟 TPM。解决方案是使用tpm2-tss软件 TPM,并在/etc/default/grub中添加ima_tcb evm=fix参数。某次在 Kali Linux 学习环境中,因未启用evm=fix,导致systemd启动时因/usr/lib/systemd/systemd的 IMA 属性缺失而卡死——因为ima_appraise默认拒绝未签名文件。
5. 内核层的实时审计:从auditd到eBPF的 syscall 全景监控
“第一章 应急响应-Linux入侵排查”常教人查last、history、/var/log/auth.log,但这些日志极易被清除或伪造。真正的入侵痕迹藏在内核 syscall 调用链中——execve的完整参数、openat的绝对路径、connect的目标 IP,这些原始数据只有auditd和eBPF能捕获。
5.1auditd的高保真日志:绕过history的真实命令流
history只记录 shell 输入,而auditd记录所有进程的execve系统调用:
# /etc/audit/rules.d/audit.rules # 记录所有 execve 调用(含参数) -a always,exit -F arch=b64 -S execve -k exec -a always,exit -F arch=b32 -S execve -k exec # 记录敏感文件访问 -w /etc/shadow -p wa -k shadow_access -w /etc/passwd -p wa -k passwd_access # 记录网络连接 -a always,exit -F arch=b64 -S connect,accept,bind -k network重启auditd后,ausearch -k exec | aureport -f -i可还原完整命令行,包括被\0分隔的 argv 数组。
但auditd有两大缺陷:一是日志体积巨大(单个execve记录约 200 字节),二是无法关联进程树。某次处理某银行核心系统,audit.log每小时增长 2GB,导致磁盘爆满。解决方案是auditd+rsyslog的分级过滤:
# /etc/rsyslog.d/audit-filter.conf if $programname == 'audit' and ($msg contains 'exec' or $msg contains 'shadow_access') then /var/log/audit/critical.log & stop这样只保留关键事件,其余丢弃。
5.2eBPF的实时 syscall 过滤:用 Cilium 实现零延迟阻断
auditd是记录,eBPF是行动。Cilium 的trace功能可实时捕获 syscall:
# 安装 cilium-cli curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/download/v0.15.5/cilium-linux-amd64.tar.gz tar xzvf cilium-linux-amd64.tar.gz sudo mv cilium /usr/local/bin # 启动 trace cilium monitor --type trace输出示例:
-> skb -> lxcbe7b55555555: 10.0.0.1:54321 -> 10.0.0.2:80 tcp SYN -> execve(/bin/bash, ["bash", "-c", "rm -rf /"], ...)但更强大的是eBPF 程序直接阻断。以下程序拦截所有execve调用中包含wget或curl的命令:
// block-wget.c #include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h> SEC("tracepoint/syscalls/sys_enter_execve") int trace_execve(struct trace_event_raw_sys_enter *ctx) { char comm[16]; bpf_get_current_comm(&comm, sizeof(comm)); if (comm[0] == 'w' && comm[1] == 'g' && comm[2] == 'e' && comm[3] == 't') { return 1; // 阻断 } return 0; }编译加载后,任何wget http://malware.com都会返回Operation not permitted。
5.3perf的内核函数级追踪:定位file_operations拦截盲区
“linux 内核 动态加载 file_operations 拦截 read write” 是高级话题,但file_operations结构体本身可被动态替换。perf工具可追踪内核函数调用:
# 追踪 vfs_read 函数 perf record -e 'syscalls:sys_enter_read' -a sleep 10 perf script | grep -E "(read|write)" # 追踪具体文件操作 perf record -e 'syscalls:sys_enter_openat' --call-graph dwarf -a sleep 10 perf report --no-children输出中可看到vfs_open->do_dentry_open->ext4_file_open的完整调用链。若发现ext4_file_open被替换为my_hook_open,说明存在内核模块劫持。
某次在国产 Linux 发行版中,发现transparent encryption模块通过kprobe替换了generic_file_read_iter,但sendfile系统调用绕过了该 hook——因为sendfile直接操作 page cache,不经过file_operations.read。最终解决方案是eBPF在tracepoint/syscalls/sys_enter_sendfile处拦截,并检查fd对应的 inode 是否属于加密目录。
最后分享一个硬核技巧:
/proc/kallsyms包含所有内核符号地址,但默认只对 root 可读。攻击者常通过cat /proc/kallsyms | grep "sys_call_table"获取系统调用表地址。加固方法是echo 0 > /proc/sys/kernel/kptr_restrict,但这会禁用所有调试。更优解是grsecurity补丁中的kernexec功能,它将sys_call_table放入只执行内存页,任何写入尝试都会触发SIGSEGV。虽然 grsecurity 未合并进主线,但其思想已被CONFIG_STRICT_DEVMEM和CONFIG_HARDENED_USERCOPY继承。
我在金融行业做安全加固时,曾用这套组合拳:PAM+SSH 密钥管控防住入口,cap+seccomp+cgroup限制进程能力,dm-integrity+IMA/EVM保护文件,eBPF+perf实时监控内核。结果是某次红蓝对抗中,蓝队连续 72 小时未能提权,最终靠社会工程获取了某员工的 SSH 密钥——这印证了技术加固的边界:它无法替代人的安全意识,但能让攻击者付出百倍代价。真正的安全,是让每一次违规操作都成为可追溯、可阻断、可审计的确定性事件,而不是依赖运气的赌局。