做Linux运维这些年,我处理过不少服务器,也亲眼见过几次入侵后的现场。说句实在话,Linux系统安全这件事,太容易被排在“先跑业务”后面了,等想起来补的时候,往往已经付出了不小的代价。这篇文章我打算把Linux系统安全涉及的核心内容完整拆一遍,包括安全架构认知、账户认证管控、文件权限防篡改、SSH加固、日志审计还有入侵排查,每块都给出可以直接照做的命令、配置和参数,适合刚接手服务器管理的新手,也适合已经干了几年运维但想系统梳理安全体系的同学。
1. 安全架构认知:把Linux安全当成一套纵深体系
1.1 纵深防御:为什么单点加固救不了场
我见过不少这样的加固方式:随便改个密码、关掉一个端口,就觉得“安全了”。但实际效果很有限,因为安全是一个体系,不是拼图碎片的堆叠。你想想小区的安保:大门保安是第一道,单元门禁是第二道,房间门锁是第三道,监控摄像头则是事后追溯的关键。如果只加固一道门,被突破了就全完了。
Linux系统的安全模型也是这样一层套一层,我习惯把它分成几个逻辑层次来看:
- 账户认证层:系统怎么确认“你是谁”,密码策略、账户锁定、sudo权限都属于这一层。
- 权限授权层:确认身份之后,你“能做什么”,文件权限、ACL、特殊权限位都在这一层。
- 网络接入层:外部流量如何进入系统,SSH加固、防火墙规则在这一层做拦截。
- 审计追溯层:系统记录了什么,日志、审计、文件完整性校验在事后发挥作用。
很多入侵事件之所以发生,并不是某一层做得太差,而是几层同时存在短板。比如SSH密码太弱(网络接入层失守),结果被人登录进来,恰好系统里有个普通用户挂在sudo组里没有限制(账户认证层失守),再一查文件权限全是777(授权层失守),最后攻击者还能通过修改业务脚本实现长期驻留(审计层没有发现)。任何一层能把风险拦住,结局都会完全不同。
这也是为什么我建议做安全时不要上来就改参数,而是先画一张“风险从哪里来”的图,再按图逐层加固。
1.2 一张表看清主要风险与对应防线
为了让大家建立整体概念,我先列一张自己常用的风险对照表。这张表是我在排查实际问题和设计加固方案时反复用到的,列得不算全,但覆盖了最常见的攻击路径。
| 风险类型 | 常见表现 | 对应防线 |
|---|---|---|
| 暴力破解 | SSH端口持续收到大量失败登录 | 密码复杂度、账户锁定、fail2ban、限制来源IP |
| 弱口令 | 账户密码过于简单或长期不变 | /etc/login.defs密码策略、PAM密码质量模块 |
| 提权利用 | 普通用户通过SUID文件或漏洞获得root | 定期扫描SUID位、及时打补丁、最小化sudo授权 |
| 权限扩散 | 业务目录777、敏感文件任何账户可读 | 严格文件权限、ACL收口、chattr锁关键文件 |
| 恶意脚本驻留 | 计划任务、自启动项被植入挖矿或木马 | 定期检查crontab、systemd自启项、rc.local |
| 痕迹灭失 | 日志被清空、文件时间被篡改 | 日志异地同步、权限锁定日志目录、auditd审计 |
对照这张表,你会发现后面几章的内容基本就是按表里的防线展开的。不是所有风险都能靠某一个工具解决,但每一层至少要有对应的措施兜底,这就是“纵深防御”落到实际操作里的意义。
2. 账户与认证安全:把入口看住
2.1 密码策略落地:改好这两个配置文件
Linux下的密码策略,绝大多数发行版默认是很宽松的。我在一些服务器上见过密码就六位、还持续用了三年以上的情况,这种账户基本等于为攻击者预留了一个后门。做账户安全第一步,就是把密码策略收紧。
要改的配置文件主要有两个,分工完全不一样。第一个是/etc/login.defs,它决定密码的有效期。我常用的配置段是这样的:
PASS_MAX_DAYS 90 PASS_MIN_DAYS 7 PASS_WARN_AGE 7三个参数的含义分别是密码最长使用90天、两次改密至少间隔7天、密码过期前7天开始提醒。这里有个容易踩的坑:/etc/login.defs只对新建用户和后续修改密码时生效,已存在账户的密码过期时间不会自动更新。要让存量用户也按策略执行,需要配合chage命令逐个应用,例如:
chage -M 90 -m 7 -W 7 用户名第二个文件是针对密码复杂度的。现代发行版普遍使用 PAM 的 pwquality 模块,配置文件在/etc/security/pwquality.conf。我会把核心参数调成下面这样:
minlen = 12 dcredit = -1 ucredit = -1 lcredit = -1 ocredit = -1 minclass = 3解释一下:minlen 是指密码最短长度,dcredit、ucredit、lcredit、ocredit 分别代表数字、大写字母、小写字母、特殊字符必须出现的个数,这里的负数含义是“至少包含多少个”,而不是“最多”。minclass 则要求密码至少覆盖三类字符。设置完之后,任何试图通过passwd修改密码的行为都会被这个模块检查。
需要提醒的是,这些配置项对 root 自身不一定生效,因为 root 修改密码通常在 PAM 层会被跳过。生产环境里建议对 root 之外的账户统一执行策略,这也是最小权限原则的一部分。
2.2 账户锁定与暴力破解防护配置
密码再复杂,也架不住一直尝试。暴力破解工具在几十个小时里可以跑上百万次密码组合,所以我们必须让“猜密码”这件事的成本无限抬高。账户锁定就是干这个的。
Linux 系统自带一个账户锁定模块,RHEL 系的发行版叫 pam_faillock,Debian 系常用 pam_tally2。以 pam_faillock 为例,我通常在/etc/pam.d/system-auth和/etc/pam.d/password-auth里配置,核心逻辑是在认证开始前检查是否已被锁定,在认证失败后记录失败次数。
auth required pam_faillock.so preauth audit silent deny=5 unlock_time=900 auth sufficient pam_unix.so auth [default=die] pam_faillock.so authfail audit deny=5 unlock_time=900 account required pam_faillock.so这段配置的意思是连续失败5次就锁定900秒,也就是15分钟。这里必须强调一下顺序问题:pam_faillock 的 authfail 行必须放在 pam_unix.so 之后,因为它的作用是“认证失败之后”记录并拒绝。如果顺序放错,你会遇到一个经典症状:连续输错几次后,即使输入完全正确的密码也登录不了,因为失败分支直接中断了认证流程。
我在实际配置中踩过这个坑,当时第一反应是“系统是不是挂了”,后来才发现是 PAM 顺序问题。所以配置完后一定要用单独的终端保持一个已登录会话不要断开,再开新窗口测试登录,避免把自己锁在外面。
除了系统自带的锁定,生产环境我还会再加一层来源管控。比如只允许办公网段的IP访问SSH端口,那么在防火墙层面就做了第一道过滤,暴力破解的人连端口都探测不到,这个细节放在后文SSH加固部分展开。
2.3 特权账户与sudo收口管理
账户安全还有一个容易被忽略的点:检查哪些账户拥有特权。攻击者拿到一个普通账户后,最想做的事情就是提权到root。如果系统里存在多余的UID为0的账户,那就相当于直接送了一把钥匙。
我建议定期执行下面两个检查。第一个是找出所有UID为0的账户:
awk -F: '($3 == 0) {print $1}' /etc/passwd正常输出应该只有 root 一个名字,如果出现了其他用户名就要立刻排查。第二个是检查哪些账户有空的密码字段:
awk -F: '($2 == "") {print $1}' /etc/shadow空密码账户意味着不需要密码就能登录,这种账户一旦存在,后果不堪设想。另外还要看哪些账户拥有登录Shell、是不是早就该被清理掉:
awk -F: '($7 != "/sbin/nologin" && $7 != "/bin/false") {print $1, $7}' /etc/passwd普通业务账户如果没有交互登录需求,Shell一律改成/sbin/nologin,这样即使密码泄露,也无法通过SSH进入系统。
sudo 的收口管理同样重要。我见过把整个wheel组都加到 sudo 里的配置,等于几十个用户全部有root权限。正确的做法是/etc/sudoers里只保留确实需要管理权限的用户,并且尽量细化命令范围。如果你喜欢直接编辑/etc/sudoers,记得用visudo命令而不是普通编辑器,因为它会在保存前做语法检查,避免写错格式导致整个sudo瘫痪。
3. 文件权限与防篡改:守住数据完整性
3.1 常规权限模型的三个易错点
Linux文件权限看起来简单,却是我在给团队做安全培训时发现误区最多的地方。先说最容易忽略的:目录的 x 权限和文件的 x 权限含义完全不同。目录的 x 权限代表能否把目录当作路径的一部分进入,r 权限代表能否列出目录内容,w 权限代表能否在目录里创建或删除文件。很多人给目录配了 r 却没有 x,于是 ls 能看到列表,cd 却进不去,排查问题时非常迷惑。
第二个易错点是把所有业务文件一概chmod 777。这种做法在测试环境里确实省事,但生产环境里一个777的脚本目录,意味着任何账户都可以向其中写入恶意文件,再配合目录的执行权限,基本等于开放了代码执行能力。我见过的最恶劣案例,就是攻击者往777的spool目录里放了一个可执行的脚本,再利用计划任务周期触发,整个过程完全不需要提权。
第三个值得关注的是权限继承问题。很多运维改权限时只改当前目录,忘了-R递归,或者反过来把不该递归的都递归了。我的习惯是配置好后立刻用ls -ld和find抽查几个关键路径,确认权限符合预期,而不是相信命令一次性执行成功。
3.2 特殊权限位的风险与排查
Linux里除了常见的rwx,还有三个特殊权限位:SUID(4)、SGID(2)、Sticky(1)。其中风险最大的是SUID,因为带有SUID的可执行文件在运行时,进程的有效身份会变成文件属主。典型例子是/bin/passwd,它以root身份运行,普通用户才能借此修改自己的密码。
攻击者拿到一个普通Shell后,如果系统里存在可利用的SUID程序,就可能用它完成提权。我曾经在一次安全测试里遇到过系统/usr/bin/find带SUID位的配置,这意味着任何用户执行 find 时都临时拥有root身份,配合 find 的命令执行参数就能直接拿到高权限。这种问题的修复方式很简单:
chmod -s /usr/bin/find排查系统中所有带SUID位的文件,一条命令就能扫出来:
find / -perm -4000 -type f 2>/dev/nullSGID 的影响要复杂一些。文件带SGID时,执行者会临时获得该组成员的身份;目录带SGID时,里面新建的文件和目录会继承父目录的组。这在协作共享目录里很实用,但如果被错误设置,可能导致普通用户访问到本不该接触的组资源。Sticky位反而是防护性的,常见于/tmp,它保证只有文件属主能删除自己的文件,避免了共享目录里互相删文件的问题。
3.3 用chattr与ACL实现关键文件防篡改
权限模型之上,Linux还有一些更硬核的保护手段,我最常用的两个是 chattr 和 ACL。先讲 chattr,它能给文件加上特殊的属性标志。比如chattr +i表示不可修改,设置之后即使是 root,也没办法对文件做删除、重命名、写入等操作。这个特性用来保护关键配置和业务脚本非常合适。
举个例子,某业务系统里有一个自动发布的脚本,一旦被篡改,发布流程就可能被注入恶意操作。我给脚本执行:
chattr +i /opt/business/release.sh之后任何人都改不动它。等需要更新脚本时,先chattr -i解除锁定,改完再锁回去。用之前一定想清楚,+i是能拦住root的,所以操作时不要锁错文件,尤其是别把用户的主目录或者数据库配置目录直接锁死,否则后续维护会非常麻烦。查看文件是否被加了锁用lsattr。
再来看ACL,ACL允许对单个用户或单个组设置独立权限,而不改变文件原有的属主和属组。命令是setfacl和getfacl。比如我需要让某个运维账号能读日志目录,但不想把它加进日志属组里,可以这样:
setfacl -m u:tom:r /var/log/app/ getfacl /var/log/app/用-R参数可以对目录递归设置ACL,但要注意ACL和普通权限位可能互相覆盖,设置后用getfacl检查是必不可少的环节。
4. SSH远程管理加固:暴露面最小化
4.1 SSH为什么总是第一批被打
如果有一台Linux服务器直接暴露在公网,扫描和暴力破解几乎是24小时不停歇的。SSH服务在默认的22端口上,裸奔的密码认证加上root直接登录,这三个条件凑齐,等于把系统的钥匙挂在门外。我见过最夸张的一次,一天之内某个IP段对一个测试机的22端口尝试了上万次密码组合。
有些同学觉得“我的密码够复杂,不怕”,这话有一定道理,但暴力破解只是其中一种方式。如果系统存在漏洞、密码被人从别的途径泄露,或者内部某台机器被攻破之后横向移动,SSH都是进入内网最顺手的通道。所以SSH的加固核心就三件事:关闭不需要的登录入口、改用更强的认证方式、限制能连进来的来源。
4.2 从密码登录切换到密钥认证
密钥认证是目前性价比最高的SSH登录方式。原理不复杂:客户端生成一对密钥,公钥放到服务器的~/.ssh/authorized_keys里,私钥留在本地。登录时服务器用公钥验证客户端的私钥签名,不通过任何密码明文交换,比密码安全得多。
生成密钥我推荐用 ed25519 算法,比传统的 RSA 更短更快,安全性也足够。命令如下:
ssh-keygen -t ed25519 -a 100 -C "your-comment"其中-a 100是增加密钥派生时的计算次数,目的是防止私钥文件泄露后被快速暴力破解。生成之后把公钥内容追加到服务器的 authorized_keys 里,这一步可以用ssh-copy-id来完成:
ssh-copy-id 用户名@服务器地址确认用密钥能登录之后,再修改/etc/ssh/sshd_config,把密码认证关掉:
PermitRootLogin no PubkeyAuthentication yes PasswordAuthentication no MaxAuthTries 3 LoginGraceTime 30 AllowUsers tom jerry这几行的含义分别是禁止root直接登录、启用公钥认证、关闭密码认证、最多尝试3次、登录超时限制在30秒、只允许指定用户通过SSH登录。改完先检查配置文件语法:
sshd -t确认没有报错后,用systemctl reload sshd重载服务。这里我需要反复提醒一句:不要直接重启,reload 更温和;而且远程操作时必须保持一个已登录会话不要断开,确认新登录方式可用后再关闭旧连接。我遇到过同事修改SSH配置后重启了服务,结果发现自己之前用的密码认证已经关闭,新配置又没生效,人被锁在机器外面,最后只能走物理终端处理,非常狼狈。
4.3 远程管理白名单与防火墙收敛
密钥认证解决的是身份验证问题,但SSH服务还是暴露在网络里。更进一步的做法是把SSH的访问来源限制在白名单内,也就是只允许特定网段的IP连接。以常见的 firewalld 为例,我可以把SSH端口只放给内网管理网段:
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" service name="ssh" accept' firewall-cmd --reload如果是云环境,还需要在安全组层面同步配置来源IP白名单,两层都要收敛。如果确实需要从外部网络管理,可以再叠加一层跳板机或者堡垒机,而不应该直接把生产服务器的SSH端口暴露给所有地址。
另外一个实用操作是修改SSH端口。虽然有争议,但把默认端口从22改成高位端口,确实能大幅减少自动化扫描的噪音。配合防火墙白名单,效果更好。这里要注意,改端口是在 sshd_config 里改Port参数,改完后别忘了同步放行防火墙的新端口,然后把旧端口从防火墙规则里移除。端口不是灵丹妙药,它只挡那些无脑扫描器,真正的攻击者如果盯上你,一样能发现新端口,但作为纵深防御的一环还是值得做。
5. 日志审计与入侵痕迹追踪
5.1 Linux日志体系速览与关键文件
安全做得再好,也难免有万一。一旦发生入侵,日志就是还原现场、确定损失范围的第一手证据。很多运维对日志的熟悉程度其实不够,出了问题只知道去翻/var/log/messages,其实Linux的日志是有分工的。
我最常用的几个日志文件和用途如下:
/var/log/messages:系统通用日志,很多内核和服务的运行信息都在这里。/var/log/secure:认证相关日志,SSH登录成功、失败、sudo授权都在记录。/var/log/cron:计划任务执行日志。/var/log/btmp和/var/log/wtmp:记录登录成功与失败的账户信息,配合last、lastb命令使用。journald:现代发行版的系统日志服务,通过journalctl查询,可以精确按时间和服务过滤。
查看最近登录记录的命令很简单:
last -n 20 lastb -n 20lastb能直观看到多少次失败的登录尝试及其来源IP,这是判断是否被暴力破解的首选命令。
5.2 rsyslog与logrotate的日常配置
日志本身也要做两层设置。第一层是确保安全相关日志有足够长的保存周期,第二层是防止日志把磁盘占满。rsyslog 负责把客户端发来的日志和本机日志按规则写入对应文件,我通常会在/etc/rsyslog.conf或/etc/rsyslog.d/下单独建一个针对安全事件的配置:
authpriv.* /var/log/secure cron.* /var/log/cron需要注意的是,日志目录如果权限太宽,攻击者入侵后就能直接清空日志消灭痕迹。我会把/var/log/secure这类关键日志文件设成只有 root 和 adm 组可读,必要时用前面提到的 chattr+a属性设置成只能追加,不能修改已有内容。+a和+i的区别在于前者允许追加,适合日志这种持续写入的场景。
第二个重点是 logrotate,它负责日志的轮转和清理。默认配置在/etc/logrotate.conf和/etc/logrotate.d/下。我可以为安全日志单独配置:
/var/log/secure { daily rotate 30 compress missingok notifempty }这段配置表示每天轮转、保留30份、压缩旧日志、缺失不报错、空文件也轮转。日志如果永远不轮转,磁盘迟早会被写满,服务反而会出问题。配完可以用logrotate -d /etc/logrotate.conf做预演,确认语法没有问题。
5.3 一次入侵排查的完整流程记录
日志排查不仅仅是看文件,更要形成一套有序的排查流程。我举一个实际处理过的典型场景:某台内网测试服务器突然CPU飙高,业务虽然没挂但明显不对劲。
我登录后的第一件事是看进程状态,发现一个名字伪装成系统内核进程的可疑进程占用CPU超过100%,进程名和系统里的kworker极其相似,但不属于标准的内核线程列表。接着我用下面的命令确认进程的可执行文件路径:
ls -al /proc/[进程PID]/exe结果发现它的可执行文件指向/tmp/.x/upd,这个路径已经足够说明问题。/tmp下的隐藏目录加随机文件名,是恶意脚本最常见的驻留方式。
第二步检查计划任务,执行crontab -l查看当前用户的计划任务,同时在/etc/cron.d/、/etc/cron.daily/等目录里翻了一遍,发现了一条每天定时执行下载脚本的任务,脚本指向外部地址。这就是典型的持久化后门。
第三步检查开机自启动项,用systemctl list-unit-files --type=service --state=enabled列出开机启动的服务,重点看有没有名字看起来像系统服务但实际上指向非系统目录的单元。同时查看/etc/rc.local和/etc/ld.so.preload,这两个文件也是恶意脚本常挂的地方。
第四步检查SSH授权公钥,执行cat ~/.ssh/authorized_keys,发现里头多了几条陌生的公钥,说明攻击者已经为自己预留了登录通道,即使我修复了密码和进程,它仍然可以随时进来。
确认完整之后我才动手清理,顺序是:先断开可疑进程,删除恶意脚本文件,移除计划任务和自启动项,清掉陌生公钥,最后修改所有账户密码,并补上前面几章的加固措施。整个流程里最重要的一点是:先收集证据再动手,不要一上来就 kill 进程、删文件,因为很多痕迹一旦删除就没有了,后面想分析都无从下手。
6. 常见问题与排查技巧实录
6.1 安全加固后服务异常的典型症状
安全加固本身也会引入一些问题,而且往往是在你满怀信心地做完配置之后突然冒出来。我这里整理几个最常见的症状和原因,基本都是我或同事在环境里亲眼见到的。
- 现象一:SSH连不上,报错“Connection refused”。常见原因是sshd配置语法错误、服务没启动、防火墙新规则没放行新端口。先检查进程是否在跑,再
ss -tlnp看端口有没有监听,最后看防火墙规则。 - 现象二:密码正确但一直提示认证失败。多半是 PAM 配置顺序出问题,或者账户被 pam_faillock 锁定了。可以用
pam_tally2 --user 用户名查看失败次数,或者在/var/log/secure里看具体的拒绝原因。 - 现象三:sudo 直接无法使用,提示“sudo: parse error”。这是
/etc/sudoers语法写坏了,需要用 root 身份执行visudo -c排查,如果实在修复不了,可以用pkexec visudo从图形环境或另一个终端进入修复。 - 现象四:服务正常但业务访问很慢。这种有时候是防火墙规则过多、日志量暴增或者 SELinux 策略误伤导致的。建议先
dmesg看内核日志,再查/var/log/messages里有没有大量的denied记录。
任何一次加固改动,都建议单点执行、逐步验证,不要一次把所有配置全部堆上去,否则出了问题根本定位不到原因。
6.2 Linux安全自查清单(可直接照着做)
为了方便大家新接手一台服务器时快速摸底,我把自己的检查流程整理成一个表格。这个清单覆盖了最容易出问题的几个方向,照着跑一遍大概十分钟,能筛掉绝大多数低级的风险隐患。
| 检查项 | 参考命令 | 期望结果 |
|---|---|---|
| UID 0账户 | awk -F: '($3==0){print $1}' /etc/passwd | 只有root |
| 空密码账户 | awk -F: '($2==""){print $1}' /etc/shadow | 无输出 |
| 登录Shell账户 | awk -F: '($7!="/sbin/nologin"){print $1,$7}' /etc/passwd | 只有必要账户 |
| 监听端口 | ss -tlnp | 端口列表与业务一致 |
| SUID文件 | find / -perm -4000 -type f 2>/dev/null | 文件均为已知系统程序 |
| 计划任务 | crontab -l && ls -l /etc/cron.* | 无异常脚本 |
| 自启动服务 | systemctl list-unit-files --type=service --state=enabled | 无异常服务 |
| SSH配置 | `grep -E 'PermitRootLogin | PasswordAuthentication' /etc/ssh/sshd_config` |
| 登录记录 | lastb -n 20 | 无大量异常失败记录 |
| 系统补丁 | 包管理器检查更新列表 | 安装最新安全更新 |
这个清单在每次新系统上线前和定期巡检时都很实用。我自己的习惯是每季度至少跑一遍,遇到大版本升级或者安全通告出来之后,再额外加一次重点检查。
6.3 几个值得记住的踩坑经验
最后分享几个藏在细节里的经验,都是平时文档里不容易看到、但实操中让我印象深刻的。
第一个是关于修改密码策略的生效时机。我在前文已经提过/etc/login.defs只对新用户生效,这里再补充一个案例。有一次我在某台机器上修改了 PASS_MAX_DAYS,以为所有用户都开始按90天过期了,结果三个月后发现一个核心账户还能登录,一查才发现它的 shadow 条目里chage字段根本没更新。后来我才养成了一个习惯:改完配置文件之后,一定要用chage -l 用户名逐个核对实际值,不能想当然。
第二个是关于用chattr +i锁文件的边界。锁文件确实好用,但如果锁错了范围会很麻烦。有一次我想保护某个应用的配置目录,直接对目录执行了chattr +i,结果目录本身是挡住了新文件写入,但目录里已有的子目录没有全部继承这个锁定属性,应用还在正常写文件。这其实不算事故,但让我意识到 chattr 的递归效果和预期不同。后来我锁目录时都会先find出所有子项再逐一处理,或者明确只锁单个关键文件,而不是盲目-R。
第三个是我反复强调的远程操作保底原则。每次对线上设备做SSH或防火墙加固,我都会遵守一个流程:先开一个不打算关闭的备用连接,然后执行改动,再从新连接验证,最后才关闭备用连接。还有一点就是尽量不要在生产高峰期做改动,你永远不知道一个看似无害的重载服务会带来什么连锁反应。按我自己处理过的线上问题来看,绝大多数是因为准备不足和操作太急,安全意识不光是技术问题,更是流程问题。