第 12 章:系统加固
第 12 章:系统加固
高级系统加固技术:最小化安装、只读根、SecComp 和命名空间隔离。
12.1 最小化安装原则
最小化系统裁剪
# 1. 从最小 rootfs 开始
wget https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/alpine-minirootfs-3.20.3-x86_64.tar.gz
# 2. 解压到目标目录
mkdir -p /target/rootfs
tar -xzf alpine-minirootfs-3.20.3-x86_64.tar.gz -C /target/rootfs
# 3. chroot 进入
chroot /target/rootfs /bin/sh
# 4. 最小化安装
apk update
apk add --no-cache \
busybox \
musl \
alpine-baselayout \
alpine-keys \
apk-tools \
libc-utils
# 5. 清理不需要的文件
rm -rf /var/cache/apk/*
rm -rf /tmp/*
rm -rf /root/.ash_history
# 6. 查看最终大小
du -sh /target/rootfs
# 通常可压缩到 ~3MB
服务最小化
# 查看当前运行的服务
rc-status -a
# 禁用所有不需要的服务
for svc in $(rc-status default 2>/dev/null | awk '{print $1}'); do
case "$svc" in
sshd|networking|cron) ;; # 保留
*) rc-update del "$svc" default 2>/dev/null ;;
esac
done
# 最小化开机服务列表
# 只保留: networking, sshd, crond
包审计
# 列出所有已安装包
apk info | sort
# 查找不需要的包
# 移除开发工具(生产环境)
apk del gcc g++ make cmake
# 移除调试工具
apk del gdb strace ltrace
# 移除文档
apk del man-pages linux-docs
# 查看包的依赖关系
apk info -R nginx # nginx 的依赖
apk info -r nginx # 依赖 nginx 的包
12.2 只读根文件系统
配置 tmpfs 运行时
# /etc/fstab 只读配置
cat > /etc/fstab << 'EOF'
# 设备 挂载点 类型 选项 dump fsck
/dev/sda1 / ext4 ro,noatime,errors=remount-ro 0 1
/dev/sda2 /boot ext4 ro,noatime 0 2
tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev,size=2G 0 0
tmpfs /run tmpfs defaults,noexec,nosuid,nodev,mode=0755 0 0
tmpfs /var/log tmpfs defaults,noexec,nosuid,nodev,size=512M 0 0
tmpfs /var/tmp tmpfs defaults,noexec,nosuid,nodev,size=512M 0 0
EOF
# 为需要写入的目录创建绑定挂载
# /etc 中需要写入的文件
mkdir -p /overlay/etc
mount -t tmpfs tmpfs /overlay/etc
# 将需要写入的配置文件软链接到 tmpfs
使用 overlayfs 实现可写层
# overlayfs 脚本
cat > /usr/local/bin/overlay-mount << 'SCRIPT'
#!/bin/sh
# 使用 overlayfs 包装只读根
LOWER="/target/rootfs"
UPPER="/tmp/overlay-upper"
WORK="/tmp/overlay-work"
MERGED="/target/merged"
mkdir -p "$UPPER" "$WORK" "$MERGED"
mount -t overlay overlay \
-o lowerdir=$LOWER,upperdir=$UPPER,workdir=$WORK \
"$MERGED"
SCRIPT
chmod +x /usr/local/bin/overlay-mount
12.3 Seccomp 系统调用过滤
Seccomp 基础
# 检查内核 Seccomp 支持
grep -i seccomp /boot/config-$(uname -r) 2>/dev/null || \
zgrep -i seccomp /proc/config.gz 2>/dev/null
# 安装 seccomp 工具
apk add libseccomp libseccomp-dev
# 使用 seccomp-tools 分析程序的系统调用
apk add strace
strace -c ./myprogram
# 输出系统调用统计,用于确定允许列表
Seccomp 配置文件
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 1,
"architectures": [
"SCMP_ARCH_X86_64"
],
"syscalls": [
{
"names": [
"read", "write", "open", "close", "stat",
"fstat", "lstat", "poll", "lseek", "mmap",
"mprotect", "munmap", "brk", "rt_sigaction",
"rt_sigprocmask", "ioctl", "access", "pipe",
"select", "sched_yield", "mremap", "msync",
"mincore", "madvise", "dup", "dup2", "nanosleep",
"getpid", "socket", "connect", "accept",
"sendto", "recvfrom", "sendmsg", "recvmsg",
"bind", "listen", "getsockname", "getpeername",
"clone", "fork", "vfork", "execve", "exit",
"wait4", "kill", "uname", "fcntl", "flock",
"fsync", "fdatasync", "truncate", "ftruncate",
"getdents", "getcwd", "chdir", "mkdir", "rmdir",
"creat", "unlink", "readlink", "chmod", "chown",
"gettimeofday", "getuid", "getgid", "geteuid",
"getegid", "getppid", "getpgrp", "set_tid_address",
"exit_group", "epoll_wait", "epoll_ctl", "tgkill",
"openat", "mkdirat", "newfstatat", "unlinkat",
"set_robust_list", "get_robust_list", "epoll_create1",
"pipe2", "dup3", "epoll_create", "getrandom",
"statx", "rseq", "clone3", "close_range",
"readlinkat", "faccessat", "fchmodat", "fchownat"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
Docker 中使用 Seccomp
# 运行容器时指定 seccomp 配置
docker run --security-opt seccomp=seccomp-profile.json \
myapp:latest
# Docker 默认 seccomp 配置
# 禁止约 44 个危险系统调用
# 包括: reboot, mount, unmount, kexec_load 等
# 查看默认配置
docker info --format '{{.SecurityOptions}}'
12.4 Linux 命名空间隔离
命名空间类型
| 命名空间 | 隔离内容 | 系统调用标志 |
|---|---|---|
| PID | 进程 ID | CLONE_NEWPID |
| NET | 网络栈 | CLONE_NEWNET |
| MNT | 挂载点 | CLONE_NEWNS |
| UTS | 主机名 | CLONE_NEWUTS |
| IPC | 进程间通信 | CLONE_NEWIPC |
| USER | 用户/组 | CLONE_NEWUSER |
| CGROUP | cgroup 根目录 | CLONE_NEWCGROUP |
| TIME | 系统时钟 | CLONE_NEWTIME |
使用 unshare 创建命名空间
# 创建隔离环境
# PID + 网络 + 挂载 + 主机名隔离
unshare --pid --net --mount --uts --fork /bin/sh
# 在新命名空间中
hostname isolated-host
mount -t proc proc /proc
ps aux # 只能看到当前命名空间的进程
# 使用 nsenter 进入现有命名空间
# 获取目标进程的 PID
PID=$(docker inspect -f '{{.State.Pid}}' container_name)
# 进入该容器的命名空间
nsenter --target $PID --pid --net --mount /bin/sh
使用 bubblewrap 沙箱
# 安装 bubblewrap
apk add bubblewrap
# 创建沙箱环境
bwrap \
--ro-bind / / \
--dev /dev \
--proc /proc \
--tmpfs /tmp \
--bind /home/user/sandbox-home /home/user \
--unshare-net \
--unshare-pid \
--die-with-parent \
/bin/sh
# 程序沙箱化
bwrap \
--ro-bind /usr /usr \
--ro-bind /lib /lib \
--ro-bind /lib64 /lib64 \
--symlink usr/bin /bin \
--symlink usr/sbin /sbin \
--proc /proc \
--dev /dev \
--tmpfs /tmp \
--unshare-all \
--die-with-parent \
--as-pid-1 \
/usr/bin/myapp
12.5 cgroups 资源限制
cgroups v2 配置
# 检查 cgroups 版本
stat -fc %T /sys/fs/cgroup/
# cgroup2fs 表示 v2
# 创建 cgroup
mkdir /sys/fs/cgroup/mygroup
# 限制 CPU(最多使用 50% 单核)
echo "50000 100000" > /sys/fs/cgroup/mygroup/cpu.max
# 限制内存(最多 256MB)
echo "268435456" > /sys/fs/cgroup/mygroup/memory.max
# 限制 IO(读取 10MB/s)
echo "8:0 rbps=10485760" > /sys/fs/cgroup/mygroup/io.max
# 将进程加入 cgroup
echo $PID > /sys/fs/cgroup/mygroup/cgroup.procs
# 查看使用情况
cat /sys/fs/cgroup/mygroup/cpu.stat
cat /sys/fs/cgroup/mygroup/memory.current
使用 systemd-run (替代方案)
# 在 Alpine 上使用 cgroup-tools
apk add cgroup-tools
# 创建和管理 cgroup
cgcreate -g cpu,memory:/mygroup
cgset -r cpu.cfs_quota_us=50000 mygroup
cgset -r memory.limit_in_bytes=256M mygroup
cgexec -g cpu,memory:/mygroup mycommand
12.6 网络命名空间隔离
# 创建网络命名空间
ip netns add isolated
# 在隔离命名空间中运行程序
ip netns exec isolated ip addr show
ip netns exec isolated ping 8.8.8.8 # 失败:无网络
# 创建 veth 对连接命名空间
ip link add veth0 type veth peer name veth1
ip link set veth1 netns isolated
# 配置 IP
ip addr add 10.200.0.1/24 dev veth0
ip link set veth0 up
ip netns exec isolated ip addr add 10.200.0.2/24 dev veth1
ip netns exec isolated ip link set veth1 up
ip netns exec isolated ip link set lo up
# 测试连通性
ip netns exec isolated ping 10.200.0.1
# 允许隔离命名空间访问外网
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A POSTROUTING -s 10.200.0.0/24 -o eth0 -j MASQUERADE
ip netns exec isolated ip route add default via 10.200.0.1
12.7 能力(Capabilities)管理
# 查看程序的能力
apk add libcap
getcap /usr/bin/ping
# 删除不必要的能力
# Docker 容器默认去掉大部分能力
# --cap-drop ALL --cap-add <仅需要的能力>
# 常用能力说明
# CAP_NET_BIND_SERVICE - 绑定 <1024 端口
# CAP_NET_RAW - 使用原始套接字
# CAP_SYS_ADMIN - 管理操作(危险)
# CAP_SYS_PTRACE - 跟踪进程
# CAP_DAC_OVERRIDE - 覆盖文件权限
# 程序能力设置
setcap 'cap_net_bind_service=+ep' /usr/local/bin/myapp
# 现在 myapp 可以绑定 80 端口而无需 root
# 删除能力
setcap -r /usr/local/bin/myapp
12.8 内核安全参数
cat >> /etc/sysctl.conf << 'EOF'
# ====== 内核安全加固 ======
# 禁用 SysRq 键
kernel.sysrq = 0
# 禁止 core dump
fs.suid_dumpable = 0
# 限制 dmesg 访问
kernel.dmesg_restrict = 1
# 限制 kernel 指针泄露
kernel.kptr_restrict = 2
# 启用地址空间随机化
kernel.randomize_va_space = 2
# 限制 perf_event 访问
kernel.perf_event_paranoid = 3
# 禁用 unprivileged BPF
kernel.unprivileged_bpf_disabled = 1
# 禁用 user namespaces(可选,影响某些应用)
# kernel.unprivileged_userns_clone = 0
# PID 最大值限制
kernel.pid_max = 32768
# 文件描述符限制
fs.file-max = 2097152
fs.nr_open = 1048576
# 网络安全
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.tcp_syncookies = 1
net.ipv6.conf.all.accept_redirects = 0
EOF
sysctl -p
12.9 安全加固自动化脚本
cat > /usr/local/bin/harden-alpine << 'SCRIPT'
#!/bin/sh
set -e
echo "=== Alpine Linux Hardening Script ==="
echo "[1/8] Updating system..."
apk update && apk upgrade
echo "[2/8] Removing unnecessary packages..."
apk del gcc g++ make gdb strace 2>/dev/null || true
echo "[3/8] Setting file permissions..."
chmod 600 /etc/shadow
chmod 600 /etc/gshadow
chmod 644 /etc/passwd
chmod 644 /etc/group
chmod 700 /root
chmod 600 /etc/ssh/sshd_config
echo "[4/8] Configuring SSH..."
cat > /etc/ssh/sshd_config << 'SSHEOF'
Port 22
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
X11Forwarding no
AllowAgentForwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
SSHEOF
echo "[5/8] Setting sysctl parameters..."
cat >> /etc/sysctl.conf << 'SYSEOF'
kernel.sysrq = 0
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
kernel.randomize_va_space = 2
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
SYSEOF
sysctl -p
echo "[6/8] Configuring firewall..."
iptables -F
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
/etc/init.d/iptables save
echo "[7/8] Setting up tmpfs..."
echo "tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev,size=2G 0 0" >> /etc/fstab
echo "[8/8] Hardening complete!"
echo "Please reboot to apply all changes."
SCRIPT
chmod +x /usr/local/bin/harden-alpine
12.10 本章小结
| 加固技术 | 效果 | 难度 |
|---|---|---|
| 最小化安装 | 减少攻击面 | ⭐ |
| 只读根 | 防止持久化攻击 | ⭐⭐ |
| Seccomp | 限制系统调用 | ⭐⭐⭐ |
| 命名空间 | 进程隔离 | ⭐⭐⭐ |
| cgroups | 资源限制 | ⭐⭐ |
| Capabilities | 细粒度权限 | ⭐⭐ |
| 内核参数 | 全局安全策略 | ⭐⭐ |
扩展阅读
上一章:第 11 章:开发环境 下一章:第 13 章:故障排查