2. 基于规则的密码复杂度策略审计

基于规则的密码复杂度策略审计 学习卡片

审计对象与基线规则

密码复杂度策略审计的核心是读取目标系统的策略配置,逐项与安全基线比对。以 Linux PAM 体系为例,策略分散在 /etc/login.defs、/etc/security/pwquality.conf 和 /etc/pam.d/system-auth(或 password-auth)三处,审计时需要把它们合并成一份"有效策略",再逐条判定。

本案例采用如下基线(可按等保/内控要求调整):

规则项基线要求配置来源
最小长度 minlen≥ 12pwquality.conf
字符类别 credit至少 3 类(大写/小写/数字/符号)pwquality.conf
历史密码 remember≥ 5system-auth / pwquality.conf
失败锁定 deny≤ 5 次system-auth (pam_faillock)
锁定时间 unlock_time≥ 300 秒system-auth (pam_faillock)
密码有效期 PASS_MAX_DAYS≤ 90 天login.defs

策略读取与合并逻辑

审计脚本需要处理三类解析难点:pwquality.conf 是 key = value 格式;login.defs 是 KEY value 空格分隔;system-auth 中的参数嵌在 PAM 模块行里,形如 pam_pwquality.so ... remember=5 或 pam_faillock.so deny=5 unlock_time=300。下面给出可直接运行的审计脚本。

#!/usr/bin/env python3
# policy_audit.py —— 密码复杂度策略审计
import re, sys, os

BASELINE = {
    "minlen":        {"op": ">=", "val": 12,  "src": "pwquality"},
    "credit_classes":{"op": ">=", "val": 3,   "src": "pwquality"},
    "remember":      {"op": ">=", "val": 5,   "src": "pam"},
    "deny":          {"op": "<=", "val": 5,   "src": "pam"},
    "unlock_time":   {"op": ">=", "val": 300, "src": "pam"},
    "PASS_MAX_DAYS": {"op": "<=", "val": 90,  "src": "login.defs"},
}

def parse_kv(path):
    """解析 key = value 或 KEY value 两种格式"""
    cfg = {}
    if not os.path.exists(path):
        return cfg
    for line in open(path, encoding="utf-8", errors="ignore"):
        line = line.split("#", 1)[0].strip()
        if not line:
            continue
        m = re.match(r"^([A-Za-z_]+)\s*[=\s]\s*(\S+)", line)
        if m:
            cfg[m.group(1)] = m.group(2)
    return cfg

def parse_pam(path):
    """从 PAM 配置行中提取模块参数"""
    cfg = {}
    if not os.path.exists(path):
        return cfg
    for line in open(path, encoding="utf-8", errors="ignore"):
        line = line.split("#", 1)[0].strip()
        if not line or "pam_" not in line:
            continue
        for k, v in re.findall(r"(\w+)=(\w+)", line):
            cfg[k] = v
    return cfg

def count_credit_classes(cfg):
    """统计启用的字符类别数(credit 参数 > 0 视为启用)"""
    keys = ["ucredit", "lcredit", "dcredit", "ocredit"]
    return sum(1 for k in keys if int(cfg.get(k, 0)) > 0)

def audit(root="/etc"):
    pwq  = parse_kv(os.path.join(root, "security/pwquality.conf"))
    ld   = parse_kv(os.path.join(root, "login.defs"))
    pam  = parse_pam(os.path.join(root, "pam.d/system-auth"))
    if not pam:
        pam = parse_pam(os.path.join(root, "pam.d/password-auth"))

    # 合并有效策略:PAM 行内参数优先于独立配置文件
    eff = {}
    eff.update(pwq)
    eff.update(ld)
    eff.update(pam)
    eff["credit_classes"] = count_credit_classes(pwq)

    findings = []
    for rule, spec in BASELINE.items():
        raw = eff.get(rule)
        if raw is None:
            findings.append((rule, "MISSING", "-", spec["val"], spec["op"]))
            continue
        try:
            actual = int(raw)
        except ValueError:
            findings.append((rule, "INVALID", raw, spec["val"], spec["op"]))
            continue
        ok = actual >= spec["val"] if spec["op"] == ">=" else actual <= spec["val"]
        if not ok:
            findings.append((rule, "FAIL", actual, spec["val"], spec["op"]))
    return findings

if __name__ == "__main__":
    root = sys.argv[1] if len(sys.argv) > 1 else "/etc"
    rows = audit(root)
    if not rows:
        print("[PASS] 所有策略项均满足基线")
    else:
        print(f"{'规则':<16}{'状态':<9}{'实际值':<10}{'基线':<10}")
        print("-" * 46)
        for r, st, a, b, op in rows:
            print(f"{r:<16}{st:<9}{str(a):<10}{op}{b}")

审计结果判读

脚本对每个规则项输出三种状态:PASS(未出现在清单中)、FAIL(值不满足基线)、MISSING(配置项缺失,等效于未启用该策略)、INVALID(值无法解析为整数)。MISSING 和 INVALID 在合规审计中通常按不通过处理,因为缺失意味着系统采用默认值,而默认值往往弱于基线。

下面这张图展示了从三处配置文件到有效策略合并、再到逐项判定的完整数据流,以及各规则项的判定分支。

配置源 pwquality.conf minlen / ucredit / lcredit login.defs PASS_MAX_DAYS / PASS_MIN_LEN pam.d/system-auth remember / deny / unlock_time 有效策略合并 PAM 行内参数优先 覆盖独立配置文件 逐项基线比对 op: >= 或 <= actual vs baseline PASS(不输出) FAIL 值不满足基线 MISSING 配置项缺失 INVALID 值无法解析 基线规则项与判定条件 规则项 来源 判定条件 不通过状态 风险等级 minlen pwquality ≥ 12 FAIL / MISSING 高 credit_classes pwquality ≥ 3 类 FAIL / MISSING 高 remember pam ≥ 5 FAIL / MISSING 中 deny pam ≤ 5 FAIL / MISSING 高 unlock_time pam ≥ 300 秒 FAIL / MISSING 中 PASS_MAX_DAYS login.defs ≤ 90 天 FAIL / MISSING 中

运行与验证

在目标主机上以只读权限运行:

python3 policy_audit.py /etc

若需在离线样本目录上测试,可准备一个模拟根目录,例如 ./sample/etc/security/pwquality.conf、./sample/etc/login.defs、./sample/etc/pam.d/system-auth,然后执行 python3 policy_audit.py ./sample/etc。预期输出示例(假设 minlen=8、remember=3、deny=10,其余合规):

规则              状态      实际值    基线
----------------------------------------------
minlen            FAIL      8         >=12
remember          FAIL      3         >=5
deny              FAIL      10        <=5

验证要点:PASS 项不出现在清单中;MISSING 项说明该配置未显式设置,需人工确认系统默认值是否满足基线;INVALID 项说明配置值格式异常,应优先修复。

审计前先备份三处配置文件,审计脚本本身只读不写。若系统使用 pam_faillock 而非 pam_tally2,参数名可能为 deny 和 unlock_time;若使用 pam_tally2,则对应 deny 和 unlock_time 语义相同但模块名不同,解析逻辑无需改动。
不要把 MISSING 直接当作通过。很多发行版默认 minlen 为 8 甚至更短,缺失配置项意味着系统实际策略弱于基线,必须列入不符合项清单。