1. 引言
查看目录或文件里的内容时报 Permission denied;可执行文件无法运行,这些问题的根源都在于文件权限。权限配置过松,攻击者拿到低权限账号就能读取或篡改文件;配置过紧,业务进程连自己的日志目录都写不进去。
权限模型大概是:对每个文件或目录,定义三类人(所有者、所属组、其他人)各自能做什么(读、写、执行)。但日常运维中容易遇到的是:为什么目录明明给了 w 权限还是删不了文件、为什么普通用户能改密码、/tmp 里为什么删不掉别人的临时文件,这需要把几层机制串起来才能理解透彻。
2. 用户分类与三个权限位
2.1 文件与目录 rwx 的差异
权限位附着在文件或目录上,针对三类访问者分别定义:
- 所有者(owner):默认是创建者,可通过
chown更改。 - 所属组(group):默认是创建者的主组,可通过
chgrp更改。 - 其他人(others):除所有者和所属组之外的所有用户。
三类访问者各自有一套 rwx 位,但是普通文件与目录上 rwx 的含义是不同的。
普通文件:
- r(读):可读取文件内容,对应 cat、less 等命令。
- w(写):可修改文件内容,对应 vi、echo > 等操作。
- x(执行):可将文件作为程序运行,对应 ./file 或绝对路径调用。
目录:
r(读):可列出目录内容,即ls能看到文件名列表。但如果只有r没有x,只能看见文件名,查看不了目录下文件的大小、修改时间、权限等任何详细属性。w(写):可在目录内创建或删除文件/子目录。但光有写权限没用,必须搭配执行权限 x 才能生效;只有拥有进入文件夹的权限,才能修改、增减里面的文件。x(执行):可cd进入目录,也可访问目录内文件的 inode 元数据。目录的x是访问其下所有路径的前提。
目录的写权限依赖执行权限 这是初学者比较容易忽视的地方:给一个目录设置 chmod 666(有 rw 无 x),结果发现既进不去也删不了文件,因为内核检查到缺少 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
- 第一个字符:
-表示普通文件,d表示目录。/tmp最后一位t是特殊权限位(Sticky Bit)。 - 剩下的 9 位:前三位
rw-是 owner 的权限(root 可读写不可执行),中间三位r--是 group 的权限(root 组只读),后三位r--是 others 的权限(其他用户只读)。/tmp的 9 位是rwxrwxrwt——owner 和 group 都是rwx,others 是rwt。
用 stat 看数字形式的权限位:
stat -c "%a %n" /etc/passwd /tmp
644 /etc/passwd
1777 /tmp
644 由 owner(6=4+2)、group(4)、others(4) 组成。/tmp 的 1777 中,前导 1 表示 Sticky Bit 已开启,后三位仍是 777。
3. 修改属主与权限的基本操作
3.1 chown、chgrp、chmod 的作用
chown:更改文件或目录的所有者。只有 root 可执行。chgrp:更改所属组。文件所有者可改到自己所属的组,root 可改任意组。chmod:更改权限位。文件所有者或 root 可操作。
权限修改有两种表达方式:
符号模式(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=4、w=2、x=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。需求是:
webapp可进入目录并读取文件,但不可写。- 运维组的其他成员(
webgroup组)可进入目录排查,也不可写。 - 其他用户无任何权限。
步骤 1:创建专用用户和组
sudo groupadd webgroup
sudo useradd -g webgroup webapp
-g webgroup 将 webapp 的主组设为 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-x,webgroup(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 有写权的用户都能随意删除其他用户的临时文件,这在多租户系统上是灾难。
SUID、SGID、Sticky 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,用 webapp 和 opsuser 先后创建文件,观察新文件的属组。
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 得到最终权限:
- 普通文件默认基值:
666(rw-rw-rw-),因为文件默认没有执行权。 - 目录默认基值:
777(rwxrwxrwx),目录需要执行权才能进入。
最终权限 = 基值 & ~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--) |
- 文件:
666 & ~022 = 644(rw-r--r--) - 目录:
777 & ~022 = 755(rwxr-xr-x)
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 之外,挂了一串扩展规则,允许给任意用户或任意组单独设权限。
内核检查权限时按这个顺序来:
- 进程的身份是不是文件 owner?如果是 → 用 owner 权限。
- 不是 owner,那 ACL 里有没有专门给这个用户设的规则?如果有 → 用这条规则。
- 没有用户级规则,那进程所属的组有没有匹配文件属组或 ACL 里的组规则?如果有 → 用组规则。
- 以上都没有 → 用 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,所有开发组成员都能读写。但需要额外控制:
- 实习生
intern-leo:只读,不能写。 - 组长
leader-zhang:完全控制(读写删改)。 - 普通开发
dev-wang:继承组权限即可,不需要额外规则。
先创建组和三个角色用户:
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-leo 和 leader-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-zhang 有 rwx,intern-leo 只有 r-x。mask::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 在这个基础模型上扩展了提权、继承和细粒度授权的能力,但是它们不改变检查顺序,只是在各自的插入点改变了“命中后应用谁的权限”。弄清楚这个逐级检查的顺序,就能把权限问题变成可推理的确定的过程。