chmod 777 埋下的木马:一次 Linux 文件权限排查复盘

为消除 Permission denied 用 chmod 777,三个月后上传目录被传木马并执行。排查梳理:读懂 ls -l 与 ugo 权限三元组、chmod 数字法与符号法、chown 改属主才是正解、用户与用户组管理、SUID/SGID/Sticky 与 umask,以及为什么永远不该用 777。

2024 年我部署一个 Web 服务,服务起来后日志狂刷 Permission denied——它写不进上传目录。我当时赶时间,脑子里冒出那句流传甚广的"咒语":chmod -R 777。一敲,服务果然不报错了,我松了口气。三个月后,安全扫描告警:那个 777 的上传目录里,被人传进来一个 PHP 木马文件,还被成功执行了。我盯着告警,后知后觉地意识到——当初那句"777 大法",根本不是解决问题,而是把一扇门彻底拆掉了。这件事逼着我把 Linux 的文件权限、用户、用户组这一整套,认认真真从头理清了。本文复盘这次实战。

问题背景

环境:CentOS 7,一个用 www 用户跑的 PHP Web 服务
事故现象:
- 起初:服务写上传目录报 Permission denied
- 当时处理:chmod -R 777 /data/web/uploads —— "解决"了
- 三个月后:安全扫描发现上传目录里有 PHP 木马并被执行

现场排查:
# 1. 先看当初为什么会 Permission denied
$ ls -ld /data/web/uploads
drwxr-xr-x 2 root root ... /data/web/uploads
#  权限      属主 属组
#  目录属主是 root,组是 root,其他人(包括 www)只有 r-x
#  www 用户对它【没有 w 权限】 -> 写不进去 -> Permission denied

# 2. 服务是用哪个用户跑的
$ ps -ef | grep php-fpm
www  3721  ... php-fpm
# —— 服务进程的身份是 www,它需要的是"www 能写"

# 3. 看 chmod 777 之后变成了什么
$ ls -ld /data/web/uploads
drwxrwxrwx 2 root root ... /data/web/uploads
#  rwxrwxrwx —— 【所有人】都能读、写、执行
#  这意味着:任何能往这写文件的人,都能写入【可执行】文件

根因(后来想清楚的):
1. 真正的问题是【属主不对】:目录属于 root,
   而服务以 www 身份运行 —— 该做的是把属主改成 www。
2. 我却用 777 绕过了它:777 = 所有人可读可写可执行。
   "可写" + "可执行" 叠加在一个能被外部上传文件的目录上,
   等于允许任何人上传一个脚本【并运行它】 —— 这就是木马的入口。
chmod 777 不是解决权限问题,是放弃权限控制。

修复 1:读懂 ls -l——权限三元组与 ugo

# === ls -l 第一列那 10 个字符,是权限的全部信息 ===
$ ls -l app.sh
-rwxr-xr--  1  www  dev  2048 May 14 10:00 app.sh
# 第 1 个字符:文件类型(- 普通文件  d 目录  l 软链接)
# 后面 9 个字符,分成三组,每组 rwx

# === 三组分别对应谁 ===
# 第 1 组 rwx = u(user,属主)的权限
# 第 2 组 r-x = g(group,属组)的权限
# 第 3 组 r-- = o(other,其他人)的权限
# r=读  w=写  x=执行  - 表示没有这一项权限

# === ★ 一个进程访问文件时,系统怎么判定 ===
# 1. 进程的用户 == 文件属主?  -> 用 u 那一组权限
# 2. 否则,进程的用户在文件属组里? -> 用 g 那一组
# 3. 都不是 -> 用 o(其他人)那一组
# ★ 注意:匹配上哪一组就用哪一组,不是三组叠加。
#   属主是 www 时,哪怕 o 是 rwx,只要 u 是 r--,www 也只能读。

# === 对"目录"来说,rwx 的含义和文件不一样 ===
# r = 能 ls 列出目录里有什么
# w = 能在目录里【创建/删除/改名】文件 ★ 这条最关键
# x = 能 cd 进入、能访问目录里的文件
# —— 想往一个目录里写文件,需要的是对那个目录的 w + x。

# === 查看权限的几个命令 ===
$ ls -l 文件          # 看文件权限
$ ls -ld 目录         # 看【目录本身】的权限(不加 d 会列出里面的内容)
$ stat app.sh         # 看更详细,含数字形式的权限(如 0755)
$ namei -l /a/b/c     # 逐层看路径上每一级的权限(排查很有用)

修复 2:chmod——数字法与符号法

# === chmod 改权限,有两种写法 ===

# === 写法一:数字法(每组权限折算成一个数字)===
# r=4  w=2  x=1,把一组里的几项加起来:
# rwx = 4+2+1 = 7    r-x = 4+0+1 = 5    r-- = 4+0+0 = 4
# rw- = 4+2+0 = 6    --- = 0
$ chmod 755 app.sh    # u=rwx(7) g=r-x(5) o=r-x(5)
$ chmod 644 conf.yml  # u=rw-(6) g=r--(4) o=r--(4)
$ chmod 600 id_rsa    # u=rw-(6) g=---(0) o=---(0) 私钥就该这样

# === 常用数字权限的"标准答案" ===
# 755  目录、可执行脚本/程序(属主可改,其他人可读可进)
# 644  普通文件、配置文件、网页文件(属主可改,其他人只读)
# 600  私密文件:私钥、密码文件(只有属主能读写)
# 700  私密目录(只有属主能进)
# ★ 记住这几个,日常 99% 的场景就够了 —— 根本用不到 777。

# === 写法二:符号法(更直观,适合"只改某一项")===
$ chmod u+x app.sh      # 给属主【加】执行权限
$ chmod o-w file        # 把其他人的写权限【去掉】
$ chmod g+w,o-rwx file  # 组加写,其他人全去掉(逗号分隔多条)
$ chmod a+r file        # a = all,给所有人加读
$ chmod u=rwx,go=rx d   # 用 = 直接【设定】成某个值

# === -R 递归改整个目录树 ===
$ chmod -R 755 /data/web
# ★ -R 对目录和文件【一视同仁】,这会有个坑:
#   目录需要 x 才能进入,普通文件一般不该有 x。
#   chmod -R 755 会把所有普通文件也变成 755(带上了 x)。

# === 更精细:目录和文件分开设(推荐)===
$ find /data/web -type d -exec chmod 755 {} +   # 目录 755
$ find /data/web -type f -exec chmod 644 {} +   # 文件 644
# 这样目录能进、文件该读读该写写,普通文件不会平白多出 x 权限。

修复 3:chown / chgrp——属主与属组

# === 这次事故真正该做的:不是改权限,是改属主 ===
# 服务以 www 身份跑,那上传目录就该【属于 www】。

# === chown 改属主(也能同时改属组)===
$ chown www /data/web/uploads          # 属主改成 www
$ chown www:www /data/web/uploads      # 属主和属组都改成 www
$ chown :dev file                      # 只改属组(冒号开头)
$ chgrp dev file                       # chgrp 专门改属组

# === ★ -R 递归:这次该用,但要想清楚 ===
$ chown -R www:www /data/web/uploads
# 把 uploads 目录及其下面所有文件/子目录的属主都改成 www。
# 上次出事时,如果我做的是这一步(改属主),而不是 chmod 777,
# 既能让服务正常写入,又完全没有打开"所有人可写"的安全口子。

# === -R 的危险案例:在错的目录上递归 ===
$ chown -R www:www /          # 【灾难】把整个系统的属主都改了
# 系统文件属主一旦被改乱,sshd、sudo 等会大面积出问题。
# ★ 用 -R 前,把目标路径看三遍,绝对路径,别带错。

# === 改属主需要 root 权限 ===
# 普通用户不能把自己的文件"送"给别人(防止滥用配额等)。
# chown 通常要 sudo / root 来做。

# === 排查"服务为什么写不进去"的标准动作 ===
# 1. 服务以谁的身份跑?
$ ps -ef | grep 服务名               # 看进程的用户列
# 2. 目标目录属于谁、权限如何?
$ ls -ld /目标目录
# 3. 这条路径上每一级,该用户能不能进得去?
$ namei -l /目标目录                  # 逐级检查,常发现是中间某级缺 x
# 4. 对症:属主不对就 chown,缺某项权限就 chmod 加【那一项】
#    —— 永远是"精确补上缺的那一点",不是"777 全开"。

修复 4:用户与用户组管理

# === 用户和组,是权限体系的"主体" ===
# 每个用户有个 UID,每个组有个 GID。
# 一个用户有一个【主组】,还可以属于多个【附加组】。

# === 看"我是谁、我在哪些组" ===
$ id                       # 看当前用户的 uid/gid/所有组
$ id www                   # 看指定用户
$ whoami                   # 只看用户名
$ groups www               # www 属于哪些组
$ cat /etc/passwd          # 所有用户(每行:名:x:UID:GID:...:家目录:shell)
$ cat /etc/group           # 所有组

# === 新建一个【服务专用】用户(部署服务的规范做法)===
$ useradd -r -s /sbin/nologin -d /data/web www
# -r            建系统用户(UID 较小,不是给人登录用的)
# -s /sbin/nologin  ★ 禁止这个用户登录 shell —— 服务用户就该这样
# -d /data/web  指定家目录
# 服务用户不需要能登录,给它 nologin,被攻破时危害小得多。

# === 新建普通用户(给人用的)===
$ useradd -m -s /bin/bash alice    # -m 建家目录  -s 给登录 shell
$ passwd alice                     # 设密码

# === 用户组管理 ===
$ groupadd dev                     # 新建组 dev
$ usermod -aG dev alice            # ★ 把 alice 加进 dev 组
# ★ 一定要带 -a(append)!
$ usermod -G dev alice             # 【危险】不带 -a:会把 alice 的
#                                   附加组【替换】成只剩 dev,其他组全丢
$ gpasswd -d alice dev             # 把 alice 从 dev 组移除

# === 改了组,要重新登录才生效 ===
# usermod -aG 之后,alice 已经在的那个 shell 还是旧的组身份,
# 要她重新登录(或 newgrp dev)新组才生效。

# === 删除用户 ===
$ userdel alice                    # 删用户(保留家目录)
$ userdel -r alice                 # -r 连家目录一起删

修复 5:特殊权限位与 umask

# === 除了 rwx,还有三个"特殊权限位" ===

# === SUID(4):执行时临时获得"文件属主"的身份 ===
$ ls -l /usr/bin/passwd
-rwsr-xr-x ... /usr/bin/passwd
#    ↑ 属主那组的 x 位变成了 s = SUID
# passwd 要改 /etc/shadow(只有 root 能写),靠 SUID 让普通用户
# 执行它时临时变成 root。★ SUID 程序是攻击重点,要严格管控。
$ find / -perm -4000 -type f 2>/dev/null   # 列出全系统 SUID 文件
# 定期巡检这份清单 —— 多出来不明的 SUID 文件,极可能是后门。

# === SGID(2):用在【目录】上特别有用 ===
$ chmod g+s /data/share
# 之后在 /data/share 里【新建的文件】,属组会【自动继承】
# 这个目录的属组 —— 团队共享目录靠它保证组一致。

# === Sticky 位(1):用在公共可写目录上 ===
$ ls -ld /tmp
drwxrwxrwt ... /tmp
#         ↑ o 那组的 x 变成了 t = Sticky
# /tmp 谁都能写,但 Sticky 保证:你只能删【自己的】文件,
# 删不了别人的。公共可写目录必须有 Sticky,否则互相能删。

# === 给特殊位赋值:数字法在前面多加一位 ===
$ chmod 2775 /data/share     # 2 = SGID
$ chmod 1777 /data/pubtmp    # 1 = Sticky
$ chmod 4755 someprog        # 4 = SUID(慎用!)

# === umask:决定"新建文件/目录"的默认权限 ===
$ umask
0022
# 新文件的权限 = 666 - umask;新目录 = 777 - umask。
# umask 022 -> 新文件 644、新目录 755(常见默认,合理)。
$ umask 077          # 收紧:新文件 600、新目录 700(更私密)
# ★ 服务/脚本里如果新建的文件权限"太开放",
#   先查 umask 是不是被设得太松了。
# 永久改:写进 ~/.bashrc 或 /etc/profile。

修复 6:权限管理纪律

# === 这次事故暴露的权限管理问题,定几条纪律 ===

# === 1. ★ 第一纪律:永远不要用 chmod 777 ===
# 777 不是"解决权限问题",是"放弃权限控制"。
# 遇到 Permission denied,要做的是【精确定位缺什么】:
#   是属主不对?-> chown
#   是缺某一项权限?-> chmod 只补【那一项】(如 u+w)

# === 2. 服务用专用低权限用户跑,别用 root ===
$ useradd -r -s /sbin/nologin svcuser
# 服务进程一旦被攻破,攻击者拿到的就是这个用户的权限。
# 是 root 还是一个 nologin 的受限用户,后果天差地别。

# === 3. 标准权限就用标准值,别自创 ===
# 目录 755、文件 644、私钥 600、私密目录 700。
# 上传目录这种"外部可写"的:属主给服务用户、权限 755,
# 靠【属主】让服务能写,而不是靠"对所有人开放写"。

# === 4. ★ 外部可写目录,务必禁止脚本执行 ===
# 用户能上传文件的目录,在 Web 服务器层面禁掉它解析/执行脚本:
#   Nginx: location ~ ^/uploads/.*\.(php)$ { deny all; }
# 上次的木马就是"可写 + 可执行"叠出来的 ——
# 哪怕能写进去,不能执行,危害也小得多。

# === 5. 用 -R 之前,把目标路径核对三遍 ===
# chmod -R / chown -R 一旦路径写错,影响是整棵子树。
# 绝对路径、看清楚、再回车。

# === 6. 团队共享目录用 SGID,公共可写目录用 Sticky ===
$ chmod 2775 /data/teamshare    # SGID:新文件自动归属同一个组
$ chmod 1777 /data/pubtmp       # Sticky:只能删自己的

# === 7. 定期做权限巡检 ===
$ find / -perm -4000 -type f 2>/dev/null    # SUID 文件清单
$ find /data -perm -002 -type f 2>/dev/null # "其他人可写"的文件
$ find / -nouser -o -nogroup 2>/dev/null    # 孤儿文件
# 把这几条做成定期巡检脚本,异常权限早发现。

命令速查

需求                        命令
=============================================================
看文件权限                  ls -l 文件
看目录本身的权限            ls -ld 目录
逐级查路径权限              namei -l /某/路径
改权限(数字法)            chmod 755 文件
改权限(只改一项)          chmod u+w 文件
目录文件分开设权限          find 路径 -type d/f -exec chmod ...
改属主和属组                chown www:www 文件
递归改属主                  chown -R www:www 目录
建服务专用用户              useradd -r -s /sbin/nologin 名
把用户加进组(必带 -a)     usermod -aG 组 用户
看自己的 uid/组             id
列出全系统 SUID 文件        find / -perm -4000 -type f 2>/dev/null

口诀:Permission denied 先查"谁在跑/目标属于谁"
      -> 属主不对就 chown,缺权限就精确补 -> 永不 777

避坑清单

  1. chmod 777 不是解决权限问题而是放弃权限控制,任何时候都不该用
  2. ls -l 的 10 个字符:第 1 个是类型,后 9 个是 u/g/o 三组 rwx
  3. 权限判定是"匹配上哪组用哪组",不是三组叠加
  4. 对目录而言 w 是能增删文件、x 是能进入,往目录写文件需要 w+x
  5. 服务写不进目录,优先查属主对不对,该 chown 而不是放开权限
  6. chmod -R 会给普通文件也加上 x,目录文件应分开用 find -type 设
  7. chown -R / usermod 这类递归/替换操作,路径或参数写错影响巨大
  8. usermod 加组必须带 -a,不带 -a 会替换掉用户原有的所有附加组
  9. 服务用 useradd -r -s /sbin/nologin 的专用低权限用户跑,别用 root
  10. 外部可写目录要在 Web 层禁止脚本执行,SUID 文件要定期巡检

总结

这次从一个 PHP 木马倒查回去的排查,纠正了我一个用了很多年、却从来没认真反思过的坏习惯——把 chmod 777 当成解决一切权限报错的万能咒语。出事之后我才痛切地想明白,当年我敲下那条 chmod -R 777 时,我以为自己"解决"了 Permission denied,可我实际做的,是把这扇门连同门框一起拆掉了:报错确实消失了,因为门没了,自然也就没有了"被挡在门外"这回事。要真正理解这次事故,得先理解 Linux 权限模型的运作方式。一个文件的权限,被清清楚楚地划分成三组:属主 u、属组 g、其他人 o,每一组各自独立地拥有 r、w、x 三种权限。而当一个进程去访问这个文件时,系统的判定逻辑是"匹配优先":它先看这个进程的用户是不是文件的属主,是,就只用 u 那一组权限来裁决,后面两组看都不看;不是属主,再看它在不在文件的属组里,在,就用 g 那一组;两者都不是,才落到 o 那一组。理解了这个"匹配上哪一组就用哪一组"的逻辑,我当初那次 Permission denied 的真正病因就浮出水面了:服务进程是以 www 这个用户的身份在运行的,而那个上传目录的属主是 root、属组也是 root,www 既不是属主也不在属组里,于是它只能落到"其他人"那一组,而那一组恰恰没有 w 权限。所以这个问题的本质,从来不是"权限给得不够多",而是"属主根本就不对"。对症的解法,应该是用 chown 把这个目录的属主改成 www——让真正需要写它的那个用户,成为它的主人。这样一来,www 访问它时走的就是 u 那一组权限,我只要给 u 配上该有的读写权限,服务就能正常工作,而这一切完全不需要对"其他人"敞开任何东西。可我当年没有走这条正路,我走的是 777 这条歧路。777 意味着 u、g、o 三组,人人都是 rwx——所有人可读、所有人可写、所有人可执行。问题最致命的地方,在于这个目录是一个"上传目录",它的设计用途就是允许外部用户往里面放文件。"任何人都能往里写文件"和"任何人都能执行里面的文件"这两件事,一旦同时成立在同一个目录上,就等于我亲手为攻击者铺好了一条完整的攻击链:他先利用上传功能,把一个 PHP 木马文件写进这个目录(因为人人可写),然后再通过一个 URL 去访问、触发它(因为人人可执行)。三个月后那个被执行的木马,正是这条我亲手铺就的链路结出的果。这次事故之后,我给自己立下了几条关于权限的铁律。第一条,也是最重要的一条:永远、永远不要再用 chmod 777;遇到权限报错,我要做的是冷静地、精确地定位——到底是属主不对,还是仅仅缺了某一项具体的权限?属主不对就 chown,缺哪一项就用 chmod 精确地只补上那一项,绝不大手一挥全部敞开。第二条:服务一定要用一个专门创建的、nologin 的低权限用户来跑,这样万一服务被攻破,攻击者拿到的也只是一个处处受限的身份,而不是 root。第三条:像上传目录这种天然就"外部可写"的地方,必须在 Web 服务器这一层再加一道锁,明确禁止它解析和执行任何脚本——这样哪怕木马文件被写了进去,它也只是一个躺在那里、无法运行的死文件。这次从一个木马文件回溯到三个月前那条 777 命令,我最大的收获,是终于戒掉了"用最大的权限去消除报错"这个又懒又危险的本能,换上了一个朴素而正确的习惯:看清楚到底是谁、要访问什么、缺的究竟是哪一点,然后只把那一点,精确地补上。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
Linux教程

重启后新盘数据"蒸发"了:一次 Linux 磁盘挂载与 fstab 排查复盘

2026-5-20 18:09:18

Linux教程

重启后崩溃日志全没了:一次 Linux journalctl 日志排查复盘

2026-5-20 18:18:27

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索