
引言默认配置不是“能用就行”而是“随时可能被攻破”一台刚刚开通的云服务器往往具备几个致命特征SSH 监听 22 端口、允许 root 密码登录、防火墙默认放行、sudo 权限随意分配、日志只存在本地且从不轮转。攻击者只需要一个自动化脚本就能在数小时内完成从端口扫描、弱口令爆破到植入挖矿木马的完整链条。很多团队把安全加固理解为“改个 SSH 端口、装个防火墙”但真正的加固是攻击面收敛、最小权限、纵深防御、可观测性四件事的组合。本文从 SSH、用户权限、防火墙、日志四个维度出发结合可落地的脚本与配置给出一套可审计、可回滚、可持续运行的 Linux 服务器加固方案。先看一个真实感很强的攻击时间线服务器开通后 3 分钟22 端口被扫描到10 分钟内攻击者用常见用户名 root、admin、ubuntu 配合弱口令字典开始爆破如果 root 允许密码登录爆破成功的概率会随着时间快速上升登录成功后攻击者通常会执行uname -a、cat /etc/os-release、whoami、id判断环境随后下载挖矿程序、添加 SSH 公钥、修改 crontab、清理/var/log/secure或/var/log/auth.log最后用chattr i锁住恶意文件防止被删除。整个过程不需要人工介入全部由僵尸网络自动完成。所以安全加固不是“装一个软件就结束”而是一个持续收敛的过程。本文的四个维度分别解决不同问题SSH 解决入口认证问题用户权限解决横向移动与提权问题防火墙解决网络暴露面问题日志解决发现、追溯与取证问题。四者缺一不可。一、核心原理四层防御模型1.1 SSH 层认证与通道安全SSH 是服务器管理的入口也是被攻击最频繁的服务。加固的核心不是“隐藏”而是把认证方式从“共享秘密”升级为“非对称密钥”并限制谁能登录、从哪里登录、能做什么。关键配置包括禁用 root 直接登录、禁用密码认证、限制用户和来源 IP、缩短认证宽限期、关闭不必要的转发功能。端口修改只能降低日志噪音不能替代认证加固。从协议层面看SSH 建立连接时会经历版本交换、算法协商、密钥交换、用户认证、会话通道几个阶段。密码认证的风险在于“共享秘密”可被猜测、可被钓鱼、可在多台机器重复使用而公钥认证使用非对称密钥私钥不出本地服务器只保存公钥爆破难度从“猜密码”变成“猜私钥”实际不可行。更重要的是公钥认证可以配合authorized_keys中的from、command、no-port-forwarding、no-pty等选项把一把密钥限制到特定来源 IP 或特定命令实现更细粒度的最小权限。但公钥认证也不是没有坑。常见问题包括私钥权限过宽OpenSSH 会直接拒绝使用authorized_keys权限或属主错误导致登录失败AllowUsers与Match块顺序写错导致预期规则未生效修改 SSH 端口后忘记同步 SELinux 端口标签或云安全组导致自己也被挡在外面。因此SSH 加固必须遵循“先验证、再重载、保留后路”的原则。还需要注意禁用密码认证后ssh-agent转发、ProxyJump、SSH 证书会成为更高效的运维方式。对于大规模服务器建议使用 SSH CA 签发短期证书而不是把同一把公钥散落到所有机器。对于高安全环境可以叠加AuthenticationMethods publickey,keyboard-interactive或 TOTP 二次认证。MaxAuthTries、LoginGraceTime、MaxStartups可以降低爆破资源消耗ClientAliveInterval与ClientAliveCountMax可以清理僵死会话。X11Forwarding、AllowAgentForwarding、AllowTcpForwarding如果业务不需要应全部关闭减少被用作跳板或隧道转发的风险。1.2 用户与权限层最小权限原则Linux 的权限模型围绕 UID/GID、文件模式、sudo、capabilities 展开。常见风险包括UID 为 0 的非 root 账户、空密码账户、sudo 免密、SUID 程序滥用、服务账户可登录 Shell。加固思路是服务账户使用/usr/sbin/nologinsudo 按命令白名单授权敏感文件设置不可变属性定期审计 SUID/SGID 文件。进一步展开Linux 的用户信息存放在/etc/passwd密码哈希与过期策略存放在/etc/shadow组信息在/etc/group。任何 UID 为 0 的账户都等价于 root因此必须确保只有 root 的 UID 是 0。空密码账户、锁定密码账户、长期未登录账户、离职人员账户都是常见隐患。/usr/sbin/nologin或/bin/false作为 Shell 可以阻止服务账户交互登录但并不能阻止su -s /bin/bash这种方式因此还需要限制su和sudo的使用范围。sudo 是运维中最容易出问题的部分。NOPASSWD虽然方便自动化但一旦普通账户被攻破攻击者可以直接以 root 执行任意命令。更好的做法是使用Cmnd_Alias按命令白名单授权例如只允许重启 nginx、查看日志、执行特定部署脚本并配合Defaults use_pty、Defaults log_input、Defaults log_output记录 sudo 会话。修改 sudoers 必须使用visudo或visudo -c -f检查语法否则一个拼写错误可能导致所有 sudo 失效。SUID/SGID 是另一个提权温床。攻击者拿到普通 Shell 后会优先寻找可写的 SUID 程序、有capabilities的二进制、可被劫持的LD_PRELOAD路径、cron 任务、systemd 服务文件。审计命令包括find / -perm -4000 -type f 2/dev/null、find / -perm -2000 -type f 2/dev/null、getcap -r / 2/dev/null。对合法 SUID 程序应建立白名单例如sudo、passwd、ping、mount等发现异常立即排查。此外/etc/shadow、/etc/sudoers、/etc/ssh/sshd_config等敏感文件可以设置chattr i防止被篡改但设置后连自己也无法直接修改需要先chattr -i因此要纳入变更流程。umask 027或umask 077可以限制新文件默认权限。systemd 服务可以使用DynamicUseryes、ProtectSystemstrict、PrivateTmpyes等沙箱选项降低服务被攻破后的影响。1.3 防火墙层默认拒绝按需放行云安全组和主机防火墙是两层独立的边界。安全组控制“谁能到达网卡”主机防火墙控制“到达后能访问哪个进程”。两者必须同时收敛。现代 Linux 推荐使用 nftables 或 firewalld核心策略是policy drop只放行已建立连接、回环、必要业务端口和受控的 ICMP。这里需要强调“两层边界”的差异。云安全组通常作用于实例网卡外层由云平台维护规则简单、性能高但可能被同 VPC 内其他实例、容器网络、IPv6、辅助网卡绕过。主机防火墙运行在操作系统内核能看到进程、用户、接口、连接状态但配置复杂容易被 Docker、Kubernetes、VPN 软件插入规则而绕过。正确做法是安全组只开放必要端口主机防火墙再次收敛对于数据库、Redis、Elasticsearch 等敏感服务安全组只允许应用层安全组访问主机防火墙再限制来源 IP。nftables 的核心概念是表、链、规则、集合。inet族可以同时处理 IPv4 和 IPv6避免只防 IPv4 漏掉 IPv6。ct state established,related accept是状态防火墙的关键它允许已建立连接的回包避免为每个响应端口单独放行。iif lo accept允许回环否则本机进程通信会异常。ICMP 不应完全禁掉否则 Path MTU 发现问题但应限速并只允许必要类型。日志规则要限速否则被扫描时日志会爆炸。另一个常见误区是“改了 SSH 端口就安全了”。端口修改只能减少自动化脚本的噪音攻击者仍然可以通过全端口扫描发现新端口。真正有效的是禁用密码、限制用户、限制来源 IP、使用密钥或证书。对于必须暴露的管理端口可以叠加fail2ban、sshguard、端口敲门或 VPN 跳板机。1.4 日志层审计与追溯没有日志的安全加固等于“闭眼开车”。需要关注/var/log/auth.log、/var/log/secure、journald、auditd、last、lastb。日志必须集中收集、防篡改、设置轮转否则入侵后攻击者第一件事就是清理痕迹。日志分为几个层次认证日志记录 SSH、sudo、su、PAM 事件系统日志记录内核、服务、cron审计日志记录文件修改、命令执行、权限变更二进制日志wtmp、btmp、utmp记录登录、失败登录、当前会话。journald默认可能只保存在内存重启即丢失应配置Storagepersistent。auditd是内核级审计能记录execve、文件写、属性变更但规则过多会影响性能需要按需启用。集中收集是防篡改的关键。攻击者拿到 root 后可以删除本地日志但无法删除已经转发到远程 SIEM 的日志。可以使用 rsyslog、journald-upload、Filebeat、Fluent Bit 将日志发送到远程服务器并设置只追加权限chattr a甚至使用远程 syslog 服务器只接收不删除。日志轮转使用logrotate避免磁盘被写满。时间同步使用chrony或systemd-timesyncd否则跨机日志无法关联分析。可观测性还包括告警。例如5 分钟内 SSH 失败登录超过 20 次、出现新的 UID 0 账户、/etc/passwd被修改、sudo 执行了敏感命令、auditd 服务停止、日志文件被截断。没有告警的日志只是“事后考古”无法及时止损。二、实战案例从裸机到基线合规以下操作默认在 root 或具备 sudo 的账户下执行。生产环境请先在测试机验证并确保拥有云控制台 VNC、救援模式或快照回滚能力避免把自己锁在门外。2.1 SSH 加固sshd_config 自动化收敛以下脚本以幂等方式写入关键配置并在重载前做语法检查避免把自己锁在门外。#!/usr/bin/env bashset-euopipefailSSHD_CONFIG/etc/ssh/sshd_configcp-a$SSHD_CONFIG${SSHD_CONFIG}.bak.$(date%F-%T)declare-ASETTINGS([PermitRootLogin]no[PasswordAuthentication]no[PermitEmptyPasswords]no[PubkeyAuthentication]yes[KbdInteractiveAuthentication]no[MaxAuthTries]3[LoginGraceTime]30[X11Forwarding]no[AllowAgentForwarding]no[AllowTcpForwarding]no[ClientAliveInterval]300[ClientAliveCountMax]2)forkeyin${!SETTINGS[]};doval${SETTINGS[$key]}ifgrep-qiE^\s*#?\s*${key}\b$SSHD_CONFIG;thensed-i-Es|^\s*#?\s*${key}\b.*|${key}${val}|I$SSHD_CONFIGelseecho${key}${val}$SSHD_CONFIGfidonesshd-tsystemctl reload sshdecho[] SSH 加固完成请保留当前会话并新开终端验证。解释脚本先备份原配置再用关联数组逐项写入。sshd -t是最后一道保险任何语法错误都会阻止重载。注意不要直接关闭当前会话应新开终端验证密钥登录成功后再退出。进一步说明grep -qiE ^\s*#?\s*${key}\b会匹配注释行或已存在的配置行sed -i -E将其替换为期望值。这种幂等方式可以重复执行不会不断追加重复配置。但要注意如果sshd_config中使用了Match块全局替换可能会影响Match内的覆盖关系。更稳妥的做法是把加固配置放到/etc/ssh/sshd_config.d/99-hardening.conf并在主配置中确认Include /etc/ssh/sshd_config.d/*.conf存在。这样升级或 cloud-init 覆盖主配置时加固片段仍然生效。重载前必须做三件事第一确认当前用户已经配置好公钥并且~/.ssh/authorized_keys权限为 600、.ssh目录权限为 700第二确认云安全组或主机防火墙允许当前 SSH 端口第三新开一个终端测试ssh -i ~/.ssh/id_ed25519 userhost不要关闭原会话。如果使用非 22 端口还要同步 SELinux 端口标签semanage port-a-tssh_port_t-ptcp2222semanage port-l|grepssh_port_t如果系统没有semanage需要安装policycoreutils-python-utils。Ubuntu/Debian 的服务名通常是sshRHEL/CentOS 是sshd重载命令要对应修改。回滚方式也很简单把备份文件覆盖回去执行sshd -t再systemctl reload sshd。如果已经无法 SSH 登录只能通过云控制台 VNC 或救援模式恢复。生成密钥时推荐使用 Ed25519ssh-keygen-ted25519-a100-Copsexample.comssh-copy-id-i~/.ssh/id_ed25519.pub userhost如果必须保留密码认证作为过渡至少用AllowUsers或AllowGroups限制可登录用户用Match Address限制来源网段并配合fail2ban降低爆破频率。但最终目标仍然是全面禁用密码认证。2.2 用户权限审计用 Python 发现隐藏风险人工检查/etc/passwd、/etc/sudoers.d容易遗漏下面脚本可快速识别空密码、UID 0、sudo 免密和 SUID 文件。#!/usr/bin/env python3importpwd,spwd,os,statdefcheck_empty_passwords():foruinpwd.getpwall():try:spspwd.getspnam(u.pw_name)ifsp.sp_pwdpin(,!,*)andu.pw_uid1000:print(f[!] 空/锁定密码账户:{u.pw_name})except(PermissionError,KeyError):passdefcheck_uid_zero():foruinpwd.getpwall():ifu.pw_uid0andu.pw_name!root:print(f[!] 非 root 的 UID 0 账户:{u.pw_name})defcheck_sudo_nopasswd():forroot,_,filesinos.walk(/etc/sudoers.d):forfinfiles:pos.path.join(root,f)withopen(p,errorsignore)asfh:fori,lineinenumerate(fh,1):ifNOPASSWDinlineandnotline.strip().startswith(#):print(f[!] sudo 免密:{p}:{i}:{line.strip()})defcheck_suid():forroot,dirs,filesinos.walk(/):dirs[:][dfordindirsifdnotin(proc,sys,dev,run)]forfinfiles:pos.path.join(root,f)try:ifos.lstat(p).st_modestat.S_ISUID:print(f[i] SUID:{p})exceptOSError:passif__name____main__:check_empty_passwords()check_uid_zero()check_sudo_nopasswd()# check_suid() # 全盘扫描较慢建议按需启用解释spwd读取影子文件需要 root 权限。SUID 全盘扫描耗时较长建议放入定时任务低峰执行并将输出接入 SIEM 或告警系统。运行时建议使用 sudo python3 audit_users.更多硬核网安与AI工具包请扫码获取完整源码py否则spwd可能抛出PermissionError导致