刚接手一台别人维护过的Linux服务器时,我一般不会急着部署业务,也不会先冲上去装各种安全软件。第一件事永远是做一遍系统安全基线梳理:看看哪些端口开着、哪些用户能登录、哪些服务在监听、关键文件权限是否合适。这个习惯保持了很长时间,帮我在不少项目里提前避开了麻烦。所谓“系统安全加固”,说穿了就是把一台默认配置的机器,一点点收成符合业务需要的最小开放形态,让不该出现的东西不出现,不该打开的通道都关掉。这篇文章我打算以Linux环境为场景完整展开,覆盖账户体系、SSH登录入口、防火墙收敛、文件系统权限、内核参数以及常用安全工具的配置。里面所有操作步骤、配置参数、排查思路,都是我在真实环境里验证过的,也会把我踩过的坑一并写出来。适合正在做服务器基线加固、准备上生产环境、或者刚接手老旧系统的运维和开发同学参考。1. 加固思路:先把“为什么”想清楚1.1 加固不是装软件,是收敛暴露面很多人一说安全加固,第一反应是装个杀毒软件、装个安全狗、装个检测Agent。这其实把顺序搞反了。安全的核心思路是暴露面收敛,如果一个服务根本不在监听、一个端口根本不对公网开放,那攻击者连接触的机会都没有,比任何检测工具都有效。Linux默认安装以后,系统会打开很多用不到的端口和服务。比如有些发行版默认开着CUPS打印服务、Avahi广播、Telnet等旧协议,这些平时业务根本用不上,却是攻击者最爱扫描的入口。加固的第一步不是一个一个去堵漏洞,而是先盘清楚“这台机器上到底跑着什么”,然后把你不需要的东西全部停掉。我通常会在拿到一台机器后,先跑一遍端口和服务盘点,再结合业务确认最小集合。这个环节花不了多少时间,但整个加固方案的依据都在这里,跳过这一步后面很容易做出“看起来很安全实则稀烂”的配置。1.2 最小权限原则怎么落到每一条命令上最小权限不是一个悬在空中的概念,它最后会落到账户、文件权限、sudo授权、防火墙策略这些非常具体的对象上。比如一个应用账户,它只需要读取自己的配置文件和日志,那就不该有/home下其他用户的读权限;只允许通过特定IP访问SSH,那防火墙策略就该精确到IP而不是“全部允许”。落到实操里,我一般会坚持几条很朴素的规则:能用普通用户跑的进程,绝不用root跑。像Nginx的master process可以绑定80端口后降权,Web应用的服务账户单独建,数据目录所有权独立隔离。sudo授权精确到命令,不直接给ALL。比如某个维护账号只需要重启某个服务,那sudoers里就只写那一条命令。文件权限能收则收,目录默认755、文件默认644是在没有特殊需求的前提下,特殊目录根据实际需要单独处理。这些动作单独看都很小,但组合起来会让攻击者即使拿到一个低权限shell,横向移动成本也高很多。1.3 纵深防御:即使一层被突破,也要有兜底我的加固方案从来不是单点防御。比如SSH改成了密钥登录,这只是防住了口令爆破,如果本机账户密码被撞库,攻击者还能绕过密钥直接凭密码登录吗?所以还要配合禁用密码登录、限制登录用户、fail2ban、防火墙只放行指定来源,形成链条。纵深防御在Linux环境里的表现就是:网络层有防火墙收敛端口,应用层有服务降权,系统层有文件权限基线,登录层有密钥认证和来源限制,最后还有审计日志记录所有敏感操作。每一层不追求绝对安全,但即使某一层失灵了,下一层还能兜底。提示:做加固最忌讳“毕其功于一役”的心态,任何单一配置都不能保证安全。参考上面这个思路,逐层检查、逐层加固,会发现系统安全性提升的不是一点半点。2. SSH与账户体系:登录入口是第一道门2.1 禁用root直接登录,先改成密钥认证Linux服务器的登录入口是最容易被打爆的地方,尤其是SSH端口暴露在公网的情况下,密码爆破几乎是24小时不间断的。默认情况下root可以直接登录,而且很多初始化场景还开着密码认证,这等于把最高权限账户暴露在暴力破解的射程内。改法也不复杂,先创建普通用户并加入sudo组,然后把自己本地的公钥拷过去,确认能通过普通用户登录后,再去修改SSH配置。# 创建普通用户,加入sudo组 useradd -m -s /bin/bash ops passwd ops usermod -aG sudo ops # 把本机公钥放到目标机器的用户目录 ssh-copy-id ops@server_ip接下来修改SSH服务端配置,这一步建议先开一个新终端保持一个已有连接,避免改错了把自己锁在外面:sudo vim /etc/ssh/sshd_config重点改这几个参数,改完重启sshd服务:# 禁用root直接登录 PermitRootLogin no # 关闭密码认证,只允许密钥 PasswordAuthentication no # 限制能登录的用户,白名单模式 AllowUsers ops # 空密码账户禁止登录,默认就是no,保持住 PermitEmptyPasswords no实测下来的效果很明显:关闭密码认证后,SSH端口上的暴力破解日志数量会断崖式下降,因为攻击者拿到你的公钥文件几乎没有可能,剩下的扫描再频繁也只是空转。2.2 清理无效账户和组,关掉不必要的登录Shell系统安装后默认会有很多系统账户,这些账户本身没有登录Shell,危害不大,但如果某个账户被改成可登录Shell,又刚好是弱口令,就成了一个后门。所以我做加固时会统一盘一遍账户列表。# 列出所有可登录账户 awk -F: '($7 != "/usr/sbin/nologin" && $7 != "/bin/false") {print $1, $7}' /etc/passwd正常环境里,可登录账户应该只有root、自己创建的运维账户、业务应用账户(如果有需要)。其余的一律改成nologin或者/bin/false,让它们无法交互登录:usermod -s /usr/sbin/nologin 用户名还要检查一下空密码账户和UID为0的账户。UID为0意味着管理员权限,如果非root账户出现UID为0,基本可以判定是有问题的,需要立刻处理:# 检查空密码账户 awk -F: '($2 == "") {print $1}' /etc/shadow # 检查UID为0的账户 awk -F: '($3 == 0) {print $1}' /etc/passwd用户组也值得花两分钟过一遍,重点看sudo组和wheel组里有没有不该出现的成员,以及是否存在遗留的旧员工账户。我见过不少项目,人员都换了好几轮了,服务器上还留着离职同事的账户,一旦口令泄露就是个入口。2.3 登录保护:失败限制与二次验证即使启用了密钥认证,SSH也建议做一层登录保护。因为总有些场景需要开着密码登录,比如部分堡垒机对接、安全合规要求。这种情况下,可以用fail2ban对多次认证失败的来源IP做临时封禁。我一般在/etc/fail2ban/jail.local里这样配置:[sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 3600maxretry 3表示同一个来源IP尝试3次失败就封禁,bantime 3600表示封1小时。参数可以根据线上误判情况调整,但不要设成太宽松,否则失去了拦截意义。我习惯把findtime也配上,表示多长时间窗口内累计次数:findtime = 600这表示10分钟内失败3次就触发封禁,比单纯累计次数合理一些。如果对安全性要求更高,还可以考虑给SSH加上二次验证,比如配置TOTP动态口令。不过这个方案涉及客户端也要装验证器,团队规模大时推行成本不低。我一般的建议是:先用密钥加fail2ban把绝大部分爆破挡掉,二次验证作为可选增强项,在核心跳板机、数据库运维机上优先启用。3. 网络层收敛:防火墙不是装饰品3.1 默认拒绝策略,先把大原则定下来很多服务器自带防火墙,但默认策略基本上是“放行所有流量”,等于没装。我理解的防火墙策略只有一条硬原则:默认拒绝,显式放行。所有流量先禁止,然后根据业务需求一条一条加白名单。以iptables为例,先把默认策略全部改成DROP:iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT这样配置完以后,这台机器基本与外界隔绝了,然后我们再自己执行“开放”。这种方式的好处是,你每放行一个端口都经过思考,不会有“反正开着就先留着”的侥幸。不过现在很多Linux发行版也推荐用nftables或者ufw作为前端,底层逻辑是一样的。比如用ufw做默认拒绝就简洁一些:ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp ufw allow 80/tcp ufw enable这里22端口是SSH、80端口是Web应用,其他端口继续保持关闭状态。注意:改防火墙策略前,一定先确认自己有一条能连接到服务器的通道,比如通过带外管理口或者控制台。网络层配置失误导致“远程把自己锁死”的案例,我身边发生过太多次了。3.2 最小端口清单:一眼能看懂这台机器在干嘛整理防火墙策略时,我会顺手做一张端口清单表,写清楚端口号、协议、服务名、放行来源、用途。这个表格既是加固文档的一部分,也是未来排查问题的依据。端口协议服务放行来源说明22TCPsshd办公网出口IP远程登录入口80TCPnginx所有HTTP入口443TCPnginx所有HTTPS入口3306TCPmysqld内网应用服务器网段数据库访问凡是表格里没有的端口,防火墙默认就是拒绝。有了这张表,后续任何异常端口开放都容易发现。另外还要注意几个细节:不要用iptables默认的allow all from anywhere这种粗放规则,来源尽量收敛到IP段或具体IP。管理类端口(SSH、数据库、监控面板)不要暴露在公网,要么走内网,要么走堡垒机。某些服务如果只有内网访问需求,监听地址直接绑到内网IP,而不是0.0.0.0。我会在配置服务时顺手把监听地址一起改了,比如MySQL只监听内网IP:bind-address = 192.168.x.x这比单独依赖防火墙又多了一层保护,属于典型的纵深防御思路。3.3 防火墙规则的常见坑:顺序与持久化防火墙规则里有个容易被忽视的问题:规则顺序。比如先插入了一条DROP ALL,又追加了一条ALLOW 22,那22端口依然被前面那条DROP挡掉了。iptables是顺序匹配的,所以规则顺序错了,结果完全相反。所以更稳妥的方式是先落默认策略再写放行规则,或者用像ufw这样的前端工具来管理,它内部会处理好顺序。如果是纯iptables环境,我建议把配置写成脚本文件,而不是在命令行一条条敲,这样既能保证顺序可控,也能后续维护复用。持久化是另一个高频坑。命令行配置的iptables规则,重启后会全部丢失。我一般用iptables-save导出规则文件,再配置开机自动恢复:iptables-save > /etc/iptables/rules.v4这样至少在系统重启后,规则不会悄然消失。4. 文件系统、内核参数与存储保护4.1 权限基线:关键目录与SUID排查Linux文件权限的默认值通常不会太离谱,但时间一长,各种手工操作会把权限搅乱。比如有人图省事chmod -R 777,又或者把web目录直接设成nobody用户可写,一旦Web层面有漏洞,攻击者就能直接写马。我建议做一次全盘权限基线检查,重点关注这几个点:/etc/shadow必须只有root可读,其他账户不可读不可写。所有SUID文件都要过一遍。SUID位意味着任何用户执行这个文件时,都以文件属主身份运行,一旦有可利用的漏洞,很容易提权。排查SUID文件可以用一条命令:find / -perm -4000 -type f 2>/dev/null正常的SUID文件集中在/usr/bin、/usr/lib等系统目录里,一般是passwd、sudo、mount这类程序。如果某个奇怪目录下出现SUID文件,比如/tmp、/var/tmp、/home下,高度怀疑是入侵痕迹,立即删除或进一步排查。对于web目录,我习惯的基线是:代码文件属主是应用账户,目录权限755、文件权限644,只有需要写入的upload、cache目录放开写权限,并且要将传入的文件与可执行权限彻底隔离。这样即使攻击者上传了脚本,也因为没有执行权限而无法运行。4.2 内核参数:几个值得开启的加固项内核参数层面的加固,不需要做太多,重点调整几个和安全强相关的选项就行。配置文件在/etc/sysctl.conf或/etc/sysctl.d/下,调整后执行sysctl -p生效。比较值得关注的几个参数:# 禁止响应ICMP重定向,防止中间人通过重定向改路由 net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.all.send_redirects = 0 # 开启IP转发(如果需要做转发,则设1,否则建议设为0) net.ipv4.ip_forward = 0 # 开启TCP SYN Cookie,缓解SYN Flood攻击 net.ipv4.tcp_syncookies = 1 # 关闭源路由,防止基于源路由的绕过 net.ipv6.conf.all.accept_source_route = 0这些参数单独看都没什么“大招”感,但它们把一些不太被注意的网络层攻击路径封死了。尤其是将tcp_syncookies和accept_redirects处理好之后,短期内就能明显感觉扫描利用的成功率下降。另外还有一个我踩过坑的细节:修改sysctl前先确认参数在当前内核里存在,有些老内核版本不支持个别参数,直接写进去会导致sysctl -p报错,有时候还会影响启动流程。4.3 数据加密:磁盘层与文件层系统加固很容易只盯着“不让别人进来”,却忽视了“数据被偷走之后还能不能用”这一层。磁盘加密的作用就在这里,尤其对于笔记本、移动工作站、带敏感数据的物理机,全盘加密能保证硬盘被人拔走也读不出有效内容。Linux下常用LUKS做磁盘加密。创建加密分区的过程倒不复杂,但需要注意几点:加密后一定要备份LUKS头部信息,否则一旦头部损坏,数据基本无法恢复。开机密码要设成强密码,但也要保证在紧急情况下能找到解密途径,建议把恢复密钥放到加密分区之外的安全位置。对已经运行很久、数据量很大的服务器做全盘加密迁移难度不小,需要停机、数据迁移、恢复验证,生产环境谨慎评估。如果做不了全盘加密,至少要对敏感文件做加密存储,比如用gpg加密备份数据库、用openssl加密配置文件。我见过很多备份明文躺在备份服务器上,主服务器安全做得很好,备份数据反倒成了突破口。5. 安全工具:装上不等于安全5.1 自动安全更新,怎么配更稳Linux发行版都有自动更新机制,比如Debian/Ubuntu下有unattended-upgrades,RHEL系有dnf-automatic。自动更新这个事,既要开又怕开:不开的话漏洞补丁打不上,全开的话又可能碰上更新导致服务重启、驱动不兼容。我的做法是折中策略:安全更新自动装,非安全更新手动评估。在/etc/apt/apt.conf.d/50unattended-upgrades里,把安全更新源打开:Unattended-Upgrade::Allowed-Origins { "${distro_id}:${distro_codename}-security"; };同时设置自动重启策略,把需要重启的内核更新也纳入管理,但指定重启时间段,避免深夜跑着任务被重启打断:Unattended-Upgrade::Automatic-Reboot "true"; Unattended-Upgrade::Automatic-Reboot-Time "03:00";这里有一点要提醒:自动更新只是一个兜底,关键应用的版本升级和兼容性验证仍要靠人工流程。别把自动更新当成“什么都不管”的理由,尤其线上有自编译模块、内核模块、第三方源的情况,自动更新经常引发兼容性问题。5.2 fail2ban的实际配置经验前面提到过fail2ban在SSH上的基础用法,其实它的能力远不止拦截SSH爆破。Nginx、Apache、Postfix、vsftpd这些服务都有现成的filter,配置好了也能防目录扫描和密码爆破。我常用的是给Nginx加一条防目录扫描的规则,效果不错。写一个filter文件:[Definition] failregex = ^<HOST> .* "(GET|POST|HEAD) .* (\.php|\.env|\.git).*" (403|404) ignoreregex =然后在jail配置里启用:[nginx-scan] enabled = true port = http,https filter = nginx-scan logpath = /var/log/nginx/access.log maxretry = 5 findtime = 300 bantime = 1800这样配置之后,短时间连续命中敏感路径的IP会被自动封禁半小时。实际部署后,日志里那些扫描.env、.git目录的请求明显消停了。需要注意filter的正则写好后一定要用fail2ban-regex测试,否则规则不生效,fail2ban会在日志里静默跳过。5.3 审计日志与监控告警日志和审计是最后一道兜底,平时不起眼,出事后才知道重要。我建议至少完成三件事:第一,打开系统审计服务auditd,配置对关键文件的改动监控。比如监控/etc/passwd、/etc/shadow、/etc/ssh/sshd_config的写操作,一旦被改动,立刻记入审计日志。auditctl -w /etc/passwd -p wa -k passwd-changes auditctl -w /etc/ssh/sshd_config -p wa -k sshd-config第二,把系统日志做一下集中转发,至少保证日志不被本地轻易篡改。最简单的方式是配置远程syslog,把auth.log、syslog等核心日志发到独立的日志服务器上。第三,设一个简单的告警,定期扫描auth.log中的异常登录现象。我一般写个小脚本配合cron,每天检查认证日志里是否有陌生IP登录成功,有就发通知。这个脚本不用很复杂,但要保证它一直在跑。5.4 主机完整性校验:校验文件,而不是猜测文件完整性校验工具能够给关键文件建立初始哈希库,之后定期比对,一旦有文件被篡改就会报警。常见选择是AIDE这类工具。AIDE的配置思路比较直接:初始化数据库后,定期检查。初始化动作:aideinit mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz然后定时执行:aide --check实际使用中要注意两点:一是初始化数据库必须在系统刚装好、确认没有被入侵的状态下做,否则保留的是“脏状态”;二是系统升级、改配置文件后,需要重新更新数据库,否则会持续误报。我的做法是把AIDE检查和配置发布的流程绑定在一起,每次有合法变更后就手动更新一次数据库,同时保留历史数据库,方便回溯。6. 常见问题与排查技巧实录6.1 改完SSH配置连不上了这是最高频的事故,我早期也翻过车。改sshd_config时把PasswordAuthentication no一开,然后直接在当前会话里重启了sshd,但密钥认证那边又出了点小问题,结果一断开就再也连不回去,最后只能跑机房。后来我总结了一套不容易出事的流程:改SSH配置前,先另开一个终端窗口,保持一个长连接不要关。用sshd -t检查配置语法,确保没有语法错误再重启。重启服务不选restart,用reload,这样可以保留现有连接不断开。如果用的是systemctl restart sshd这类命令,也先确认另一个终端连接还在,万一有问题还能原地改回来。如果已经锁在外面,且无法通过带外管理卡操作,那就比较被动了。所以这个流程不是建议,是强需求,务必遵守。6.2 防火墙把自己关在门外防火墙规则的调整同样容易出事故,尤其是搞不清当前规则顺序的时候。我遇到过一次,给数据库端口加新规则,顺手把原来的放行规则覆盖了,结果所有应用连接数据库全部超时,业务直接不可用。排查思路其实比较直接,先看规则列表,再逐条确认:iptables -L -n --line-numbers如果是临时加规则,建议用-I把放行规则插在DROP规则前面;删除规则时一定要先确认删除的是哪一条,别按“看起来像”的那条删。更稳妥的办法是先把规则导出一份备份,再改配置,改完原样导出一份新规则做对比。改完后立即用新的SSH连接、服务请求测试一遍,别等到隔天线上出问题再去追。6.3 自动更新导致服务起不来自动更新本身是好意,但第三方源和自编译组件很容易因为依赖升级而出现“起不来的服务”。最典型的是升级了底层动态库版本,Web服务开始报错;或者内核更新后,内核模块不匹配,整个系统根本进不去图形界面或者网络起不来。我的处理策略是这样:第三方软件源尽量少用,只保留发行版官方源和确需的厂商源。自编译的服务,建议把二进制和相关依赖放在独立目录,比如/opt/app,减小被系统更新波及的概率。更新前看一遍更新列表,遇到内核、libc这类大组件,先确认有没有第三方依赖它们。真出问题时,先看journalctl -u 服务名的报错日志,多数情况下能找到缺失的库文件或版本不匹配的线索。如果服务被更新覆盖了配置,备份目录里翻一下旧配置,改回去并且暂时锁住对应软件包的版本。6.4 误报与性能开销安全工具装多了,误报和性能开销不可避免。比如fail2ban的规则写太严,会把正常用户扫个文件就封掉;AIDE在文件多的目录上做全量比对,会产生明显的CPU和IO开销。我的建议是分级处理:核心资产和敏感目录做高频率监控和严格策略,普通业务目录放宽阈值。像是几百G的文件目录,AIDE没必要全量监控,挑出配置文件和关键二进制目录就够了。fail2ban的封禁时间也不宜太长,配合人工复核比一刀切更符合实际情况。另外,很多安全工具的日志量非常大,尤其是审计服务全开时,一天能产生几个GB的日志。需要提前规划磁盘空间或者日志轮转策略,避免日志反过来把系统盘塞满。7. 我的实际体验与最后几句建议这些年做过的加固项目里,我最大的体会是:Linux系统安全加固并不是什么高深莫测的事,它更像是一个整理房间的过程,把不需要的东西丢掉,把重要的东西锁好,把窗户和门的位置搞清楚。只要思路清晰、步骤明确,一台默认状态的Linux服务器可以在很短的时间内收敛到一个比较安全的基线之上。有几个经验想单独拿出来再强调一次。第一,所有改动必须可回退。改SSH、防火墙之前,先把旧配置备份好,确认自己的逃生通道还存在,这比任何华丽的安全策略都重要。第二,加固文档至少要包含端口清单、账户清单、关键配置基线这三张表,否则过三个月后没人能说清这台机器到底开了什么。第三,监控和安全工具不是越多越好,报警量和误报率需要平衡,否则告警风暴会导致真正重要的问题被淹没。如果你正在做Linux环境的安全加固,建议从最简单的三步开始:关掉不需要的端口、禁用root密码登录、清理可登录账户。这三步做完,这台机器的基本安全性已经超过绝大多数默认安装的服务器了。然后在此基础上逐步完善审计、监控、文件完整性校验这些成本更高的环节。安全是个持续迭代的过程,不是某个时间点一劳永逸的事,保持定期的基线复查习惯,比偶尔一次大扫除更有价值。
刚接手一台别人维护过的Linux服务器时,我一般不会急着部署业务,也不会先冲上去装各种安全软件。第一件事永远是做一遍系统安全基线梳理:看看哪些端口开着、哪些用户能登录、哪些服务在监听、关键文件权限是否合适。这个习惯保持了很长时间,帮我在不少项目里提前避开了麻烦。所谓“系统安全加固”,说穿了就是把一台默认配置的机器,一点点收成符合业务需要的最小开放形态,让不该出现的东西不出现,不该打开的通道都关掉。这篇文章我打算以Linux环境为场景完整展开,覆盖账户体系、SSH登录入口、防火墙收敛、文件系统权限、内核参数以及常用安全工具的配置。里面所有操作步骤、配置参数、排查思路,都是我在真实环境里验证过的,也会把我踩过的坑一并写出来。适合正在做服务器基线加固、准备上生产环境、或者刚接手老旧系统的运维和开发同学参考。1. 加固思路:先把“为什么”想清楚1.1 加固不是装软件,是收敛暴露面很多人一说安全加固,第一反应是装个杀毒软件、装个安全狗、装个检测Agent。这其实把顺序搞反了。安全的核心思路是暴露面收敛,如果一个服务根本不在监听、一个端口根本不对公网开放,那攻击者连接触的机会都没有,比任何检测工具都有效。Linux默认安装以后,系统会打开很多用不到的端口和服务。比如有些发行版默认开着CUPS打印服务、Avahi广播、Telnet等旧协议,这些平时业务根本用不上,却是攻击者最爱扫描的入口。加固的第一步不是一个一个去堵漏洞,而是先盘清楚“这台机器上到底跑着什么”,然后把你不需要的东西全部停掉。我通常会在拿到一台机器后,先跑一遍端口和服务盘点,再结合业务确认最小集合。这个环节花不了多少时间,但整个加固方案的依据都在这里,跳过这一步后面很容易做出“看起来很安全实则稀烂”的配置。1.2 最小权限原则怎么落到每一条命令上最小权限不是一个悬在空中的概念,它最后会落到账户、文件权限、sudo授权、防火墙策略这些非常具体的对象上。比如一个应用账户,它只需要读取自己的配置文件和日志,那就不该有/home下其他用户的读权限;只允许通过特定IP访问SSH,那防火墙策略就该精确到IP而不是“全部允许”。落到实操里,我一般会坚持几条很朴素的规则:能用普通用户跑的进程,绝不用root跑。像Nginx的master process可以绑定80端口后降权,Web应用的服务账户单独建,数据目录所有权独立隔离。sudo授权精确到命令,不直接给ALL。比如某个维护账号只需要重启某个服务,那sudoers里就只写那一条命令。文件权限能收则收,目录默认755、文件默认644是在没有特殊需求的前提下,特殊目录根据实际需要单独处理。这些动作单独看都很小,但组合起来会让攻击者即使拿到一个低权限shell,横向移动成本也高很多。1.3 纵深防御:即使一层被突破,也要有兜底我的加固方案从来不是单点防御。比如SSH改成了密钥登录,这只是防住了口令爆破,如果本机账户密码被撞库,攻击者还能绕过密钥直接凭密码登录吗?所以还要配合禁用密码登录、限制登录用户、fail2ban、防火墙只放行指定来源,形成链条。纵深防御在Linux环境里的表现就是:网络层有防火墙收敛端口,应用层有服务降权,系统层有文件权限基线,登录层有密钥认证和来源限制,最后还有审计日志记录所有敏感操作。每一层不追求绝对安全,但即使某一层失灵了,下一层还能兜底。提示:做加固最忌讳“毕其功于一役”的心态,任何单一配置都不能保证安全。参考上面这个思路,逐层检查、逐层加固,会发现系统安全性提升的不是一点半点。2. SSH与账户体系:登录入口是第一道门2.1 禁用root直接登录,先改成密钥认证Linux服务器的登录入口是最容易被打爆的地方,尤其是SSH端口暴露在公网的情况下,密码爆破几乎是24小时不间断的。默认情况下root可以直接登录,而且很多初始化场景还开着密码认证,这等于把最高权限账户暴露在暴力破解的射程内。改法也不复杂,先创建普通用户并加入sudo组,然后把自己本地的公钥拷过去,确认能通过普通用户登录后,再去修改SSH配置。# 创建普通用户,加入sudo组 useradd -m -s /bin/bash ops passwd ops usermod -aG sudo ops # 把本机公钥放到目标机器的用户目录 ssh-copy-id ops@server_ip接下来修改SSH服务端配置,这一步建议先开一个新终端保持一个已有连接,避免改错了把自己锁在外面:sudo vim /etc/ssh/sshd_config重点改这几个参数,改完重启sshd服务:# 禁用root直接登录 PermitRootLogin no # 关闭密码认证,只允许密钥 PasswordAuthentication no # 限制能登录的用户,白名单模式 AllowUsers ops # 空密码账户禁止登录,默认就是no,保持住 PermitEmptyPasswords no实测下来的效果很明显:关闭密码认证后,SSH端口上的暴力破解日志数量会断崖式下降,因为攻击者拿到你的公钥文件几乎没有可能,剩下的扫描再频繁也只是空转。2.2 清理无效账户和组,关掉不必要的登录Shell系统安装后默认会有很多系统账户,这些账户本身没有登录Shell,危害不大,但如果某个账户被改成可登录Shell,又刚好是弱口令,就成了一个后门。所以我做加固时会统一盘一遍账户列表。# 列出所有可登录账户 awk -F: '($7 != "/usr/sbin/nologin" && $7 != "/bin/false") {print $1, $7}' /etc/passwd正常环境里,可登录账户应该只有root、自己创建的运维账户、业务应用账户(如果有需要)。其余的一律改成nologin或者/bin/false,让它们无法交互登录:usermod -s /usr/sbin/nologin 用户名还要检查一下空密码账户和UID为0的账户。UID为0意味着管理员权限,如果非root账户出现UID为0,基本可以判定是有问题的,需要立刻处理:# 检查空密码账户 awk -F: '($2 == "") {print $1}' /etc/shadow # 检查UID为0的账户 awk -F: '($3 == 0) {print $1}' /etc/passwd用户组也值得花两分钟过一遍,重点看sudo组和wheel组里有没有不该出现的成员,以及是否存在遗留的旧员工账户。我见过不少项目,人员都换了好几轮了,服务器上还留着离职同事的账户,一旦口令泄露就是个入口。2.3 登录保护:失败限制与二次验证即使启用了密钥认证,SSH也建议做一层登录保护。因为总有些场景需要开着密码登录,比如部分堡垒机对接、安全合规要求。这种情况下,可以用fail2ban对多次认证失败的来源IP做临时封禁。我一般在/etc/fail2ban/jail.local里这样配置:[sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 3600maxretry 3表示同一个来源IP尝试3次失败就封禁,bantime 3600表示封1小时。参数可以根据线上误判情况调整,但不要设成太宽松,否则失去了拦截意义。我习惯把findtime也配上,表示多长时间窗口内累计次数:findtime = 600这表示10分钟内失败3次就触发封禁,比单纯累计次数合理一些。如果对安全性要求更高,还可以考虑给SSH加上二次验证,比如配置TOTP动态口令。不过这个方案涉及客户端也要装验证器,团队规模大时推行成本不低。我一般的建议是:先用密钥加fail2ban把绝大部分爆破挡掉,二次验证作为可选增强项,在核心跳板机、数据库运维机上优先启用。3. 网络层收敛:防火墙不是装饰品3.1 默认拒绝策略,先把大原则定下来很多服务器自带防火墙,但默认策略基本上是“放行所有流量”,等于没装。我理解的防火墙策略只有一条硬原则:默认拒绝,显式放行。所有流量先禁止,然后根据业务需求一条一条加白名单。以iptables为例,先把默认策略全部改成DROP:iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT这样配置完以后,这台机器基本与外界隔绝了,然后我们再自己执行“开放”。这种方式的好处是,你每放行一个端口都经过思考,不会有“反正开着就先留着”的侥幸。不过现在很多Linux发行版也推荐用nftables或者ufw作为前端,底层逻辑是一样的。比如用ufw做默认拒绝就简洁一些:ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp ufw allow 80/tcp ufw enable这里22端口是SSH、80端口是Web应用,其他端口继续保持关闭状态。注意:改防火墙策略前,一定先确认自己有一条能连接到服务器的通道,比如通过带外管理口或者控制台。网络层配置失误导致“远程把自己锁死”的案例,我身边发生过太多次了。3.2 最小端口清单:一眼能看懂这台机器在干嘛整理防火墙策略时,我会顺手做一张端口清单表,写清楚端口号、协议、服务名、放行来源、用途。这个表格既是加固文档的一部分,也是未来排查问题的依据。端口协议服务放行来源说明22TCPsshd办公网出口IP远程登录入口80TCPnginx所有HTTP入口443TCPnginx所有HTTPS入口3306TCPmysqld内网应用服务器网段数据库访问凡是表格里没有的端口,防火墙默认就是拒绝。有了这张表,后续任何异常端口开放都容易发现。另外还要注意几个细节:不要用iptables默认的allow all from anywhere这种粗放规则,来源尽量收敛到IP段或具体IP。管理类端口(SSH、数据库、监控面板)不要暴露在公网,要么走内网,要么走堡垒机。某些服务如果只有内网访问需求,监听地址直接绑到内网IP,而不是0.0.0.0。我会在配置服务时顺手把监听地址一起改了,比如MySQL只监听内网IP:bind-address = 192.168.x.x这比单独依赖防火墙又多了一层保护,属于典型的纵深防御思路。3.3 防火墙规则的常见坑:顺序与持久化防火墙规则里有个容易被忽视的问题:规则顺序。比如先插入了一条DROP ALL,又追加了一条ALLOW 22,那22端口依然被前面那条DROP挡掉了。iptables是顺序匹配的,所以规则顺序错了,结果完全相反。所以更稳妥的方式是先落默认策略再写放行规则,或者用像ufw这样的前端工具来管理,它内部会处理好顺序。如果是纯iptables环境,我建议把配置写成脚本文件,而不是在命令行一条条敲,这样既能保证顺序可控,也能后续维护复用。持久化是另一个高频坑。命令行配置的iptables规则,重启后会全部丢失。我一般用iptables-save导出规则文件,再配置开机自动恢复:iptables-save > /etc/iptables/rules.v4这样至少在系统重启后,规则不会悄然消失。4. 文件系统、内核参数与存储保护4.1 权限基线:关键目录与SUID排查Linux文件权限的默认值通常不会太离谱,但时间一长,各种手工操作会把权限搅乱。比如有人图省事chmod -R 777,又或者把web目录直接设成nobody用户可写,一旦Web层面有漏洞,攻击者就能直接写马。我建议做一次全盘权限基线检查,重点关注这几个点:/etc/shadow必须只有root可读,其他账户不可读不可写。所有SUID文件都要过一遍。SUID位意味着任何用户执行这个文件时,都以文件属主身份运行,一旦有可利用的漏洞,很容易提权。排查SUID文件可以用一条命令:find / -perm -4000 -type f 2>/dev/null正常的SUID文件集中在/usr/bin、/usr/lib等系统目录里,一般是passwd、sudo、mount这类程序。如果某个奇怪目录下出现SUID文件,比如/tmp、/var/tmp、/home下,高度怀疑是入侵痕迹,立即删除或进一步排查。对于web目录,我习惯的基线是:代码文件属主是应用账户,目录权限755、文件权限644,只有需要写入的upload、cache目录放开写权限,并且要将传入的文件与可执行权限彻底隔离。这样即使攻击者上传了脚本,也因为没有执行权限而无法运行。4.2 内核参数:几个值得开启的加固项内核参数层面的加固,不需要做太多,重点调整几个和安全强相关的选项就行。配置文件在/etc/sysctl.conf或/etc/sysctl.d/下,调整后执行sysctl -p生效。比较值得关注的几个参数:# 禁止响应ICMP重定向,防止中间人通过重定向改路由 net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.all.send_redirects = 0 # 开启IP转发(如果需要做转发,则设1,否则建议设为0) net.ipv4.ip_forward = 0 # 开启TCP SYN Cookie,缓解SYN Flood攻击 net.ipv4.tcp_syncookies = 1 # 关闭源路由,防止基于源路由的绕过 net.ipv6.conf.all.accept_source_route = 0这些参数单独看都没什么“大招”感,但它们把一些不太被注意的网络层攻击路径封死了。尤其是将tcp_syncookies和accept_redirects处理好之后,短期内就能明显感觉扫描利用的成功率下降。另外还有一个我踩过坑的细节:修改sysctl前先确认参数在当前内核里存在,有些老内核版本不支持个别参数,直接写进去会导致sysctl -p报错,有时候还会影响启动流程。4.3 数据加密:磁盘层与文件层系统加固很容易只盯着“不让别人进来”,却忽视了“数据被偷走之后还能不能用”这一层。磁盘加密的作用就在这里,尤其对于笔记本、移动工作站、带敏感数据的物理机,全盘加密能保证硬盘被人拔走也读不出有效内容。Linux下常用LUKS做磁盘加密。创建加密分区的过程倒不复杂,但需要注意几点:加密后一定要备份LUKS头部信息,否则一旦头部损坏,数据基本无法恢复。开机密码要设成强密码,但也要保证在紧急情况下能找到解密途径,建议把恢复密钥放到加密分区之外的安全位置。对已经运行很久、数据量很大的服务器做全盘加密迁移难度不小,需要停机、数据迁移、恢复验证,生产环境谨慎评估。如果做不了全盘加密,至少要对敏感文件做加密存储,比如用gpg加密备份数据库、用openssl加密配置文件。我见过很多备份明文躺在备份服务器上,主服务器安全做得很好,备份数据反倒成了突破口。5. 安全工具:装上不等于安全5.1 自动安全更新,怎么配更稳Linux发行版都有自动更新机制,比如Debian/Ubuntu下有unattended-upgrades,RHEL系有dnf-automatic。自动更新这个事,既要开又怕开:不开的话漏洞补丁打不上,全开的话又可能碰上更新导致服务重启、驱动不兼容。我的做法是折中策略:安全更新自动装,非安全更新手动评估。在/etc/apt/apt.conf.d/50unattended-upgrades里,把安全更新源打开:Unattended-Upgrade::Allowed-Origins { "${distro_id}:${distro_codename}-security"; };同时设置自动重启策略,把需要重启的内核更新也纳入管理,但指定重启时间段,避免深夜跑着任务被重启打断:Unattended-Upgrade::Automatic-Reboot "true"; Unattended-Upgrade::Automatic-Reboot-Time "03:00";这里有一点要提醒:自动更新只是一个兜底,关键应用的版本升级和兼容性验证仍要靠人工流程。别把自动更新当成“什么都不管”的理由,尤其线上有自编译模块、内核模块、第三方源的情况,自动更新经常引发兼容性问题。5.2 fail2ban的实际配置经验前面提到过fail2ban在SSH上的基础用法,其实它的能力远不止拦截SSH爆破。Nginx、Apache、Postfix、vsftpd这些服务都有现成的filter,配置好了也能防目录扫描和密码爆破。我常用的是给Nginx加一条防目录扫描的规则,效果不错。写一个filter文件:[Definition] failregex = ^<HOST> .* "(GET|POST|HEAD) .* (\.php|\.env|\.git).*" (403|404) ignoreregex =然后在jail配置里启用:[nginx-scan] enabled = true port = http,https filter = nginx-scan logpath = /var/log/nginx/access.log maxretry = 5 findtime = 300 bantime = 1800这样配置之后,短时间连续命中敏感路径的IP会被自动封禁半小时。实际部署后,日志里那些扫描.env、.git目录的请求明显消停了。需要注意filter的正则写好后一定要用fail2ban-regex测试,否则规则不生效,fail2ban会在日志里静默跳过。5.3 审计日志与监控告警日志和审计是最后一道兜底,平时不起眼,出事后才知道重要。我建议至少完成三件事:第一,打开系统审计服务auditd,配置对关键文件的改动监控。比如监控/etc/passwd、/etc/shadow、/etc/ssh/sshd_config的写操作,一旦被改动,立刻记入审计日志。auditctl -w /etc/passwd -p wa -k passwd-changes auditctl -w /etc/ssh/sshd_config -p wa -k sshd-config第二,把系统日志做一下集中转发,至少保证日志不被本地轻易篡改。最简单的方式是配置远程syslog,把auth.log、syslog等核心日志发到独立的日志服务器上。第三,设一个简单的告警,定期扫描auth.log中的异常登录现象。我一般写个小脚本配合cron,每天检查认证日志里是否有陌生IP登录成功,有就发通知。这个脚本不用很复杂,但要保证它一直在跑。5.4 主机完整性校验:校验文件,而不是猜测文件完整性校验工具能够给关键文件建立初始哈希库,之后定期比对,一旦有文件被篡改就会报警。常见选择是AIDE这类工具。AIDE的配置思路比较直接:初始化数据库后,定期检查。初始化动作:aideinit mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz然后定时执行:aide --check实际使用中要注意两点:一是初始化数据库必须在系统刚装好、确认没有被入侵的状态下做,否则保留的是“脏状态”;二是系统升级、改配置文件后,需要重新更新数据库,否则会持续误报。我的做法是把AIDE检查和配置发布的流程绑定在一起,每次有合法变更后就手动更新一次数据库,同时保留历史数据库,方便回溯。6. 常见问题与排查技巧实录6.1 改完SSH配置连不上了这是最高频的事故,我早期也翻过车。改sshd_config时把PasswordAuthentication no一开,然后直接在当前会话里重启了sshd,但密钥认证那边又出了点小问题,结果一断开就再也连不回去,最后只能跑机房。后来我总结了一套不容易出事的流程:改SSH配置前,先另开一个终端窗口,保持一个长连接不要关。用sshd -t检查配置语法,确保没有语法错误再重启。重启服务不选restart,用reload,这样可以保留现有连接不断开。如果用的是systemctl restart sshd这类命令,也先确认另一个终端连接还在,万一有问题还能原地改回来。如果已经锁在外面,且无法通过带外管理卡操作,那就比较被动了。所以这个流程不是建议,是强需求,务必遵守。6.2 防火墙把自己关在门外防火墙规则的调整同样容易出事故,尤其是搞不清当前规则顺序的时候。我遇到过一次,给数据库端口加新规则,顺手把原来的放行规则覆盖了,结果所有应用连接数据库全部超时,业务直接不可用。排查思路其实比较直接,先看规则列表,再逐条确认:iptables -L -n --line-numbers如果是临时加规则,建议用-I把放行规则插在DROP规则前面;删除规则时一定要先确认删除的是哪一条,别按“看起来像”的那条删。更稳妥的办法是先把规则导出一份备份,再改配置,改完原样导出一份新规则做对比。改完后立即用新的SSH连接、服务请求测试一遍,别等到隔天线上出问题再去追。6.3 自动更新导致服务起不来自动更新本身是好意,但第三方源和自编译组件很容易因为依赖升级而出现“起不来的服务”。最典型的是升级了底层动态库版本,Web服务开始报错;或者内核更新后,内核模块不匹配,整个系统根本进不去图形界面或者网络起不来。我的处理策略是这样:第三方软件源尽量少用,只保留发行版官方源和确需的厂商源。自编译的服务,建议把二进制和相关依赖放在独立目录,比如/opt/app,减小被系统更新波及的概率。更新前看一遍更新列表,遇到内核、libc这类大组件,先确认有没有第三方依赖它们。真出问题时,先看journalctl -u 服务名的报错日志,多数情况下能找到缺失的库文件或版本不匹配的线索。如果服务被更新覆盖了配置,备份目录里翻一下旧配置,改回去并且暂时锁住对应软件包的版本。6.4 误报与性能开销安全工具装多了,误报和性能开销不可避免。比如fail2ban的规则写太严,会把正常用户扫个文件就封掉;AIDE在文件多的目录上做全量比对,会产生明显的CPU和IO开销。我的建议是分级处理:核心资产和敏感目录做高频率监控和严格策略,普通业务目录放宽阈值。像是几百G的文件目录,AIDE没必要全量监控,挑出配置文件和关键二进制目录就够了。fail2ban的封禁时间也不宜太长,配合人工复核比一刀切更符合实际情况。另外,很多安全工具的日志量非常大,尤其是审计服务全开时,一天能产生几个GB的日志。需要提前规划磁盘空间或者日志轮转策略,避免日志反过来把系统盘塞满。7. 我的实际体验与最后几句建议这些年做过的加固项目里,我最大的体会是:Linux系统安全加固并不是什么高深莫测的事,它更像是一个整理房间的过程,把不需要的东西丢掉,把重要的东西锁好,把窗户和门的位置搞清楚。只要思路清晰、步骤明确,一台默认状态的Linux服务器可以在很短的时间内收敛到一个比较安全的基线之上。有几个经验想单独拿出来再强调一次。第一,所有改动必须可回退。改SSH、防火墙之前,先把旧配置备份好,确认自己的逃生通道还存在,这比任何华丽的安全策略都重要。第二,加固文档至少要包含端口清单、账户清单、关键配置基线这三张表,否则过三个月后没人能说清这台机器到底开了什么。第三,监控和安全工具不是越多越好,报警量和误报率需要平衡,否则告警风暴会导致真正重要的问题被淹没。如果你正在做Linux环境的安全加固,建议从最简单的三步开始:关掉不需要的端口、禁用root密码登录、清理可登录账户。这三步做完,这台机器的基本安全性已经超过绝大多数默认安装的服务器了。然后在此基础上逐步完善审计、监控、文件完整性校验这些成本更高的环节。安全是个持续迭代的过程,不是某个时间点一劳永逸的事,保持定期的基线复查习惯,比偶尔一次大扫除更有价值。