服务器密码策略怎么设置?密码复杂度、登录限制与账号安全

服务器被入侵的原因里,弱口令长期排在前列。攻击者不需要什么高级漏洞,只要用一份常见的账号密码字典,对着 SSH 端口不停地试,就能撞开大量防护薄弱的机器。很多"服务器莫名其妙中了挖矿"的案例,追溯到最后,就是 root 密码设成了 Admin@123 或者某个业务账号还留着默认密码。

防御弱口令不能靠"提醒大家设复杂密码",而要靠系统强制。Linux 提供了几个层面的机制:密码本身的复杂度与有效期由 PAM 的 pam_pwquality 和 chage/login.defs 控制;登录失败次数与锁定由 pam_faillock 控制;SSH 层面的爆破防护可以交给 Fail2ban 或系统自带的机制;账号的生命周期(是否可登录、密码是否过期)则由 useradd/passwd 的参数决定。把这几个层面配齐,才叫一套完整的密码策略。

这篇文章按"密码复杂度、密码有效期、登录失败锁定、SSH 加固、账号治理"五个层面来讲,每个层面都给出真实可用的配置文件位置和命令。特别提醒:PAM 配置改错会导致所有人(包括 root)无法登录,动手前务必保留一个可用 root 通道,改完先开新会话验证。

一、密码复杂度:pam_pwquality 与 login.defs

密码复杂度由 PAM 模块 pam_pwquality(老系统叫 pam_cracklib)控制。它的配置文件是 /etc/security/pwquality.conf,同时 PAM 的调用行里也可以带参数。要让策略生效,先确认 PAM 里调用了这个模块。

# 查看 PAM 中密码相关的配置行
grep -n "pwquality\|cracklib" /etc/pam.d/system-auth /etc/pam.d/password-auth  # RHEL/CentOS/Rocky 系
grep -n "pwquality" /etc/pam.d/common-password                                  # Debian/Ubuntu 系

RHEL 系里,system-auth 中通常有一行类似:

password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=

Debian 系里 common-password 一般包含:

password requisite pam_pwquality.so retry=3

确认调用存在后,把具体策略写进 /etc/security/pwquality.conf,这个文件对所有发行版通用:

# 最小长度,服务器建议 12 以上
minlen = 12

# 至少包含的字符类别数:小写、大写、数字、符号
minclass = 4

# 或者用 credit 方式精细控制各类字符最少个数(与 minclass 二选一)
# dcredit = -1   数字至少1个
# ucredit = -1   大写至少1个
# lcredit = -1   小写至少1个
# ocredit = -1   符号至少1个

# 不允许包含用户名的正反写、长度超过3的子串
usercheck = 1

# 不允许新旧密码有连续相同字符(difok 老版本语义相反,注意发行版)
difok = 5

# 连续重复字符限制,比如不允许 1111 这种
maxrepeat = 3
# 连续单调序列限制,比如 abcdef 或 123456
maxsequence = 3

# 拒绝基于字典的密码(需配合 cracklib 字典)
dictcheck = 1

这些数字是经验值,可以按团队习惯调整。minlen=12 是当前比较主流的底线,超过 16 会显著增加用户负担,收益递减。要注意 minclass 和 dcredit/ucredit 是两套机制,同时配置时以更严格的计算为准,建议只用其中一套,避免困惑。

二、密码有效期:chage 与 login.defs

复杂度解决"密码够不够强",有效期解决"密码多久换一次、多久不换就废掉"。这里有两个层面:新用户的默认策略(login.defs)和存量用户的实际策略(chage)。

/etc/login.defs 控制新建用户的默认值:

# 密码最长使用天数,建议 90
PASS_MAX_DAYS   90
# 两次修改密码之间的最短天数,防止用户改回旧密码绕过期
PASS_MIN_DAYS   7
# 密码到期前多少天开始提醒
PASS_WARN_AGE   7
# 最小密码长度(作为 pwquality 的兜底)
PASS_MIN_LEN    12

修改 login.defs 只影响之后新建的用户,对已存在的用户无效。存量用户要用 chage 逐个设置,或者批量处理:

# 查看某用户的密码策略
chage -l alice

# 设置 alice 的密码策略:最长90天、最短7天、提前7天提醒
chage -M 90 -m 7 -W 7 alice

# 强制用户下次登录必须改密码(新账号交付、密码泄露后的标准动作)
chage -d 0 alice

# 批量对所有可登录用户应用策略(排除系统账号,通常 UID >= 1000)
for u in $(awk -F: '$3>=1000 && $3<65534 {print $1}' /etc/passwd); do
  chage -M 90 -m 7 -W 7 "$u"
done

关于"多久换一次密码"其实有个现代观点值得提一句:强制过于频繁地换密码(比如 30 天)反而会让用户写下来或改成 Passw0rd1、Passw0rd2 这类可预测的变化,安全收益有限。90 天是比较平衡的选择,对高敏感系统的特权账号可以缩短到 60 天。

三、登录失败锁定:pam_faillock

这是最有效的防爆破手段之一:连续输错几次密码,就把账号临时锁一段时间。RHEL 系传统用 pam_tally2,新版本(RHEL 8+)改用 pam_faillock;Debian/Ubuntu 也可以配 pam_faillock。

# RHEL 8+/Rocky 8+ 查看是否启用 faillock
grep -n "faillock" /etc/pam.d/system-auth /etc/pam.d/password-auth

# Debian/Ubuntu 查看
grep -n "faillock" /etc/pam.d/common-auth

典型的 faillock 配置是在 PAM 里加两行 auth。在 /etc/pam.d/system-auth(和 password-auth)的 auth 段合适位置:

# 在 auth 段的最前面加:检查是否已被锁定
auth        required      pam_faillock.so preauth
# 在 auth 段的末尾加:记录失败次数
auth        [default=die] pam_faillock.so authfail

策略参数写在 /etc/security/faillock.conf(新式)或在 PAM 行里带参数:

# /etc/security/faillock.conf
# 连续失败几次后锁定
deny = 5
# 计数窗口(秒),这段窗口内的失败才累加
fail_interval = 900
# 锁定时长(秒),设为 0 表示必须管理员解锁
unlock_time = 1800
# 是否也统计 root(默认 root 例外,防止把自己锁死)
even_deny_root = false

这里有个极其重要的坑:如果设置 even_deny_root = true 且没有 root_unlock_time,root 也会被锁,可能把自己锁在系统外。稳妥做法是保持 root 例外,或者给 root 一个较短或为 0 的解锁时间并确保有其他 root 通道。

查看和解锁被锁定的账号:

# 查看某个用户的失败记录
faillock --user alice

# 手动解锁
faillock --user alice --reset

# 老系统用 pam_tally2
pam_tally2 --user alice
pam_tally2 --user alice --reset

四、SSH 与爆破防护

密码策略之外,SSH 本身的加固决定了攻击者有没有机会试密码。核心动作是尽量关闭密码登录,改用密钥认证。如果业务必须用密码,那就一定要配合失败锁定和爆破防护。

# /etc/ssh/sshd_config 关键项
PermitRootLogin no              # 禁止 root 直接登录(强烈建议)
PasswordAuthentication yes      # 需要密码登录时保持 yes,能关则关
MaxAuthTries 3                  # 每次连接最多尝试3次
LoginGraceTime 30               # 登录握手超时30秒
AllowUsers alice ops            # 只允许指定用户登录

改完先 sshd -t 校验语法,开新会话验证能登录后再重启:

sshd -t
systemctl restart sshd     # Debian/Ubuntu 服务名可能是 ssh

在系统层面,还可以用 Fail2ban 对付 SSH 爆破。它的原理是读日志里的失败记录,达到阈值就临时封 IP。配置文件在 /etc/fail2ban/jail.local:

[sshd]
enabled  = true
port     = ssh
filter   = sshd
logpath  = /var/log/secure      # RHEL系;Debian系为 /var/log/auth.log
maxretry = 5
findtime = 600
bantime  = 3600

不同发行版安装方式不同:

# RHEL/CentOS/Rocky 系(需先启用 EPEL)
dnf install -y epel-release
dnf install -y fail2ban
systemctl enable --now fail2ban

# Debian/Ubuntu 系
apt update && apt install -y fail2ban
systemctl enable --now fail2ban

查看封禁情况:

fail2ban-client status sshd

需要说明的是,Fail2ban 只能防"单 IP 反复试"这种低速爆破,对分布式慢速爆破效果有限。真正一劳永逸的办法还是关掉密码登录用密钥,或者把 SSH 端口限定在特定来源 IP(安全组 + 防火墙双重限制)。如果为了防爆破而改 SSH 默认端口,也要注意先放行新端口、验证能登录、再关闭旧端口,顺序错了会连不上。

五、账号治理:清理与最小化

最后是容易被忽略的一环:账号本身。可登录账号越多,攻击面越大。

# 列出所有可登录(有 shell)的账号
grep -vE "/(nologin|false)$" /etc/passwd

# 找出 UID 为 0 的账号(除了 root 之外都不正常)
awk -F: '$3==0 {print $1}' /etc/passwd

# 查看哪些账号设了密码(第二列非 * 或 ! 表示有可用密码)
awk -F: '$2!="*" && $2!="!!" && $2!="" {print $1}' /etc/shadow

# 检查空密码账号(高危)
awk -F: '$2=="" {print $1}' /etc/shadow

对确认不用的账号,采取先锁后删的安全顺序:

# 先锁定账号(禁止登录),观察一段时间确认无影响
usermod -L alice          # 锁密码
usermod -s /sbin/nologin alice   # 改shell为不可登录(RHEL系路径 /sbin/nologin)

# 确认无人使用后,再删除
userdel -r alice          # -r 同时删除家目录,注意确认里面没有需要保留的数据

userdel -r 会删除家目录,这是危险操作,执行前务必确认家目录里没有业务数据、密钥或配置。稳妥做法是保留家目录,只删账号:userdel alice(不加 -r),或者先把家目录备份到别处。

账号治理还有两个容易被忽略的检查项。一是服务账号是否被误设了可登录 shell,很多应用在安装时会创建一个同名系统账号(如 mysql、nginx、redis),本意是让服务降权运行,但如果它的 shell 是 /bin/bash 而不是 /sbin/nologin,攻击者拿到这个账号就能直接登录系统。二是共享账号问题,团队里用同一个 admin 账号运维,出了事根本分不清是谁操作的;应该给每个人独立的实名账号,再通过 sudo 或用户组授予相应权限,这样审计日志才能对应到具体的人。把这两项连同前面的 UID=0 检查、空密码检查一起做成一份定期巡检清单,每月跑一次,账号层面就不会留下明显的口子。

下面这张表汇总了本方案涉及的关键参数和推荐值:

层面 配置项 推荐值 作用
复杂度 minlen 12+ 密码最小长度
复杂度 minclass 4 至少包含四类字符
有效期 PASS_MAX_DAYS 90 密码最长使用天数
有效期 PASS_MIN_DAYS 7 两次改密最短间隔
锁定 deny 5 失败几次后锁定
锁定 unlock_time 1800 锁定时长(秒)
SSH PermitRootLogin no 禁止 root 直登
SSH MaxAuthTries 3 单连接最大尝试次数
防护 fail2ban bantime 3600 封禁时长(秒)

常见问题

Q:改了 pam_pwquality 后,root 还设得进简单密码吗?
如果 pwquality 的 PAM 行带了 enforce_for_root,root 也受约束;否则 root 通常可以绕过复杂度检查。建议给 root 也启用约束,但要确保自己记得住,或者用密码管理器存储。

Q:为什么我给用户设了复杂密码,还是被爆破成功?
密码复杂度防的是"用户主动设弱密码",防不住"密码在某次泄露中被拿到"。如果密码曾在别处泄露,再复杂也会被撞库。所以复杂度和"不重复使用密码"要一起做,且高价值账号尽量上密钥 + 二次验证。

Q:faillock 把管理员锁了怎么办?
如果有其他 root 通道(云控制台、其他可提权账号),直接 faillock --user 用户名 --reset 解锁。如果彻底进不去,只能进单用户模式或救援模式解锁。这也是为什么 even_deny_root 要慎开。

Q:chage 改了策略,怎么确认用户下次登录真的会强制改密?
chage -d 0 用户名 把"上次改密日期"设为 0,用户下次登录时系统会判定密码已过期,强制其修改。用 chage -l 用户名 可以看到"密码过期时间"(Password expires)已经变成过去时间。

Q:这套策略能一次全配吗,有没有备份方案?
可以全部配齐,但要分批验证,尤其是 PAM 相关改动。每改一处就用一个新会话登录测试,通过后再改下一处。至少要保证有一个 root 会话始终在线,或者服务器有带外控制台,否则 PAM 配错会导致永久无法登录。

总结

  • 一套完整的密码策略要覆盖四层:复杂度(pwquality)、有效期(chage/login.defs)、失败锁定(faillock)、爆破防护(fail2ban + SSH 加固),缺一层就有短板。
  • 改 login.defs 只对新建用户生效,存量用户必须用 chage 批量设置,注意排除系统账号。
  • faillock 默认让 root 例外是合理的自我保护;even_deny_root 慎开,否则可能把自己锁在系统外。
  • 与其纠结密码设多复杂,不如尽量关闭 SSH 密码登录改用密钥,这是防爆破最彻底的办法;必须用密码时,务必配失败锁定。
  • 定期清理冗余可登录账号(先锁定后删除)、检查 UID=0 账号和空密码账号,账号越少,攻击面越小。
  • PAM 相关改动一律"改前备份 + 保留 root 通道 + 新会话验证",这是所有账号安全操作的安全底线。