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 实测通过。

Linux命令Linux系统UbuntuUbuntu 22.04命令行用户权限2026/10/15

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登录式切到家目录
SHLVL10登录 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 怎么选? 两者解决的是同一件事的两端——

维度susudo
认证对象输入目标用户的密码(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 自身非零退出。脚本里统一判 $? 即可,但要明白两种非零的含义不同——一个是「你的命令失败了」,一个是「压根没切成功」。

六、参考链接