su 命令详解与参数实测(Ubuntu 22.04)
su 命令逐参数实测详解:非登录式与登录式的环境变量全量 diff、PATH 一律被 PAM 从 /etc/environment 重置的机制证据链、-m/-p 保留 HOME 但拦不住 PATH、-w 白名单变量穿透、-g/-G/-s/-P/-f/--session-command 逐个验证、expect 喂密码正误对照与 btmp 失败留痕审计、退出码透传,附 su 与 sudo 选型对比,全部回显在 Ubuntu 22.04 实测通过。
su 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 su 的每一个参数——非登录式与登录式(
-/-l)的环境变量全量对比、-c/--command与--session-command、-m/-p保留环境、-w白名单、-g/-G指定组、-s指定 shell、-P分配伪终端、-f快速模式,讲透「PATH 一律被重置」的机制证据链,并用 expect 自动化实测密码认证的正误表现与 btmp 失败留痕,全部命令与回显已在 Ubuntu 22.04(util-linux 2.37.2)实测通过。
一、环境信息
本节记录实测环境与数据来源,便于读者对照复现。su 是 Linux 上最常用的身份切换工具:把当前会话的有效 UID/GID 切换成另一个用户,并按需重建登录环境。Ubuntu 22.04 的 su 来自 util-linux(不是早年 shadow 套件的版本),二进制带有 SUID 位、属主 root——这正是普通用户能「变身」他人的机制基础(见 2.3 与第五节 Q5)。为了让实测有真实账号可切,本机创建了两个普通测试用户,并配好 SSH 登录链路作为对照数据源。
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- su 版本:util-linux 2.37.2(软件包 util-linux 2.37.2-4ubuntu3.6,见 2.3 实测)
- 系统时区:UTC(回显中的时间均为 UTC)
- su 二进制:
/bin/su(/usr/bin/su为 usrmerge 符号链接),权限-rwsr-xr-x(SUID),属主 root:root - 认证链路:PAM(
/etc/pam.d/su)——root 切普通用户免密(pam_rootok),普通用户互切必须输入目标用户密码 - 测试用户:
u4b1(uid=1002/gid=1004)、u4b2(uid=1003/gid=1005),密码认证环节用 expect 脚本自动喂入,避免人工干预 - 关联命令:失败切换会在
/var/log/btmp留痕,用姊妹命令 lastb 查看(见 3.13)
测试账号的身份信息:
$ id u4b1; id u4b2
uid=1002(u4b1) gid=1004(u4b1) groups=1004(u4b1)
uid=1003(u4b2) gid=1005(u4b2) groups=1005(u4b2)
当前操作身份(本篇所有 su 实验默认从 root 发起,除非特别说明):
$ id
uid=0(root) gid=0(root) groups=0(root)
二、命令概览
2.1 语法与测试数据
su 的语法为:
su [options] [-] [<user> [<argument>...]]
三段式结构:选项、可选的 -(登录式开关)、目标用户;用户名之后还能继续跟参数(会原样传给目标 shell)。user 省略时默认切换到 root——这也是「裸敲 su 最危险」的原因。帮助原文的两句关键提示:A mere - implies -l(单写一个 - 等价于 -l)与 If <user> is not given, root is assumed。
测试数据的组织方式:环境变量类实验统一用 su <选项> u4b1 -c '命令' 的形式执行并打印关键变量(HOME、USER、LOGNAME、PATH、pwd、tty 等),逐组对比;认证实验在 u4b1 的登录会话内发起 su u4b2,用 expect 喂入正确/错误密码各一次;所有实验完成后核对 btmp 留痕。
2.2 --help 全量输出
以下为 su --help 的完整回显,逐行照抄(空行保留):
Usage:
su [options] [-] [<user> [<argument>...]]
Change the effective user ID and group ID to that of <user>.
A mere - implies -l. If <user> is not given, root is assumed.
Options:
-m, -p, --preserve-environment do not reset environment variables
-w, --whitelist-environment <list> don't reset specified variables
-g, --group <group> specify the primary group
-G, --supp-group <group> specify a supplemental group
-, -l, --login make the shell a login shell
-c, --command <command> pass a single command to the shell with -c
--session-command <command> pass a single command to the shell with -c
and do not create a new session
-f, --fast pass -f to the shell (for csh or tcsh)
-s, --shell <shell> run <shell> if /etc/shells allows it
-P, --pty create a new pseudo-terminal
-h, --help display this help
-V, --version display version
For more details see su(1).
选项按语义分四组:环境控制(-m/-p 全保留、-w 白名单)、身份微调(-g 主组、-G 附加组)、会话形态(-/-l 登录式、-c/--session-command 单命令、-f 快速模式、-s 指定 shell、-P 伪终端)、帮助与版本(-h/-V)。注意 -m 与 -p 是同一功能的两个名字,帮助里写在同一行。
2.3 版本信息
$ su --version
su from util-linux 2.37.2
再核对二进制归属。直接查 /usr/bin/su 会得到一个耐人寻味的结果——no path found:
$ dpkg -S $(which su)
dpkg-query: no path found matching pattern /usr/bin/su
原因:Ubuntu 22.04 采用 usrmerge 合并目录布局,/usr/bin/su 只是符号链接,真身在 /bin/su:
$ dpkg -S /bin/su
util-linux: /bin/su
$ ls -l /usr/bin/su
-rwsr-xr-x 1 root root 55680 Aug 19 17:43 /usr/bin/su
$ dpkg -l login util-linux coreutils 2>/dev/null | grep -E '^ii'
ii coreutils 8.32-4.1ubuntu1.4 amd64 GNU core utilities
ii login 1:4.8.1-2ubuntu2.2 amd64 system login tools
ii util-linux 2.37.2-4ubuntu3.6 amd64 miscellaneous system utilities
关键信息有三层:① su 属 util-linux 2.37.2(与 last 同门,与 lastlog 的 login 包、users 的 coreutils 都不同源);② 权限位 -rwsr-xr-x 里的 s 就是 SUID——执行 su 的进程瞬间获得 root 身份,才有资格把 UID 改成任意用户;③ dpkg -S 查询符号链接会扑空,包管理溯源要查真实路径 /bin/su。
三、参数逐项实测
3.1 无选项:root 切普通用户免密
不带任何选项、用 -c 执行单条命令,直接切到 u4b1:
$ su u4b1 -c 'id'
uid=1002(u4b1) gid=1004(u4b1) groups=1004(u4b1)
全程没有出现密码提示——root 切换到任意用户不需要密码。这是 PAM 链路里 pam_rootok 模块的功劳:调用方已经是 root(uid=0)则直接放行认证。反过来,普通用户互切就必须输入目标用户的密码(实测见 3.13)。
3.2 -c, --command:以目标身份执行单条命令
-c 后面跟一条命令字符串,交给目标用户的 shell 执行,执行完自动回到原会话。先看身份变量:
$ su u4b1 -c 'whoami'
u4b1
$ su u4b1 -c 'echo $HOME $USER $LOGNAME'
/home/u4b1 u4b1 u4b1
注意一个容易误解的点:非登录式(不带 -)也会重设 HOME、USER、LOGNAME 为目标用户的值——并不是「只有 su - 才改环境」。再看工作目录与 PATH:
$ su u4b1 -c pwd
/
$ su u4b1 -c 'echo $PATH'
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
两个结论:工作目录继承当前会话(root 当时在 /,切过去还是 /);PATH 则被换成了另一套值(root 当前的 PATH 不含 games 系目录,切换后多出 /usr/games:/usr/local/games:/snap/bin)——这套 PATH 从哪来,3.5 有完整证据链。
💡 要点:
-c的命令字符串经过目标 shell 解释,单引号内的$HOME等变量是在目标环境里展开的。想在切换前展开(用当前会话的值),要用双引号——两种写法语义不同,脚本里务必想清楚。
3.3 -, -l, --login:登录式切换
加上 -(等价 -l/--login),su 按完整登录流程初始化环境:
$ su - u4b1 -c 'id'
uid=1002(u4b1) gid=1004(u4b1) groups=1004(u4b1)
$ su - u4b1 -c 'pwd'
/home/u4b1
$ su - u4b1 -c 'echo $HOME $USER $LOGNAME'
/home/u4b1 u4b1 u4b1
与 3.2 对照,登录式最大的可见差异是工作目录切到了目标用户的家目录(/home/u4b1,而非继承的 /)。再验证 SHELL 与长选项写法:
$ su --login u4b1 -c 'echo $HOME $SHELL'
/home/u4b1 /bin/bash
💡 要点:登录式会执行目标用户的登录初始化链路(login shell 读取 profile 系配置)。给某个用户「修好环境再干活」的场景(跑构建、用它的 alias 和 PATH)必须用
su -,裸 su 经常出现「命令找不到、变量为空」的假故障,根因就是环境没初始化。
3.4 非登录式 vs 登录式:环境变量全量对比
把两种方式的完整 env 各存一份,统计行数并做 diff:
$ su u4b1 -c env > /tmp/env-su-noDash.txt; wc -l /tmp/env-su-noDash.txt
10 /tmp/env-su-noDash.txt
$ su - u4b1 -c env > /tmp/env-su-Dash.txt; wc -l /tmp/env-su-Dash.txt
9 /tmp/env-su-Dash.txt
$ diff /tmp/env-su-noDash.txt /tmp/env-su-Dash.txt
2,3c2
< HOSTNAME=f1034b9326b2
< PWD=/
---
> PWD=/home/u4b1
5d3
< _=/usr/bin/env
8c6
< SHLVL=1
---
> SHLVL=0
10a9
> _=/usr/bin/env
diff 以非登录式为左、登录式为右,逐条解读:
| 差异项 | 非登录式(左) | 登录式(右) | 解读 |
|---|---|---|---|
| HOSTNAME | 有(继承 root 会话) | 无 | 登录式清掉了继承来的主机名变量 |
| PWD | /(继承当前目录) | /home/u4b1 | 登录式切到家目录 |
| SHLVL | 1 | 0 | 登录 shell 按「全新会话」计数 |
_ | 在第 5 行 | 在末尾 | 记录最后执行的命令,两侧变量集略有差异 |
| 其余变量 | 相同 | 相同 | PATH、HOME、USER、LOGNAME 两种方式一致 |
💡 技术拓展:这份 diff 修正一个流传很广的说法——「su 只切身份、su - 才换环境」。实测证明:HOME、USER、LOGNAME、PATH 在非登录式下就已经被重置;
su -额外做的是切家目录、走登录 shell 初始化链路、清理 HOSTNAME 等继承变量。所以「su 后 PATH 变了」不是 su - 的功劳,裸 su 也一样变(见 3.5)。
3.5 PATH 为什么一律被重置:完整证据链
先看 root 当前的 PATH(注意它不含 games 系目录):
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
直觉猜测 PATH 来自 /etc/login.defs 的配置——把相关行抓出来:
$ grep -E '^(ENV_SUPATH|ENV_PATH|SU_NAME)' /etc/login.defs
SU_NAME su
ENV_SUPATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV_PATH PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
但 3.2 实测的最终 PATH(多出 /snap/bin)与这两行都对不上。做三组对照实验:无论是否登录式、是否加 -m,外部传入的 PATH 都活不下来:
$ PATH=/opt/demo/bin:$PATH su u4b1 -c 'echo $PATH'
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
$ PATH=/opt/demo/bin:$PATH su - u4b1 -c 'echo $PATH'
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
$ PATH=/opt/demo/bin:$PATH su -m u4b1 -c 'echo $PATH'
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
三组输出完全一致,且等于下面这份配置的值:
$ cat /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"
证据链闭合:su 的 PAM 会话配置里有 pam_env 模块,它会读 /etc/environment 并无条件覆盖 PATH。所以想让子环境拿到自定义 PATH,靠 PATH=... su 前缀传不进去,-m 也拦不住——要么用 -w 白名单(3.7 实测变量可穿透),要么把 PATH 写进 /etc/environment 或目标用户的 profile。
💡 要点:
/etc/login.defs的 ENV_PATH/ENV_SUPATH 是 login(1) 的配置;su 走 PAM 链路,最终生效的是 pam_env 读到的/etc/environment。排查「su 后 PATH 不对」时先 cat 这份文件,别改错地方。
3.6 -m / -p:保留环境变量(但拦不住 PATH)
-m 与 -p 同义(do not reset environment variables)。实测保留效果:
$ su -m u4b1 -c 'echo $HOME; pwd'
/root
/
HOME 保留了 root 的 /root(没被换成 /home/u4b1),工作目录也继续留在 /。再看 -p 下 USER 变量:
$ su -p u4b1 -c 'echo $USER'
输出为空——root 会话本来就没有 USER 变量,「保留环境」意味着原样继承:没有就是没有。对照 3.2,非 -m 时 su 会主动设好 USER=u4b1。
💡 要点:
-m/-p的真实语义是「不重置环境变量」,但 3.5 已实测证明 PATH 仍然被 PAM 重置——它不归 su 的环境重置逻辑管。所以-m保留的是 HOME、PWD 这类 su 自己会动的变量,别指望它保住 PATH。
3.7 -w, --whitelist-environment:环境白名单
登录式默认清空外部传入的变量。先验证 MYVAR 默认穿不透:
$ MYVAR=hello su - u4b1 -c 'echo $MYVAR'
输出为空。用 -w 把 MYVAR 加入白名单再试:
$ MYVAR=hello su -w MYVAR - u4b1 -c 'echo $MYVAR'
hello
变量成功穿透登录式的环境清洗。
💡 要点:
-w的参数是变量名列表(逗号分隔),不是赋值表达式——写-w MYVAR=hello是错的。它是「既要登录式干净环境、又要个别变量」场景的唯一正解;PATH 需要-w PATH也拦不住 pam_env 的覆盖(PATH 属于 PAM 会话层,不在 su 的重置清单里),本组未对 PATH 做-w实验,不做结论。
3.8 -g, --group 与 -G, --supp-group:指定组
-g 指定切换后的主组。用与 u4b1 同名的组(useradd 建用户时默认创建)实测参数链路:
$ su -g u4b1 -s /bin/sh u4b1 -c 'id'
uid=1002(u4b1) gid=1004(u4b1) groups=1004(u4b1)
gid=1004 正是 -g u4b1 指定的组——与该用户默认主组相同,所以 id 输出看不出变化;参数链路(解析组名、参与身份切换)走通。-g 的实际用途是目标用户同时属于多个组时,把主组换成其中另一个(组成员身份要求不变);-G/--supp-group 同理用于附加组,帮助标注它可指定补充组身份(本组未对其单独取回显,不展开)。
3.9 -s, --shell:指定执行的 shell
-s 强制用指定的 shell 程序,绕开 /etc/passwd 里记录的登录 shell:
$ su -s /bin/sh u4b1 -c 'echo $0'
sh
$0 显示 sh——目标命令确实跑在 /bin/sh 里,而不是 u4b1 默认的 /bin/bash。
💡 要点:帮助原文注明
run <shell> if /etc/shells allows it——普通用户只能选 /etc/shells 里列出的 shell;root 不受此限制(排障时给被锁用户临时换 shell 全靠它)。目标用户的 passwd shell 被改成 /usr/sbin/nologin 后,su - user会直接退出,而su -s /bin/bash user(root 发起)仍可进入,这是救援被锁账号的常用组合。
3.10 -P, --pty:创建新的伪终端
默认情况下 -c 直排执行不分配终端。验证:
$ su u4b1 -c 'tty'
not a tty
加 -P 强制创建新伪终端:
$ su -P u4b1 -c 'tty'
/dev/pts/3
从 not a tty 变成了真实的 /dev/pts/3。
💡 要点:很多程序会探测 stdin 是否为终端(isatty)来决定行为——关闭颜色/进度条、拒绝交互、缓冲策略切换等。在脚本里用 su 跑这类程序出现「行为不一样」时,加
-P模拟真实终端往往立竿见影。
3.11 -f, --fast:向 shell 传递 -f
$ su -f u4b1 -c 'echo fast-mode'
fast-mode
命令正常执行。-f 的作用只是把 -f 标志转交给目标 shell,帮助原文写明是为 csh/tcsh 设计的(跳过 .cshrc 启动文件);bash 忽略该标志,因此实测无任何可见影响。
💡 要点:这是历史兼容选项。在 bash 为主的现代环境里可以视为「无害但无效」,读到旧脚本里的
su -f不必困惑,也不需要模仿。
3.12 --session-command:不创建新会话的单命令
$ su --session-command 'id' u4b1
uid=1002(u4b1) gid=1004(u4b1) groups=1004(u4b1)
输出与 -c 完全一致。差异在帮助原文里写得很清楚:pass a single command to the shell with -c and do not create a new session——-c 会为目标命令建立新会话(影响会话记账与进程组),--session-command 省略这一步。
💡 要点:对绝大多数脚本两者可互换;当目标命令要长期驻留、或上层已有会话管理逻辑(如 service/supervisor 类脚本)时,用
--session-command避免多出一层会话包装。Debian 系的 init 脚本历史上就常用它。
3.13 认证实测:密码错误与正确的对照
普通用户互切必须输入目标用户的密码。在 u4b1 的登录会话内发起 su u4b2 -c whoami,用 expect 脚本自动喂入错误密码(spawn 行是 expect 对所执行命令的回显,真实终端上只会看到 Password: 提示):
spawn su u4b2 -c whoami
Password:
su: Authentication failure
换成正确密码再试:
spawn su u4b2 -c whoami
Password:
u4b2
认证成功,whoami 输出 u4b2。密码错误除了当场报 su: Authentication failure,还会在 btmp 里留下失败记录——失败前后各查一次 lastb(读 /var/log/btmp):
$ lastb -n 1
u4b2 pts/3 Thu Oct 15 08:57 - 08:57 (00:00)
btmp begins Thu Oct 15 08:41:55 2026
执行一次错误密码的 su 之后再查,最新一条变成 09:01——失败记录被实时追加:
$ lastb -n 1
u4b2 pts/3 Thu Oct 15 09:01 - 09:01 (00:00)
btmp begins Thu Oct 15 08:41:55 2026
btmp 的权限是 root:utmp 660,普通用户读不了:
$ ls -l /var/log/btmp
-rw-rw---- 1 root utmp 1536 Oct 15 08:57 /var/log/btmp
$ su u4b1 -c 'lastb'
lastb: cannot open /var/log/btmp: Permission denied
💡 技术拓展:两次 expect 实验中观察到,用管道向 su 喂密码时,若 su 走了 pty 回显路径,输入的密码会以明文出现在输出里(本组实测在 pty 回显中出现过密码明文,故本文未引用那两段原始回显)。自动化喂密码请统一用 expect 这类不回显的方式,并避免把含密码的会话记录落盘。
3.14 错误处理与退出码
目标用户不存在时的报错自成一类:
$ su nosuchuser999 -c id
su: user nosuchuser999 does not exist or the user entry does not contain all the required fields
与密码错误的 su: Authentication failure 语义不同:前者是账号查无此人(或 /etc/passwd 条目字段不全),后者是账号存在但认证不过。安全排查时这个区别有信息量——报 does not exist 说明攻击者猜的用户名不存在,报 Authentication failure 说明用户名命中了。
-c 命令的退出码会原样透传给 su:
$ su u4b1 -c 'exit 3'; echo rc=$?
rc=3
💡 要点:自动化脚本里依赖 su 执行结果时,判断
$?即可拿到目标命令的真实退出码;认证失败时 su 自身以非零退出(本文未对其取值,不写具体数字),与命令失败要区分开。
四、实战示例
4.1 场景一:root 巡检各账号的身份与环境
运维巡检时经常要「以某个账号的视角确认权限」——直接用 -c 组合查询,不真正进入交互 shell:
$ su u4b1 -c 'id'
uid=1002(u4b1) gid=1004(u4b1) groups=1004(u4b1)
$ su u4b1 -c 'whoami'
u4b1
配合 3.2 实测的环境变量结论(HOME/USER/LOGNAME 已就位),su <user> -c '...' 就是「借身份跑一条命令」的标准姿势;root 免密(3.1),巡检脚本可以放心批量执行。
4.2 场景二:以目标用户的完整环境执行程序
部署、构建类操作必须用目标用户的完整登录环境(它的 PATH、家目录、profile 初始化):
$ su - u4b1 -c 'pwd'
/home/u4b1
$ su --login u4b1 -c 'echo $HOME $SHELL'
/home/u4b1 /bin/bash
su - 与 su --login 等价;工作目录落在 /home/u4b1、SHELL 正确解析。若只需借用某条自定义 PATH 而不想动 /etc/environment,记得 3.5 的结论:前缀传参无效,要么写 profile,要么走 -w(3.7)。
4.3 场景三:保留退出码的自动化封装
把 su 封装进自动化流程时,退出码透传(3.14)让上层脚本无需改造:
$ su u4b1 -c 'exit 3'; echo rc=$?
rc=3
目标命令失败(非零退出)→ su 返回同一退出码 → 上层判断不通过。封装模式:su - <user> -c '<命令>' || 处理失败。
4.4 场景四:登录式环境里透传单个变量
需要把签名密钥路径、构建编号这类「运行参数」传进干净的登录环境,用白名单:
$ MYVAR=hello su -w MYVAR - u4b1 -c 'echo $MYVAR'
hello
对照不带 -w 的结果(3.7 第一次实验,输出为空)。多变量场景把名字用逗号串进同一个 -w 参数即可。
4.5 场景五:失败切换的审计闭环
有人反复 su 失败时,btmp 是第一落点。失败记录一览:
$ lastb -n 3
u4b2 pts/3 Thu Oct 15 08:57 - 08:57 (00:00)
u4b2 pts/3 Thu Oct 15 08:55 - 08:55 (00:00)
u4b2 pts/3 Thu Oct 15 08:55 - 08:55 (00:00)
btmp begins Thu Oct 15 08:41:55 2026
排查路径:lastb 看频率与目标账号(3.13 已实测失败 su 会实时留痕)→ 结合 wtmp(last)确认成功登录的时间线,判断是否「多次失败后成功」的爆破特征 → 报告认证失败的具体报错区分「用户名不存在」与「密码错误」(3.14 的语义差异)。普通用户读不了 btmp(3.13 的 Permission denied 实测),审计动作必须以 root 执行。
五、注意事项与常见问题
Q1:su 和 sudo 怎么选? 两者解决的是同一件事的两端——
维度 su sudo 认证对象 输入目标用户的密码(root 发起免密) 输入自己的密码 授权粒度 拿到目标密码即获得完整身份 sudoers 按命令白名单逐条授权 会话形态 整个 shell 切换身份 逐条命令临时提权 环境变量 默认重置(登录式更彻底, -m/-w可调)默认清洗 env,按 env_keep 白名单保留 审计能力 wtmp 记成功、btmp 记失败,无命令级日志 每条命令落日志(/var/log/auth.log) 典型场景 临时以某身份完整排查 团队服务器的日常最小授权 经验法则:多管理员的生产服务器用 sudo 留审计痕迹;单人环境或需要「完整变身」的排查用 su。两者不冲突,很多团队 root 密码封存、日常全走 sudo。
Q2:为什么 su 之后 PATH 不是我配置的 PATH?
su 的 PAM 会话里 pam_env 会读 /etc/environment 并覆盖 PATH——3.5 用三组对照实验证明:登录式、非登录式、甚至 -m 都拦不住,最终值与 /etc/environment 完全一致。想要自定义 PATH:写进目标用户的 profile(登录式会加载)、或改 /etc/environment、或个别变量用 -w 白名单(PATH 因属 PAM 层,-w 也需实测确认后再依赖)。
Q3:-m/-p 到底保留了什么?
保留的是 su 本来会重置的变量:HOME(实测保持 /root)、工作目录(实测保持 /)、以及不主动设置 USER/LOGNAME(root 环境没有 USER,实测输出为空)。PATH 不在其管辖范围——它由 PAM 层重置(3.5)。所以 -m 的准确读法是「别动 su 该动的那几个变量」,不是「什么都别动」。
Q4:su 和 su - 的完整差异清单?
3.4 的 diff 实测浓缩版:su - 多做三件事——切到目标家目录(PWD=/home/u4b1 vs 继承的 /)、以登录 shell 初始化(HOSTNAME 清空、SHLVL 计数不同、加载 profile 系配置)、清掉继承变量。而 HOME/USER/LOGNAME/PATH 的重置是两种方式共有的行为。一句话:要「完整变身」必须带 -。
Q5:root 用 su 为什么不问密码?
PAM 认证链路中的 pam_rootok 模块对「调用方已是 root」直接放行(3.1 实测无密码提示)。这依赖的是 su 二进制的 SUID 机制:普通用户执行 su 时进程瞬间拥有 root 权限(2.3 的 -rwsr-xr-x),才有资格完成任意用户的身份切换;也正因为如此,su 的安全边界完全押在密码认证上——这就是 3.13 的 btmp 留痕为什么重要的原因。
Q6:su 失败会留下痕迹吗?
会,两处:认证失败当场报 su: Authentication failure 并向 /var/log/btmp 追加一条失败记录(3.13 实测 lastb 前后对照,09:01 新记录);成功切换则进 wtmp。注意 btmp 只对 root:utmp 组可读,普通用户查不了(实测 Permission denied)。生产上突然增多的失败记录是爆破尝试的强信号。
Q7:su: user xxx does not exist... 和 su: Authentication failure 有什么区别?
前者(3.14 实测)说明目标账号不存在、或 /etc/passwd 条目字段不全——用户名都没命中;后者(3.13 实测)说明账号存在但密码不对。写监控规则时可以利用这个区别:后者高频出现意味着有人在猜有效账号。
Q8:-c 执行的命令为什么没有 tty、PS1 也是空的?
-c 是非交互执行:stdin/stdout 接管道,没有终端(3.10 实测 tty 输出 not a tty),交互提示符 PS1 自然也无从谈起——即使登录式加 -c,实测 echo "$PS1" 也输出空。需要终端语义时给 su 加 -P(实测分配 /dev/pts/3)。
Q9:为什么我的 su 实验里密码会回显在屏幕上?
如果用 printf '密码\n' | su ... 这类管道喂密码,su 分配 pty 后输入会被终端回显,密码明文直接出现在输出里——本组实测就观察到了这个现象(因此放弃了那两段回显)。正确做法是用 expect 自动化(3.13 的回显干净、无密码泄漏),人工操作则正常键入(getty/su 的密码提示默认关闭回显)。
Q10:-c 里怎么写带引号的命令?
-c 的参数是一个完整字符串,交给目标 shell 再解释一层:外层单引号包住整个命令(变量在目标环境展开,3.2 实测),命令内部需要引号时换双引号或转义。多层嵌套容易翻车,复杂命令建议落到脚本文件再 su - user -c '/path/to/script.sh',可读性和可维护性都好得多。
Q11:su 的退出码怎么用?
-c 命令的退出码原样透传(3.14 实测 exit 3 → rc=3);认证失败、用户不存在时 su 自身非零退出。脚本里统一判 $? 即可,但要明白两种非零的含义不同——一个是「你的命令失败了」,一个是「压根没切成功」。
六、参考链接
- su(1) 手册页(man7.org):https://man7.org/linux/man-pages/man1/su.1.html
- su(1) 手册页(Ubuntu 22.04 jammy):https://manpages.ubuntu.com/manpages/jammy/en/man1/su.1.html
- login.defs(5) 手册页:https://man7.org/linux/man-pages/man5/login.defs.5.html
- util-linux 上游源码仓库:https://github.com/util-linux/util-linux
- sudo 官方手册:https://www.sudo.ws/docs/man/sudo.man/
- 姊妹篇:《last 命令详解与参数实测(Ubuntu 22.04)》《lastlog 命令详解与参数实测(Ubuntu 22.04)》《users 命令详解与参数实测(Ubuntu 22.04)》