Hardening/Linux

From Segfault
Jump to navigation Jump to search

Login

System users

Disable login for system users:

for u in `getent passwd | awk -F: '!/^(root|joe)/ {print $1}'`; do
   chsh -s /usr/sbin/nologin $u    # passwd -e on Solaris
   passwd -d $u                    # delete password entry
done

To verify which users are left with a valid shell/password, use:

getent passwd | grep -v nologin
getent shadow | grep -F \$         # $1$  - MD5
                                   # $2a$ - Blowfish
                                   # $2y$ - Blowfish, correctly handling 8-bit characters
                                   # $5$  - SHA-256
                                   # $6$  - SHA-512 - see also shadow passwords

Password hashing

On most Linux systems, pam_unix(8) is able to hash ("encrypt") the user's passwords with a strong cryptographic hash function:

$ grep ^p /etc/pam.d/common-password
password        [success=1 default=ignore]      pam_unix.so obscure sha512
password        requisite                       pam_deny.so
password        required                        pam_permit.so
password        optional                        pam_ecryptfs.so

libpam-unix2 is problematic, more on this later! See also Unix crypt with SHA-256/512 and bcrypt in Debian! It may also not work with eCryptfs!

To tighten password security even more, we use passwdqc or pam_cracklib and pam_unix2(8):

$ apt-get install libpam-unix2 libpam-passwdqc

$ grep ^[CB] /etc/security/pam_unix2.default
CRYPT=des
CRYPT_FILES=blowfish
BLOWFISH_CRYPT_FILES=5
CRYPT_YP=des

And add those to the PAM password stack:

$ grep ^p /etc/pam.d/common-password
password        requisite                       pam_cracklib.so retry=3 minlen=8 difok=3
password        [success=1 default=ignore]      pam_unix2.so blowfish
password        requisite                       pam_deny.so
password        required                        pam_permit.so
password        optional                        pam_ecryptfs.so

tmpfs

Moving temporary filesystems to tmpfs and setting them to nosuid,nodev,noexec[1] could help mitigate random program execution as well:

echo "tmpfs  /tmp  tmpfs nodev,nosuid,noexec,mode=1777 0 0" >> /etc/fstab
mv /tmp /tmp2 && mkdir -m1777 /tmp && mount /tmp && rsync -avP /tmp2/ /tmp && rm -rf /tmp2

Now, mount(8) should look something like this:

$ mount | grep tmpfs
tmpfs   on /lib/init/rw type tmpfs (rw,nosuid,mode=0755,size=10485760)
varrun  on /var/run  type tmpfs (rw,nosuid,mode=0755,size=10485760)
varlock on /var/lock type tmpfs (rw,nosuid,nodev,noexec,mode=1777,size=10485760)
udev    on /dev      type tmpfs (rw,mode=0755)
tmpfs   on /dev/shm  type tmpfs (rw,nosuid,nodev,size=10485760)
tmpfs   on /tmp      type tmpfs (rw,nosuid,nodev,noexec,mode=1777)

For SELinux enabled systems, this might help:

$ restorecon -v -R /tmp
restorecon reset /tmp context unconfined_u:object_r:default_t:s0->system_u:object_r:tmp_t:s0
 
$ echo "tmpfs   /tmp tmpfs  nosuid,noexec,context=system_u:object_r:tmp_t:s0 0 0"  >> /etc/fstab

Note:

  • /var/run and /var/lock are on tmpfs since Fedora 15.
  • /tmp on tmpfs will be available in Fedora 18

Per-user /tmp

Per-user /tmp is a way to create temporary files securely[2] and is implemented quite differently.

Debian/Ubuntu

apt-get install libpam-tmpdir
echo 'session optional        pam_tmpdir.so' >> /etc/pam.d/common-session

Logout & login again:

$ ls -ld $TMP $TMPDIR
drwx------ 2 alice root 40 Jan 11 09:35 /tmp/user/1000
drwx------ 2 alice root 40 Jan 11 09:35 /tmp/user/1000

Fedora

There's no libpam-tmpdir for Fedora but there is pam_namespace[3], which can be configured[4] in a similar way:

TBD!

openSUSE

pam_mktemp is a PAM plugin to provide per-user TMP directories:

zypper install pam_mktemp
echo 'session optional        pam_mktemp.so' >> /etc/pam.d/common-session

Logout & login again:

$ ls -ld $TMP $TMPDIR 
drwx-----T 2 root root 4096 Sep 11 05:10 /tmp/.private/root
drwx-----T 2 root root 4096 Sep 11 05:10 /tmp/.private/root

Encrypted swap

Assuming sda2 is our current swap partition:

swapoff -a
DEV=/dev/sda2
[ "$PARANOID" = 1 ] && dd if=/dev/urandom of=$DEV bs=1M

cryptsetup -c twofish -s 128 -d /dev/urandom create swap $DEV
mkswap -f /dev/mapper/swap

To enable enable encrypted swap during boot:

echo "swap $DEV /dev/urandom swap,noearly,cipher=twofish-xts-essiv:sha256,size=256,hash=sha512" >> /etc/crypttab

Or, with AES:

echo "swap $DEV /dev/urandom swap,noearly,cipher=aes-xts-essiv:sha256,size=256,hash=sha512" >> /etc/crypttab
echo "/dev/mapper/swap  none  swap  sw  0 0" >> /etc/fstab

Start the newly configured swap device via systemd:

systemctl daemon-reload
systemctl restart cryptsetup.target

Gentoo

For Gentoo the procedure is slightly different:

emerge sys-fs/lvm2

Deactivate the current swap device (sda2) and create a dm-crypt layer on top of it:

DEV=/dev/sda2
swapoff $DEV
echo "0 `blockdev --getsize $DEV` crypt aes-cbc-essiv:sha256 `openssl rand -hex 1024 | cut -c-64` 0 $DEV 0" | dmsetup create swap
 
mkswap -f /dev/mapper/swap
swapon /dev/mapper/swap

To enable encrypted swap during startup, an executable boot script can be put in e.g. /etc/local.d/01-cryptswap.start, containing the commands above.

Note: sys-fs/device-mapper has been merged into sys-fs/lvm2, c.f. #262836 and #389361

Kernel

dmesg

With Linux v2.6.37-rc2 the kernel config option CONFIG_SECURITY_DMESG_RESTRICT can be set. Once enabled, setting kernel.dmesg_restrict to true restricts non-root users from being able to view the kernel's log buffer:

$ /sbin/sysctl kernel.dmesg_restrict
kernel.dmesg_restrict = 1

$ dmesg 
klogctl: Operation not permitted

Unecessary kernel modules

$ while true; do
  lsmod | awk '/0 $/ {print $1}' | grep -vE "b43|coretemp|e1000|ideapad_laptop|microcode|tg3|usb_storage" | \
          xargs rmmod -v 2>/dev/null || break
  done
  • Replace e1000 with your NIC's driver name!
  • Now we can add something like this to /etc/rc.local:
/sbin/rmmod psmouse lp ppdev parport_pc parport ...
  • Or, better yet, blacklist these modules:
$ cat /etc/modprobe.d/local.conf
blacklist psmouse
blacklist lp
blacklist ppdev
blacklist parport_pc
blacklist parport

initrd.img

$ ls -lhgo /boot/initrd.img*
-rw-r--r-- 1 9.8M Feb 11 23:21 /boot/initrd.img-3.2.0-4-amd64

$ rmmod ....
$ lsmod | awk '!/^Module/ {print $1}' | sort >> /etc/initramfs-tools/modules
$ sed 's/^MODULES=.*/MODULES=list/' -i /etc/initramfs-tools/initramfs.conf
$ update-initramfs -u

$ ls -lhgo /boot/initrd.img*
-rw-r--r-- 1 2.4M Feb 11 23:28 /boot/initrd.img-3.2.0-4-amd64

Filesystem security

We also want to keep track of filesystem security[5].

Permission lockdown

For example, /var/log doesn't need to be visiable by anyone but the system administrator:

chmod 0711 /var/log && chmod o-rwx /var/log/*
setfacl -m g:staff:r-- /var/log/wtmp                  # Needed for last(1)

Logrotate tends to create new logfiles with world-readable pemissions again, thus reverting our last step on the next logrotate run. The following should change all logrotate configuration files from "create 644" to "create 640":

cd /etc/logrotate.d
for a in *; do sed '/create/s/4\ /0 /' -i.bak "$a"; done

Check if everything is in order, then:

rm -f *.bak

SUID

Find all SUID/SGID files:

find / -type f \( -perm -04000 -o -perm -02000 \)

Find world-writable files and directories:

find / -perm -2 ! -type l

Find files and directories with an unknown user and/or group:

find / \( -nouser -o -nogroup \)

We could eliminate all SUID binaries by just mounting with the nosuid[6] mount option.

Or we could remove[7] all SUID (and SGID) bits from the affected binaries:

$ sudo find / -xdev -type f \( -perm -04000 -o -perm -02000 \) -ls
/bin/mount
/bin/umount
/bin/rdisc6
/bin/ping6
/bin/ping
/bin/su
/sbin/pam-tmpdir-helper
/usr/lib/eject/dmcrypt-get-device
/usr/lib/openssh/ssh-keysign
/usr/lib/pt_chown
/usr/bin/sudoedit
/usr/bin/fping6
/usr/bin/ndisc6
/usr/bin/chfn
/usr/bin/fping
/usr/bin/sudo
/usr/bin/gpasswd
/usr/bin/chsh
/usr/bin/rltraceroute6
/usr/bin/newgrp
/usr/bin/passwd

Note: we may want to leave SUID bits in place for e.g. su or sudo, if needed:

$ find / -xdev -type f -perm -4000 | grep -vwE 'ping|ping6|su|sudo|pam-tmpdir-helper' | xargs chmod -c u-s

Capabilities

Removing SUID bits may leave the affected binaries unusable for normal users (e.g. ping) and we could implement POSIX capabilities[8] instead:

sudo apt-get install libcap2-bin                                         # Debian, Ubuntu
sudo yum install libcap                                                  # Fedora, RHEL
ping CAP-NET-RAW (13)
traceroute CAP-NET-RAW (13)
chsh CAP-CHOWN (0), CAP-DAC-READ-SEARCH (2), CAP-FSETID (4), CAP-SETUID (7)
chfn CAP-CHOWN (0), CAP-DAC-READ-SEARCH (2), CAP-FSETID (4), CAP-SETUID (7)
chage CAP-DAC-READ-SEARCH (2)
passwd CAP-CHOWN (0), CAP-DAC-OVERRIDE (1), CAP-FOWNER (3)
mount CAP-DAC-OVERRIDE (1), CAP-SYS-ADMIN (21)
umount CAP-DAC-OVERRIDE (1), CAP-SYS-ADMIN (21)

Example:

$ fping localhost
fping: can't create raw socket (must run as root?) : Operation not permitted

$ sudo setcap 13=ep /usr/bin/fping
$ fping localhost
localhost is alive

Or, for multiple capabilities:

sudo setcap 0,1,3=ep /usr/bin/passwd

/proc

Hiding some elements of /proc can help making potential attacks harder.

chmod 1775 /dev/shm                     # ECryptfs needs /dev/shm to be world-writable![9]
chmod 0440 /proc/interrupts
chown root:munin /proc/interrupts       # This way, Munin can still print interrupt statistics

chmod go-rwx /boot /sys/devices/*/*/resources /sys/kernel/slab /sys/kernel/slab/*/ctor \
      /proc/buddyinfo /proc/bus /proc/cmdline /proc/devices /proc/iomem /proc/ioports \
      /proc/kallsyms /proc/modules /proc/net/ptype /proc/pagetypeinfo \
      /proc/slabinfo /proc/timer_list /proc/vmallocinfo /proc/zoneinfo /dev/kmsg \
      /proc/cgroups /proc/consoles /proc/crypto /proc/diskstats \
      /proc/dma /proc/driver /proc/execdomains /proc/fb /proc/filesystems /proc/fs \
      /proc/irq /proc/key-users /proc/locks /proc/misc /proc/mtrr /proc/partitions \
      /proc/softirqs /proc/swaps /proc/sysvipc /proc/tty \
      /proc/device-tree /proc/pmu /proc/powerpc

The Linux kernel v3.3-rc1[10] can use the hidepid= and gid= options to resrict access to /proc/PID directories:

$ mount | grep ^proc
proc on /proc type proc (rw,noexec,nosuid,nodev,hidepid=2)

$ ps -e | wc -l
14

$ sudo ps -e | wc -l
169
  • hidepid=0 is the default behaviour, all users can see all /proc/PID directories.
  • hidepid=1 means users may not access any /proc/<pid>/ directories, but their own.
  • hidepid=2 means hidepid=1 plus all /proc/PID will be invisible to other users.

Note: this has been backported into Debian/wheezy, see Debian #669028 for details!

Random Numbers

On most system, the following random sources should be available:

  • /dev/urandom Serves as a PRNG, non-blocking, good throughput.
  • /dev/random Serves as a RNG, blocking I/O when not enough entropy is available, poor throughput.

The Linux entropy pool[11] can be queried via the /proc filesystem:

$ sysctl kernel.random.entropy_avail
kernel.random.entropy_avail = 3457

On systems with limited sources of real randomness[12], we'd like to use some kind of PRNG to gather additional random bits.

rng-tools

rngd needs a kernel device for random source. Also, it's only available for Linux.

apt-get install rng-tools                              # Debian[13], Ubuntu
dnf install rng-tools                                  # Fedora, CentOS

As root, we can list all available (and disabled) sources of entropy:

$ sudo rngd -l
Entropy sources that are available but disabled
1: TPM RNG Device (tpm)
4: NIST Network Entropy Beacon (nist)

Available and enabled entropy sources:
0: Hardware RNG Device (hwrng)
2: Intel RDRAND Instruction RNG (rdrand)
5: JITTER Entropy generator (jitter)

Check kernel.random.entropy_avail to verify that it's working! Test with rngtest:

$ rngtest -c 1000 < /dev/random
rngtest: starting FIPS tests...
rngtest: bits received from input: 20000032
rngtest: FIPS 140-2 successes: 999
rngtest: FIPS 140-2 failures: 1
rngtest: FIPS 140-2(2001-10-10) Monobit: 0
rngtest: FIPS 140-2(2001-10-10) Poker: 0
rngtest: FIPS 140-2(2001-10-10) Runs: 0
rngtest: FIPS 140-2(2001-10-10) Long run: 1
rngtest: FIPS 140-2(2001-10-10) Continuous run: 0
rngtest: input channel speed: (min=26.982; avg=38.635; max=11028.374)Kibits/s
rngtest: FIPS tests speed: (min=17.030; avg=44.287; max=181.652)Mibits/s
rngtest: Program run time: 505958874 microseconds

Without a proper random source[14] we get:

$ rngtest -c 1000 < /dev/zero 
rngtest: starting FIPS tests...
rngtest: bits received from input: 20000032
rngtest: FIPS 140-2 successes: 0
rngtest: FIPS 140-2 failures: 1000
rngtest: FIPS 140-2(2001-10-10) Monobit: 1000
rngtest: FIPS 140-2(2001-10-10) Poker: 1000
rngtest: FIPS 140-2(2001-10-10) Runs: 1000
rngtest: FIPS 140-2(2001-10-10) Long run: 1000
rngtest: FIPS 140-2(2001-10-10) Continuous run: 1000
rngtest: input channel speed: (min=1.693; avg=17.183; max=18.626)Gibits/s
rngtest: FIPS tests speed: (min=82.928; avg=460.623; max=733.596)Mibits/s
rngtest: Program run time: 42713 microseconds

haveged

haveged is based on the HAVEGE[15] random number generator and is said to work on systems withouth a hardware RNG while still producing good[16] random numbers. While this may or may not be true, it can be run additionally to rngd.[17]

apt-get install haveged                                # Debian, Ubuntu
dnf install haveged                                    # Fedora, CentOS

Let's run it in foreground for demonstration purposes:

$ haveged -F -v1 -w 1024
haveged starting up
haveged: ver: 1.8; arch: x86; vend: GenuineIntel; opts: (T); collect: 128K
haveged: cpu: (L4 VC); data: 32K (L2 L4 V); inst: 32K (L2 L4 V); idx: 21/40; sz: 32701/60538
haveged: tot tests: BA8: A:1/0 B: 1/0; continuous tests: B: A:0/0 B: 0/0; last entropy estimate 8.00131
haveged: fills: 0, generated: 0 

Check kernel.random.entropy_avail to verify that it's working or test with rngtest again.

See also

Links

References