拓冰建站拓冰建站
首页 / 资讯中心 / 正文

本地密码哈希读取与审计:从shadow字段到安全加固实战

1. 为什么盯着本地 Hash 看一次巡检揪出的两个隐患有一次我例行巡检公司的一台 Linux 服务器cat /etc/shadow一打开就发现两个问题一个账号的密码字段以!开头说明这个账户被锁了另一个账号对应的字段居然是空的意思是它根本没有密码。这台机器部署了 SSH 服务虽然平时靠密钥登录但空密码账户的存在意味着任何能触达登录端口的人都有机会在 PAM 配置宽松的情况下绕过认证。那天的巡检其实就是一次最典型的读取本地密码 Hash。读取本地密码 Hash说白了就是把系统里本地账号的密码哈希值从存储位置取出来并看懂它。它不是黑产里那种破解密码的入侵动作也不是单纯把一串字符打印到屏幕上就完事。它首先是一项安全审计能力运维人员通过它检查有没有空密码账户、有没有被锁的僵尸账户、密码过期策略是不是真的在生效安全评估人员在拿到书面授权后也需要先完整地读一遍本机所有账号的哈希才能判断整体凭据暴露面有多大。1.1 读取本地 Hash 到底在做什么很多人一听到Hash就想到哈希碰撞、彩虹表、暴力破解这些都是误解。实际上现代 Linux 和 Windows 系统都不会明文保存密码而是保存经过加盐和迭代计算的哈希值。读取本地 Hash就是把/etc/shadow或 SAM 数据库里这些哈希值摘出来再根据哈希的前缀、字段结构判断账号状态。它解决的核心问题有三个一是这个系统有没有空密码账户二是哪些账户被锁定、哪些账户的哈希不可用三是系统用的密码算法是否已经过时到可以轻松被碰撞。这三个问题在等保测评、基线加固、日常健康检查里都是必查项。我见过不少运维同行服务器跑了好几年从来没主动看过一眼 shadow 文件。他们觉得密码是密码Hash 是 Hash反正登录时系统自己会判断不需要人工介入。可一旦出事比如发现某个僵尸账户被人登录过再去追踪就晚了。把读取本地 Hash当成定期巡检项目的一部分和看磁盘空间、看日志占用一样应该成为基础习惯。1.2 哪些场景真正需要读 Hash第一个场景是安全基线检查。公司做等级保护或者内部安全合规时需要核对所有服务器账号的状态这时候要批量读取所有本地账号的哈希状态确认没有空密码、没有长期不动的高权限僵尸账号。第二个场景是密码策略验收比如你在/etc/login.defs里配置了密码 90 天过期但实际系统里有些老账户没套上这个策略通过读取 shadow 的过期字段就能验证。第三个场景比较特殊是自己管理的机器忘记密码后的排查。注意忘记密码时多数情况下你要做的是重置而不是读哈希还原明文这个区分我后面会讲。所以这篇博文的核心思路是先知道 Hash 存在哪、长什么样再学会读取工具和识别方法最后把读到的结果变成加固动作。整个链路不需要任何黑产手段它就是一名系统管理员和一线安全工程师的基本功。2. Linux 下密码哈希的存在位置与 /etc/shadow 字段拆解Linux 系统的本地密码哈希绝大多数发行版都存在/etc/shadow这个文件里。这个文件不是给普通用户看的它的权限通常为-rw-r-----属主是 root所属组是 shadow普通用户执行cat /etc/shadow会直接 Permission denied。只有在使用 sudo 或具备 root 权限的前提下才能读取。2.1 passwd 和 shadow 的分工很多人会把/etc/passwd和/etc/shadow搞混。/etc/passwd保存的是账号的基础信息比如用户名、UID、主组、家目录、登录 shell密码字段几乎清一色是一个x占位符真正的密码哈希早就被挪到 shadow 文件里了。这样设计的历史原因是早年 passwd 文件需要让所有进程可读如果里面直接放哈希等于把哈希公开给所有本地用户。后来引入 shadow 机制哈希被隔离到仅 root 和部分特权进程可读的文件中普通用户即使看到 passwd 里那个x也拿不到任何敏感信息。2.2 九个字段逐项说明/etc/shadow每一行对应一个账号字段之间用冒号分隔完整结构是九段tom:$6$rounds656000$EhmXXXX$aaaaaaaaaa...:18622:0:99999:7:::第一个字段是用户名第二个字段是密码哈希或状态标记第三个字段是最后一次修改密码时间单位是自 1970-01-01 起的天数比如 18622 表示距离 1970 年 1 月 1 日过去了 18622 天。第四个字段是两次修改密码的最小间隔天数0 表示可以随时改。第五个字段是密码最长有效天数99999 表示永不过期。第六个字段是密码过期前提前多少天提醒7 表示提前一周开始提示。第七个字段是密码过期后多少天锁定账号通常是空。第八个字段是账号失效日期也是空。第九个字段保留未用。你可能已经发现了第三个字段到第七个字段就是 Linux 密码过期策略的原始数据源。我前面说的等保检查很多时候就是在看这些数字到底配没配。如果第五个字段是 99999说明这台机器上该账户的密码从不过期这和热搜里linux密码过期提醒通知直接相关——提醒通知靠的并不是某个后台服务凭空发消息而是登录 PAM 读取这些字段后在控制台或 SSH 登录时把密码将在 N 天后过期打印出来。2.3 权限要求与读取工具读取 shadow 文件最直接的是sudo cat /etc/shadow但如果你只是想要某个特定账号的信息用getent shadow更精确sudo getent shadow tomgetent的好处是它会走系统 NSS 模块不受文件中注释、回车等格式问题干扰。想要逐行拆字段awk很顺手sudo awk -F: {print $1, $2, $3, $5} /etc/shadow上面的命令会输出用户名、哈希、上次修改时间和最大有效天数一眼就能看出哪些账号的密码策略是异常的。另外vipw -s可以安全编辑 shadow 文件它会在保存前做格式校验避免手抖把行写坏pwck则适合事后验证 shadow 与 passwd 的一致性我习惯在每次巡检脚本跑完之后执行一次pwck -q。提示shadow 文件里第二字段为!或者*开头的账号并不代表密码哈希不存在而是表示这个哈希被禁用了一般出现在账户刚创建未设密码、管理员手动锁定等场景。做审计时不能简单认为这个账号没有哈希就是安全的要看具体标记。3. Windows 的 SAM 数据库锁定中的注册表怎么取出来Windows 下的本地密码哈希不叫 shadow它存放在注册表的一个独立配置单元里路径是C:\Windows\System32\config\SAM。与 Linux 的差异在于Windows 运行期间 SAM 文件被系统独占锁定哪怕你是管理员直接copy这个文件也会报正在被另一个进程使用。3.1 SAM 为什么不能直接复制SAM 里面有 SYSKEY 加密哈希并不是明文存放在文件里的同时还被系统核心进程占用。所以想完整拿到本地账号哈希常规思路不是复制文件而是通过受支持的系统机制把注册表配置单元导出来或者借助卷影副本绕过文件锁定。这中间涉及两个关键文件SAM 和 SYSTEM。SAM 存储账号与哈希本体SYSTEM 存储解密 SAM 所需的部分密钥信息SYSKEY两者缺一不可。导出的时候务必一起拿。3.2 注册表导出与卷影复制两种取法第一种方法是用管理员权限打开 CMD执行reg savereg save HKLM\SAM sam.hiv /y reg save HKLM\SYSTEM system.hiv /y这条命令会把注册表中的 SAM 和 SYSTEM 配置单元完整导出为文件/y参数表示覆盖同名文件。导出的两个文件配合后面的离线解析工具就能还原出本地账号的哈希。需要注意导出的文件带有系统的安全描述符在传输到其他分析机器时要保证是在受控环境内操作。第二种方法是卷影复制。系统管理场景下如果目标机器不方便执行注册表导出可以用 VSS 把整个系统盘快照出来然后从快照里拿 SAM。示例命令vssadmin create shadow /forC:执行后会生成一个卷影设备路径比如\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1。接着从卷影中复制 DA 那两个文件copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\System32\config\SAM D:\audit\SAM copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\System32\config\SYSTEM D:\audit\SYSTEM这种方法的优点是不需要复制正在占用的文件也能在系统跑着的情况下拿到一致性较好的配置单元副本。卷影复制在 Windows 的备份还原功能里本来就大量使用属于正规的系统管理接口。3.3 离线解析secretsdump 把 hash 变成人能看懂的格式拿到sam.hiv和system.hiv之后需要离线解析。我用得最多的是 Impacket 套件里的secretsdump它可以在不触碰目标系统的情况下在本地分析 SAM 与 SYSTEM 文件secretsdump.py -sam sam.hiv -system system.hiv LOCAL输出大致长这样Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::每一行由四段组成分别是用户名、RID、LM Hash、NT Hash。现在的新系统基本不再使用 LM Hash所以 LM 字段几乎全是aad3b435b51404eeaad3b435b51404ee这个固定空值。NT Hash 才是真正需要关注的对象它是 32 位的十六进制字符串。如果你看到某个账户的 NT Hash 是31d6cfe0d16ae931b73c59d7e0c089c0那说明这个账户的密码是空字符串——这是审计时要第一时间标记的高危账户。提示secretsdump 这类工具属于安全测试常用工具我只建议在自有机器、已获得书面授权的评估环境或者搭建的靶场中使用。不要试图拿它去处理别人系统的 SAM 文件那属于违法行为。4. 哈希字符串辨识从 $ 前缀看出算法与风险拿到哈希字符串之后最关键的并不是看到一串乱码而是快速判断它是什么算法、什么状态。Linux 的哈希字段其实是结构化的不是随便写的。4.1 特殊标记空字段、*、!、!!先看第二字段为空的情况。这个账号在 shadow 里没有任何哈希等同于没有密码。在某些系统上这种账号甚至可以直接通过本地终端登录审计时必须第一时间处理。*开头表示账户是系统服务账户或已经禁用密码登录例如*LK*、*!*在某些发行版里也能见到含义类似。!开头代表该账户的密码被锁定!!则常见于新账户尚未设置密码且被锁定的状态。还有一种容易被忽略的情况哈希以!开头但后面还跟着一串$6$...这表示账户曾经设置过密码后来被锁定但哈希并没有被清除。审计脚本如果只判断以!开头就是锁定会把这种已设置过密码但又锁定的账号和从未设置密码的账号混为一谈所以识别时要把!后面的内容也拿出来看。4.2 算法对照表$1$/$5$/$6$/$y$/$2b$/ argon2现代 Linux 哈希字符串通常以$算法标识$盐$实际哈希的形式出现。下表是实际运维中最高频的几种前缀算法常见场景审计关注点$1$MD5老系统、嵌入式设备算法过弱建议禁止新账户使用$2a$/$2b$/$2y$bcrypt部分应用、旧系统强度尚可注意 rounds 是否过低$5$SHA-256老版本 Linux 可选项不如$6$常见$6$SHA-512绝大多数现代 Linux 的默认值审计基线里的主流合格算法$y$yescryptRHEL 9、Fedora、新版 Debian新算法强度高$argon2i$/$argon2id$Argon2部分新系统、应用抗 GPU 破解能力强需要注意算法标识不是随便能换的。系统用的是哪一种取决于发行版编译时选择的密码算法以及/etc/login.defs里的ENCRYPT_METHOD。如果一台服务器上既有老账号的$1$又有新账号的$6$说明系统经历了升级或配置变更老账号的弱哈希不会自动升级这就给审计留下了清晰的切入点优先处理还在使用$1$的老账户。4.3 盐和 rounds 在审计中的信息量哈希字符串中间那一段带$包裹的内容是盐salt它的作用是让相同密码产生不同哈希防止两个账号密码相同就直接暴露。盐本身不是秘密它跟着哈希存但它的存在意味着你不能用预先算好的固定彩虹表直接匹配。另外要注意$6$或$y$后面可能跟着roundsN参数这表示哈希迭代次数。比如$6$rounds656000$....就是在告诉系统要迭代 656000 次。迭代次数越多每次密码验证消耗的 CPU 时间越长暴力破解时需要尝试每个密码的成本也越高。审计时如果发现老账号的 rounds 值很低即使算法是 SHA-512强度也是打折扣的。为了快速识别我常用一小段 Python 脚本直接判断哈希属于哪种算法def identify(hash_str: str) - str: if not hash_str: return 空哈希(无密码) if hash_str.startswith(!): return f锁定账户, 后缀: {hash_str[1:60]} if hash_str.startswith(*): return 系统账户或禁用登录 algo hash_str.split($)[1] if hash_str.startswith($) else ? algos { 1: MD5, 5: SHA-256, 6: SHA-512, y: yescrypt, 2a: bcrypt, 2b: bcrypt, 2y: bcrypt, argon2i: Argon2i, argon2id: Argon2id, } return algos.get(algo, algo)把这段代码接到读取 shadow 的逻辑里几十行就能做出一个初版的本地哈希审计小工具。后面第 5 部分我会给出一份可以直接用的脚本。5. 实操写一个本地哈希审计脚本代替肉眼翻 shadow人工cat /etc/shadow在服务器数量少的时候还能接受但一旦面对几十台机器逐行看字段非常容易漏。我用 Python 写过一个审计脚本作用是遍历本地所有账号按哈希状态分类输出帮我把空密码锁定账户弱算法账户一次性列出来。5.1 用 Python 的 spwd 模块读取并分类Linux 的 Python 标准库里有一个spwd模块专门用于读取 shadow 数据库前提是进程必须有 root 权限。脚本逻辑分三步先用pwd.getpwall()拿到全部本地账号再通过spwd.getspnam()获取每个账号的 shadow 记录最后对哈希字段做状态归类。#!/usr/bin/env python3 # 本地密码哈希审计脚本需要在 root 或 sudo 下运行 # 用法: sudo python3 audit_hash.py import pwd import spwd def hash_status(s: str) - str: if not s or s : return 危险密码字段为空无密码 if s.startswith(!!): return 锁定双重感叹号无密码且被锁 if s.startswith(!) and len(s) 1: return 锁定原哈希被禁用 if s.startswith(!): return 锁定哈希未设置 if s.startswith(*): return 锁定系统账户星号 if s.startswith($): algo s.split($)[1] names { 1: 有密码MD5弱, 5: 有密码SHA-256, 6: 有密码SHA-512, y: 有密码yescrypt, 2a: 有密码bcrypt, 2b: 有密码bcrypt, 2y: 有密码bcrypt, argon2i: 有密码Argon2i, argon2id: 有密码Argon2id, } return names.get(algo, f有密码算法 {algo}) return 未知格式 def main(): print(f{用户名:20}{状态:30}{哈希前缀}) print(- * 85) for u in pwd.getpwall(): try: sp spwd.getspnam(u.pw_name) except KeyError: continue status hash_status(sp.sp_pwdp or ) prefix (sp.sp_pwdp or )[:35] print(f{sp.sp_namp:20}{status:30}{prefix}) if __name__ __main__: main()如果是在 Ubuntu 20.04、22.04 上运行sudo python3 audit_hash.py即可。脚本会输出类似下面的内容tom 有密码SHA-512 $6$rounds656000$EhmXXXX... mysql 锁定系统账户星号 * test 危险密码字段为空无密码 zabbix 锁定原哈希被禁用 !$6$rounds...看到test那一行基本就是高危信号了接下来要做的是立刻给该账号设密码或者直接锁定。看到zabbix的!$6$说明这是个曾经能用密码登录、后来被锁定的账号如果该账号已经不再使用最好连同用户一起删除。5.2 输出解读与运行细节脚本里我把 UID 小于 1000 的系统账号也列出来了因为很多系统服务账户虽然不能交互登录但同样需要检查哈希状态。比如mysql账户如果显示星号锁定这是正常的如果显示有密码SHA-512反而值得关注说明一个服务账户存在实际可用的密码哈希一旦服务被攻破这个哈希可能成为横向移动的凭据。另外脚本打印的是哈希前缀而不是完整哈希这是有意的。审计记录里只需要算法和状态不需要把完整的哈希原样留在日志里降低泄露风险。有些发行版的 Python 环境中spwd模块需要机器上具备 GNU shadow 相关库标准安装基本都带了。如果你用的是 macOS 或者 BSD 环境情况会略有不同本文只讨论 Linux 场景。拿到脚本输出后我会习惯性追加执行一步sudo pwck -qpwck用来校验/etc/passwd与/etc/shadow的一致性如果有账号只存在于一个文件中日志会有提示免得脚本统计到幽灵账号。5.3 Windows 哈希的同类体检Windows 那边没有 Linux 这种结构清晰的字段但审计思路可以复用。secretsdump导出的哈希中重点看 NT Hash。把所有非固定值的 NT Hash 收集到一个清单作为存在实际密码哈希的账户而 NT Hash 等于31d6cfe0d16ae931b73c59d7e0c089c0的账户视为空密码账户。如果某账户的哈希带 LM Hash 且不是固定空值说明系统还启用了过时且脆弱的 LM 认证需要在组策略中关闭。这样 Windows 和 Linux 的审计在输出高危清单这条线上就统一了空密码、锁定状态、弱算法、可登录的非预期账号都是必查项。6. 读完之后要动手从哈希状态引出加固动作审计的终点不是输出一份报告而是把这些状态变成实际加固动作。我几乎每次跑完哈希审计脚本都会顺带把下面几类问题处理掉。6.1 空密码与锁定账户的处理空密码账户直接处理方式是先设置一个随机强密码再考虑是否锁定。对普通用户用passwd设置新密码sudo passwd test如果确认该账号不再需要就直接锁定或移除sudo usermod -L test sudo userdel -r test注意usermod -L会在密码哈希前加一个!本质上是禁用这个哈希而不是删除它。要恢复的话用usermod -U。不过我的建议是对僵尸账号别想着恢复直接删除才是最干净的。对于锁定类型的账号比如日志里显示原哈希被禁用的老账户如果业务上确实需要启用则重新设置密码否则一删了之。删除前一定要确认该账号没有持有在跑的进程可以用ps -u 用户名先看一眼。6.2 密码过期策略与到期提醒配置前面 shadow 字段的第三到第七位就是密码过期策略的数据源。如果大量账号的第五字段是 99999那就等于密码永不过期这通常过不了安全合规检查。配置方式有两种。第一种是修改/etc/login.defsPASS_MAX_DAYS 90 PASS_MIN_DAYS 7 PASS_WARN_AGE 14修改后只对之后新建的账户生效存量账户需要用chage命令挨个调整sudo chage -M 90 -m 7 -W 14 tom-M设置最大有效天数-m设置两次修改最小间隔-W设置过期前提醒天数。配置完成后可以用chage -l tom查看当前策略是否生效。这个字段配置好之后用户登录时终端会出现类似密码将在 14 天后过期的提示这就是热搜里linux密码过期提醒通知的实际机制。它不依赖额外的邮件系统只要 PAM 配置了 pam_lastlog 或对应模块控制台和 SSH 登录就会自动输出提醒。想让提醒更显眼也可以在/etc/ssh/sshd_config里配合 Banner 在登录前展示策略说明但核心的过期计数逻辑还是来自 shadow 字段。6.3 算法强度与下一代哈希的选择当我发现系统里还有$1$开头的老哈希时会对这些账号执行一次密码重置强制用户重新设置密码这样系统会把新密码按照/etc/login.defs里配置的算法重新哈希。具体重置方式很简单sudo chage -d 0 用户名-d 0会把最后一次修改密码时间清零强制该用户下次登录时必须修改密码。这样既完成了密码过期又顺手让系统按照新一代算法重新生成哈希。对于新系统我建议在/etc/login.defs里明确将ENCRYPT_METHOD保持为发行版默认值不要为了兼容老工具把它降级为 MD5。MD5 哈希在今天的 GPU 算力下已经很不安全能不用就不用。7. 边界、误区与我的操作习惯喜欢和安全沾边的操作边界永远比技术本身更重要。读取本地密码 Hash 这件事同样有明确的红线。7.1 授权边界哪些机器可以读自己管理的服务器、正式工单里分配给你的机器、搭建的本地靶场环境这些都是可以放心操作的范围。但任何一台不属于你、没有书面授权、不属于你所在组织资产范围的机器都不要尝试读取它的哈希。哪怕只是好奇或测试工具在未授权情况下触碰他人系统凭据数据性质完全不同。安全审计的合理路径是拿到授权 - 在受控环境操作 - 输出报告给相关方 - 删除中间产物。这条路径里授权是第一步也是不可省略的一步。7.2 很容易踩的坑第一个坑是把读取哈希和恢复密码混为一谈。哈希是单向的你看到系统登录时校验的是一串哈希但它不会主动变成明文密码。忘记密码时应该做的是重置Linux 用chpasswd、单用户模式等Windows 用启动修复或安全模式工具而不是反复分析哈希。第二个坑是误判!开头。有些发行版里!!表示哈希为空且被锁定有些发行版!后跟哈希表示锁定但不删除处理方式完全不同一定要先确认分布特征再批量操作。第三个坑是把哈希字符串随手贴到网上。如果审计过程中你需要比对某个哈希值请先脱敏把中间大部分字符打码只保留算法前缀和后几位用于区分。完整的哈希一旦公开配合在线碰撞服务等于把账号凭据半公开了。7.3 安全处理哈希数据的好习惯我自己的习惯是每次审计完把系统里所有账号的哈希状态整理成表格存到权限受限的本地文件夹不放到公网云笔记里。脚本输出只保留用户名、算法、策略字段不保留完整哈希。做完加固之后把auth.log、secure日志里有关于passwd、useradd、chage的关键动作拉出来检查一遍确保没有异常的新增账户。最后再分享一个小技巧给审计脚本加一个简单的阶段对比逻辑记录上次运行时的账号清单下次运行后自动比对有新增账号或哈希状态变化的单独告警。这套流程跑顺之后密码哈希从一次性的神秘数据就变成了日常巡检里最普通、也最可靠的一个指标项。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门