1. 一场权限事故,让我把Linux权限管理重新学了一遍
前阵子半夜被电话叫醒,生产环境一台服务器上跑着的定时任务突然大面积报错,报的全是"Permission denied"。登录上去一看,某个数据目录的属主和权限全乱了,原本应该是drwxr-xr-x的目录,不知什么时候被改成drwx------,更离谱的是属主变成了一个早已离职同事的UID。最后排查下来,是有人用root执行了一串脚本,脚本里有个chown -R拼写错误,把不该动的目录一并处理了。
这件事让我意识到一个常被忽略的事实:在Linux系统里,权限管理不是"会不会敲chmod"的问题,而是整个系统安全模型的承重墙。多少人平时只记了chmod 777和chown user:group几条命令就觉得自己会权限管理了,真到出问题时才发现,rwx背后藏着一整套设计逻辑——普通权限位、特殊权限位、ACL、umask、用户与组的关系、提权路径的攻防博弈,每一项都在暗处发力。
这篇内容我会从一次次的实操教训出发,把Linux权限管理的核心逻辑拆开讲清楚。无论是刚入门的小白,还是已经写过几年脚本的老手,这篇文章里的很多坑,我相信你迟早也会踩到。看完之后,你能更系统地去理解权限设计,而不是只会背命令。
2. rwx三位一体的底层逻辑:权限位不是"记数字"那么简单
2.1 权限位在文件系统里的真实身份
很多人对权限位的理解停留在"r=4,w=2,x=1,加起来就是权限数字",这没错,但只是表层的。权限位在文件系统层面,本质上是一组比特位(bit),它们直接编码在inode的权限字段里。rwxr-xr-x这样的字符串,其实是12个比特位的组合:3个特殊权限位(SUID、SGID、Sticky)+ 3个用户权限位 + 3个组权限位 + 3个其他用户权限位。
数字表示法755之所以成立,是因为每一组权限位正好是一个3-bit的二进制数。r对应100(4),w对应010(2),x对应001(1),三者相加就是0到7的整数。理解这一点很重要,因为它决定了chmod的很多细节行为。比如chmod 644 file,等价于把user位设为rw-,group位设为r--,other位设为r--,这直接对应着inode里的比特位变化。
需要特别说清楚的是,文件和目录的rwx含义是完全不同的。对普通文件而言,r代表可读内容,w代表可修改内容,x代表可执行;但对目录来说,r代表可列出目录里的文件名,w代表可以在目录里创建或删除文件,x代表可以进入目录(也就是可以访问目录内文件的inode)。很多人搞不懂"为什么我对一个目录有rwx权限,还是进不去",就是因为把目录当文件来理解了。
2.2 umask与默认权限的计算关系
再来说umask,这个值几乎每天都在起作用,却很少有人真正搞明白它怎么算。当你新建一个文件时,系统并不是直接给满权限,而是先给出一个"最大默认权限"——普通文件是666,目录是777,然后减去umask里屏蔽掉的位。
我见过太多人背结论:"umask 022出来的是644和755"。这句话对,但换个umask就会算错。正确的计算逻辑不是简单的数字相减,而是按位取反再按位与:
# 以umask=027为例 # 权限位的二进制表示 666 = 110 110 110 027 = 000 010 111 # 计算方式:先对umask按位取反,再与默认权限按位与 # 文件:666 & ~027 = 110 110 110 & 111 101 000 = 110 100 000 = 640 # 目录:777 & ~027 = 111 111 111 & 111 101 000 = 111 101 000 = 750所以umask=027时,新文件权限是640,新目录是750。如果你用数字减法去算,得到639和750,文件就错了。这个细节在面试里经常被拿出来考,而在实际工作中,umask设置不当会导致团队协作时互相读不了文件。
提示:如果你在某台服务器上发现新建文件权限不对劲,先查umask,
umask命令直接查看,改的话临时执行umask 022,永久修改写到/etc/profile或/etc/bashrc里。
2.3 setuid位对权限位的"伪装"
还有个点容易被忽略——当你在ls -l下看到权限位里出现s而不是x时,说明特殊权限位被启用了。比如-rwsr-xr-x,那个user位的s表示SUID。为什么是"s"?因为SUID位和x位共用同一个位,启用SUID时如果本身有x,就显示s;如果没有x,就显示S。这一点我后面会专门展开讲,这里先提个醒:看到S大写时,别以为权限没问题,那是"空有特殊位却没有执行权限"的坑。
3. 新建用户不是一条useradd那么简单:用户与组的坑
3.1 useradd、adduser与userdel的边界
很多教程喜欢用adduser,但在CentOS/RHEL系列上,adduser其实是指向useradd的符号链接,功能完全一样;而在Debian/Ubuntu系列上,adduser是一个更友好的交互式脚本,会自动帮你创建家目录、设置密码、填充用户信息。这种"同名不同命"的情况,是跨平台操作时最容易踩的坑。
我个人的习惯是:脚本化批量操作一律用useradd,手动偶尔建一两个用户用adduser。useradd的精髓在参数组合上,最基本的一条:
# 创建用户并指定家目录、shell、附加组 useradd -m -d /home/zhangsan -s /bin/bash -G wheel,data zhangsan这里-m是自动创建家目录(不加的话只有用户没有家目录),-d指定家目录路径,-s指定登录shell,-G指定附加组。一个最常见的错误是忘了加-m,结果用户创建成功但家目录不存在,用户登录后直接落在/目录,后面各种奇奇怪怪的问题都来了。
userdel的坑更大。默认的userdel只是删除用户账号,并不会删除用户的家目录和邮件池。要彻底清理,需要userdel -r。问题是如果不加-r,家目录和文件还在,但文件的属主显示成一串数字(UID),时间一长系统里全是孤儿文件,找不出是谁的。我处理过最极端的一个案例,某项目目录下几千个文件属主UID相同,但那个UID对应的用户早就不存在了,只能靠逐一比对时间戳和内容来归属。
3.2 主组与附加组的设计逻辑
这里我必须用一点篇幅强调主组和附加组的区别,因为这是权限管理里最容易被误解的概念之一。每个用户必须有一个主组(primary group),同时可以属于多个附加组(supplementary groups)。主组决定用户新建文件时文件的默认属组,附加组则决定用户额外拥有哪些组的访问权限。
现代Linux发行版通常会为每个新用户创建一个同名的私有组(user private group),这是有道理的:每个用户一个独立主组,可以有效避免多个用户共享一个主组时,新建文件权限互相影响的问题。但在一些老的Unix系统(包括某些商业Unix)上,所有普通用户共享一个users组,结果就是任何用户新建的文件,同组的其他人都有读取权限——这放在多租户环境里就是赤裸裸的信息泄露。
在设计测试环境或多人协作环境时,我的建议是:细粒度的项目组作为附加组来管理,不要轻易改用户的主组。比如开发A和生产发布都归ops组管,ops作为附加组加到用户上,而不是把用户的主组改成ops。理由很简单:主组影响新建文件的默认属组,如果你把主组改成ops,那么用户新建的所有文件,组权限都是针对ops组成员的,如果ops组里有不该看这些文件的人,权限就失控了。
3.3 兼容shell与被锁定的账号
还有一个常见场景是创建服务账号,比如给某个Web服务建一个运行账号:
useradd -r -s /sbin/nologin webapp-r是创建系统账号(UID小于1000,通常不分配家目录),-s /sbin/nologin是让该用户无法交互式登录。这个组合在创建服务账号时几乎是最优解,能防止有人通过su切到该账号再进行交互操作。但要注意,/sbin/nologin只是在登录时拒绝shell,不代表该用户不能执行其他命令。真正要限制权限,还得配合sudo策略或更细粒度的访问控制。
4. SUID、SGID与粘滞位:特殊权限位的攻防边界
4.1 SUID的本质与典型案例
特殊权限位是整个Linux权限模型里最容易出安全问题的地方,也是面试和实际运维中的高频话题。SUID(Set User ID)的核心机制是:当一个可执行文件设置了SUID位,任何用户执行该文件时,进程的有效用户ID(effective UID)会变成文件属主的UID,而不是当前执行者的UID。
最经典的例子是/usr/bin/passwd:
ls -l /usr/bin/passwd -rwsr-xr-x 1 root root 68208 May 13 2024 /usr/bin/passwd普通用户要修改自己的密码,需要写/etc/shadow,这个文件只有root能写。如果没有SUID机制,普通用户永远无法改密码。正是因为passwd命令的属主是root且设置了SUID位,普通用户执行它时,进程以root身份运行,才能安全地修改shadow文件。
这个机制本身很精妙,但也非常危险。如果某个文件属主是root且可写,又被设置了SUID,那就相当于给任何能执行该文件的人一把root权限的钥匙。排查SUID文件是Linux安全加固的必做项:
# 找出系统中所有设置了SUID位的文件 find / -perm -4000 -type f 2>/dev/null这条命令的结果里如果出现了你完全不认识的可疑路径,基本可以断定系统被动过手脚。常见的提权攻击手法,就是上传一个SUID shell到/tmp之类的目录,然后等着某个粗心的管理员去执行与它相关的操作。
4.2 SGID的两种作用和Directory SGID
SGID(Set Group ID)比SUID复杂一点,分为文件和目录两种情况。
文件上的SGID:进程的有效组ID变成文件属组。典型例子是/usr/bin/write,它让用户能以tty组的身份写其他用户的终端。
目录上的SGID:这是实际工作中更常碰到的。一个目录设置了SGID后,在该目录下新建的文件,属组会自动继承目录的属组,而不是创建者自己的主组。这个特性非常适合团队共享目录:
mkdir -p /data/share chown root:devteam /data/share chmod 2770 /data/share这样设置后,任何属于devteam组的成员在/data/share下创建的文件,属组都是devteam,组内其他成员就能顺畅地读写这些文件。如果没有SGID,每个人用自己的主组去创建文件,组权限形同虚设。
提示:目录的SGID位一旦设置,对"新建文件"生效,但对"已有文件"无效。所以早期迁移数据到共享目录时,要记得对已有文件统一执行
chgrp -R,否则历史的权限遗留问题会在后续折磨你。
4.3 粘滞位:/tmp目录的守护者
Sticky Bit(粘滞位)是最容易理解的特殊权限位。它的效果是:在一个设置了粘滞位的目录下,只有文件属主、目录属主或root才能删除或重命名文件,其他用户即使对该目录有写权限,也无法删除别人的文件。
最典型的就是/tmp:
ls -ld /tmp drwxrwxrwt 20 root root 4096 Jan 20 10:32 /tmp那个t就是粘滞位。所有用户都对/tmp有写权限,如果没有粘滞位,任何用户都能删除/tmp里的任何文件,那简直是灾难现场。tar解压、临时文件、socket文件全堆在/tmp里,互相删来删去,系统分分钟崩给你看。
设置粘滞位的方法很简单:
chmod +t /data/public # 或用数字:chmod 1777 /data/public4.4 特殊权限位的数字表示法
既然聊到数字表示法,顺带把特殊权限位的位置说清楚。特殊权限位是权限数字的第4位:
- SUID = 4
- SGID = 2
- Sticky = 1
比如chmod 4755 file就是设置SUID+rwxr-xr-x,chmod 1777 /tmp就是粘滞位+rwxrwxrwx。这个第4位经常有人搞混,实际敲命令时我建议先想清楚这个文件的属主和属组是谁,再决定要不要加特殊位,绝大多数业务文件是不需要SUID的。
5. 提权路径复盘:权限配置里常见的漏洞与加固
5.1 sudoers文件里那些"省事"配置的风险
Linux提权这个话题,网上的攻击文章一大堆,但作为使用者和保护者,我更关注的是"哪些配置会给自己留后门"。sudo是Linux权限管理中最高频使用的提权入口,也是最容易被配置失误的地方。
先看一条看起来无害的配置:
# /etc/sudoers zhangsan ALL=(ALL) NOPASSWD: /usr/bin/vim这条配置的本意是允许zhangsan免密执行vim。但实际操作中,vim本身支持在命令行里执行shell命令,vim里输入:!bash就能直接获得root shell。等价危险的配置还有:/usr/bin/less(通过!命令执行)、/usr/bin/man(同样是分页器的shell逃逸)、/usr/bin/python(本身就是解释器)。这就是为什么sudoers里给单个编辑器或解释器授权是最危险的操作之一。
更稳的做法是尽量缩小授权范围,或者只在特定脚本上放行:
zhangsan ALL=(root) NOPASSWD: /usr/local/bin/deploy.sh而且只要条件允许,一定要加上命令参数的校验。比如只允许带特定参数执行:
zhangsan ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx这样比直接放行/usr/bin/systemctl安全得多,因为systemctl本身也能被利用去启用任意服务。
5.2 可写路径与PATH劫持
另一个常见权限漏洞与PATH环境变量有关。如果当前用户的PATH里包含了一个可写目录,而这个目录排在系统目录之前,那么当你执行一个不带路径的命令时,可能被劫持成恶意的同名脚本。
举个例子:攻击者在/tmp下放了一个叫ls的脚本,内容是一段恶意代码,然后诱导root用户进入一个PATH包含了/tmp的shell环境,再执行ls——结果执行的是/tmp/ls,而不是/bin/ls。脚本以root身份运行,于是系统就被攻破了。
检查方法很简单:
echo $PATH如果输出里出现.或者可写的临时目录,就要警惕了。安全的PATH里不应该包含任何普通用户可写的目录。另外,在执行系统命令时,我习惯直接用绝对路径,比如/bin/ls而不是ls,尤其在编写root环境的脚本中,这个习惯能挡掉一大部分风险。
5.3 文件属主混乱与过度宽松的权限
权限提权的第三个大来源是属主和权限配置的"脏"。最常见的两种脏配置:
一是属主为root但其他用户可写的文件。比如-rwxrwxrwx root root,这类文件如果被业务进程执行,攻击者只需改内容就能实现代码注入。排查命令:
find / -type f -perm -0002 -uid 0 2>/dev/null二是目录权限给了777。业务目录用777表面上解决了"大家都能写"的问题,但同时也意味着任何人都能删除里面的任何文件,包括日志文件和数据文件。一旦日志文件被删或替换,入侵痕迹就被清除了。
我的建议是:业务目录权限给到770或750就足够了,组内共享用SGID配合,跨组协作用ACL(后面会讲),尽量不要用777这种"一揽子方案"。权限管理不是越宽松越好,而是最小够用。
5.4 sudo日志与审计的落地
提权风险不可能完全清零,所以审计必须跟上。sudo默认会通过syslog把执行记录写到/var/log/secure(RHEL系)或/var/log/auth.log(Debian系)。我建议单独为sudo开一个日志文件,集中审计:
# /etc/sudoers Defaults logfile=/var/log/sudo.log这样每次谁在什么时候执行了什么sudo命令,都会留痕。排查入侵时,这份日志是还原攻击路径的第一手证据。
6. ACL扩展权限:传统权限位不够用时的解药
6.1 传统权限位的天花板在哪
传统rwx权限模型有一个硬限制:每个文件只能表达一个属主、一个属组、一组其他用户权限。但实际工作里经常出现"一个文件需要同时给多个组不同权限"的需求。
举个典型场景:某个项目目录/data/project,devteam组的成员需要读写权限,opsteam组的成员只需要只读权限,而其他用户完全不可访问。用传统权限位怎么配?属主设成devteam,属组设成opsteam,权限设成rwxr-x---,表面上满足了。但项目里还有个testteam组需要只读权限,这就没法用一组属组权限表达了。
ACL(Access Control List,访问控制列表)就是为这种场景设计的。它允许你在传统属主/属组/其他权限之外,为任意指定的用户或组单独设置权限。
6.2 setfacl与getfacl的实际操作
操作ACL主要用两个命令:getfacl查看,setfacl设置。
# 给testteam组增加只读权限 setfacl -m g:testteam:r-x /data/project # 给用户lisi单独增加读写权限 setfacl -m u:lisi:rwx /data/project # 查看ACL getfacl /data/project设置之后执行ls -l,输出末尾会多一个+:
ls -ld /data/project drwxr-x---+ 4 root devteam 4096 Jan 20 11:30 /data/project这个+表示该文件存在ACL扩展条目。有ACL的文件,传统ls显示的组权限位可能不代表真实的组权限了,因为ACL引入了mask的概念。
这里有一个特别容易踩的坑:ACL的mask值会卡住所有命名用户和命名组的最大权限。比如你给lisi设置了rwx,但mask是r-x,那么lisi实际能拿到的权限只有r-x。mask的作用是限制所有ACL条目(除了属主和其他用户)的最大权限。修改mask用:
setfacl -m m::rwx /data/project6.3 默认ACL与目录继承的坑
目录还支持默认ACL(default ACL),它决定了该目录下新建文件的初始ACL:
# 设置默认ACL,使新建文件自动继承 setfacl -d -m g:testteam:r-x /data/project这个功能非常方便,但也带来了隐患:如果默认ACL里带了w权限,那么新建的所有文件都会被写入,就算文件属主自己没想给组写权限。我遇到过不止一次,因为默认ACL设置不当,导致代码目录里的新文件全部对某个组开放了写权限,结果被同事误改后追查了半天。
所以用默认ACL时,建议先明确"新建文件的默认权限到底该是什么",再配合umask一起设计。另外,删除ACL条目用setfacl -x,清空全部ACL用setfacl -b,别搞混,不然会误删。
7. 一次"Permission denied"的完整排查链路
7.1 从报错到定位:分层排查思路
权限问题的报错都是一句话"Permission denied",但背后原因可能千差万别。我总结了一套排查链路,每次照这个顺序走,基本不会漏。
第一步:确认当前身份
id uid=1000(zhangsan) gid=1000(zhangsan) groups=1000(zhangsan),10(wheel)先搞清楚自己是谁、在哪些组里。很多人排查半天,最后发现是自己用了错误的用户登录。
第二步:看目标文件的属主和权限
ls -ld /data/project注意看属主、属组、权限位,以及是否带+(有ACL)。这里最容易漏的是文件在哪个文件系统上——如果文件在NFS挂载或FUSE挂载上,权限判断会涉及服务端的export选项,本地权限对了也可能无法访问。
第三步:检查路径上每一层目录的权限
这是最常被忽略的一步。你想访问/data/project/file.txt,但路径上的/data权限是700,属于另一个用户,那你就算对file.txt有rwx权限也进不去。权限判定是逐层叠加的,任何一层的x权限缺失都会导致不可达。用namei -l可以一次性看完整条路径的权限:
namei -l /data/project/file.txt f: /data/project/file.txt drwxr-xr-x root root / drwxr-xr-x root root /data drwxr-x--- root devteam /data/project -rw-r----- root devteam /data/project/file.txt一眼就能看出卡在哪一层。
第四步:区分是"不可读"还是"不可写"
同样的Permission denied,可能是读权限缺失,也可能是写权限缺失。用strace跟踪一下实际操作,能看到具体的系统调用和错误码:
strace -f -e trace=openat,read,write touch /data/project/test.txt 2>&1 | tail -20如果错误是EACCES,就是权限不足;如果是EROFS,那是文件系统只读挂载——这是另一个完全不同的问题。
7.2 典型的权限故障速查
下面这张表是我这些年遇到的权限故障精华,直接对着查能省很多时间:
| 现象 | 可能原因 | 快速验证方法 |
|---|---|---|
| 目录内文件可见,但无法创建新文件 | 目录缺少w权限 | ls -ld查看目录权限位 |
| 文件可读,但执行时提示Permission denied | 文件缺少x权限,或挂载了noexec | mount查看挂载选项 |
| 目录能进入但看不到文件列表 | 目录有x但没有r | 执行ls -l对比是否报错 |
| 新文件属组不对 | 父目录没有SGID,或umask设置异常 | getfacl .查看目录ACL |
| 某用户能写但另一用户不能 | 文件有ACL条目限制 | getfacl查看mask值 |
| root也提示Operation not permitted | 文件有immutable属性 | lsattr查看,chattr -i解除 |
7.3 权限排查中的两个哲学问题
最后说两个排查权限问题时我会反复自问的点:
第一个问题是"这个文件到底该属于谁"。很多权限混乱的根源,是当初创建文件的人根本没有思考属主和属组,随便默认生成的。所以在修权限之前,先明确业务上正确的属主属组应该是什么,然后执行chown user:group和chmod把基线定下来。
第二个问题是"我是不是在用root解决问题"。很多人一看到Permission denied,顺手就是sudo chmod -R 777或干脆sudo su切到root去干活。这种操作表面上解决了问题,实际上是把权限问题掩盖了,并且在日志里留下一堆root操作痕迹。正确的做法永远是把权限调整到业务所需的最小集合,而不是用root绕过去。
8. 结尾:权限管理是系统运维的地基,值得多花心思
写到最后,分享一个我自己的体会。Linux权限管理这个知识点,看起来是入门内容,但真到了生产环境里,它往往是最容易反噬你的地方——一个多余的777、一条不严谨的sudo规则、一次错误的chown,都可能让你半夜爬起来擦屁股。
从我排查过的事故来看,权限问题很少是"单独一个命令打错了",更多是长期积累的结果:账号体系混乱、特殊权限位不受控、ACL和传统权限混合使用导致mask被忽略、sudo配置图省事只给命令不给参数限制。这些问题单独看都不算大,但叠加在一个系统上就是安全隐患。
所以我给你们的建议是:每隔一段时间,有意识地做一次权限审计。跑一跑find查SUID文件,梳理一遍/etc/sudoers,检查业务目录的权限位和ACL,清理掉那些长期不用的账号。这套动作看似简单,但在关键时刻真的能救命。权限管理不值得你追求炫技,它值得你做到干净、可控、可审计——这比任何花哨的操作都重要。