理解Linux文件权限

Linux 的安全性

每个进入 Linux 系统的用户会分配唯一的用户账户。用户权限是通过创建用户时分配的 用户ID(UID) 来追踪的,每个用户有唯一的 UID,但登录时用的是“登录名”,不是 UID。

在讨论文件权限前,先看看管理用户账户的文件和工具

/etc/passwd 文件

/etc/passwd 用于将用户的登录名匹配到对应的 UID. 下面是我的 /etc/passwd 文件的部分内容:

$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin

每一行包含如下信息:

  • 登录用户名
  • 用户密码(隐藏为 x,真实密码在 /etc/shadow)
  • UID
  • GID
  • 文字描述(称为备注字段)
  • 用户 HOME 目录位置
  • 用户默认 shell

第一行的 root 用户账户是 Linux 系统的管理员,固定分配的 UID 是 0。除了 root 外,系统会为各种功能创建不同的用户账户,称为 系统账户,是各种服务进程访问资源用的特殊账户。所有后台服务都需要用一个系统账户登录到 Linux 系统上。Linux 为系统账户预留了 500 以下的 UID 值。

/etc/shadow 文件

/etc/shadow 文件保存的密码和相关的设置,只有 root 用户才能访问。

$ sudo cat /etc/shadow
pi:$6$j.c1dy4MwJsxSw.Y$kRSXfoGsSt5UlXb12d6MqcSwxGx0Eh7zEIvQkdzoCu3Suc5FDfb74hl8GzHCllXS8wU0pTf5.yCslmPNT3SrD0:18629:0:99999:7:::

用户名后面的就是加密后的密码,除此之外,还有一些如“多少天后必修更改密码”的设置。

添加新用户

我们可以用 useradd 来添加新用户。一些发行版把 Linux 用户和组工具放在 /usr/sbin 目录下,需要手动将这个目录添加到 PATH 环境变量,或用绝对文件路径来使用这些工具。下面试着新建一个 test 账户

$ sudo /usr/sbin/useradd test
$ cat /etc/passwd | grep test
test:x:1001:1003::/home/test:/bin/sh

默认情况下 useradd 用默认值来创建新用户。默认值可以用 useradd -D 来查看:

$ /usr/sbin/useradd -D
GROUP=100
HOME=/home
INACTIVE=-1
EXPIRE=
SHELL=/bin/sh
SKEL=/etc/skel
CREATE_MAIL_SPOOL=no

各行的意义如下:

  • 添加到 GID=100 的公共组
  • HOME 目录位于 /home/loginname
  • 密码过期后不会禁用
  • 未设置过期日期
  • 默认 shell 为 /bin/sh
  • 系统会将 /etc/skel 目录下的内容复制到 HOME 目录下
  • 不会在 mail 目录下创建一个用于接收邮件的文件

想要改变默认值或默认行为,可以用命令行参数指定:

参数

描述

-c comment

给新用户添加备注

-d home_dir

为主目录指定一个名字(如果不想用登录名作为主目录名的话)

-e expire_date

用YYYY-MM-DD格式指定一个账户过期的日期

-f inactive_days

指定这个账户密码过期后多少天这个账户被禁用;0 表示密码一过期就立即禁用,1 表示禁用这个功能

-g initial_group

指定用户登录组的GID或组名

-G group ...

指定用户除登录组之外所属的一个或多个附加组

-k

必须和-m 一起使用,将/etc/skel目录的内容复制到用户的HOME目录

-m

创建用户的HOME目录

-M

不创建用户的HOME目录(当默认设置里要求创建时才使用这个选项)

-n

创建一个与用户登录名同名的新组

-r

创建系统账户

-p passwd

为用户账户指定默认密码

-s shell

指定默认的登录shell

-u uid

为账户指定唯一的UID

删除用户

可以用 userdel 删除用户,但这只会删除 /etc/passwd 中的用户信息,并不会删除任何文件。如果加上 -r 参数,userdel 会删除用户的 HOME 目录和邮件目录,但系统上仍可能存在该用户的其他文件,这在有些环境中可能会造成问题。

$ sudo /usr/sbin/userdel test
$ cat /etc/passwd | grep test

修改用户

  1. usermod:用于修改 /etc/passwd 文件中的字段
    • -c 修改备注字段
    • -e 修改过期日期
    • -g 修改默认的登录组
    • -l 修改登录名
    • -L 锁定账户,使之无法登录
    • -U 解除锁定
    • -p 修改密码
  2. passwdchpasswd
    • 单独的 passwd 会改自己的密码,如果是 passwd 用户名 则会修改别人的密码(但只有 root 用户有权限)
    • chpasswd 可以用于大量用户修改密码,它从输入读取 userid:passwd 形式的列表,然后为账户设置。
  3. chshchage:用于修改默认 shell,后者用于修改

Linux 组

组权限允许多个用户对系统中的对象(文件、设备等)共享一组权限,这样能方便管理多个用户。每个组有唯一的组名和 GID。有些发行版会创建一个默认组,然后把新用户当作这个组的成员;有的则为每个用户单独创建一个组(比如 Ubuntu),这样更安全一点。

/etc/group 文件

/etc/group 文件中包含系统中每个组的信息。比如:

$ cat /etc/group
root:x:0:pi
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:pi
tty:x:5:
disk:x:6:
lp:x:7:
mail:x:8:
  • 组名
  • 组密码
  • GID
  • 属于该组的用户列表

系统账户的组通常是 500 以下的 GID,而用户组的 GID 则会从 500 开始分配。

创建组

groupadd 命令可以在系统上创建新组。和 useradd 一样,需要用绝对路径。

$ sudo /usr/sbin/groupadd test
$ tail /etc/group
libvirt:x:119:
libvirt-qemu:x:64055:libvirt-qemu
Debian-exim:x:120:
spi:x:1000:pi
i2c:x:1001:pi
gpio:x:1002:pi
cockpit-ws:x:121:
systemd-coredump:x:998:
jellyfin:x:122:
test:x:1003:

然后我们可以用 usermod 将用户添加到该组:

$ sudo /usr/sbin/usermod -G test pi
pi@raspbian:~$ tail /etc/group
libvirt:x:119:
libvirt-qemu:x:64055:libvirt-qemu
Debian-exim:x:120:
spi:x:1000:
i2c:x:1001:
gpio:x:1002:
cockpit-ws:x:121:
systemd-coredump:x:998:
jellyfin:x:122:
test:x:1003:pi

如果更改的是已登录系统账户的用户组,则需要重新登录后组关系的变更才生效。

另外,如果用的是 -g 选项,则会替换用户的默认组,-G 则是将该组添加到用户的属组的列表里。

修改组

groupmod 可以修改 GID(加 -g 选项)或组名(加 -n 选项)

理解文件权限

前面介绍 ls 命令时说过可以查看 Linux 系统上的文件、目录、权限:

$ ls -l
total 7772
drwxr-xr-x 7 pi sudo    4096 Feb 15 00:04 blog
drwxr-xr-x 6 pi root    4096 Feb 25 08:07 code
drwxr-xr-x 3 pi sudo    4096 Feb 16 00:04 frp
-rw-r--r-- 1 pi sudo 7926056 Feb 12 13:11 frp_0.35.1_linux_arm64.tar.gz
-rw-r--r-- 1 pi sudo   13226 Feb 12 12:42 install_nvm.sh

第一个字段就是描述文件和目录权限的编码,第一个字符代表了对象的类型:

  • - 代表文件
  • d 代表目录
  • l 代表链接
  • c 代表字符型目录
  • b 代表块设备
  • n 代表网络设备

之后有 3 组三字符的编码,每一组定义了 3 种访问权限权限:

  • r 代表可读
  • w 代表可写
  • x 代表可执行
  • - 代表没有对应权限

3 组权限分别对应 3 个安全级别:

  • 对象的属主
  • 对象的属组
  • 系统的其他用户

比如,以输出的其中一行为例:

drwxr-xr-x 7 pi sudo    4096 Feb 15 00:04 blog

该目录有下面三组权限:

  • rwx:目录的属主
  • r-x:目录的属组
  • r-x:系统上其他人

默认文件权限

可以用 umask 来查看或修改默新建文件的默认权限:

$ umask
0022
$ touch newfile
$ ls -al newfile
-rw-r--r-- 1 pi sudo 0 Feb 28 09:12 newfile

umask 输出的数字的第一位叫黏着位(sticky bit),我们后面再详细介绍。之后的三位对应着权限,对应方法如下:

每一组权限对应 8 进制的一位数,即:

权限

二进制值

八进制值

描述

---

000

0

没有任何权限

--x

001

1

只有执行权限

-w-

010

2

只有写入权限

-wx

011

3

有写入和执行权限

r--

100

4

只有读取权限

r-x

101

5

有读取和执行权限

rw-

110

6

有读取和写入权限

rwx

111

7

有全部权限

umask 值是个掩码,把umask 值从对象的全权限值中减掉。对文件来说,全权限的值是666 (所有用户都有读和写的权限);而对目录来说,则是777 (所有用户都有读、写、执行权限)。所以在上例中,文件一开始的权限是666 ,减去umask 值022 之后,剩下的文件权限就成了644 。

在大多数Linux发行版中,umask 值通常会设置在/etc/profile启动文件中(参见第6章),不过有一些是设置在/etc/login.defs文件中的(如Ubuntu)。可以用umask 命令为默认umask 设置指定一个新值。

$ umask 026
$ touch newfile
$ ls -al newfile
-rw-r----- 1 pi sudo 0 Feb 28 09:24 newfile

改变安全性设置

改变权限

chmod 命令可以改变文件和目录的权限,格式:chmod options mode file,比如:

$ ls -al newfile
-rw-r----- 1 pi sudo 0 Feb 28 09:24 newfile
$ chmod 760 newfile
$ ls -al newfile
-rwxrw---- 1 pi sudo 0 Feb 28 09:24 newfile

但用数字并不是那么直观,我们一般用下面这种格式:

chmod [ugoa][+-=][rwxXstugo] file

第一个方括号内的字符定义了权限作用的对象:

  • u - user 用户
  • g - group 组
  • o - others 其他
  • a - all 上面所有

第二组字符表示需要增加权限(+)、移除权限(-)、还是设置成后面的值(=)

第三组字符表示权限,除了读(r)、写(w)、执行(x)外,还有以下几项:

  • X :如果对象是目录或者它已有执行权限,赋予执行权限。
  • s :运行时重新设置UID或GID。
  • t :保留文件或目录。
  • u :将权限设置为跟属主一样。
  • g :将权限设置为跟属组一样。
  • o :将权限设置为跟其他用户一样。

示例:

$ chmod o+r newfile
$ ls -lF newfile
-rwxrw-r-- 1 pi sudo 0 Feb 28 09:24 newfile*
$ chmod u-x newfile
$ ls -lF newfile
-rw-rw-r-- 1 pi sudo 0 Feb 28 09:24 newfile

chmod 支持递归(加 -R 选项)和通配符。

改变所属关系

chown 命令可以改变属主/属组,chgrp 命令可以改变默认属组。

chown 的格式如下:

$ chown options owner[.group] file

只有 root 用户能够改变文件的属主。任何属主都可以改变文件的属组,但前提是属主必须是原属组和目标属组的成员。

示例:

$ sudo chown root.root newfile
$ ls -lF newfile
-rw-rw-r-- 1 root root 0 Feb 28 09:24 newfile
$ sudo chown .sudo newfile
$ ls -lF newfile
-rw-rw-r-- 1 root sudo 0 Feb 28 09:24 newfile