CoursesLinux essentialsAccounts: passwd, shadow, groups

Accounts: passwd, shadow, groups

How Linux knows who you are.

Beginner12 min · lesson 17 of 25

A large office building does not really run on names. When you start, the security desk hands you a badge with a number baked into it, and from that moment every door you open, every entry in the log, every access rule is tied to that number. Your name is printed on the front so humans can read it. The machinery behind the doors only ever checks the number. Linux runs its accounts the same way.

That number is your UID (user ID, the plain integer the system uses to identify you), and every group has one too, its GID (group ID). When you try to read a file or start a program, the kernel (the core part of the operating system that talks straight to the hardware) compares your UID against the file's owner. It never looks at the word 'deploy' or 'alice'. The names are a convenience layer painted over the numbers so you do not have to memorize them.

~/secopslog — bash
$ id id root whoami
uid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo),999(docker) uid=0(root) gid=0(root) groups=0(root) deploy

One number is special. UID 0 is root, the superuser, the account that skips every permission check on the machine. root is the master key that opens every door in the building. This is worth burning in now: Linux treats you as all-powerful because your UID is 0, not because your name happens to be 'root'. Give any account UID 0 and it is root, no matter what you called it.

Where The Accounts Live: /etc/passwd

Every account on the machine is written down in one plain-text file, /etc/passwd. Treat it as the building's public staff directory: anyone can flip through it, because it holds no secrets, only the facts you need to look a person up. It is world-readable on purpose. Programs all over the system need to turn a UID back into a name, and they read this file to do it.

/etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
sshd:x:107:65534::/run/sshd:/usr/sbin/nologin
deploy:x:1000:1000:Deploy User:/home/deploy:/bin/bash

Take the last line, split it on the colons, and you have read a whole account. Username (deploy). A lone 'x' where a password once sat. The UID (1000). The GID of the primary group (1000). A free-text comment field (its old name is GECOS, a historical label; these days it just holds the full name). The home directory (/home/deploy). And the login shell (/bin/bash), the program that starts when the person logs in. That 'x' is a signpost. Long ago the password hash lived right here in this world-readable file, so anyone could copy it and start guessing. Now the 'x' means the hash lives in /etc/shadow, look there instead.

Not every account belongs to a person. The programs that run your website, your database, and your SSH server (SSH, Secure Shell, the standard way to log into a machine over the network) each get their own account, so that if one is broken into the attacker lands as that limited user and not as root. A compromised web server should not be a compromised machine. Look at the daemon, www-data, and sshd lines above: their login shell is /usr/sbin/nologin, a tiny program whose only job is to refuse the login, print a short 'this account is currently not available', and exit. Those accounts can own files and run services, but nobody can open a shell as them. When you audit a box, a service account whose shell is /bin/bash instead of nologin is worth a hard second look. Someone may have quietly turned a dormant account into a way back in.

You do not have to read the raw file every time. getent (short for 'get entries') asks the system for one account record and hands it back in the same colon-separated format. It reads /etc/passwd, and on machines wired into a central company directory such as LDAP (Lightweight Directory Access Protocol, a network service that stores accounts in one shared place) it reads that too. Building the habit of getent over grepping the file means your commands keep working when the accounts move onto the network.

~/secopslog — bash
$ getent passwd deploy
deploy:x:1000:1000:Deploy User:/home/deploy:/bin/bash

Where The Secrets Live: /etc/shadow

If /etc/passwd is the public directory, /etc/shadow is the locked filing cabinet in the manager's office. Same list of people, but this file holds the real password hashes and the rules for when passwords expire. A hash is the scrambled, one-way fingerprint of a password: you can check a guess against it, but you cannot run it backward to get the password out. Only root can open this file.

~/secopslog — bash
$ ls -l /etc/shadow sudo getent shadow deploy
-rw-r----- 1 root shadow 1097 Jul 12 09:18 /etc/shadow deploy:$y$j9T$85N.rXvB0qc4Lm2/f3Kd8.$kQ9rIePq0nT7bZ2xY1mWd5:19850:0:99999:7:::

The permissions tell the story before you read a single byte. rw for root, r for the group named 'shadow', and nothing at all for everyone else. An ordinary user cannot even read it, so they cannot copy the hashes off and attack them at their leisure. Inside, each line splits on colons again: the username, then the hash, then a run of numbers that govern password aging. The day the password was last changed (counted in days since the 1st of January 1970), the fewest days before it may be changed again, the most days before it must be changed, and how many days of warning the user gets first.

The hash is self-describing. It opens with a marker between dollar signs that names the algorithm that made it. '$y$' is yescrypt, the modern default on Debian and Ubuntu. '$6$' is the older SHA-512 (Secure Hash Algorithm, 512-bit). '$1$' is MD5 (Message Digest 5), which is ancient and weak, and finding it on a running system is a small alarm bell. Two other values can appear where a hash belongs. A single '*' means the account never had a usable password, which is normal for service accounts. A '!' at the front means the account is locked.

A leaked /etc/shadow is a slow-motion breach
Anyone who copies /etc/shadow can carry it to a machine full of graphics cards and grind guesses against those hashes offline, where no login screen slows them down and nothing gets logged. How fast they go depends on the hash. An old MD5 ('$1$') falls at billions of guesses a second; a modern yescrypt ('$y$') hash is deliberately built to crawl. Either way the weak point is the password itself, because the attacker is not fighting your login prompt, they are working on their own time, so weak or reused passwords hurt badly. That is why the file is root-only, why backups of it need the same care as the file itself, and why a world-readable /etc/shadow, or a '$1$' MD5 hash sitting inside it, is something you fix today.

Groups: Who Belongs To What

A group is a labeled keyring. Rather than granting access to twenty files one teammate at a time, you hang those permissions on a single ring called, say, 'developers', and hand people the ring. Every user has one primary group (the GID from their /etc/passwd line, which owns any new file they create) plus any number of extra supplementary groups that stack on more access.

~/secopslog — bash
$ getent group sudo docker deploy
sudo:x:27:deploy,alice docker:x:999:deploy,alice deploy:x:1000:

Each line is the group name, an 'x' placeholder, the GID, and then a comma-separated list of members. (Your primary group does not list you here; that link lives in your /etc/passwd line, which is why deploy's own group looks empty.) Two of these carry real weight. Membership in 'sudo' (or 'wheel' on Red Hat-style systems) is what lets a user run commands as root through sudo (superuser do, the command that runs one command as root after checking you are allowed). And 'docker' looks harmless but is not: anyone in the docker group can start a container that mounts the entire host filesystem and hands them root over it. Being in the docker group is, in practice, being root. When you review who can administer a machine, do not stop at the sudo group. Read the docker group exactly the same way.

The three files that define an account
/etc/passwd
World-readable
anyone can read it
name -> UID / GID
the identity
home + login shell
nologin blocks logins
/etc/shadow
root-only (0640)
rw root, r shadow group
password hash
$y$ yescrypt today
aging + lock
expiry dates, ! = locked
/etc/group
World-readable
anyone can read it
name -> GID
the keyring
member list
sudo, docker = root
Read left to right: who exists, the secrets, who belongs where.

Creating, Locking, And Removing Accounts

Adding a person is a couple of commands. useradd is the low-level tool; adduser is the friendlier Debian and Ubuntu wrapper that asks a few questions and fills in sane defaults. Give every human their own named account from day one.

~/secopslog — bash
$ sudo useradd -m -s /bin/bash alice # -m creates her home dir sudo passwd alice # set her first password sudo usermod -aG sudo,docker alice # -aG APPENDS groups; drop the -a and you WIPE her others id alice
New password: Retype new password: passwd: password updated successfully uid=1001(alice) gid=1001(alice) groups=1001(alice),27(sudo),999(docker)

When someone leaves, you have two moves and the order matters. Locking is reversible and takes effect at once: it disables the password so they cannot log in, while keeping their files and their name in the logs for any later investigation. Deleting is permanent and burns that trail. Lock first, delete much later if ever. An account that still accepts a password is a standing open door, and forgotten accounts belonging to people who left months ago are one of the most common ways attackers stroll back in. chage (change age, which edits the expiry rules in /etc/shadow) can also expire an account outright.

~/secopslog — bash
$ sudo passwd -l bob # lock: puts a ! in front of bob's hash, refusing password logins sudo chage -E 0 bob # -E 0 = expired as of 1970, so it is already expired; blocks every login method, keys included sudo passwd -S bob # show account status
bob L 07/12/2026 0 99999 7 -1

There is one habit worth setting in concrete: one human, one account, and never a shared login. A shared 'admin' account the whole team uses, or several people logging in straight as root, quietly wrecks your ability to investigate anything later. When something breaks or gets breached, the logs say 'admin did it' and you cannot tell which of the eight people that was. Give every person their own named account, have them escalate with sudo (which records who ran which command as root), and disable both direct root login and generic shared logins. You cannot hold anyone accountable for an action you cannot trace to a name, and that traceability is the ground every other control stands on.

What To Check First On A Strange Machine

Put all of this together and you have a quick first pass on any Linux box you inherit or are told to secure. Three questions: who is secretly root, who has no password at all, and who can become root. Three short commands answer them, using awk (a small tool that splits each line into fields and prints the ones you ask for).

~/secopslog — bash
$ awk -F: '($3 == 0) {print $1}' /etc/passwd # who is UID 0 (root)? sudo awk -F: '($2 == "") {print $1}' /etc/shadow # who has an empty password? getent group sudo # who can become root?
root sudo:x:27:deploy,alice

The first command prints the name of every account whose third field, the UID, is 0. A healthy system prints exactly one name, root. A second name there is a backdoor superuser and one of the oldest persistence tricks there is. The second command prints any account with an empty password field, which would let someone in with no password at all; it should print nothing, and here it does. The third shows precisely who holds admin rights. Run these against a box you were sure was clean and now and then you will still get a surprise.

Quick check
01You run awk -F: '($3==0)' /etc/passwd and it prints two lines: root:... and backup:x:0:0:.... The 'backup' account has UID 0. What does that actually mean?
Incorrect — Debian does ship a real 'backup' account, but its normal UID is 34, not 0. The UID 0 here is the red flag, not the name.
Correct — The kernel grants root by UID, not by name. Any account with UID 0 is root, whatever it is called.
Incorrect — Linux does not stop you from having two UID-0 accounts. It happily treats both as root, which is exactly the danger.
Incorrect — The UID field says nothing about where the hash lives; that is signalled by the 'x' in the second field.
02Reviewing a host, you run getent group docker and see docker:x:999:deploy,alice. Neither user is in the sudo group. What is the real security picture?
Incorrect — sudo is not the only route to root; docker membership is another that the lesson calls out specifically.
Incorrect — a container can mount the whole host filesystem, so that isolation does not hold.
Correct — being in the docker group is, in practice, being root, so it must be reviewed exactly like sudo.
Incorrect — docker access grants full control of the host, not merely read access to one file.
03You mean to add alice to the docker group and run sudo usermod -G docker alice (note: no -a). alice was already in the sudo group. What happens?
Incorrect — that is what -aG does; without the -a there is no appending.
Incorrect — it runs happily; -G without -a is valid and simply replaces the group set.
Incorrect — usermod does modify group membership, and docker is not special here.
Correct — -G sets the complete supplementary list, so without -a it wipes her other groups, including sudo.

So next time you land on an unfamiliar server, before you touch anything, run id to see who the box thinks you are, then read /etc/passwd and run getent group sudo to see who else is on the machine and which of them could take it over. Those three checks cost about ten seconds, and they are the whole difference between guessing about a box and actually knowing it.

Try this

Work through “What To Check First On A Strange Machine” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.

Takeaway

The trap worth remembering here: a leaked /etc/shadow is a slow-motion breach. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.

Related