1. 引言

查看目录或文件里的内容时报 Permission denied;可执行文件无法运行,这些问题的根源都在于文件权限。权限配置过松,攻击者拿到低权限账号就能读取或篡改文件;配置过紧,业务进程连自己的日志目录都写不进去。

权限模型大概是:对每个文件或目录,定义三类人(所有者、所属组、其他人)各自能做什么(读、写、执行)。但日常运维中容易遇到的是:为什么目录明明给了 w 权限还是删不了文件、为什么普通用户能改密码、/tmp 里为什么删不掉别人的临时文件,这需要把几层机制串起来才能理解透彻。


2. 用户分类与三个权限位

2.1 文件与目录 rwx 的差异

权限位附着在文件或目录上,针对三类访问者分别定义:

三类访问者各自有一套 rwx 位,但是普通文件与目录上 rwx 的含义是不同的

普通文件
- r(读):可读取文件内容,对应 catless 等命令。
- w(写):可修改文件内容,对应 viecho > 等操作。
- x(执行):可将文件作为程序运行,对应 ./file 或绝对路径调用。

目录

目录的写权限依赖执行权限 这是初学者比较容易忽视的地方:给一个目录设置 chmod 666(有 rwx),结果发现既进不去也删不了文件,因为内核检查到缺少 x 时直接拒绝后续操作。因为如果 w 能独立生效,攻击者可以在一个无法进入的目录里凭空删除文件,逻辑上说不通。

2.2 权限字符串

查看两个比较典型对象的权限字符串:普通文件 /etc/passwd 和特殊目录 /tmp

ls -l /etc/passwd
ls -ld /tmp
-rw-r--r-- 1 root root 1544 Jun 20 14:58 /etc/passwd
drwxrwxrwt 10 root root 4096 Jun 21 15:54 /tmp

stat 看数字形式的权限位:

stat -c "%a %n" /etc/passwd /tmp
644 /etc/passwd
1777 /tmp

644 由 owner(6=4+2)、group(4)、others(4) 组成。/tmp1777 中,前导 1 表示 Sticky Bit 已开启,后三位仍是 777。


3. 修改属主与权限的基本操作

3.1 chown、chgrp、chmod 的作用

权限修改有两种表达方式:

符号模式(symbolic):用 u(owner)、g(group)、o(others)、a(all)加上 +-= 操作。适合对现有权限做微调:

chmod u+x script.sh    # 给所有者加执行权
chmod g-w config.conf  # 去掉所属组的写权
chmod o=r file.txt     # 将 others 的权限设为只读(覆盖原有)

八进制模式(octal):每位权限用数字表示——r=4w=2x=1。三位分别对应 owner、group、others。适合批量设置标准权限:

chmod 755 app          # owner=7(rwx), group=5(r-x), others=5(r-x)
chmod 640 access.log   # owner=6(rw-), group=4(r--), others=0(---)

3.2 实践:为 Web 应用配置安全的目录权限

场景:模拟一个静态站点,Nginx 以专用用户 webapp 运行,代码放在 /var/www/app。需求是:

步骤 1:创建专用用户和组

sudo groupadd webgroup
sudo useradd -g webgroup webapp

-g webgroupwebapp 的主组设为 webgroup

创建另一个用户 opsuser 并加入 webgroup,模拟运维同事:

sudo useradd -G webgroup opsuser

-G 添加附加组,opsuser 的主组仍是自己的同名组,但附加组包含 webgroup

步骤 2:创建应用目录并设置属主和属组

sudo mkdir -p /var/www/app
sudo chown -R webapp:webgroup /var/www/app

目录属主为 webapp,属组为 webgroup

步骤 3:设置目录权限为 550

sudo chmod 550 /var/www/app

验证权限:

ls -ld /var/www/app
dr-xr-x--- 2 webapp webgroup 4096 Jun 21 16:36 /var/www/app

webapp(owner)有 r-xwebgroup(group)有 r-x,others 无任何权限。

步骤 4:验证 webapp 用户可读可进入,但不可写

先以 webapp 身份创建测试文件:

sudo -u webapp sh -c 'echo "Hello" > /var/www/app/index.html'
sh: 1: cannot create /var/www/app/index.html: Permission denied

写入被拒绝,因为目录缺少 w 权。

验证可进入和读取:先由 root 临时放入一个测试文件

sudo sh -c 'echo "Hello" > /var/www/app/index.html'

webapp 身份列出目录:

sudo -u webapp ls -l /var/www/app
total 4
-rw-r--r-- 1 root root 6 Jun 21 17:01 index.html

webapp 身份读取文件:

sudo -u webapp cat /var/www/app/index.html
Hello

结论:webapp 拥有 r-x,可读可进入,但 w 位缺失,写操作被拒绝。

步骤 5:验证 opsuser(同组成员)同样可读不可写

opsuser 属于 webgroup,应继承 group 权限 r-x

列出目录:

sudo -u opsuser ls /var/www/app
index.html

尝试写入:

sudo -u opsuser touch /var/www/app/ops.txt
touch: cannot touch '/var/www/app/ops.txt': Permission denied

步骤 6:验证其他用户(如 nobody)完全无权限

sudo -u nobody ls /var/www/app
ls: cannot open directory '/var/www/app': Permission denied

步骤 7:结论

整个配置符合预期:webapp 可提供服务(读文件),运维组可排查(进入目录),其他用户被阻断。550 的设定同时剥夺了所有者和同组成员的写权限,杜绝了运行用户和运维人员的无意识篡改。如果需要写入日志,应该单独创建 /var/log/app 并配置 770 权限,而不是放开根目录的写权限。


4. 特殊权限位:SUID、SGID 与 Sticky Bit

普通权限的三位八进制(owner/group/others)只覆盖了常规访问控制。还有三个特殊位挂在权限模型的高位上,分别解决三类场景:提权执行、组继承、互删保护。

4.1 设计与运行机制

SUID(Set User ID,八进制 4000)

设置在可执行文件上。当普通用户运行该文件时,进程的有效用户 ID(effective UID)切换为文件所有者,而非执行者。

典型的例子是 /usr/bin/passwd

ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 64152 May 30  2024 /usr/bin/passwd

权限位中的 s 出现在 owner 的执行位上(rws)。普通用户执行 passwd 时,进程以 root 身份运行,因此能写入 /etc/shadow。如果没有 SUID,普通用户是改不了自己的密码的。

SUID 只对二进制可执行文件有效,对 shell 脚本无效,对目录无效。

SGID(Set Group ID,八进制 2000)

可执行文件上设置时,进程的有效组 ID 切换为文件所属组;在目录上设置时,新创建的文件/子目录继承该目录的所属组,而非创建者的主组。

可执行文件的典型例子是 /usr/bin/ssh-agent——在某些配置下,该文件设置 SGID 位,允许非特权用户运行时获得 ssh 组的权限,从而访问 /etc/ssh/ 下的全局配置套接字:

ls -l /usr/bin/ssh-agent
-rwxr-sr-x 1 root ssh 309688 Aug  9  2024 /usr/bin/ssh-agent

权限位中的 s 出现在 group 的执行位上(r-s),文件属组是 ssh。普通用户运行 ssh-agent 时,进程的有效组变为 ssh,因此能访问属组为 ssh 的共享资源。不过,SGID 在可执行文件上的使用在现代系统中逐渐减少了,更常见的用途是目录 SGID,其影响范围更广且更不易被攻击者利用。

目录 SGID 的典型场景是共享协作目录。假设 /project 的属组是 dev,没有 SGID 时,用户 A(主组 a-group)在其中创建的文件属组是 a-group,用户 B(主组 b-group)无法访问。开启 SGID 后,新文件属组自动变成 dev,所有 dev 组成员都能协作。

Sticky Bit(八进制 1000)

设置在目录上。目录内每个文件只有文件所有者、目录所有者或 root 才能删除或重命名,其他用户即使对该目录有写权限也无法删除别人的文件。

典型例子是 /tmp

ls -ld /tmp
drwxrwxrwt 10 root root 4096 Jun 21 17:26 /tmp

末尾的 t 表示 Sticky Bit。没有它,任何对 /tmp 有写权的用户都能随意删除其他用户的临时文件,这在多租户系统上是灾难。

SUIDSGIDSticky Bit 这三者的八进制位固定在 12 位权限的高三位,

与普通权限组合时格式为 [SUID][SGID][Sticky][owner][group][others],例如 4755 = SUID + 755,1777 = Sticky + 777。

4.2 实践:三类特殊权限的效果

SUID 验证

/bin/sleep 复制到测试目录,开启 SUID,观察进程的 effective user。

mkdir -p /tmp/test-suid
cp /bin/sleep /tmp/test-suid/
sudo chown root:root /tmp/test-suid/sleep
sudo chmod u+s /tmp/test-suid/sleep
ls -l /tmp/test-suid/sleep
-rwsr-xr-x 1 root root 35336 Jun 21 17:33 /tmp/test-suid/sleep

用普通用户(假设 webapp)运行该程序,另开一个终端查看进程身份:

终端 1:

sudo -u webapp /tmp/test-suid/sleep 60

终端 2:

ps -eo pid,user,comm | grep sleep
2795833 root     sleep

可以看到,进程的有效用户是 root,而不是执行者 webapp。文件所有者的 s 位成功将进程提权。

SGID 验证

创建共享目录 /srv/shared,属组设为 webgroup,开启 SGID,用 webappopsuser 先后创建文件,观察新文件的属组。

sudo mkdir -p /srv/shared
sudo chown :webgroup /srv/shared
sudo chmod 2770 /srv/shared   # 2700 (SGID) + 770 (owner+group 全权)
ls -ld /srv/shared
drwxrws--- 2 root webgroup 4096 Jun 21 17:40 /srv/shared

rws 中的 s 表示 SGID 已开启,且 group 有 rwx。用 webapp(主组 webgroup)创建文件:

sudo -u webapp touch /srv/shared/webapp-file
ls -l /srv/shared/webapp-file
-rw-r--r-- 1 webapp webgroup 0 Jun 21 17:45 /srv/shared/webapp-file

文件属组是 webgroup,而非 webapp 的主组(本例中 webapp 的主组正是 webgroup,所以暂时看不出差异)。用 opsuser(附加组包含 webgroup,但主组是 opsuser)创建文件:

sudo -u opsuser touch /srv/shared/opsuser-file
ls -l /srv/shared/opsuser-file
-rw-rw-r-- 1 opsuser webgroup 0 Jun 21 17:46 /srv/shared/opsuser-file

opsuser 的主组是 opsuser(而非 webgroup),但文件属组仍继承为 webgroup——这正是 SGID 的效果:新文件继承目录的属组,与创建者的主组无关。opsuser 虽然在 3.2 节被加入了 webgroup 附加组,但 SGID 演示中关键点在于“新文件属组 = 目录属组”,这与用户的主组或附加组归属无关,SGID 位强制覆盖了创建者的主组。

Sticky Bit 验证

创建测试目录并设为 1777(Sticky + 777),webapp 创建文件,opsuser 尝试删除。

sudo mkdir /tmp/test-sticky
sudo chmod 1777 /tmp/test-sticky
ls -ld /tmp/test-sticky
drwxrwxrwt 2 root root 4096 Jun 21 17:49 /tmp/test-sticky

末尾是 t(others 的执行位原本存在,变为 t)。webapp 创建文件:

sudo -u webapp touch /tmp/test-sticky/webapp.dat

opsuser 尝试删除:

sudo -u opsuser rm /tmp/test-sticky/webapp.dat
rm: cannot remove '/tmp/test-sticky/webapp.dat': Operation not permitted

尽管 opsuser 对目录有 w 权(目录权限为 777),但 Sticky Bit 阻止了跨用户删除。只有 webapp、root 或目录所有者(root)可删。


5. 默认权限的来源:umask

5.1 计算公式与默认值背后的取舍

用户创建文件或目录时没有显式指定权限,系统用一组默认值减去 umask 得到最终权限:

最终权限 = 基值 & ~umask(按位取反后与运算)。例如 umask 022 的二进制是 000 010 010,取反后为 111 101 101,与基值按位与后,group 和 others 的写权(值为2的那一位)被清除:

身份 基值位(666=110 110 110) umask 位(022=000 010 010) 最终位
owner 110(rw-) 000(---) 110(rw-)
group 110(rw-) 010(-w-) 100(r--)
others 110(rw-) 010(-w-) 100(r--)

umask 022 是大多数 Linux 发行版的默认值,含义是“屏蔽 group 和 others 的写权限”——只允许所有者写,其他人只读/执行。这个策略平衡了安全和协作:系统文件不被随意篡改,而用户个人文件默认不暴露给同机其他人。

umask 002 是另一个常见值,尤其是在开发机器或共享服务器上:屏蔽 others 的写权,保留 group 的写权。新建文件为 664,目录为 775,同组成员可直接协作。但生产服务器上通常不用 002,因为任何同组用户都能修改你的文件,风险偏高。

5.2 实践:调整 umask 并观察新建文件/目录的默认权限

当前 shell 查看 umask:

umask
0022

切换到 0002,观察差异:

umask 0002
touch file-002
mkdir dir-002
ls -l file-002
ls -ld dir-002
-rw-rw-r-- 1 root root 0 Jun 22 17:36 file-002
drwxrwxr-x 2 root root 4096 Jun 22 17:36 dir-002

文件 664(rw-rw-r--),目录 775(rwxrwxr-x)。切回 022:

umask 0022
touch file-022
mkdir dir-022
ls -l file-022
ls -ld dir-022
-rw-r--r-- 1 root root 0 Jun 22 17:38 file-022
drwxr-xr-x 2 root root 4096 Jun 22 17:38 dir-022

文件 644,目录 755。输出中的 group 位从 rw- 降为 r--(文件)或 r-x(目录),这也印证了 022 屏蔽了 group 的写权。

umask 命令在 shell 中直接执行时,修改的是当前 shell 进程内部的 umask 值,不写入任何配置文件。关闭当前 shell 后,新打开的 shell 会重新从 /etc/profile~/.bashrc 等启动文件中读取 umask 设置,恢复到默认值。如果需要全局生效,需要将 umask 0022 写入 /etc/profile(影响所有用户)或 ~/.bashrc(影响当前用户)。写入后,新登录的 shell 自动应用该值,已存在的 shell 不受影响。


6. 突破传统三元组的细粒度控制:ACL

6.1 传统权限在多人协作中的局限

owner/group/others 三元组只能定义三种身份。实际协作中可能会有这种需求:
- 目录是 webgroup 开发组的,但实习生 dev-leo 只能看代码,不能改。
- 组长 dev-leader 需要有完全控制权,随时能删改。
- 普通成员 dev-zhang 继承组权限,能读写。

传统的三位权限没法给“特定某个人”单独授权。ACL(访问控制列表)就是用于解决这个短板的——它在标准的 owner/group/others 之外,挂了一串扩展规则,允许给任意用户或任意组单独设权限。

内核检查权限时按这个顺序来:

  1. 进程的身份是不是文件 owner?如果是 → 用 owner 权限。
  2. 不是 owner,那 ACL 里有没有专门给这个用户设的规则?如果有 → 用这条规则。
  3. 没有用户级规则,那进程所属的组有没有匹配文件属组或 ACL 里的组规则?如果有 → 用组规则。
  4. 以上都没有 → 用 others 权限。

ACL 不影响原有的三位权限,只是在它们前面插了两层更细的检查。

操作 ACL 主要用两个命令:setfacl 用来添加或修改规则,getfacl 用来查看现有规则。

setfacl -m <规则> <文件或目录>
getfacl <文件或目录>

规则格式为 u:用户名:权限(针对用户)或 g:组名:权限(针对组),权限用 rwx 组合或八进制数字(如 5 表示 r-x)。例如:

setfacl -m u:zhang:rwx /project/shared   # 给 zhang 完全控制
setfacl -m g:test:rx /project/shared     # 给 test 组只读+执行

查看规则用 getfacl,输出会列出标准权限位加所有 ACL 扩展条目。移除规则用 setfacl -x

6.2 实践:给开发组目录里的特定角色单独授权

场景:/project/dev-shared 是开发组共享目录,属组为 dev-group,所有开发组成员都能读写。但需要额外控制:

先创建组和三个角色用户:

sudo groupadd dev-group
sudo useradd -m -G dev-group leader-zhang
sudo useradd -m -G dev-group dev-wang
sudo useradd -m -G dev-group intern-leo

-G dev-group 把三个人都加入 dev-group 附加组。这样默认他们都继承 dev-group 的组权限,然后再用 ACL 对 intern-leoleader-zhang 做覆盖。

创建目录并设属组和权限:

sudo mkdir -p /project/dev-shared
sudo chown :dev-group /project/dev-shared
sudo chmod 770 /project/dev-shared

770 表示属主和属组都有完整读写执行权,其他人无任何权限。

确认文件系统是否支持 ACL。直接看 mount 输出不一定可靠(因为很多系统默认开启但不显示 acl 字样),用 tune2fs 查文件系统特性更准确:

sudo tune2fs -l $(df -T / | awk 'NR==2 {print $1}') | grep -i acl
Default mount options:    user_xattr acl

输出中包含 acl,表示根文件系统支持 ACL。

查看目录当前权限:

ls -ld /project/dev-shared
drwxrwx--- 2 root dev-group 4096 Jun 22 18:17 /project/dev-shared

现在用 ACL 覆盖两个特定用户:

sudo setfacl -m u:intern-leo:rx /project/dev-shared
sudo setfacl -m u:leader-zhang:rwx /project/dev-shared

查看完整权限:

getfacl /project/dev-shared
# file: project/dev-shared
# owner: root
# group: dev-group
user::rwx
user:leader-zhang:rwx
user:intern-leo:r-x
group::rwx
mask::rwx
other::---

user::rwx 是 owner(root)的权限,group::rwx 是属组 dev-group 的权限。ACL 新增的条目:leader-zhangrwxintern-leo 只有 r-xmask::rwx 是有效权限掩码,相当于所有命名用户规则的上限,这里保持 rwx 不做额外限制。

验证 intern-leo(实习生):能进目录看内容,但写不进去。

sudo -u intern-leo touch /project/dev-shared/leo-test
touch: cannot touch '/project/dev-shared/leo-test': Permission denied

验证 leader-zhang(组长):能创建文件。

sudo -u leader-zhang touch /project/dev-shared/leader-test
ls -l /project/dev-shared/leader-test
-rw-rw-r-- 1 leader-zhang leader-zhang 0 Jun 22 19:32 /project/dev-shared/leader-test

验证 dev-wang(普通开发):没有单独的 ACL 规则,走的是 dev-group 的组权限 rwx,应该也能创建文件。

sudo -u dev-wang touch /project/dev-shared/wang-test
ls -l /project/dev-shared/wang-test
-rw-rw-r-- 1 dev-wang dev-wang 0 Jun 22 19:34 /project/dev-shared/wang-test

移除 ACL 条目用 setfacl -x

sudo setfacl -x u:intern-leo /project/dev-shared

ACL 的好处是增量添加,不影响底层的 owner/group/others 权限。实习生离职了删掉他对应的 ACL 条目就行,不用动整个目录的权限位。组长和普通开发各走各的规则,互不干扰。


结语

文件权限检查不是只看目标文件那 9 个位。每次路径访问,内核从根目录开始逐级校验每一层的执行权;到达目标后,再根据进程的有效 UID/GID,依次匹配 owner、ACL 命名的用户、group、ACL 命名的组、others,任一环节被阻断都会返回 Permission denied。特殊权限(SUID/SGID/Sticky)和 ACL 在这个基础模型上扩展了提权、继承和细粒度授权的能力,但是它们不改变检查顺序,只是在各自的插入点改变了“命中后应用谁的权限”。弄清楚这个逐级检查的顺序,就能把权限问题变成可推理的确定的过程。