110.1 Perform security administration tasks¶
Weight: 3
Candidates should know how to review system configuration to ensure host security in accordance with local security policies.
Objectives
- Audit a system to find files with the suid/sgid bit set.
- Set or change user passwords and password aging information.
- Being able to use nmap and netstat to discover open ports on a system.
- Set up limits on user logins, processes and memory usage.
- Determine which users have logged in to the system or are currently logged in.
- Basic sudo configuration and usage.
Terms
find, passwd, fuser, lsof, nmap, chage, netstat, sudo, /etc/sudoers, su, usermod, ulimit, who, w, last
Becoming another user: su and sudo¶
su¶
su ("substitute user") switches your session to another account. It asks for the target user's password: to become root, you need root's password.
carol@debian:~$ whoami
carol
carol@debian:~$ su -
Password:
root@debian:~# whoami
root
root@debian:~# exit
logout
carol@debian:~$
| Command | Gives you |
|---|---|
su - mimi |
a login shell as mimi, with mimi's environment |
su - or su - root |
a login shell as root (the name is optional for root) |
su |
root, but keeping your old environment |
The - loads the target user's environment. Without it you stay in your own environment, as the prompt shows:
Sharing the root password with many people is a bad security practice. That is what sudo avoids.
sudo¶
sudo runs one command as root (or as another user), so sudo ls runs ls as root. It has two big advantages over su:
- It asks for your own password, not root's. Whether you are allowed is decided by a security policy, the sudoers files
/etc/sudoersand/etc/sudoers.d/*. - It raises privileges for one command, instead of opening a whole root shell.
sudo -u user command runs as that user; without -u it runs as root. After you type your password, sudo remembers it for 15 minutes per user and terminal. Defaults timestamp_timeout=1 in /etc/sudoers changes that to one minute.
sudo su - combines the two: sudo runs su - as root, so you get a root login shell by typing your own password.
/etc/sudoers¶
The sudoers file says who may run what, as whom, and on which machines:
$ sudo cat /etc/sudoers
#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
Defaults env_reset
Defaults mail_badpass
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# User privilege specification
root ALL=(ALL:ALL) ALL
# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) ALL
#includedir /etc/sudoers.d
Every rule has the same shape:
So root ALL=(ALL:ALL) ALL means: root, from all hosts, as any user and any group, may run all commands. A % in front of a name means a group, so %sudo is every member of the group sudo.
You can be much more precise. To let carol check Apache's status, and nothing else:
Without having to type a password:
Two more examples of the same idea:
web ALL=(root) /usr/bin/systemctl # 'web' may run only this, as root
web ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx
Only on host 192.168.1.7, and only as user mimi:
carol@debian:~$ sudo -u mimi systemctl status apache2
● apache2.service - The Apache HTTP Server
Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2020-06-09 13:12:19 CEST; 29min ago
To give a user full admin rights, the simplest way is to add them to the admin group: sudo on Debian systems, wheel on Red Hat systems. Use -a, or the user loses all other groups:
Always edit with visudo¶
A mistake in /etc/sudoers can lock everyone out of sudo. Edit it with visudo, never directly with an editor. visudo checks the syntax before saving. It opens your default editor, which you can change with Defaults editor=/usr/bin/nano in the file, or for one run with EDITOR=/usr/bin/nano visudo.
Aliases¶
For bigger setups, sudoers can name groups of hosts, users and commands:
Host_Alias SERVERS = 192.168.1.7, server1, server2
User_Alias REGULAR_USERS = john, mary, alex
User_Alias PRIVILEGED_USERS = mimi
User_Alias ADMINS = carol, %sudo, PRIVILEGED_USERS, !REGULAR_USERS
Cmnd_Alias SERVICES = /usr/bin/systemctl *
ADMINS SERVERS=SERVICES
| Alias | Lists |
|---|---|
Host_Alias |
host names, IP addresses, networks, netgroups (+name) |
User_Alias |
user names, groups (%group), netgroups (+name). ! excludes |
Cmnd_Alias |
commands. * means any arguments. A directory means every file in it |
Runas_Alias |
users to run as, also by UID |
The last line reads: every user in ADMINS may run any SERVICES command on any SERVERS host.
Who is logged in: who, w and last¶
| Command | Shows |
|---|---|
who |
the users logged in now |
w |
the users logged in now, and what they are doing |
last |
the login history, newest first, including people who have logged out |
lastb |
the failed login attempts |
who¶
root@debian:~# who
carol pts/0 2020-06-06 17:16 (192.168.1.4)
mimi pts/1 2020-06-06 17:28 (192.168.1.4)
The columns are user, terminal, login date and time, and the host they came from. Useful options:
| Option | Shows |
|---|---|
-b |
the time of the last boot |
-r |
the current runlevel |
-H |
column headings |
A pts terminal is a pseudo terminal, like an SSH session or a terminal window; tty is a real console. Each shell window counts as a separate login.
w¶
w adds a summary line and what each user is running:
root@debian:~# w
17:56:12 up 40 min, 2 users, load average: 0.04, 0.12, 0.09
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
carol pts/0 192.168.1.4 17:16 1.00s 0.15s 0.05s sshd: carol [priv]
mimi pts/1 192.168.1.4 17:28 15:08 0.05s 0.05s -bash
The top line is the time, how long the system has been up, the number of users, and the load average over 1, 5 and 15 minutes (as in uptime).
| Column | Is |
|---|---|
IDLE |
how long since the user last typed something |
JCPU |
CPU time used by all processes on that terminal |
PCPU |
CPU time used by the current process |
WHAT |
the command running now |
w mimi and who both accept a user name.
last¶
who and w show only people logged in now. last shows the history, read from /var/log/wtmp:
root@debian:~# last
carol pts/0 192.168.1.4 Sat Jun 6 14:25 still logged in
reboot system boot 4.19.0-9-amd64 Sat Jun 6 14:24 still running
mimi pts/0 192.168.1.4 Sat Jun 6 12:07 - 14:24 (02:16)
reboot system boot 4.19.0-9-amd64 Sat Jun 6 12:07 - 14:24 (02:17)
(...)
wtmp begins Sun May 31 14:14:58 2020
mimi logged in at 12:07 and out at 14:24, a session of 2 hours 16 minutes. The reboot lines show each boot, with the kernel version and how long the system ran. last carol shows one user only. Failed logins are kept in /var/log/btmp; read them with lastb, or last -f /var/log/btmp.
Passwords and password aging¶
passwd -S shows the status of an account:
| Field | Here | Means |
|---|---|---|
| 1 | carol |
the user name |
| 2 | P |
the password state: P usable password, L locked, NP no password |
| 3 | 12/07/2019 |
the date of the last change |
| 4 | 0 |
minimum days between changes. 0 means any time |
| 5 | 99999 |
maximum days the password is valid. 99999 means it never expires |
| 6 | 7 |
days of warning before it expires |
| 7 | -1 |
inactive days after expiry before the account is locked. -1 means no limit |
Root uses passwd for basic account control:
| Command | Does |
|---|---|
passwd carol |
set carol's password |
passwd -l carol |
lock the password |
passwd -u carol |
unlock it |
passwd -e carol |
expire it: carol must choose a new one at next login |
passwd -d carol |
delete the password |
Here root locks carol's password, carol then cannot change it, and root unlocks it:
root@debian:~# passwd -l carol
passwd: password expiry information changed.
root@debian:~# su - carol
carol@debian:~$ passwd -S
carol L 05/31/2020 0 99999 7 -1
carol@debian:~$ passwd
Changing password for carol.
Current password:
passwd: Authentication token manipulation error
passwd: password unchanged
carol@debian:~$ exit
logout
root@debian:~# passwd -u carol
passwd: password expiry information changed.
usermod -L carol and usermod -U carol lock and unlock too, and usermod -f 3 carol sets 3 inactive days after the password expires.
chage¶
chage ("change age") is the main tool for aging. Anyone can list their own settings with -l; changing them needs root:
carol@debian:~$ chage -l carol
Last password change : Aug 06, 2019
Password expires : never
Password inactive : never
Account expires : never
Minimum number of days between password change : 0
Maximum number of days between password change : 99999
Number of days of warning before password expires : 7
With only a user name, root gets an interactive prompt for each value:
root@debian:~# chage carol
Changing the aging information for carol
Enter the new value, or press ENTER for the default
Minimum Password Age [0]:
Maximum Password Age [99999]:
Last Password Change (YYYY-MM-DD) [2020-06-01]:
Password Expiration Warning [7]:
Password Inactive [-1]:
Account Expiration Date (YYYY-MM-DD) [-1]:
| Option | Long form | Example | Sets |
|---|---|---|---|
-m |
--mindays |
chage -m 5 carol |
minimum days between changes (0 = any time) |
-M |
--maxdays |
chage -M 30 carol |
maximum days the password is valid (99999 = never expires) |
-d |
--lastday |
chage -d 0 carol |
the last change date. 0 forces a new password at next login |
-W |
--warndays |
chage -W 7 carol |
days of warning before expiry |
-I |
--inactive |
chage -I 10 carol |
inactive days after expiry before the account is locked (same as usermod -f) |
-E |
--expiredate |
chage -E 2050-12-13 carol |
the date the account is locked |
Finding SUID and SGID files¶
Besides read, write and execute, files can carry special permission bits:
| Bit | Number | Shown as | Effect |
|---|---|---|---|
| SUID | 4000 | s in the owner's execute place |
the program runs with the rights of the file's owner |
| SGID | 2000 | s in the group's execute place |
on a file: runs with the rights of the file's group. On a directory: new files inherit the directory's group |
A lowercase s means the execute bit is also set. An uppercase S means it is not.
passwd is the classic SUID program. It must write to /etc/shadow, which only root may change, even when a normal user runs it. The SUID bit makes it run as its owner, root:
Setting SGID on a directory, by adding 2 in front of the normal mode:
carol@debian:~$ ls -ld shared_directory
drwxr-xr-x 2 carol carol 4096 May 30 23:55 shared_directory
carol@debian:~$ sudo chmod 2755 shared_directory/
carol@debian:~$ ls -ld shared_directory
drwxr-sr-x 2 carol carol 4096 May 30 23:55 shared_directory
Why audit them? An SUID root program gives root's rights to whoever runs it. Imagine an attacker who got root once and set SUID on a copy of vi under an innocent name: from then on any user could edit any file as root. So check now and then which SUID and SGID files exist, and that each one should be there. An unexpected suid binary, especially a shell or editor, is a red flag.
find -perm searches by permission. The prefix in front of the value changes the meaning:
| Form | Finds files with |
|---|---|
-perm 4000 |
exactly this mode (SUID and nothing else) |
-perm -4000 |
at least these bits (SUID, plus any other permissions) |
-perm /6000 |
any of these bits (SUID or SGID) |
The value can be numeric or symbolic: -perm -u+s is the same as -perm -4000, and -perm -g+s the same as -perm -2000.
carol@debian:~$ sudo find /usr/bin -perm -4000
/usr/bin/umount
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/chfn
/usr/bin/mount
/usr/bin/passwd
/usr/bin/chsh
/usr/bin/sudo
/usr/bin/su
carol@debian:~$ sudo find /usr/bin -perm /6000
/usr/bin/dotlock.mailutils
/usr/bin/umount
/usr/bin/newgrp
/usr/bin/wall
/usr/bin/ssh-agent
/usr/bin/chage
(...)
/usr/bin/crontab
/usr/bin/su
To audit the whole system, search from /:
Limiting resources: ulimit¶
ulimit limits the resources a user's shell and its programs may use. Each limit has two values:
- the soft limit (
-S), the one in force. A user may raise it, up to the hard limit. - the hard limit (
-H), the ceiling. Only root can raise it. Users can only lower it.
ulimit -a shows all soft limits (-Ha the hard ones):
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 47457
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 47457
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
Some of the resources:
| Option | Limits |
|---|---|
-f |
the size of files the shell and its children may write |
-u |
the number of processes for one user |
-t |
CPU time per process, in seconds |
-v |
virtual memory |
-m |
resident memory (RSS, the part in RAM) |
-l |
memory that may be locked |
-n |
open files |
-b |
socket buffer size |
Reading a limit: give -S or -H and the resource. Without either, you get the soft value:
Setting a limit: without -S or -H, both are set. The value can be a number, or soft, hard or unlimited:
root@debian:~# ulimit -f 500
root@debian:~# ulimit -Sf
500
root@debian:~# ulimit -Hf
500
root@debian:~# ulimit -Sf 200
root@debian:~# ulimit -Sf
200
root@debian:~# ulimit -Hf
500
ulimit -t 1 would limit every process in this shell to one second of CPU time; a process that uses more is killed. CPU time is not clock time: time firefox shows how much CPU a program really used.
ulimit is a Bash builtin, so it has no man page of its own (see man bash), and its changes last only for the current shell.
/etc/security/limits.conf¶
For limits that last, and for limits on other users, use /etc/security/limits.conf. It protects a machine from a runaway process or a fork bomb. Each line is:
| Field | Can be |
|---|---|
| domain | a user name, @group, * for everyone (group and * limits do not apply to root) |
| type | soft, hard, or - for both |
| item | what to limit, see below |
| value | the limit |
Common items: nproc (number of processes), maxlogins (simultaneous logins for this user), maxsyslogins (logins on the whole system), fsize (file size), nofile (open files), cpu (CPU time in minutes), as (address space), core (core file size), priority, nice.
#<domain> <type> <item> <value>
* soft core 0
root hard core 100000
@student hard nproc 20
@faculty soft nproc 20
@faculty hard nproc 50
ftp hard nproc 0
@student - maxlogins 4
So every student may run at most 20 processes and log in at most 4 times at once.
Finding open ports¶
Every open port is a door into the machine, and malware often opens one so an attacker can talk to it. Check your open ports regularly. Four tools do it: lsof, fuser, netstat and nmap.
lsof¶
lsof ("list open files") lists every open file, and on Linux network connections are files too. -i shows the network ones:
root@debian:~# lsof -i
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
dhclient 357 root 7u IPv4 13493 0t0 UDP *:bootpc
sshd 389 root 3u IPv4 13689 0t0 TCP *:ssh (LISTEN)
sshd 389 root 4u IPv6 13700 0t0 TCP *:ssh (LISTEN)
apache2 399 root 3u IPv6 13826 0t0 TCP *:http (LISTEN)
apache2 401 www-data 3u IPv6 13826 0t0 TCP *:http (LISTEN)
sshd 557 root 3u IPv4 14701 0t0 TCP 192.168.1.7:ssh->192.168.1.4:60510 (ESTABLISHED)
sshd 569 carol 3u IPv4 14701 0t0 TCP 192.168.1.7:ssh->192.168.1.4:60510 (ESTABLISHED)
You see the program, PID, user, and whether each socket is listening or connected. Here SSH and Apache listen, bootpc is the DHCP client, and there is one SSH connection. Filters:
| Command | Shows |
|---|---|
lsof -i |
all network files |
lsof -i4, lsof -i6 |
IPv4 only, IPv6 only |
lsof -i :22 |
port 22 only |
lsof -i@192.168.1.7 |
connections of that address |
lsof -i@192.168.1.7:22,80 |
that address, ports 22 and 80 (ranges with -) |
fuser¶
fuser ("file user") shows which processes use a file, and how. With -v (verbose) you get a table:
The ACCESS letters: c current directory, e executable being run, f open file, F open file for writing, r root directory, m mmap'ed file or shared library.
With -n you name a network port and protocol, which answers "who is using port 80?":
root@debian:~# fuser -vn tcp 80
USER PID ACCESS COMMAND
80/tcp: root 402 F.... apache2
www-data 404 F.... apache2
www-data 405 F.... apache2
The short form fuser 22/tcp -v works too. fuser -k 80/tcp kills the processes using that port.
netstat¶
netstat prints network statistics. With no options it lists active connections and Unix sockets, which is long, so pipe it to less. The useful options:
| Option | Shows |
|---|---|
-l |
listening sockets only |
-t |
TCP |
-u |
UDP |
-a |
all, listening and connected |
-n |
numbers instead of names (22 instead of ssh) |
-e |
extra information: the user and inode |
-p |
the program using each socket |
carol@debian:~$ netstat -lt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN
tcp 0 0 localhost:smtp 0.0.0.0:* LISTEN
tcp6 0 0 [::]:http [::]:* LISTEN
tcp6 0 0 [::]:ssh [::]:* LISTEN
carol@debian:~$ netstat -uten
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State User Inode
tcp 0 0 192.168.1.7:22 192.168.1.4:39144 ESTABLISHED 0 15103
Without -l you see the established connections; with -n, ssh became 22. A popular combination is netstat -tuna: TCP and UDP, all sockets, numeric:
$ netstat -tuna
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 192.168.1.35:49574 173.194.122.231:443 ESTABLISHED
udp 0 0 0.0.0.0:5353 0.0.0.0:*
LISTEN lines are servers waiting for connections. ESTABLISHED lines are open connections. 0.0.0.0 means any address: the web server on port 80 accepts connections from anywhere, while the database on 3306 listens only on 127.0.0.1, the machine itself. ss (109.3) does the same job with the same options.
nmap¶
nmap (network mapper) is a port scanner: it tests a machine from the outside, the way an attacker would. By default it checks 1000 ports:
root@debian:~# nmap localhost
Starting Nmap 7.70 ( https://nmap.org ) at 2020-06-04 19:29 CEST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.0000040s latency).
Other addresses for localhost (not scanned): ::1
Not shown: 998 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 1.58 seconds
| You scan | Example |
|---|---|
| several hosts | nmap localhost 192.168.1.7 |
| a range of hosts | nmap 192.168.1.3-20 |
| a whole subnet | nmap 192.168.1.* or nmap 192.168.1.0/24 |
| a subnet but one host | nmap 192.168.1.0/24 --exclude 192.168.1.7 |
| one port | nmap -p 22 localhost or nmap -p ssh localhost |
| several ports | nmap -p ssh,80 localhost |
| a range of ports | nmap -p 22-80 localhost |
| all 65535 ports | nmap -p- localhost |
| the 100 most common ports, fast | nmap -F localhost |
| with more detail | nmap -v (or -vv) |
root@debian:~# nmap -p 22-80 localhost
(...)
Not shown: 57 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Another machine might show more, for example a database and a proxy:
Comparing nmap (outside view) with netstat/ss (inside view) shows whether a service is exposed more widely than intended. Only scan machines you are allowed to.
More examples¶
The same commands once more, with other names and values, as a quick reference:
$ w # logged-in users and what each is running, plus load average
$ who # logged-in users and their login time
$ last # login history from /var/log/wtmp (most recent first)
$ passwd # change my own (asks for the current one)
# passwd nagato # root sets another user's, no old password needed
$ passwd -S nagato # status
nagato P 2023-09-14 0 99999 7 -1
# passwd -l nagato # lock
# passwd -u nagato # unlock
# passwd -e nagato # expire now, forcing a change at next login
# chage -l nagato # list current aging
# chage nagato # interactive, prompts for each field
# chage -M 90 -m 7 -W 3 nagato # max 90 days, min 7, warn 3 days ahead
# find / -perm -4000 -type f 2>/dev/null # suid files (-u+s is the symbolic form)
/usr/bin/passwd
/usr/bin/sudo
# find / -perm -2000 -type f 2>/dev/null # sgid files (-g+s)
# find / -perm /6000 -type f 2>/dev/null # suid OR sgid (4000 + 2000)
$ ulimit -a # all current limits
$ ulimit -u # max user processes
$ ulimit -t 1 # cap CPU time at 1 second; a longer process is killed
$ ulimit -Sn 2048 # -S soft / -H hard pick which limit (here the soft open-files cap)
# /etc/security/limits.conf
@students hard nproc 20 # the students group: at most 20 processes
* hard nofile 8192 # everyone: at most 8192 open files
nagato - maxlogins 4 # nagato: at most 4 simultaneous logins
$ netstat -tuna
Proto Local Address Foreign Address State
tcp 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 127.0.0.1:3306 0.0.0.0:* LISTEN
tcp 192.168.1.35:49574 173.194.122.231:443 ESTABLISHED
# lsof -i
COMMAND PID USER NODE NAME
nginx 11095 root TCP *:http (LISTEN)
# fuser 22/tcp -v
USER PID ACCESS COMMAND
22/tcp: root 1 F.... systemd
# fuser -k 80/tcp # -k: kill whatever is using this port
Summary¶
I review a host along several lines. I prefer sudo over su, because it grants rights per command and is logged. su switches to another user and needs that user's password, with su - loading their environment; sudo runs single commands as root with my own password, as allowed by /etc/sudoers and /etc/sudoers.d/. A sudoers rule reads who hosts=(user:group) commands, % marks a group, NOPASSWD: skips the password, and aliases (User_Alias, Host_Alias, Cmnd_Alias) name lists. I always edit it with visudo, and give full rights by adding a user to the sudo (or wheel) group with usermod -aG.
who and w show who is logged in now, w adding idle time and the running command, while last reads the login history from /var/log/wtmp, and lastb (or last -f /var/log/btmp) the failed attempts. passwd -S shows a password's state (P, L or NP) and aging, and as root I lock, unlock, expire or delete passwords with -l, -u, -e and -d, and change accounts with usermod. chage -l lists aging, and chage -M, -m, -W, -I, -E and -d 0 change it.
SUID (4000) makes a program run as its owner and SGID (2000) as its group, so I hunt dangerous permissions with find / -perm -4000 (or -u+s), -perm -2000 (or -g+s) or -perm /6000 for either. ulimit caps resources now: it sets soft and hard limits for the current shell, and users can only lower hard limits; permanent per-user or per-group limits such as nproc and maxlogins go in /etc/security/limits.conf.
I check exposure two ways. From inside, I use lsof -i (with :port or @address), fuser -vn tcp 80 to see which process holds a port, and netstat with -l, -t, -u, -n, -e and -p (or -tuna), or ss. From the network, nmap scans hosts, ranges and subnets, with -p choosing ports and -F a fast scan.