news 2026/10/24 2:39:17

Linux用户管理与md5sum文件完整性巡检脚本

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux用户管理与md5sum文件完整性巡检脚本

做 Linux 运维的同学大概率都遇到过这样的场景:一批机器要交付给新同事使用,账号得批量建、家目录得初始化、密码策略得统一;过一段时间做安全自查,又要确认系统里那几个关键配置文件有没有被人动过。前一件事属于 Linux 用户管理的范畴,后一件事则需要一个可靠的文件完整性校验手段,而md5sum往往是手边最容易拿到的工具。本文就把这两条线串起来讲:先把用户管理的命令体系和底层配置文件讲透,再讲 MD5 校验的使用边界,最后给出一个"批量建用户 + 关键文件基线巡检"的完整脚本方案。内容偏运维实战,零基础可以照着敲,有经验的同学可以直接拿脚本去改。

1. 先搞清楚用户管理和 MD5 校验各管什么

在动手之前,有必要把两个主题的定位说清楚。很多教程把它们拆开讲,读者学完之后仍然不知道什么时候该用哪个命令、校验和到底能证明什么。先把概念边界划清,后面的命令才不会用错。

1.1 Linux 用户管理到底在管理什么

Linux 是一个多用户操作系统,这句话不是口号,而是有具体落地形式的。系统里每一个能被识别的身份,最终都会体现为一条记录:一个用户名、一个数字 UID、一个主组 GID、一个家目录路径、一个登录 Shell。所谓"用户管理",本质上就是对这批记录做增删改查,同时配套处理密码、组关系、家目录权限、账号有效期这些附属信息。

需要特别区分两个容易混淆的概念:用户(user)和组(group)。用户是身份的载体,组是权限的载体。一个用户至少属于一个主组(primary group),还可以加入若干附加组(supplementary group)。文件权限里的"属主 / 属组 / 其他人"三段模型,就是靠 UID 和 GID 来判定的。

另一个容易混淆的点是系统用户和普通用户。系统用户(也叫服务账号)通常 UID 小于 1000(不同发行版的划分阈值在/etc/login.defs里定义),它们不需要登录 Shell,一般设置为/sbin/nologin,家目录也可能不存在。普通用户的 UID 从 1000 起分配。批量建账号时如果不区分这两类,很容易把服务账号的 UID 段占掉,后续装软件就可能出现 UID 冲突。

1.2 MD5 校验和能证明什么,不能证明什么

MD5(Message-Digest Algorithm 5)是一种摘要算法,输入任意长度的数据,输出固定 128 位、也就是 32 个十六进制字符的结果。它的核心特性是:输入变一点点,输出就面目全非;同样的输入,输出永远一致。正是第二个特性让它可以用来做完整性比对——把一份文件"指纹"记录下来,过一段时间重新算一遍,两个值一样就说明文件内容没变。

但必须把话说在前面:MD5 的抗碰撞性已经被证明不安全。也就是说,攻击者可以构造出两段内容不同、但 MD5 值完全一样的数据。所以在对抗性场景下,比如数字签名、证书、软件发布包的安全验证,不应该再单独依赖 MD5,应该用 SHA-256 或更强的算法。

那 MD5 还有没有用?有,而且很常见。它适合非对抗性的完整性检查:比如确认配置文件在传输过程中没有损坏、确认批量部署的多个节点上的同名文件内容一致、确认某个脚本没有被误改。这类场景里没有恶意攻击者,MD5 的速度优势(比 SHA-256 快不少)就很划算。本文的巡检脚本会同时给出 MD5 和 SHA-256 两个版本,实际项目中更推荐后者。

1.3 为什么把这两件事放在一起讲

单独看,用户管理和文件校验是两件不相干的事。但放到运维交付的场景里,它们会自然结合:新建一批账号之后,/etc/passwd、/etc/shadow、/etc/group、/etc/sudoers这几个文件都发生了变化,你需要知道"哪些变化是我做的,哪些不是我做的"。做法就是在新账号建好、确认无误之后,立刻生成一份基线校验和清单,之后定期比对,一旦发现这几个文件在非预期时间被改动,就能第一时间介入排查。

这也是很多等保、内审场景里的常见要求:关键配置文件的完整性需要可追溯。理解了这层关系,本文的实战部分就顺理成章了。

2. 环境准备与版本说明

本节说明实验环境。需要强调的是,Linux 各发行版在用户管理命令的实现上有差异,useradd属于 shadow-utils(Debian/Ubuntu 系)或 shadow-utils 对应的 rpm 包(RHEL/CentOS 系),参数行为大体一致但细节不同。本文不锁定具体版本号,请以你实际机器的--help输出为准,重点看配置思路和脚本写法。

2.1 操作系统与权限要求

实验涉及用户创建、/etc/shadow修改,因此必须使用 root 权限,或者使用具备sudo提权能力且被授权执行用户管理命令的账号。这里要强调最小权限原则:不要为了图方便给普通账号配置NOPASSWD: ALL,正确做法是在/etc/sudoers.d/下写细粒度规则,只放行必要的命令。

建议在测试环境或虚拟机里完成全部实验。批量建账号、修改密码策略、调整/etc/login.defs都属于会影响全系统的操作,生产环境执行前必须做好快照或备份。本文涉及删除用户的示例,会特别标注风险。

2.2 相关命令属于哪个软件包

排查问题时经常需要确认命令来源,这里给出常用的查询方式:

# 查看命令所属软件包(RHEL / CentOS 系) rpm -qf $(which useradd) rpm -qf $(which md5sum) # 查看命令所属软件包(Debian / Ubuntu 系) dpkg -S $(which useradd) dpkg -S $(which md5sum)

useradd、usermod、userdel、groupadd、chage一般来自 shadow-utils 包;md5sum、sha256sum来自 coreutils 包,几乎所有发行版默认安装,不需要额外装。如果你在某些极简镜像里发现md5sum: command not found,装 coreutils 即可。

2.3 实验目录与命名约定

为了让脚本可复用,本文约定一个工作目录:

# 创建实验目录,权限收紧到仅 root 可访问 mkdir -p /opt/usermgr/{conf,baseline,log} chmod 700 /opt/usermgr

三个子目录的职责分别是:conf放用户清单和策略配置,baseline放校验和基线文件,log放脚本执行日志。把这三类文件分开存放,后面排查时能快速定位,也方便单独做备份和权限控制。

3. Linux 用户管理核心语法拆解

这一节把用户管理涉及的核心文件和命令逐项拆开。建议对照自己的机器边看边验证,效果比单纯读要好。

3.1 四个关键文件的字段含义

用户管理的所有操作,最终都落到下面四个文件上。理解它们的格式,比背命令参数重要得多。

/etc/passwd:每行七个字段,用冒号分隔。

用户名:口令占位:UID:GID:描述信息:家目录:登录Shell

示例行dev01:x:1001:1001:开发账号:/home/dev01:/bin/bash中,第二字段是x,表示真正的密码哈希不在这里,而在/etc/shadow。这是历史演进的结果——早期密码哈希确实放在passwd里,但该文件需要全局可读(很多程序要查用户名),把哈希暴露出去风险太大,于是拆出了 shadow 文件。

/etc/shadow:每行九个字段,权限通常是000或0400,只有 root 能读。

用户名:加密口令:最后修改时间:最小间隔:最大间隔:警告天数:不活动天数:失效时间:保留字段

其中"最后修改时间"是自 1970-01-01 起的天数,不是日期字符串;"最小间隔"表示两次改密之间至少要隔多少天,"最大间隔"表示密码多少天后必须改,"警告天数"是到期前多少天开始提示,"不活动天数"是密码过期后账号还能用多少天,"失效时间"是账号本身的绝对失效日期(同样是天数)。这几个字段配合chage命令使用。

/etc/group:每行四个字段。

组名:口令占位:GID:组内附加用户列表

注意第四字段只放附加组成员,主组成员不在这里体现。所以查一个用户属于哪些组,用id 用户名比翻这个文件靠谱。

/etc/gshadow:组密码相关,包含组管理员字段。现代系统里组密码几乎不用了,但文件仍然存在,权限同样需要收紧。

3.2 useradd 常用参数逐项说明

useradd的参数很多,但日常真正高频的也就那么几个。下面这段命令覆盖了大部分场景:

# 创建一个带附加组、指定 Shell、指定 UID 的普通用户 useradd \ --uid 1500 \ --gid developers \ --groups wheel,deploy \ --home-dir /home/dev01 \ --create-home \ --shell /bin/bash \ --comment "业务开发账号 dev01" \ dev01

逐个解释关键参数的作用和注意点:

  • --uid:手工指定 UID。不指定时由系统从/etc/login.defs里的UID_MIN起找空闲值。手工指定要在变更管理里记录下来,避免和后续批量建号冲突。
  • --gid:指定主组,可以写组名也可以写 GID。主组必须已存在,useradd不会自动创建主组。
  • --groups:附加组列表,逗号分隔,同样要求组已存在。注意这里没有--add语义,是直接设置。
  • --create-home:创建家目录。不带这个参数时家目录不会生成,用户登录后会直接落在/,非常容易出问题。这是新手最容易漏的参数之一。
  • --shell:登录 Shell。服务账号应显式设置为/sbin/nologin或/usr/sbin/nologin,阻止交互登录。
  • --comment:写到passwd第五字段的描述信息,建议填用途,方便日后审计。

如果不带任何参数直接useradd dev01,系统会用/etc/default/useradd里的默认值:默认家目录/home/dev01、默认 Shell 由SHELL变量决定、默认不创建家目录。这些默认值可以用useradd -D查看:

# 查看 useradd 的默认配置 useradd -D # 查看系统级的 UID/GID 分配范围与密码策略默认值 grep -E "^(UID_MIN|UID_MAX|GID_MIN|GID_MAX|PASS_MAX_DAYS|PASS_MIN_LEN|CREATE_HOME)" /etc/login.defs

/etc/login.defs是全局策略文件,修改它会影响之后创建的所有账号,属于需要走变更流程的操作。

3.3 passwd、chage 与密码策略设置

用户建好之后要设密码。交互式方式最简单:

# 交互式设置密码,需要输两遍 passwd dev01

批量场景下更常用非交互方式,下面两种写法都可以:

# 方式一:chpasswd,从标准输入读取 用户名:密码 echo "dev01:Init@2024Pass" | chpasswd # 方式二:passwd --stdin,部分发行版支持 echo "Init@2024Pass" | passwd --stdin dev01

这里必须提醒:不要把明文密码直接写进脚本或 Shell 历史。chpasswd支持-e参数传入已经加密好的哈希值,更安全的做法是由程序生成随机密码、通过加密通道分发给用户、并强制首次登录修改。同时HISTCONTROL=ignorespace配合行首加空格可以避免命令进历史记录,但这只是权宜之计,不能当作安全方案。

密码策略用chage管理:

# 设置:最短 1 天可改、最长 90 天必须改、提前 7 天警告、过期后 15 天禁用 chage -m 1 -M 90 -W 7 -I 15 dev01 # 查看某个账号的密码时效信息 chage -l dev01 # 强制用户下次登录必须改密码 chage -d 0 dev01

chage -d 0这个用法特别实用——批量初始化账号时,管理员设一个初始密码,然后用它把"最后修改时间"归零,用户首次登录就会被强制改密,管理员也就不知道最终密码了。

3.4 usermod、userdel 与家目录处理

修改已有账号用usermod,参数名和useradd基本对应,但有几个坑要特别注意:

# 追加附加组(-a 必须带上,否则是覆盖!) usermod -aG docker dev01 # 修改登录 Shell usermod -s /bin/bash dev01 # 锁定账号(在 shadow 密码字段前加感叹号) usermod -L dev01 # 解锁账号 usermod -U dev01

-aG里的-a是 append 的意思。如果只写-G docker,用户原有的所有附加组会被整体替换成 docker,这是运维事故的高发点。养成习惯:只要是想"加组",永远写-aG。

删除账号用userdel:

# 只删账号,保留家目录和邮件 userdel dev01 # 连家目录和邮件一起删(高风险操作!) userdel -r dev01

userdel -r会递归删除家目录。执行前必须确认家目录里没有需要保留的数据,并且确认没有其他服务以该用户身份运行(比如某个进程的属主是这个用户,或者 crontab 里有它的任务)。稳妥的顺序是:先usermod -L锁定账号观察几天,确认无影响后再删除;删除前先tar备份家目录。

另外,userdel不会自动清理这些残留:用户自己的 crontab(/var/spool/cron/下)、/tmp下的临时文件、以该用户身份运行的后台进程、以及被该用户属主化的散落文件。生产环境做账号回收时,这几项要作为检查清单逐条确认。

4. MD5 校验的原理与 md5sum 用法

讲完用户管理,转到校验这一侧。md5sum的用法本身很简单,但校验和文件的格式、批量校验的写法、以及输出结果的解读,还是有不少细节值得说清楚。

4.1 校验和的基本工作流程

整个过程分两步:生成基线和比对验证。

第一步,对目标文件计算 MD5,把结果连同文件路径写进一个文本文件,这个文件通常叫xxx.md5或checksums.md5,内容就是"哈希值 + 文件名"的组合。第二步,过一段时间后,用md5sum -c读取这个文件,让它重新计算每个文件的哈希并与记录值对比,输出OK或FAILED。

关键点在于:基线文件本身必须被保护。如果攻击者能同时改文件内容和基线文件,那校验就失去意义了。所以基线文件要么放在只读介质上,要么单独备份到另一台机器,要么至少做到权限收紧并记录文件自身的哈希。这是很多教程忽略的一环,但恰恰是校验方案能不能真正起作用的关键。

4.2 生成校验和与单个文件校验

先看最简单的单文件场景:

# 计算单个文件的 MD5 md5sum /etc/passwd # 输出示例: # 8a1f2c...(32位哈希) /etc/passwd

把结果重定向到基线文件:

# 生成基线,注意路径写绝对路径,后续校验时不受工作目录影响 md5sum /etc/passwd /etc/shadow /etc/group /etc/gshadow /etc/sudoers > /opt/usermgr/baseline/critical.md5 # 查看生成的内容 cat /opt/usermgr/baseline/critical.md5

生成出来的文件长这样,注意哈希和路径之间是两个空格:

d41d8cd98f00b204e9800998ecf8427e /etc/passwd

这两个空格不是随便写的。md5sum的输出格式规定:哈希后跟一个空格,然后是模式字符(文本模式为空格,二进制模式为*),然后是文件名。所以文本模式下看起来就是两个空格。手工编辑基线文件时如果改成单空格,校验时可能会报格式警告。

4.3 用 -c 批量校验与结果解读

# 按基线文件逐个校验 md5sum -c /opt/usermgr/baseline/critical.md5

正常输出每个文件一行文件名: OK;如果有文件被改动,会输出文件名: FAILED,并在最后汇总md5sum: WARNING: N computed checksums did NOT match。脚本里判断成败,直接看命令的退出码即可:全部匹配返回 0,有任何不匹配返回非 0。

另外两个常用参数:

  • --quiet:只输出失败项,成功的不打印。做定时任务时用这个,日志才干净。
  • --status:完全不输出,只用退出码表示结果。适合嵌在if判断里。
# 静默模式,只关心结果 if md5sum --quiet -c /opt/usermgr/baseline/critical.md5; then echo "所有文件校验通过" else echo "检测到文件被修改,请立即排查" fi

如果只想校验某几个文件,可以把基线文件用grep过滤后传给md5sum -c -,其中-表示从标准输入读取:

grep "/etc/passwd" /opt/usermgr/baseline/critical.md5 | md5sum -c -

4.4 MD5 在 /etc/shadow 里的历史角色

这一小节讲一个容易混淆的知识点,因为标题里带了"MD5",很多人第一反应是"密码哈希"。

早期 Linux 的密码加密确实用过 MD5 的变体,也就是MD5-crypt,生成的哈希以$1$开头。你在/etc/shadow里看到$1$开头的字符串,说明这个账号的密码用的是 MD5-crypt。紧随其后的是$5$(SHA-256)和$6$(SHA-512),现在主流发行版默认用$6$或更强的 yescrypt($y$)。

验证方式很简单:

# 查看密码哈希的算法标识(只看前几个字符,不要输出完整哈希) awk -F: '{print $1, substr($2,1,3)}' /etc/shadow

输出里第二列就是算法标识。如果发现还有账号是$1$,说明它使用的是历史遗留算法,应当安排时间重置密码升级到更强的算法。

需要明确的是:MD5-crypt 不能等同于"MD5 校验和"。前者是加盐迭代的口令派生函数,后者是纯粹的摘要算法,两者用途完全不同。但它们的共同点是——都不应该在新系统里用于口令保护。MD5-crypt 的问题在于迭代次数少、盐长度短,在现代硬件下暴力尝试成本太低。

如果你确实需要手工生成密码哈希(比如往自动化脚本里传加密后的密码),可以用openssl或python的crypt模块:

# 生成 SHA-512 哈希(推荐),会提示输入密码 openssl passwd -6 # 生成 MD5-crypt 哈希(仅用于兼容老系统,不推荐新系统使用) openssl passwd -1

-6对应$6$,-1对应$1$。生成出来的字符串可以直接配合chpasswd -e使用,避免明文密码出现在命令行里:

# 先生成哈希,再写入 HASH=$(openssl passwd -6 'Init@2024Pass') echo "dev01:${HASH}" | chpasswd -e

注意上面这个例子里密码仍然是明文写在命令中的,实际脚本里应该由用户输入或者从受保护的密钥管理服务获取。这里只是为了演示chpasswd -e的用法。

5. 完整实战:批量建账号 + 关键文件完整性巡检

前面都是零件,这一节把它们组装成一套可落地的方案。需求是:从一份清单批量创建账号、统一设置初始密码和密码策略、然后生成关键配置文件的校验基线,最后用脚本做定期比对。

5.1 需求拆解与目录规划

先把要做的事拆成四步,每一步对应一个可独立执行的动作:

  1. 读取用户清单(CSV 格式),逐行创建用户及其附加组。
  2. 为每个新用户设置初始密码,并强制首次登录改密。
  3. 全部创建完成后,生成/etc/passwd等关键文件的校验基线。
  4. 编写巡检脚本,定时比对基线,异常时输出告警。

确认清单文件格式如下,放在/opt/usermgr/conf/users.csv:

username,uid,group,extra_groups,comment dev01,1501,developers,wheel,业务开发账号 dev02,1502,developers,deploy,业务开发账号 ops01,1503,ops,wheel,运维账号

这里把 UID 显式写出来,是为了让整个集群的账号 UID 保持一致,避免多机之间 NFS 挂载出现权限错乱。如果不需要统一 UID,这一列留空让系统自动分配也可以。

5.2 批量创建用户脚本

脚本放在/opt/usermgr/conf/create_users.sh:

#!/bin/bash # 文件路径:/opt/usermgr/conf/create_users.sh # 用途:按 CSV 清单批量创建 Linux 用户 set -euo pipefail CSV_FILE="/opt/usermgr/conf/users.csv" LOG_FILE="/opt/usermgr/log/create_users_$(date +%Y%m%d%H%M%S).log" log() { echo "[$(date '+%F %T')] $*" | tee -a "$LOG_FILE" } # 前置检查:必须 root 运行 if [[ $EUID -ne 0 ]]; then echo "请使用 root 权限执行本脚本" >&2 exit 1 fi if [[ ! -f "$CSV_FILE" ]]; then echo "清单文件不存在:$CSV_FILE" >&2 exit 1 fi log "开始批量创建用户,清单:$CSV_FILE" # 跳过表头,逐行处理 tail -n +2 "$CSV_FILE" | while IFS=',' read -r username uid group extra_groups comment; do # 跳过空行 [[ -z "${username// }" ]] && continue # 用户已存在则跳过,保证脚本可重复执行 if id "$username" &>/dev/null; then log "跳过:用户 $username 已存在" continue fi # 主组不存在则创建 if ! getent group "$group" &>/dev/null; then groupadd "$group" log "创建主组:$group" fi # 附加组逐个确认存在 IFS=';' read -ra extras <<< "$extra_groups" for g in "${extras[@]}"; do [[ -z "$g" ]] && continue if ! getent group "$g" &>/dev/null; then groupadd "$g" log "创建附加组:$g" fi done # 组装 useradd 参数 add_args=(--gid "$group" --create-home --shell /bin/bash --comment "$comment") [[ -n "$uid" ]] && add_args+=(--uid "$uid") [[ -n "$extra_groups" ]] && add_args+=(--groups "$extra_groups") if useradd "${add_args[@]}" "$username"; then log "创建用户成功:$username (uid=${uid:-auto})" else log "创建用户失败:$username" continue fi done log "批量创建流程结束,请检查日志:$LOG_FILE"

几个设计细节值得说明。set -euo pipefail让脚本在任何一步出错时立即退出,避免错误被静默吞掉。id "$username"做存在性判断,保证脚本幂等——重复执行不会报错,这在自动化场景里非常重要。getent group比直接grep /etc/group更可靠,因为它还能查到 LDAP、SSSD 等外部来源的组。日志文件名带时间戳,多次执行不会互相覆盖。

5.3 设置初始密码与密码策略

单独写一个脚本处理密码环节,放在/opt/usermgr/conf/init_password.sh:

#!/bin/bash # 文件路径:/opt/usermgr/conf/init_password.sh # 用途:为新用户设置初始密码并强制首次登录修改 set -euo pipefail CSV_FILE="/opt/usermgr/conf/users.csv" INIT_PASS="Init@Change#2024" if [[ $EUID -ne 0 ]]; then echo "请使用 root 权限执行本脚本" >&2 exit 1 fi tail -n +2 "$CSV_FILE" | while IFS=',' read -r username uid group extra_groups comment; do [[ -z "${username// }" ]] && continue id "$username" &>/dev/null || { echo "用户不存在,跳过:$username"; continue; } # 设置初始密码(非交互) echo "${username}:${INIT_PASS}" | chpasswd # 密码策略:1 天后可改、90 天必须改、提前 7 天警告 chage -m 1 -M 90 -W 7 "$username" # 强制下次登录修改密码 chage -d 0 "$username" # 家目录权限收紧 chmod 700 "/home/$username" echo "已初始化密码策略:$username" done

注意这里的初始密码是硬编码的,仅用于演示。真实环境应该用openssl rand -base64 16之类的方式为每个用户生成独立随机密码,记录到受保护的密码库中,再单独分发给用户。全公司共用一个初始密码是明确的安全反模式。

chage -d 0是这套流程的关键:用户拿到初始密码后,第一次登录会被强制要求改密,之后管理员就无从知晓其真实密码了,责任边界清晰。

5.4 生成关键文件校验基线

用户创建完成、策略确认无误之后,立刻生成基线:

#!/bin/bash # 文件路径:/opt/usermgr/conf/make_baseline.sh # 用途:生成系统关键配置文件的校验基线 set -euo pipefail BASELINE_DIR="/opt/usermgr/baseline" TIMESTAMP=$(date +%Y%m%d%H%M%S) TARGET_FILES=( /etc/passwd /etc/shadow /etc/group /etc/gshadow /etc/sudoers /etc/login.defs ) mkdir -p "$BASELINE_DIR" # 生成 MD5 基线 md5sum "${TARGET_FILES[@]}" > "${BASELINE_DIR}/critical_md5_${TIMESTAMP}.md5" # 同时生成 SHA-256 基线(更推荐用于安全场景) sha256sum "${TARGET_FILES[@]}" > "${BASELINE_DIR}/critical_sha256_${TIMESTAMP}.sha256" # 生成一个指向最新基线的软链接,方便定时任务引用固定路径 ln -sfn "${BASELINE_DIR}/critical_sha256_${TIMESTAMP}.sha256" "${BASELINE_DIR}/critical.latest" # 记录基线文件自身的哈希,并收紧权限 chmod 600 "${BASELINE_DIR}"/critical_* chmod 644 "${BASELINE_DIR}/critical.latest" echo "基线已生成:${BASELINE_DIR}/critical_sha256_${TIMESTAMP}.sha256" ls -l "${BASELINE_DIR}"

这里同时生成 MD5 和 SHA-256 两份。MD5 用来快速比对(速度快),SHA-256 用来做正式记录(安全性高)。如果你只想保留一份,直接删掉 MD5 那行即可,建议保留 SHA-256 版本。

基线文件生成了时间戳版本而不是覆盖写,这是有意的——保留历史基线,出问题时可以追溯"什么时候变成了这个样子"。

5.5 编写巡检脚本并接入计划任务

巡检脚本放在/opt/usermgr/conf/check_integrity.sh:

#!/bin/bash # 文件路径:/opt/usermgr/conf/check_integrity.sh # 用途:比对关键文件与基线,发现变更后输出告警信息 set -uo pipefail BASELINE="/opt/usermgr/baseline/critical.latest" LOG_FILE="/opt/usermgr/log/integrity_$(date +%Y%m%d).log" ALERT_FILE="/opt/usermgr/log/integrity_alert.log" log() { echo "[$(date '+%F %T')] $*" | tee -a "$LOG_FILE" } if [[ ! -f "$BASELINE" ]]; then log "错误:基线文件不存在,请先执行 make_baseline.sh" exit 2 fi # 执行校验,捕获输出和退出码 result=$(sha256sum --quiet -c "$BASELINE" 2>&1) status=$? if [[ $status -eq 0 ]]; then log "完整性检查通过,所有关键文件未发生变化" exit 0 fi # 有变更,记录详细信息 log "检测到文件变更!详情如下:" echo "$result" | tee -a "$LOG_FILE" | tee -a "$ALERT_FILE" # 附带当前用户列表和各账号最近修改时间,方便快速定位 { echo "===== 当前登录用户 =====" who echo "===== shadow 最近变更账号 =====" awk -F: '$3 > 0 {print $1, strftime("%F", $3*86400)}' /etc/shadow 2>/dev/null | sort -k2 } >> "$ALERT_FILE" 2>&1 exit 1

接入计划任务,每天凌晨跑一次:

# 编辑 root 的 crontab crontab -e # 加入下面一行,每天 03:10 执行巡检 10 3 * * * /bin/bash /opt/usermgr/conf/check_integrity.sh >> /opt/usermgr/log/cron.log 2>&1

如果不想改 crontab,也可以放到/etc/cron.d/下,好处是配置可纳入版本管理:

# 文件路径:/etc/cron.d/integrity_check SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 10 3 * * * root /bin/bash /opt/usermgr/conf/check_integrity.sh >> /opt/usermgr/log/cron.log 2>&1

5.6 运行验证与结果解读

按顺序执行三个脚本:

chmod +x /opt/usermgr/conf/*.sh /opt/usermgr/conf/create_users.sh /opt/usermgr/conf/init_password.sh /opt/usermgr/conf/make_baseline.sh

然后验证结果:

# 确认用户和家目录都已创建 id dev01 ls -ld /home/dev01 # 确认密码策略已生效 chage -l dev01 # 手动跑一次巡检,预期输出"检查通过" /opt/usermgr/conf/check_integrity.sh

再来做个"故意的变更"验证脚本能捕捉到异常——比如新增一个用户:

# 用 root 新建一个测试账号,模拟非预期变更 useradd -m -s /bin/bash test_unauth # 再跑一次巡检,预期输出 FAILED /opt/usermgr/conf/check_integrity.sh

预期看到类似输出:

/etc/passwd: FAILED /etc/shadow: FAILED /etc/group: FAILED sha256sum: WARNING: 3 computed checksums did NOT match

验证完成后清理测试账号:

# 先锁定观察,确认没有遗留任务 usermod -L test_unauth crontab -l -u test_unauth 2>/dev/null # 确认无误后删除(-r 会删家目录,谨慎) userdel -r test_unauth # 删除后记得重新生成基线,把这次合法变更纳入 /opt/usermgr/conf/make_baseline.sh

最后一步很容易被忽略:每次合法的变更之后,都必须更新基线,否则每天的巡检都会报假警。建议把"更新基线"写进账号开通流程的固定步骤里。

6. 常见问题与排查思路

下面这张表汇总了实际操作中最容易遇到的问题。每条都建议在测试环境复现一次,印象会深很多。

问题现象常见原因解决思路
useradd: group 'developers' does not exist指定的主组或附加组未创建先groupadd建组,或用getent group确认组存在
用户能登录但落在/目录创建时未加--create-home用usermod -d /home/xxx -m迁移,或手工mkdir后chown
用户登录提示This account is currently not availableShell 被设为nologin确认是普通用户还是服务账号,普通用户改usermod -s /bin/bash
usermod -G后用户原有附加组消失缺少-a参数,执行了覆盖重新用usermod -aG补回,养成永远写-aG的习惯
chpasswd执行无报错但密码不生效输入格式错误,缺少用户名:密码的冒号用echo "user:pass" | chpasswd,检查是否有隐藏字符
md5sum -c报no properly formatted checksum lines found基线文件格式被破坏,或哈希与路径之间只有一个空格重新生成基线,不要手工编辑
md5sum -c大量文件 FAILED 但内容看起来没变文件被重新生成(如passwd被pwconv重写),即使内容等价哈希也可能变对比实际内容差异,确认变更是否合法,合法则更新基线
巡检脚本在 cron 里不执行环境变量缺失,PATH不包含命令所在目录在脚本开头显式设置PATH,或用绝对路径调用所有命令
userdel提示user is currently used by process有进程以该用户身份运行先usermod -L锁定,找到并停止相关进程后再删
sha256sum: /etc/shadow: Permission denied执行账号非 root巡检脚本必须以 root 运行,检查 crontab 的用户字段

除了表格里的条目,还有一类问题值得单独说:MD5 结果"看起来一样但校验失败"。这种情况通常不是算法问题,而是文件内容确实变了但肉眼看不出来——比如尾部多了一个换行、Windows 换行符和 Unix 换行符混用、文件编码从 UTF-8 变成 GBK。排查手段是用diff或vimdiff逐字节比对,或者用xxd看十六进制差异:

# 对比两个文件的具体差异位置 cmp -l file1 file2 | head -20

7. 最佳实践与工程建议

脚本能跑通只是起点,能不能长期稳定运行、出问题时能不能快速定位,取决于工程细节。这一节把实践中总结的要点列出来。

7.1 账号管理的规范化原则

命名规范要提前定好,比如"业务线 + 序号"的格式(dev01、ops02),避免出现test、admin、user1这类语义模糊的账号名。UID 分配建议按业务线分段(如 1500-1599 给开发、1600-1699 给运维),并维护一份 UID 分配登记表,纳入版本管理。这样多机部署时不会撞号,NFS 场景下的权限也稳定。

服务账号一律用/sbin/nologin,家目录设为/nonexistent或者干脆不建,从源头上杜绝交互登录。普通账号必须设置密码有效期,PASS_MAX_DAYS建议不超过 90 天。离职回收走标准流程:先锁定、观察一周、确认无关联任务、备份家目录、再删除,并同步清理 crontab、/tmp残留、进程和散落文件。

7.2 校验基线的生命周期管理

基线不是生成一次就完事。要明确三件事:谁有权更新基线、什么情况下允许更新、更新后如何留痕。

推荐做法是:基线更新必须走变更单,更新脚本执行时自动记录操作人、时间、变更前后的差异,并把旧基线归档保留至少 90 天。基线文件本身权限设为600,属主 root。如果条件允许,把基线的哈希值同步到一台独立的日志服务器或对象存储,防止本机被入侵后基线和文件一起被改。

另外要意识到校验的局限性:它只能发现"变了",不能发现"为什么变"。所以巡检脚本输出 FAILED 之后,一定要有人跟进排查,否则时间一长就会变成"狼来了",所有人都忽略告警。建议在告警信息里直接附上变更后的文件内容摘要,降低排查门槛。

7.3 脚本健壮性的几个要点

前面几个脚本已经体现了几个关键习惯,这里集中强调:

  • 幂等性:脚本要能重复执行不报错。用id、getent做存在性判断,比直接执行命令然后忽略错误要清晰。
  • 显式失败:set -euo pipefail是 Bash 脚本的标配。尤其pipefail,不加的话管道中间的命令失败会被忽略。
  • 绝对路径:cron 环境的PATH和登录 Shell 完全不同,脚本里所有命令和文件路径都用绝对的。
  • 日志分级:正常流程和异常告警写到不同文件,异常文件单独做监控和推送。
  • 参数校验:脚本开头检查$EUID、检查输入文件存在性,比运行到一半才发现问题要好。
  • 可读的变量名:用BASELINE_DIR而不是BD,半年后回来看还认得。

7.4 密码与哈希算法的选型建议

总结一下算法选择的结论:文件完整性校验,优先 SHA-256(sha256sum),MD5 只在追求速度且非对抗场景下使用;口令存储,让系统用默认的强算法(现代发行版通常是 SHA-512 或 yescrypt),不要手工指定 MD5-crypt。

如果发现存量系统的/etc/shadow里还有$1$开头的哈希,处理方式是安排用户重置密码,而不是直接改哈希——因为哈希无法从弱算法"转换"成强算法,只能重新生成。批量检测可以用下面这条命令:

# 统计各算法标识的账号数量 awk -F: '{if ($2 ~ /^\$/) print substr($2,1,3); else print "no-hash"}' /etc/shadow | sort | uniq -c

输出里$1$的数量就是需要重点处理的对象,$6$和$y$是当前推荐状态,no-hash表示密码字段被锁定(!或*),通常是服务账号,属正常情况。

8. 继续深入的方向与几条实操提醒

如果这套流程准备在你自己的环境里落地,有几个顺序建议:先在虚拟机里把三个脚本完整跑一遍,包括"故意制造变更 → 触发告警 → 更新基线"这个闭环;确认无误后,再挑一台非核心机器试运行一周,观察日志噪声;最后才推广到生产。计划任务的执行时间要避开业务高峰和系统自带的定时任务窗口,避免和logrotate、备份任务挤在一起。

想继续深入的话,用户管理方向可以去看 PAM 模块配置(/etc/pam.d/)、sudoers的细粒度授权语法、以及 SSSD 对接统一认证;文件完整性方向可以了解 AIDE、Tripwire 这类专业工具,它们比手写md5sum脚本多了数据库管理、规则分组、增量扫描等能力,规模上去之后更合适。另外auditd也值得一看,它能记录"谁在什么时候改了/etc/passwd",正好补上校验和"只能知道变了、不知道谁改的"这个短板。

最后一个细节:如果你修改了/etc/login.defs里的PASS_MAX_DAYS这类默认值,注意它只对之后新建的用户生效,已有账号需要用chage -M逐个调整。批量刷已有账号可以这么写:

# 对所有 UID >= 1000 的普通用户统一设置密码最长有效期 awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd | while read -r u; do chage -M 90 -W 7 "$u" echo "已更新策略:$u" done

执行前建议先awk单独跑一遍看看会命中哪些账号,确认列表符合预期再真正执行。这类"批量改全系统账号策略"的操作,宁可多花两分钟确认,也不要一次性把自己的登录账号也改出问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 2:38:28

R语言机器学习脚本实战:caret训练、交叉验证与Shiny预测

简介&#xff1a;这份R语言机器学习练习代码包面向刚接触机器学习的初学者、数据分析师及需要在R中落地建模的研究人员&#xff0c;围绕数据预处理、模型训练、预测评估与结果可视化等核心环节提供可运行示例。压缩包共25个文件&#xff0c;以12个Rmd源码和12个HTML结果页为主&…

作者头像 李华
网站建设 2026/10/24 2:37:41

OpenCV Harris角点检测实战:参数调优与工业级自适应阈值

简介&#xff1a;本资源是面向C初学者与计算机视觉入门者的OpenCV角点检测实践项目&#xff0c;聚焦Harris角点检测原理与实时调参验证。通过可交互滑动条动态调节响应阈值&#xff0c;直观呈现不同参数下建筑类图像&#xff08;含异域风格素材&#xff09;的角点检测效果&…

作者头像 李华
网站建设 2026/10/24 2:37:38

gdrcopy原理:绕过CPU实现GPU显存零拷贝直通复制

简介&#xff1a;gdrcopy 是一款面向 Linux 系统的高性能 GPU 内存复制库&#xff0c;专为利用 NVIDIA GPUDirect RDMA 技术优化跨 GPU 或 GPU 与远程设备间的数据传输而设计&#xff0c;适用于 HPC、AI 训练、实时推理等对带宽与延迟敏感的 C/C 开发场景。资源包共 51 个文件&…

作者头像 李华
网站建设 2026/10/24 2:37:38

WinUSB上位机开发实战:MFC快速构建稳定USB通信程序

简介&#xff1a;本资源是一套基于WinUSB实现Windows平台下上位机与USB设备通信的完整MFC开发工程&#xff0c;面向嵌入式/驱动开发初学者及C桌面应用开发者&#xff0c;解决无专用驱动时直接控制USB设备的数据交互难题。项目采用Visual Studio 2010 C MFC构建图形界面&#…

作者头像 李华
网站建设 2026/10/24 2:37:17

Windows C++ 崩溃 dump 生成失败排查指南

简介&#xff1a;面向 C Windows 开发者的崩溃诊断代码示例&#xff0c;聚焦程序未捕获异常时的 dump 文件生成与故障定位需求&#xff0c;适合具备一定 C 和 Visual Studio 基础、希望补齐线上排错手段的中级开发者参考。压缩包仅含 1 个 cpp 源码文件&#xff0c;体积约 731B…

作者头像 李华
网站建设 2026/10/24 2:36:34

多智能体协作训练实战:MAPPO、CTDE与共享奖励调参避坑

简介&#xff1a;这份压缩包面向已具备MATLAB与强化学习基础、希望进阶多智能体协作训练的读者&#xff0c;围绕多个智能体在同一环境中执行协作任务展开&#xff0c;可用于复现示例、理解协作策略训练流程&#xff0c;并作为课程设计或科研原型的参考。压缩包共6个文件&#x…

作者头像 李华