上个月帮某公司处理一台被爆破的数据库服务器时我打开登录日志短短 6 个小时里躺着 4 万多条失败记录。攻击者先是批量扫端口接着用常见用户名列表跑密码字典最后还尝试通过畸形请求触发服务漏洞。这种场景在公网环境里几乎每天都会发生Linux 安全加固不是可选优化项而是服务器上线前就该完成的基本工序。这篇文章我把 SSH 防爆破和 SELinux 策略配置这两块最核心的内容串起来讲从 sshd 配置、密钥登录、fail2ban 自动封禁一直讲到 SELinux 的标签体系、布尔值开关和自定义策略生成。中间会穿插我在真实环境里处理过的案例、踩过的坑以及一些常规文档里不会写的判断依据。适合刚接手服务器的新手也适合已经会基础操作但想补齐安全细节的运维同学。1. 为什么企业 Linux 服务器会被“盯上”1.1 攻击者到底在扫描什么公网 IP 一旦开放就会有扫描器不间断地探测。它们不看服务器上跑的是什么业务只看两个东西第一端口是否开放第二端口背后的服务是否存在已知弱点。SSH 默认端口 22、MySQL 默认端口 3306、Redis 默认端口 6379这些都是扫描器最喜欢的光顾对象。扫描结果里面只有一个开放端口还不够攻击者还要判断这台机器值不值得投入成本。他们的判断维度通常包括是否使用弱密码、是否允许 root 直接登录、系统版本是否偏老、服务是否存在高危漏洞。这些信息通过指纹识别和几次尝试就能摸清。所以一台刚装好系统、没有做任何加固的服务器本质上就像没锁门的仓库任何人都能推门看一眼。很多开发者觉得我的密码够复杂爆破不可能成功但实际上暴力穷举只是最低级的手段。攻击者手里有大量泄露的真实密码组合还会利用常见用户名、默认口令、键盘序列密码来批量尝试。即使单次爆破成功率很低放在上千台服务器上跑总能撞开几台。企业服务器一旦被撞开轻则被植入挖矿程序重则成为内网横向渗透的跳板。1.2 加固前先想清楚要防的是谁做安全加固之前我会先问自己三个问题这台机器暴露在什么网络环境里上面跑的是什么数据被攻破之后会造成什么影响这三个问题的答案决定了加固的强度。比如一个公网 WEB 服务器和一台纯内网运维跳板机面对的风险不一样。公网服务器受到的是持续扫描和自动化攻击内网机器则更怕员工误操作或已失陷终端的横向移动。再比如数据库服务器即使内网访问也不能只靠内网隔离因为渗透者一旦控制同网段的某一台应用服务器下一步就会尝试连接数据库。所以内网机器同样需要关闭无关端口、限制来源地址、开启 SELinux而不是一句内网很安全就不管了。这个阶段还有一个容易被忽略的动作盘点开放端口和监听服务。用ss -lntp看当前监听端口用systemctl list-unit-files --typeservice --stateenabled看开机自启服务把不认识、不需要的服务全部停掉并禁用。很多加固方案忽略这一步结果防火墙规则写得再漂亮机器上还是开着不必要的端口等于白做。2. SSH 防爆破把第一道门真正锁死2.1 sshd 配置里最容易被忽略的开关SSH 防爆破的核心在于/etc/ssh/sshd_config。我刚接触 Linux 的时候只知道改Port和PermitRootLogin后来才发现真正决定 SSH 安全等级的细节远不止这两个参数。先说端口把 SSH 从 22 改成其他高位端口能过滤掉绝大多数无差别扫描这是成本最低的措施。但注意改端口不是隐藏式安全真正有目标的攻击者依然能探测到。所以端口只能作为第一层噪声过滤不能替代密钥认证和 fail2ban。再说PermitRootLogin我给出的建议是prohibit-password而不是no。prohibit-password的意思是禁止 root 使用密码登录但允许通过密钥登录。这样做的好处是运维人员在紧急情况下仍然可以直接用 root 密钥登录不需要先切到普通用户再su -同时又堵死了暴力破解 root 密码的路径。其他容易被忽略的推荐配置PasswordAuthentication no关闭密码认证这是防爆破最关键的一步。PubkeyAuthentication yes显式开启密钥认证。MaxAuthTries 3限制单次连接的最大认证尝试次数防止在同一连接中反复试密码。LoginGraceTime 30登录超时缩短到 30 秒避免攻击者占用连接资源。AllowUsers ops admin只允许指定用户通过 SSH 登录从账号层面收口。ClientAliveInterval 300配合ClientAliveCountMax 2每 300 秒探测一次客户端连续 2 次无响应就断开避免死连接堆积。我见过一个问题管理员修改完sshd_config后直接重启 sshd 服务结果配置写错了把自己关在外面。正确的做法是先执行sshd -t做语法检查再通过另一个终端保持已有会话不断开的情况下重启服务这样即使新配置有问题旧连接还能保留方便回滚。2.2 密钥登录替代密码登录的落地流程密钥登录的原理是非对称加密。客户端生成一对密钥私钥留在本地公钥放到服务器的~/.ssh/authorized_keys中。登录时服务器用公钥验证客户端的签名私钥不经过网络传输天然避免了密码被中间人截获的问题。生成密钥时我推荐用 ED25519 算法而不是 RSA。ED25519 密钥更短、生成更快、安全性不输给 RSA在多数现代系统上都有原生支持。命令是ssh-keygen -t ed25519 -a 100 -f ~/.ssh/id_ed25519-a 100设置 KDF 迭代次数虽然生成和加载时会稍慢一点但能显著提高密钥文件被拿到后的暴力破解难度。生成后把公钥上传到服务器ssh-copy-id -i ~/.ssh/id_ed25519.pub opsyour-server-ip如果服务器禁用了密码登录还没上传公钥那就尴尬了。所以我的操作顺序是先生成并上传公钥确认密钥登录已经能正常工作再去关闭PasswordAuthentication。整个切换过程要留一个逃生窗口不要在一次变更里同时改端口、改认证方式、改允许用户列表任何一个环节出错都会导致登录失败而且很难判断到底卡在哪一步。密钥本身也要管理好。私钥文件权限必须是 600如果权限过松SSH 会直接拒绝使用该密钥。另外建议给私钥设置 passphrase这样即使私钥文件泄露攻击者还要再破解一道口令。对要求比较高的企业环境可以再把私钥放进硬件令牌或系统钥匙串中不过这些属于进阶方案普通团队先把 passphrase 用起来就足够了。2.3 fail2ban自动拉黑的最后一道防线即使关闭了密码认证公网服务器上仍然会收到大量尝试连接的空连接和无效请求。fail2ban 的作用是读取服务日志发现异常模式后自动调用防火墙规则封禁来源 IP相当于给 SSH 加了一个自动防御系统。安装后在/etc/fail2ban/jail.local中做如下设置[sshd] enabled true port ssh filter sshd logpath /var/log/secure maxretry 5 findtime 600 bantime 3600这几个参数的含义是600 秒内同一个 IP 出现 5 次认证失败就封禁 1 小时。maxretry不要设得太低否则你自己输错一次密码、换个 IP 再登录很容易误封也不建议设得太高否则等于给攻击者留了容错空间。我在生产环境里通常用 5 到 8 次配合合理的长封禁时间。封禁时间bantime也可以做成递增策略首次封 1 小时、再次触发封 24 小时、第三次封 7 天这个可以在 jail.local 里用recidive机制实现。具体配置不展开知道有递增封禁这个思路就行。fail2ban 运行后用fail2ban-client status sshd可以查看当前封禁列表。日常维护中要注意有些系统日志路径不是/var/log/secure而是/var/log/auth.log需要根据发行版调整logpath。另外如果服务器前面还有云平台的负载均衡器所有 SSH 连接都会显示成同一个入口 IPfail2ban 就无法正确识别真实来源这种情况下需要在云平台的安全组层面做访问控制而不是单纯依赖 fail2ban。3. 账号权限和最小化加固的第二层3.1 用户管理、sudo 规则和 PAM 收敛SSH 防住了端口不等于服务器就安全了。真正被攻破的服务器往往是在合法账号上出了问题。比如员工离职但账号没有禁用、测试环境使用共享账号、普通用户被误加到 sudo 组等。账号权限的收敛必须和 SSH 加固同步进行。我的原则是最小权限。每个需要登录服务器的人都应该有独立账号而不是共用 root 或共用某个业务账号。独立账号的另一个好处是可追溯出事的时候能根据日志定位到具体的人。对所有运维账号日志审计和安全审计环境要求较高的地方还可以启用jailkit或限制为rbash不过多数场景并不需要这么重的限制普通用户配合 sudo 规则就够了。举一个完整的收敛步骤创建用户ops加入wheel组然后单独配置 sudoers。在/etc/sudoers.d/ops中不直接给完整 root 权限而是只允许执行运维需要的命令ops ALL(ALL) NOPASSWD: /usr/bin/systemctl restart nginx, /usr/bin/systemctl restart sshd这样即使账号泄露攻击者能执行的命令也是有限集合。当然如果团队确实需要完整权限那至少要保证 sudo 操作会被日志记录。/etc/sudoers中的Defaults log_output可以把 sudo 会话的所有输入输出记录到日志中这是排查问题的关键证据。PAM 层面的配置也值得检查。/etc/security/pwquality.conf可以设定密码复杂度/etc/pam.d/system-auth中启用pam_pwquality、pam_faillock等模块。pam_faillock可以在连续输错密码后锁定账号一段时间配合 fail2ban 形成双重保护。注意锁定账号和锁定来源 IP 是两种不同的防护思路账号锁定能防御针对同一账号的暴力尝试来源 IP 锁定能防御大量账号的批量扫描。3.2 文件权限、umask 与不可变属性文件权限是 Linux 权限体系里最基础的一环但越是基础越容易出乱子。我接手过一台服务器根目录下有一个 0777 权限的备份目录里面放的是数据库全量导出文件。这种权限意味着任何能登录服务器的用户都能读取甚至覆盖备份一旦被入侵者发现数据泄露只是时间问题。先检查系统中权限过宽的文件尤其是配置文件、脚本和备份文件find / -xdev -type f -perm -002 -print这条命令会扫描出所有其他用户可写的文件-002表示检查文件的 other 写位。扫出来后逐一确认能去掉写权限就去掉不能去掉的也要评估原因并记录在案。umask决定新建文件的默认权限。多数系统的默认 umask 是 022也就是新建文件权限为 644、目录为 755这个值对普通环境够用。对高安全环境可以把 umask 设为 027让同组用户保持默认读权限、其他用户没有任何权限。修改位置可以放在/etc/profile或/etc/bashrc中umask 027还有一个小众但实用的机制是chattr不可变属性。对关键目录和关键配置比如/etc/ssh/sshd_config、/etc/shadow可以用chattr i加上不可变标记。加了这个标记后即使是 root 用户也不能修改或删除文件直到执行chattr -i取消标记。这对防御勒索软件和篡改配置类攻击很有效因为攻击者就算拿到了权限改不了文件也白搭。我踩过最深的一个坑是用chattr i锁定了 SSH 配置后来改配置时忘记先解锁直接重启 sshd 后发现新版配置没生效排查了半天才发现是文件被锁定了。所以这个操作只适合企业里运维流程严格的机器不适合个人偶尔改配置的服务器。4. SELinux 策略配置从原理到实战4.1 三种模式enforcing、permissive 与 disabledSELinux 是 Linux 内核的强制访问控制机制和传统的用户权限决定文件能否访问不同它会进一步检查进程和对象之间的关系。默认的 DAC 权限只看你是哪个用户、在哪个组SELinux 在此基础上添加了安全上下文也就是给进程、文件、端口都打上类型标签通过策略指定哪些类型之间可以交互。这个机制让很多人头疼因为配置不当会导致正常服务无法启动。最常见的例子把 Nginx 的网页根目录放到/home/nginx/www下结果 Nginx 启动后返回 403就是因为/home下的文件没有httpd_sys_content_t标签SELinux 阻止了 Nginx 进程读取。SELinux 有三种模式enforcing、permissive和disabled。disabled表示完全关闭permissive表示记录违规但放行enforcing表示拦截违规操作。我强烈不建议直接disabled因为关闭之后系统就不再产生 AVC 记录你无法知道业务到底会触碰哪些规则日后即使想开启要排查的问题比从permissive开始更多。正确的新系统部署路径是先把系统设为permissive跑一遍正常业务流程然后查看/var/log/audit/audit.log里的 AVC 拒绝记录。再根据记录调整标签或布尔值之后切到enforcing观察一段时间。用命令切换运行时模式setenforce 1 # 切换到 enforcing setenforce 0 # 切换到 permissive修改/etc/selinux/config中的SELINUXenforcing可以让模式在重启后保持。注意setenforce只改变当前运行模式不改配置文件两个都要做才能持久化。4.2 标签、布尔值与端口映射怎么配合SELinux 的标签规则是核心。查看文件标签用ls -Z查看进程标签用ps -eZ。例如ls -Z /var/www/html/index.html ps -eZ | grep nginx正常情况下Nginx 读取网页文件的标签应该是httpd_sys_content_tNginx 进程本身的标签是httpd_t。如果文件被放在没有正确标签的目录下执行restorecon能把这个文件恢复为策略默认的标签restorecon -Rv /var/www/html如果目录本身是自定义路径比如/data/www就需要告诉 SELinux 这个目录应该使用哪种类型标签。一种方式是用semanage fcontext添加规则semanage fcontext -a -t httpd_sys_content_t /data/www(/.*)? restorecon -Rv /data/www这里我把/data/www及其子文件都映射为httpd_sys_content_trestorecon让这条映射立即生效。很多资料只告诉你怎么用chcon临时改标签却不说chcon改的是文件当前属性一旦后续执行restorecon就会被重置。正确做法是先用semanage fcontext定义默认规则再用restorecon应用这样系统能记住这条规则。布尔值是 SELinux 提供给运维人员的可控开关。查看与 SSH 相关的布尔值getsebool -a | grep ssh比如httpd_can_network_connect这个布尔值决定了 Apache 或 Nginx 进程能否主动发起网络连接。如果应用需要从后端 API 拉取数据就需要打开它setsebool -P httpd_can_network_connect on-P表示持久化写入配置不加则重启后失效。使用布尔值是标准做法优先于编写自定义策略因为布尔值是系统维护的升级后依然有效。改 SSH 端口也会触发 SELinux 问题。如果把 sshd 的监听端口从 22 改成 2222SELinux 默认只允许 sshd 监听 22 端口此时需要添加端口映射semanage port -a -t ssh_port_t -p tcp 2222然后在/etc/ssh/sshd_config中设置Port 2222重启 sshd。如果漏掉semanage port这一步sshd 会因为 SELinux 限制而启动失败日志里通常不会直接写端口被 SELinux 禁止而是显示地址已被使用或绑定失败之类的模糊信息排查起来很费时间。4.3 用 audit2allow 生成企业常用自定义策略布尔值解决不了所有问题。当某个业务程序需要访问不在默认策略允许范围内的资源时就需要写自定义 SELinux 策略模块。这里最推荐的手段是audit2allow它能把审计日志中的拒绝记录转换成策略模块省去手写策略文件的学习成本。我的操作流程是这样的把相关服务故障复现一次确保 AVC 拒绝记录被写入审计日志。查看最近的拒绝记录ausearch -m AVC -ts recent用audit2why看拒绝原因确认是类型冲突还是布尔值问题audit2why /var/log/audit/audit.log如果确认需要放行用audit2allow生成模块audit2allow -a -M customwebapp semodule -i customwebapp.pp命令中的-a表示读取所有 AVC 拒绝记录-M customwebapp会生成customwebapp.pp和customwebapp.te两个文件然后semodule把编译好的策略模块加载进内核。整个过程不需要手写一行策略语言适合大多数开发者。但这里必须强调audit2allow是辅助工具不是无脑放行工具。生成的策略往往比实际需要的权限更宽松因为日志里面可能包含恶意扫描产生的拒绝记录如果一股脑全放行等于把攻击行为也合法化了。我在生产环境中的做法是先停止业务、清空审计日志、启动业务并复现问题再只针对这一小段时间的 AVC 记录使用audit2allow生成的模块还要人工打开.te文件检查确认放行的类型和权限是预期范围内的。5. 常见问题与排查实录5.1 SSH 加固后反而连不上怎么救SSH 加固最常见的翻车现场就是我前面提到的配置没生效就把自己锁在外面。有一次某同事把PasswordAuthentication改成no却忘了把公钥放到服务器上结果新配置生效之后所有密码登录全部被拒自己也进不去。当时好在还有云平台的管理终端可以通过管理终端登录到机器恢复配置。如果管理终端也没有那就只能祈祷之前有保留 SSH 连接。这也是为什么我反复强调修改 SSH 配置时一定要保持一个已经建立的 SSH 会话不要断开。修改完配置后先sshd -t检查语法再用systemctl reload sshd而不是restart。reload不会断开现有连接新连接使用新配置这样即便配置有问题你还能通过旧连接修复。遇到 SSH 连通性问题按下面的顺序排查先确认网络层ping通不通端口通不通可以使用telnet或nc测试端口。再确认服务层systemctl status sshd查看服务状态journalctl -u sshd -n 50查看最近日志。接着看配置文件sshd -T会输出实际生效的配置有时候你改了 A 文件但系统实际用的是 B 文件只有-T能看出真实结果。最后看认证过程journalctl -u sshd里如果有Failed publickey或Connection closed by authenticating user基本可以断定是公钥或权限问题。公钥登录失败时还有一个容易忽略的坑~/.ssh/authorized_keys文件如果权限是 644SELinux 在 enforcing 模式下会拒绝 sshd 读取。必须设置成 600并且用户主目录不能是 777。这些条件都满足后建议把 SELinux 先切到 permissive 测试一次如果能登录再把问题锁定到 SELinux 标签上。5.2 SELinux 误杀业务服务的排查流程SELinux 最让运维头大的问题是明明日志里没有报错服务就是起不来。排查时我有一套固定的流程可以大幅缩短定位时间。第一步看服务状态和主日志。很多服务报的错和真实原因对不上比如 Nginx 提示Permission denied但文件权限却完全正常。这时候不要纠结表象直接看 SELinux 的审计记录。第二步查看 AVC 拒绝记录ausearch -m AVC -ts recent --start today | tail -50或者直接打开/var/log/audit/audit.log搜索denied。审计记录里会有tcontext和scontext前者是目标资源的 SELinux 类型后者是发起访问的进程类型。看到这两个字段基本就能判断是进程类型和资源类型不匹配。第三步把记录交给audit2why分析它会给出三个结论中的一种规则阻止、布尔值关闭或者类型不匹配。如果是布尔值问题setsebool -P开启即可如果是类型不匹配要么用semanage fcontext调整标签要么用audit2allow生成模块。第四步验证问题是否解决后一次性移除多余权限。我发现很多团队遇到 SELinux 问题就临时setenforce 0然后忘了切回来等于把 SELinux 缓存成了摆设。解决完业务问题后请务必将系统切回 enforcing用后续监控来确认策略是合适的。5.3 一套可以长期执行的巡检清单安全加固不是做完就完事的一次性工程。系统升级、用户变更、业务上线都可能导致原有配置失效所以必须把安全巡检固化到日常流程中。我平时会定期检查这些项目登录失败日志lastb | head -50看是否有异常 IP 反复尝试。活跃用户last -a | head -30确认最近登录记录是否都是本人操作。新增用户和权限变更查看/etc/passwd中最近修改的账号检查 sudoers 文件内容。监听端口变化ss -lntup对比基线端口清单发现新开放端口要追问来源。SELinux 审计日志量audit.log如果暴涨说明有进程一直在被拒绝访问优先排查是否被入侵后产生的新行为。定时任务检查/etc/crontab、/var/spool/cron/下有没有新增条目警惕挖矿或持久化后门。系统更新为内核和关键安全补丁留出维护窗口不要把生产环境长期停在旧版本上。这些检查项不需要全部写成脚本前期可以先靠人工巡检积累几周的基线数据后再写成 cron 脚本或接入监控平台。比如把lastb的结果做数量统计超过阈值就告警把ss -lntup的输出和基线文件做 diff有差异就通知运维确认。有了基线就能尽早发现问题安全监控的意义不是出事后的追查而是出事之前的察觉。我个人在实际操作中的体会是Linux 安全加固的价值不在于某一项配置有多强而在于每层防护都能正常运转。SSH 防爆破挡掉了外部的高频扫描账号权限收敛减少内部误操作和横向移动的可乘之机SELinux 兜住那些想不到的越权访问。这三层只要都活着哪怕某一层被绕过了后面还有东西能拦住。如果你现在手头有一台还没加固的服务器不妨从修改 sshd 配置和查看 SELinux 状态开始这两个动作做完最危险的暴露面就已经收缩了一大半。