1. 项目概述:为什么“查看系统用户”是每个Ubuntu使用者绕不开的第一课
在Ubuntu系统里敲下
whoami
看到自己的用户名,或者用
ls -l
发现某个文件属主是
root
,这些日常操作背后其实都连着一个更底层、更关键的问题:这个系统里到底有哪些人?谁有登录权限?谁在后台默默运行服务?谁可能成为安全链条上的薄弱环节?“How To View System Users in Linux on Ubuntu”表面看只是查个列表,实则是一把打开系统治理大门的钥匙——它不单是新手入门必练的基本功,更是运维排查、安全审计、权限梳理、故障定位的起点。我带过不少刚从Windows转过来的朋友,他们第一反应往往是去图形界面找“用户账户设置”,结果发现里面只显示当前登录用户,而真正决定系统行为的,是藏在
/etc/passwd
里的几十甚至上百个账户。这里面既有你每天用的
ubuntu
,也有
syslog
、
sshd
、
www-data
这类专为服务进程创建的系统用户,还有
nobody
这种纯粹用来降低权限风险的“空壳用户”。它们不登录、不交互、不设密码,却支撑着日志记录、SSH连接、网页服务等所有后台运转。所以,这不是一个“查完就忘”的命令练习,而是一次对Ubuntu系统身份体系的实地测绘。你看到的不是一串名字,而是权限边界、服务依赖、安全水位线。本文会带你从最基础的
cat /etc/passwd
开始,一层层剥开用户信息的结构,解释每列字段的真实含义(比如为什么UID 0不等于
root
字符串,为什么
/usr/sbin/nologin
比
/bin/false
更现代),演示如何过滤出真正可登录的人、如何识别被禁用的账户、如何发现异常UID范围、如何结合
/etc/shadow
判断密码状态,最后落到真实场景:当你接手一台别人配过的服务器,怎么三分钟内摸清“谁在系统里有脚”?全文不讲虚概念,所有命令都附带输出样例和逐行解读,参数选择全部说明依据,连
awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd
里的数字区间为什么是1000到65534,都会给你算清楚——因为Ubuntu默认把1000起始分配给普通用户,而65534是
nobody
的保留UID,跨过这个数就进了系统保留区。这不只是“怎么查”,而是“查出来之后,你该信什么、不该信什么、下一步该盯住哪一行”。
2. 用户数据的源头与结构:深入
/etc/passwd
文件的七列密码学
Ubuntu系统中所有用户信息的权威来源,就是那个看似平平无奇的文本文件:
/etc/passwd
。别被名字误导,它根本不存密码——密码哈希值其实在
/etc/shadow
里,而
/etc/passwd
干的是“户口本”的活:登记每个账户的身份标识、归属关系、默认环境和权限基线。理解它的结构,是读懂一切用户命令的前提。这个文件每行代表一个用户,字段用英文冒号
:
分隔,共七列。我们拿一个典型行来拆解:
ubuntu:x:1000:1000:Ubuntu,,,:/home/ubuntu:/bin/bash:/bin/bash
第一列
ubuntu
是登录名(login name),也是你在终端输入
su - ubuntu
时用的名字;第二列
x
是密码占位符,表示真实密码哈希已移至
/etc/shadow
,这是现代Linux的标准安全实践;第三列
1000
是用户ID(UID),操作系统靠这个数字而非名字识别身份,
root
永远是0,普通用户从1000起跳;第四列
1000
是组ID(GID),对应主组
ubuntu
,决定了新创建文件的默认属组;第五列
Ubuntu,,,
是GECOS字段,存储全名、办公室、电话等描述性信息,常被忽略但对LDAP集成很重要;第六列
/home/ubuntu
是家目录路径,用户登录后自动cd到这里;第七列
/bin/bash
是登录Shell,即用户获得交互式命令行的程序,
/usr/sbin/nologin
或
/bin/false
则明确禁止交互式登录。
提示:
/etc/passwd是所有用户可读的,所以敏感信息绝不能放在这里。这也是为什么密码必须移到只有root可读的/etc/shadow中——否则任何普通用户都能cat /etc/passwd | grep root然后暴力破解。
现在问题来了:为什么
UID=0
就等于
root
?因为内核认的是数字,不是字符串。你可以用
usermod -u 0 testuser
把任意用户UID改成0,那
testuser
立刻获得
root
全部权限,哪怕名字还是
testuser
。同理,
UID=1
的
daemon
用户,其权限远低于
root
,但高于普通用户。Ubuntu默认将UID 1–999划为系统用户保留区,1000–60000为普通用户区,60001–65534为动态分配区(如NFS挂载用户)。这个分区不是硬编码,而是由
/etc/login.defs
里的
UID_MIN
、
UID_MAX
参数控制。你可以用
grep "^UID" /etc/login.defs
查看当前设置,通常输出是
UID_MIN 1000
和
UID_MAX 60000
。这意味着,当你执行
getent passwd | awk -F: '$3 >= 1000 && $3 <= 60000 {print $1}'
时,得到的就是Ubuntu官方认定的“人类可用用户”清单。而
UID<1000
的账户,比如
syslog
(UID 101)、
sshd
(UID 105)、
mysql
(UID 123),它们的存在只有一个目的:让对应服务以最低必要权限运行。比如
sshd
进程如果以
root
身份处理所有网络连接,一旦SSH漏洞被利用,攻击者直接拿到
root
shell;而让它以
sshd
用户身份运行,攻击者最多只能读写
/var/log/auth.log
和
/etc/ssh/sshd_config
,无法碰
/etc/shadow
。这就是Linux“最小权限原则”的落地体现,而
/etc/passwd
的UID列,就是这个原则的刻度尺。
再看Shell字段。
/bin/bash
意味着用户可以登录并获得完整shell;
/usr/sbin/nologin
(Ubuntu 18.04+默认)会显示一条友好提示“此账户不可用于登录”,然后退出;
/bin/false
则更粗暴,直接返回错误码1终止会话。两者效果类似,但
nologin
可定制提示语,且被PAM模块更好识别。如果你看到某行Shell是
/bin/sync
或
/bin/true
,别慌,这通常是管理员手动锁定账户的手段——
sync
命令本身无害,但作为登录shell会导致用户一登录就执行同步磁盘然后退出,根本进不去系统。这种“软锁定”比直接删用户或改密码更安全,因为服务依赖不会中断。我曾在一个生产数据库服务器上发现
postgres
用户Shell被设为
/bin/false
,导致备份脚本因无法su到postgres而失败。修复只需
sudo usermod -s /bin/bash postgres
,但前提是你要先读懂
/etc/passwd
第七列在说什么。
3. 实操命令链:从基础查看到精准筛选的四层递进
查用户不是一锤子买卖,而是一个由宽到窄、由表及里的渐进过程。我把它分成四层: 原始呈现 → 人类可读过滤 → 权限状态识别 → 动态行为验证 。每一层都解决不同问题,也对应不同使用场景。
3.1 第一层:原始呈现——
cat /etc/passwd
与
getent passwd
最原始的方式就是
cat /etc/passwd
,它把整个文件原样吐出来。但问题很明显:Ubuntu默认有30+系统用户,加上你创建的几个,动辄50行以上,眼睛扫一遍容易漏。这时
getent passwd
是更好的选择。
getent
(get entries)命令能统一查询多种名称服务源,包括本地文件、LDAP、NIS等。在纯Ubuntu桌面环境里,它和
cat /etc/passwd
输出几乎一样,但优势在于:它遵循系统配置,如果未来你接入了LDAP域控,
getent passwd
会自动合并域用户,而
cat /etc/passwd
永远只看到本地文件。执行
getent passwd | head -10
,你会看到前10行,其中
root
、
daemon
、
syslog
等系统用户排在最前面,UID依次递增。注意
getent
输出是按UID排序的,而
/etc/passwd
文件本身是按创建顺序写的,所以
getent
更符合“系统视角”。
注意:
getent passwd输出包含所有用户,包括被userdel删除但未清理干净的残留条目。真正的权威是getent passwd $(id -u),它只查当前用户的那一行,绝对精准。
3.2 第二层:人类可读过滤——
awk
与
cut
的组合技
原始输出太杂,我们需要筛掉系统用户,只看“真人”。核心逻辑是:UID在1000–60000之间,且Shell不是
/usr/sbin/nologin
或
/bin/false
。命令如下:
getent passwd | awk -F: '$3 >= 1000 && $3 <= 60000 && $7 != "/usr/sbin/nologin" && $7 != "/bin/false" {print $1}'
这里
-F:
指定冒号为分隔符,
$3
是UID,
$7
是Shell。
&&
是逻辑与,确保所有条件同时满足。为什么不用
grep -v "nologin"
?因为
grep
是行级匹配,如果某用户全名里有
nologin
(虽然罕见),就会误杀。
awk
按字段判断,精准得多。如果你只想看用户名和UID,加个格式化:
getent passwd | awk -F: '$3 >= 1000 && $3 <= 60000 {printf "%-12s UID:%d\n", $1, $3}'
输出像这样:
ubuntu UID:1000
john UID:1001
devops UID:1002
%-12s
表示左对齐、占12字符宽,让用户名列整齐。这种格式化在写监控脚本时特别有用,方便后续用
awk '{print $1}'
提取用户名。
3.3 第三层:权限状态识别——结合
/etc/shadow
看密码是否激活
光看
/etc/passwd
只能知道“谁存在”,但不知道“谁能登录”。密码状态藏在
/etc/shadow
里,它只有root可读。
sudo cat /etc/shadow
会显示每行8列,第二列是密码哈希。如果这一列是
*
或
!
,表示账户被锁定(密码失效);如果是
$6$...
这样的字符串,表示有有效密码;如果是空,表示无密码(极危险,应立即修复)。常用组合命令:
sudo awk -F: 'BEGIN{OFS=":"} NR==FNR{a[$1]=$2;next} $3>=1000&&$3<=60000{print $1,$3,$7,a[$1]}' /etc/shadow /etc/passwd | \
awk -F: '{status=$4=="*"||$4=="!"?"LOCKED":($4==""?"NO PASSWORD":"ACTIVE"); printf "%-12s UID:%-4d Shell:%-15s Status:%s\n", $1,$2,$3,status}'
这段命令做了三件事:先用
awk
把
/etc/shadow
的密码状态(第二列)存入数组
a
,键为用户名;再读
/etc/passwd
,对每个UID合规用户,把用户名、UID、Shell、密码状态拼成一行;最后用第二个
awk
格式化输出。结果清晰显示每个普通用户的登录能力:
ubuntu UID:1000 Shell:/bin/bash Status:ACTIVE
backup UID:1003 Shell:/usr/sbin/nologin Status:LOCKED
guest UID:1004 Shell:/bin/bash Status:NO PASSWORD
看到
NO PASSWORD
要立刻警觉——这意味着任何人只要知道用户名就能直接登录。修复命令是
sudo passwd -l guest
(锁定)或
sudo passwd guest
(设新密码)。
3.4 第四层:动态行为验证——
loginctl
与
ps
看谁真在线
/etc/passwd
是静态注册表,
loginctl
才是实时状态板。
loginctl list-users
列出所有已登录会话的用户,
loginctl show-user $USER
显示当前用户详细信息,包括会话ID、登录时间、所在TTY。更狠的是
ps -eo user,uid,comm,args --sort=-uid | head -10
,它按UID倒序列出进程,你能一眼看到UID最高的几个进程是谁在跑——如果
root
下面紧跟着
/usr/bin/python3 /opt/malware.py
,那基本可以确定出事了。我处理过一个案例:客户说“系统变慢”,
top
看CPU不高,但
ps -eo user,pcpu,comm --sort=-pcpu | head -5
发现
www-data
用户的一个
php
进程占了98% CPU。顺藤摸瓜查
/var/www/html
,果然被植入了挖矿脚本。所以,“查看用户”最终要落到“谁在干什么”上,这才是运维的终点。
4. 安全与治理视角:从用户列表读懂系统健康度
一张用户列表,就是一份微型系统健康报告。它不告诉你CPU用了多少,但能暴露权限设计是否合理、账户管理是否规范、潜在风险是否可控。我总结了五个关键检查点,每次接手新Ubuntu服务器必做。
4.1 检查UID分布异常:警惕越界用户
正常Ubuntu系统,UID 0–999是系统保留,1000–60000是普通用户。如果发现UID=999的用户,要立刻查
/etc/login.defs
确认
UID_MIN
是否被改过;如果发现UID=65535(
nobody
的常见值),得确认是不是有人误操作创建了高UID账户。更危险的是UID=0的非
root
用户,比如
hacker:x:0:0::/root:/bin/bash:/bin/bash
。这行在
/etc/passwd
里合法,但意味着
hacker
拥有
root
全部权限。检测命令:
sudo awk -F: '$3 == 0 && $1 != "root" {print "ALERT: Non-root user with UID 0:", $1}' /etc/passwd
输出为空才安全。如果有,立刻
sudo usermod -u 1005 hacker
改UID,并检查
/var/log/auth.log
是否有异常登录。
4.2 检查空密码与弱密码策略
空密码账户是重大安全隐患。除了前面提到的
/etc/shadow
第二列为
""
,还要检查密码策略是否启用。
sudo grep "^PASS" /etc/login.defs
看
PASS_MAX_DAYS
(密码最长有效期)、
PASS_MIN_DAYS
(最短修改间隔)、
PASS_WARN_AGE
(过期前警告天数)。Ubuntu默认
PASS_MAX_DAYS 99999
(永不过期),这在生产环境不合理。建议改为
90
,并配合
sudo chage -M 90 username
为用户单独设置。检测空密码:
sudo awk -F: '$2 == "" {print "Empty password for:", $1}' /etc/shadow
4.3 检查无人认领的旧账户
开发测试留下的临时账户、离职员工未清理的账户,都是后门温床。
lastlog -b 180
列出180天内未登录的用户(
-b
表示before),
lastlog -u username
查单个用户最后登录时间。我习惯建个清理脚本:
#!/bin/bash
# cleanup-inactive.sh
INACTIVE_DAYS=90
for user in $(getent passwd | awk -F: '$3>=1000&&$3<=60000 {print $1}'); do
if [[ $(sudo lastlog -u "$user" 2>/dev/null | tail -1 | awk '{print $5,$6,$7}') == "Never" ]]; then
echo "User $user never logged in"
elif [[ $(sudo lastlog -u "$user" 2>/dev/null | tail -1 | awk '{print $5,$6,$7}' | xargs -I{} date -d "{}" +%s 2>/dev/null) -lt $(date -d "$INACTIVE_DAYS days ago" +%s 2>/dev/null) ]]; then
echo "User $user inactive for >$INACTIVE_DAYS days"
# sudo usermod -L "$user" # 锁定,注释掉先观察
fi
done
先观察再行动,避免误锁正在用的服务账户。
4.4 检查Shell滥用:识别伪装成用户的进程
有些恶意软件会创建UID很高的用户,但Shell设为
/bin/bash
,实际只用来启动一个后台进程。
ps -eo user,uid,comm,args --sort=-uid | head -10
能揪出这些“高UID低活跃”的可疑分子。比如看到
UID:5001
的
/usr/bin/python3 /tmp/.cache/update.py
,而
getent passwd 5001
返回空,说明这用户根本没在
/etc/passwd
注册,是进程自己伪造的UID(需root权限),立刻
sudo kill -9 5001
并查
/tmp/.cache/
。
4.5 检查组成员关系:
groups
与
id
命令的深度用法
用户权限不仅看UID,更看所属组。
groups username
列出用户所有组,
id -gn username
只显示主组,
id -Gn username
显示所有组ID名。关键是要查
sudo
组:
getent group sudo | cut -d: -f4
输出所有sudoer用户名。如果发现
www-data
在sudo组里,那是严重配置错误——Web服务进程不该有提权能力。修复:
sudo gpasswd -d www-data sudo
。
5. 常见问题与排查技巧实录:那些文档里不写的坑
在Ubuntu上查用户,看似简单,实则暗坑无数。以下是我在上百台服务器上踩过的坑,以及对应的“秒级”排查法。
5.1 问题:
getent passwd
不显示LDAP用户,但
id username
能查到
现象
:公司统一用OpenLDAP管理用户,在Ubuntu客户端执行
getent passwd
只看到本地用户,但
id ldapuser
返回正确UID/GID。
原因
:
getent
默认只查
files
源,而LDAP配置在
/etc/nsswitch.conf
里是
passwd: files ldap
,需要显式指定源。
解决
:
getent passwd ldapuser
(查单个)或
getent -s ldap passwd
(查所有LDAP用户)。
-s ldap
强制指定源,
-s files
查本地。
5.2 问题:
lastlog
显示“Never”但用户明明登录过
现象
:
lastlog -u john
输出
john pts/0 1970-01-01 00:00:00 +0000
,看起来像从未登录,但
journalctl _UID=1001 | grep "session opened"
有大量记录。
原因
:
lastlog
依赖
/var/log/lastlog
二进制文件,如果该文件损坏或被
logrotate
误删,就会重置为1970年。
解决
:
sudo lastlog -u john -t 30
查最近30天,或直接
sudo journalctl _UID=1001 | grep "session opened" | tail -1
看最后登录时间。
lastlog
是快照,
journalctl
是实时日志,后者更准。
5.3 问题:
usermod -s /bin/bash username
后仍无法登录
现象
:修改Shell后,SSH登录报错
This account is currently not available.
原因
:
/etc/shadow
第二列是
!
或
*
,表示账户被
passwd -l
锁定,改Shell不解除锁定。
解决
:
sudo passwd -u username
解锁账户。记住口诀:“改Shell管入口,解锁定管开关”。
5.4 问题:
/etc/passwd
里用户家目录是
/home/oldname
,但实际登录后是
/home/newname
现象
:
usermod -l newname oldname
改了登录名,但家目录没同步,导致登录后
pwd
显示
/home/oldname
。
原因
:
-l
只改用户名,不改家目录。
-d
指定家目录,
-m
移动内容。
解决
:
sudo usermod -l newname -d /home/newname -m oldname
。
-m
是关键,它把
/home/oldname
整个挪到
/home/newname
,并修正所有文件属主。漏掉
-m
,就得手动
sudo mv /home/oldname /home/newname && sudo chown -R newname:newname /home/newname
。
5.5 问题:
ps aux | grep username
找不到用户进程,但
top
里CPU很高
现象
:
ps
查不到用户进程,但系统卡顿。
原因
:进程可能以
root
身份运行,但实际是用户启动的(如
sudo systemctl start nginx
),
ps aux
按实际UID显示为
root
。
解决
:
ps auxf
(显示树状结构),找
systemd
下的子进程;或
sudo lsof -u username
查该用户打开的所有文件和网络连接,常能发现隐藏的
python
或
node
进程。
6. 进阶场景与自动化:把用户检查变成日常巡检脚本
手动查用户适合学习,但生产环境必须自动化。我分享一个轻量级巡检脚本
check-users.sh
,它能在30秒内完成所有关键检查,并生成可读报告。
#!/bin/bash
# check-users.sh - Ubuntu用户健康巡检脚本
LOGFILE="/tmp/user-check-$(date +%s).log"
echo "=== Ubuntu User Health Check $(date) ===" > "$LOGFILE"
# 1. 检查UID 0非root用户
echo -e "\n[1] UID 0 non-root users:" >> "$LOGFILE"
sudo awk -F: '$3 == 0 && $1 != "root" {print " " $1}' /etc/passwd >> "$LOGFILE"
# 2. 检查空密码
echo -e "\n[2] Users with empty passwords:" >> "$LOGFILE"
sudo awk -F: '$2 == "" {print " " $1}' /etc/shadow >> "$LOGFILE"
# 3. 列出所有普通用户及其状态
echo -e "\n[3] Active human users (UID 1000-60000):" >> "$LOGFILE"
getent passwd | awk -F: '$3 >= 1000 && $3 <= 60000 {printf " %-12s UID:%-4d Shell:%s\n", $1, $3, $7}' | \
while read line; do
user=$(echo "$line" | awk '{print $2}')
status=$(sudo awk -F: -v u="$user" '$1==u {print $2}' /etc/shadow)
case "$status" in
"") stat="NO PASSWD" ;;
"*"|"!") stat="LOCKED" ;;
*) stat="ACTIVE" ;;
esac
echo "$line Status:$stat"
done >> "$LOGFILE"
# 4. 检查sudo组成员
echo -e "\n[4] Sudoers:" >> "$LOGFILE"
getent group sudo | cut -d: -f4 | tr ',' '\n' | sed 's/^ *//; s/ *$//' | grep -v "^$" | sort >> "$LOGFILE"
# 5. 检查180天未登录用户
echo -e "\n[5] Inactive users (>180 days):" >> "$LOGFILE"
lastlog -b 180 | awk '$1 != "Username" && $5 != "Never" {print " " $1}' | sort >> "$LOGFILE"
echo -e "\n=== Report saved to $LOGFILE ===" >> "$LOGFILE"
cat "$LOGFILE"
把这个脚本保存为
check-users.sh
,
chmod +x check-users.sh
,然后
./check-users.sh
。它会生成一个带时间戳的日志,内容包括:UID 0异常用户、空密码账户、所有普通用户详情(含登录状态)、sudo组成员、长期未登录用户。我把这个脚本加入
cron
每周日凌晨2点自动运行:
0 2 * * 0 /path/to/check-users.sh >> /var/log/user-check.log 2>&1
。日志积累起来,就能看出账户增长趋势、离职人员清理时效、密码策略执行情况。有一次,日志显示
backup
用户连续3周“NO PASSWD”,我立刻联系DBA,发现是备份脚本用
--no-password
参数直连MySQL,根本不需要系统密码——这提醒我,检查结果要结合业务理解,不能见“NO PASSWD”就删。
最后分享一个小技巧:Ubuntu桌面版用户常问“怎么让某个用户不在登录界面显示?”。答案是编辑
/var/lib/AccountsService/users/username
,添加
SystemAccount=true
。但这只影响GDM登录界面,不影响SSH或
su
。真正的隐藏方式是把UID设为<1000,比如
sudo usermod -u 999 hiddenuser
,这样
getent passwd
还会显示,但
loginctl list-users
和图形登录屏就彻底看不见了——因为系统默认只展示UID≥1000的用户。这招适合创建专用服务账户,既保证功能,又减少攻击面。

272

被折叠的 条评论
为什么被折叠?



