Access Control

One of the biggest challenges in computer security is preventing one program from interfering with another. For instance, you do not want a virus to be able to steal the passwords from your browser. For this reason, operating systems implement protection mechanisms that enable the isolation of multiple processes and restrict access to data.

In Linux, the kernel is the program that has unrestricted access to the whole machine. All other programs run as a specific identity and have their access mediated by the kernel. Access decisions are made on the basis of the `userid`/`groupid` associated with the program. If the user is `root` (`userid` = 0), access is always granted by the kernel. Users usually have a default `group`, but they may belong to several additional groups. By joining an existing group, a user inherits the permissions it grants.

Unix permissions

Using the `ls -l` command we can display the Unix permissions set to a file or a directory:

$ ls -l myfile 
-rw-r--r-- 1 r1x r1x 0 Nov  8 12:10 myfile

Each file has a owner and a group, in this case it is user `r1x` and group `r1x`. The first column `rw-rw-r–` is made of 3 triads defining the permissions granted to the owner, to the group and to all the other users, respectively. Each permission triad is commonly made up of the following characters:

  • `r`: the file can be read / the directory’s contents can be shown
  • `w`: the file file can be modified / the directory’s contents can be modified
  • `x`: the file can be executed / the directory can be traversed
  • `s`: the file is `setuid` if `s` is found in the user triad (`setgid` if `s` is in the group triad). Implies `x`. Enables the file to run with the privileges of its owner

Notice that modern Linux distributions, upon user creation, usually add a group to the system with the same name of the user being added and set this group as the primary user’s group.

As mentioned before, the set of permissions of an user includes the permissions assigned to his groups. In the following example file `rootfile` is owned by `root` and has group `users`. It gives read and write permissions to `root` and only read permission to `users`:

$ id
uid=1019(r1x) gid=1019(r1x) groups=1019(r1x),100(users),1034(pkcs11)
$ ls -l rootfile 
-rw-r----- 1 root users 61 Nov  8 12:14 rootfile
$ cat rootfile 
this file can be read by any user belonging to group 'users'
$ cat > rootfile 
-bash: rootfile: Permission denied

Unix permissions can be altered using the `chmod` command (using the symbolic mode or the numeric one), whilst the owner and group can be set using `chown`. Notice that non-root users can change the group (to one they belong to) but not the ownership.

$ ls -l myfile 
-rw-r--r-- 1 r1x r1x 0 Nov  8 12:10 myfile
$ chmod 700 myfile 
$ ls -l myfile 
-rwx------ 1 r1x r1x 0 Nov  8 12:10 myfile
$ chown r1x:users myfile 
$ ls -l myfile 
-rwx------ 1 r1x users 0 Nov  8 12:10 myfile
$ chown root:users myfile
chown: changing ownership of 'myfile': Operation not permitted
$ chmod -x myfile 
$ ls -al myfile 
-rw------- 1 r1x users 0 Nov  8 12:10 myfile

Access Control Lists (ACL)

Suppose we are in a shared hosting system where users are allowed to host personal websites in their home directories. The webroot of the user `r1x` is the world-readable directory `/home/r1x/webroot`. The home directory of `r1x` can be accessed only by the user itself:

$ ls -la /home/r1x
total 24
drwx------  3 r1x  r1x   4096 Mar 16 19:49 .
drwxr-xr-x 21 root root  4096 Feb 19 18:02 ..
-rw-------  1 r1x  r1x    711 Mar 16 19:46 .bash_history
-rw-r--r--  1 r1x  r1x     32 Mar 16 19:49 personal_notes.txt
-rw-------  1 r1x  r1x    632 Mar 16 19:49 .viminfo
drwxr-xr-x  2 r1x  r1x   4096 Mar 16 19:20 webroot

In order to allow the webserver process (running as `nginx:nginx`) to access the webroot directory, we could make even the `/home/r1x` world-traversable using `chmod o+x`. Sadly, by doing so we would enable everyone (not just the webserver) to read files in the user home directory just by knowing the path name, including the sensitive file `personal_notes.txt`!

Access Control Lists provide an additional, more fine grained permission mechanism for files and directories. With ACLs it is possible to define different permissions on a per-user/per-group basis. They have higher priority over Unix permissions and provide inheritance of attributes from directories to files. Going back to the webserver example, we can make good use of the ACLs by granting the `x` permission on `/home/r1x/` only to the `nginx` user!

$ setfacl -m "u:nginx:x" /home/r1x

The command `setfacl` is used to define ACLs, while `getfacl` allows to display them. To provide an example, we first create a `test` file in the current directory and print its ACL

$ touch test
$ ls -l test
-rw-r--r-- 1 lavish lavish 0 Mar 16 22:06 test
$ getfacl test
# file: test
# owner: lavish
# group: lavish

Now we change the ACL so that the user `r1x` can read and write the newly created file

$ setfacl -m "u:r1x:rw-" test
$ getfacl test
# file: test
# owner: lavish
# group: lavish

The mask entry reported by the `getfacl` output is called the effective rights mask. This entry limits the effective rights that can be granted to the user and group ACL. By tightening the mask further, we revoke the write permission previously granted to the user `r1x`

$ setfacl -m 'm::r--' test 
$ getfacl test
# file: test
# owner: lavish
# group: lavish
user:r1x:rw-                    #effective:r--

To remove an ACL from a file one can use the `-b` option

$ setfacl -b test

See `man getfacl` and `man setfacl` for further details. Also notice how the output of `ls -l` is affected by the presence of an ACL: in addition to the `+` symbol, the file permission bits reported by `ls` may not correspond to the actual Unix permissions. To understand the correspondance between ACL entries and file permission bits read the relevant section in the manual page of `acl` by typing `man 5 acl`.

Downsides of the traditional access control mechanism

There are various problems with the traditional Unix access control system.

  • First of all, the `root` user. An attacker gaining root privileges is all-powerful and might even alter the system logs to hide his malicious activities or to implant fake audit trails. It is possible, however, to limit the need of setuid root binaries by giving to specific processes a subset of superuser permissions via Linux capabilities (see below).
  • Second, an application running on behalf of a user has access to all the resources owned by that user. Think about a browser, which is an application perpetually running potentially-malicious code, and a sensitive resource like a private `ssh` RSA key: if the browser has a vulnerability, the attacker might get access to the private key, since the browser is running with the same permissions as the user.
    $ top
      PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
    31962 lavish    20   0 1573,6m 377,7m  87,8m S   9,9  3,2  14:40.53 chrome
    $ ls -l .ssh/id_rsa
    -rw------- 1 lavish lavish 1675 gen 16  2010 .ssh/id_rsa

    To mitigate this problem, some browsers implement layered sandboxes to prevent unauthorised access to system resources. Check the Linux sandboxing approach implemented by Google Chromium for further details.

  • Third is the lack of a centralised access control policy. Permissions are attached to resources making impossible for a system administrator to fully understand the security implications of all the single permissions leaving space for backdoors.
  • Last, users can take their own access decisions about their resources. Even if this might appear irrelevant for a single-user Desktop computer, it is a problem of crucial importance when the user is a member of an organisation and is entrusted with sensitive files. For this reason, the standard Unix access control system is called Discretionary, as opposed to Mandatory Access Control systems where a security policy is enforced independently of user actions. Example of Mandatory Access Control systems are SELinux, AppArmor and grsecurity for Linux and MIC in Windows.

Linux Capabilities

Linux Capabilities offer the possibility of giving to specific processes a subset of superuser permissions. This can be also achieved via setuid root (s) permission, but executing the whole program as root is more risky in case of vulnerabilities.

Example 1: ping

We consider the case study of the `ping` program:

$ which ping
$ ls -al /bin/ping
-rwx--x--x 1 root root 38876 Feb 10  2017 /bin/ping
$ /sbin/getcap /bin/ping
/bin/ping = cap_net_raw+ep

In the terminal (not here because of the different colouring done by wordpress), we notice that `/bin/ping` appears in a different color even if permissions look standard (in particular we do not see any `s`). This is a way for ls to make it visible that capabilities have been set for this binary. With `getcap` we can check what capabilities are set and we discover `cap_net_raw+ep`, meaning that `cap_net_raw` is permitted (p) and effective (e). `cap_net_raw` allows for raw access to network which is necessary for `ICMP` packets in `ping`. Giving this capability to `ping` is safer than giving setuid root permission, which would give full root access to the program.

Example 2: tcpdump

`tcpdump` permits to dump any packet sent and received by the host. Similarly to `ping` it would require `cap_net_raw` capability, but is not provided by default. Thus, non-root users cannot run `tcpdump` unless permissions are suitably changed.

$ /usr/sbin/tcpdump 
tcpdump: eth0: You don't have permission to capture on that device
(socket: Operation not permitted)

We first show that by setting setuid root permission `tcpdump` becomes executable by non-root users:

# chmod u+s /usr/sbin/tcpdump 
testbed ~ # ls -al /usr/sbin/tcpdump 
-rwsr-xr-x 1 root root 783000 Feb 10  2017 /usr/sbin/tcpdump

Now if we run `tcpdump` as non-root user the program starts correctly, since it is run with root privileges:

$ /usr/sbin/tcpdump port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes

A better solution is to give tcpdump `cap_net_raw` capability as done by default for `ping`:

# chmod u-s /usr/sbin/tcpdump 
# setcap cap_net_raw+ep /usr/sbin/tcpdump
# ls -al /usr/sbin/tcpdump 
-rwxr-xr-x 1 root root 783000 Feb 10  2017 /usr/sbin/tcpdump

`tcpdump` can still be run as non-root but now it only retains the `cap_net_raw` privileged capability, while running as the non-root user.

Capabilities should not be abused

It is important to keep in mind that giving capabilities weakens security and gives non-root users access to privileged operations. We use again `tcpdump` as a case study and we show how to simply eavesdrop a network communication.

netcat: the tcp/ip swiss army knife

We use netcat (nc) to setup a simple network connection. Execute the following on one host where you can run `tcpdump`:

nc -l -p 10000

This starts netcat in server mode and listens on port 10000. You can connect to this netcat instance from a different host with:

nc  10000

This basically start a ‘commandline chat’ between the two hosts. netcat, in fact, is like ‘cat’ on the network: it redirects input/output from/to terminal and network.

Using tcpdump to intercept traffic

We illustrate simple interception. To simplify we work directly on the host where the server nc is running. In practice, to intercept it is necessary to have access to the client-server connection. Either we are in the middle (e.g. on a router host) or we are on the same LAN and we perform some spoofing attack to force client-server packets to go through our host. We “simulate” this by directly acting on the server host.

The program tcpdump is a simple but powerful tool that allows you to easily sniff. We report a few usage examples from the man page:

       To print all packets arriving at or departing from sundown:
              tcpdump host sundown

       To print traffic between helios and either hot or ace:
              tcpdump host helios and \( hot or ace \)

       To print all IP packets between ace and any host except helios:
              tcpdump ip host ace and not helios

       To print all packets arriving at or departing from sundown on port ftp or ftp-data:
              tcpdump host sundownand and (port ftp or ftp-data)

The following asciicast show how to use tcpdump to sniff the netcat chat:

We report below some useful options:

       -A     Print each packet (minus its link level header) in ASCII.  Handy
              for capturing web pages.
       -i     Listen  on interface.  If unspecified, tcpdump searches the sys‐
              tem interface list for the lowest numbered, configured up inter‐
              face (excluding loopback).  Ties are broken by choosing the ear‐
              liest match.
       -l     Make stdout line buffered.  Useful if you want to see  the  data
              while capturing it.
       -n     Don't  convert  addresses  (i.e.,  host addresses, port numbers,
              etc.) to names.
       -s snaplen
              Snarf snaplen bytes of data from each  packet  rather  than  the
              default  of  68  (with SunOS's NIT, the minimum is actually 96).
              snaplen to 0 means use the required length to catch whole  pack‐

tcpdump shows the raw packets including headers. If you want to display just the payload you can use the tcpflow tool. For example, here we sniff a netcat chat:

team11 ~ # tcpflow -i eth0 -c port 10000
tcpflow[14027]: listening on eth0 hello! How are you? Fine thanks!

Option -c prints on the console. Without this option output is saved on file whose name are the IPs and ports as above.

Privilege drop

It is worth noticing that many administrative programs automatically drop root privileges whenever they do not need privileged capabilities in the subsequent execution. We can observe this running `tcpdump` as root and observing the syscalls through `strace` (we grep on uid to filter out the interesting calls):

# strace tcpdump 2>&1 | grep uid
getuid32()                              = 0
getuid32()                              = 0
setuid32(0)                             = 0
getuid32()                              = 0
setresuid32(101, 101, 101)              = 0
# grep :101: /etc/passwd
tcpdump:x:101:248:added by portage for tcpdump:/dev/null:/sbin/nologin

We see that, after a while, tcpdump sets the user id to `101` which is user `tcpdump`. This confirms that, after `tcpdump` has accessed the network with raw capabilities, it drops privileges and go on with non-root user permissions. This is an important strategy to reduce the attack surface to the portion of code that really requires privileged access.