Privilege Escalation in Unix

Guidance for creation of sudo rules

In a well controlled environment you typically do not want people logging into servers with privileged access (absent of additional external processes, such as a keystroke logged session manager). If you have 5 SAs all logging in as root then how can you audit activity and determine if it was Tom, Dick or Harry that rebooted the server? Similarly you don’t want DBAs directly logging in as oracle; how do you know who dropped the production table?

Typically this is solved by having the SA (or DBA, or AppOp, or…) login as themselves and then use a privilege escalation method. This method can use alternative authentication processes (e.g. 2FA), audit activity and, if properly configured, limit activity.

On Unix the standard tool is sudo. Some vendors may provide alternatives (e.g. Centrify has dzdo, Fox Technology’s Server Control has suexec) but the principles apply.

Business As Usual vs Elevated privileges

Any discussion of privilege escalation needs to consider the user’s work role. What an organisation considers privileged may not be the same as the operating system.

One simple example of this is the df command. If a normal user runs this then they may not see every file system mount.

$ df | grep dir1

$ sudo df | grep dir1
/dev/shm          249720       0    249720   0% /tmp/dir1/mountpoint

This situation may occur because the mountpoint may not be reachable from the normal user

drwx------ 3 root root 4096 Aug 26 15:42 /tmp/dir1

So from a Unix perspective, we need elevated permissions to run the df command and get a full listing. But from a business perspective it would seem reasonable to allow all the SAs to run sudo df.

The distinction between what the platform considers privileged and what the business considers privileged really needs to be kept in mind, especially when formulating policies and procedures around how to manage this stuff.

Read-only vs Read-write

A number of commands may be obviously read-only; cat is a great example of this; it can read files, but not write to them.

Side Bar: I’ve seen people ask for sudo echo because they think they can do things like sudo echo hello > /etc/my.file. That’s a misunderstanding of how the shell evaluates things; the > redirection happens at the current shell level, with the normal user privileges. Similarly sudo cat myfile > /etc/my.file doesn’t work; the cat may have elevated permissions, but the > redirect doesn’t.

Some commands can operate in both modes. For example miitool and ethtool can be used to see the state of the network adapter, including the negotiated speed and duplex. However it can also change the adapter settings, force renegotiation or even break network connectivity.

Similarly, on some OS’s, ifconfig will only show the MAC addresses if run as root. But now the SA has the ability to change IP addresses and other settings on the fly.

Typically commands that can make changes should not be part of a BAU profile because they may require a change control process before they can be used.

However, commands that can operate in read and write mode may be able to be limited; ifconfig, for example, could be limited to sudo ifconfig -a which would allow the SA to see the MAC addresses but not change things; sudo fdisk -l may be permitted because it allows listing of the partition tables, without making changes.

A number of commands can be limited to read-only access, and so may be considered part of the BAU scope.

Read-only may also be bad!

At a first glance, it might make sense to be able to grant lots of read-only access commands to people. After all, an SA may need to read a root-only configuration file. For example…

$ cat /etc/securetty
cat: /etc/securetty: Permission denied

What would be the harm in allowing the SA to cat every file?

Unfortunately there may be files on the system that should be protected from the SA. Should the SA be allowed to read /opt/my_app/etc/super_secret.txt ? Unlimited cat would allow this.

Unlimited read access may also allow a pivot to another server; for example if there is an ssh key used by a process to send data to another server then this key is probably passphrase-less (it’s a common scenario). Allowing unlimited cat would allow the SA to take a copy of the key and then try and access the remote server.

Obviously we may have audit logs of the abuse of this privilege, but prevention is always better than detection!

Wildcards

We need to be aware of how sudo parses commands. The cat example might be mitigated by only allowing read access to files in /etc;

%sa_group ALL=(root) /bin/cat /etc/*

Unfortunately…

$ sudo -l
User sweh may run the following commands on test1:
    (root) /bin/cat /etc/*

$ cat /tmp/dir1/secret.txt
cat: /tmp/dir1/secret.txt: Permission denied

$ sudo cat /tmp/dir1/secret.txt
Sorry, user sweh is not allowed to execute '/bin/cat /tmp/dir1/secret.txt' as root on test1.spuddy.org.

$ sudo cat /etc/../tmp/dir1/secret.txt
hidden

$ sudo cat /etc/subgid /tmp/dir1/secret.txt
hidden

In this case I was able to make use of how filenames work to display the protected file, and just to use two filenames (one permitted) to match the pattern. Two problems with one configuration entry!

If you plan on using wildcards then determine if that will allow for excessive privileges this may expose. In some cases this might be mitigated with the ! configuration, which will deny commands.

Shell escape

Some commands allow for the user to drop into a subshell to perform activity and then exit back to the program. A common example of this is the less command.

$ sudo less test
myfile
!sh
sh-4.2# id
uid=0(root) gid=0(root) groups=0(root)

Now while the less command itself was logged, all activity performed inside the subshell isn’t properly tracked. The SA has full root level permissions on the machine and we can’t audit their activity.

This may, sometimes, be mitigated with the NOEXEC option in the configuration:

$ sudo -l
User sweh may run the following commands on test1:
    (root) NOEXEC: /bin/less

$ sudo less test
myfile
!sh
!done  (press RETURN)

This isn’t infallible, however, and may block functionality the application needs. It may also not work on every operating system.

Sometimes the most unexpected of commands allows for a shell escape; the one that surprised me was the Solaris format command.

Open additional files.

This is related to shell escapes, but allows for files to be read or modified unexpectedly.

For example, we might want to allow for vi to be run on a single file, but the user can open additional files.

$ sudo -l
User sweh may run the following commands on test1:
    (root) /bin/cat
    (root) NOEXEC: /bin/vi /home/sweh/test

We’ve NOEXEC’d this, but the user can still do :r inside the session to read protected content. (This is hard to screen shot…)

$ sudo vi /home/sweh/test
myfile
:r /tmp/dir1/secret.txt
myfile
hidden
:w! /tmp/dir1/secret.txt
"/tmp/dir1/secret.txt" 2L, 14C written
:q!

$ sudo cat /tmp/dir1/secret.txt
myfile
hidden

sudoedit

Editing of files is a common requirement, so sudo adds another option, called sudoedit. This can be invoked as sudoedit or sudo -e. The way it works is clever; it makes a copy of the file you want to edit in a temporary directory, then runs your editor as your real user, and if changes have been detected copies the file back (with the correct permissions, ownership, etc). In this way any shell escape or file reads are done as the real user, and not privileged.

There are other limitations on sudoedit (eg not in a writeable directory) but it can handle a number of cases where BAU write-file access is permitted.

Functional access (passwordless)

I’ve mostly been focusing on human level access (I’ve described SAs, but this would also work for DBAs, AppOp, and similar, and the target user need not be root).

Sometimes there may be a need for a functional (or service) account to need to perform privileged activity. Examples abound, but a simple one might be a polling look that wants to see what servers have listening ports, and what processes these are. Consider this job being called from cron every hour:

for host in test1 test2 test3
do
  ssh svc_acct@$host sudo netstat -anp | grep -w LISTEN
done

We need root here because of the -p option to netstat.

Obviously we can’t enter the password each time (indeed there may not be a valid password, because we use key based authentication). Fortunately there’s a NOPASSWD: option

$ sudo -l
    (root) NOPASSWD: /bin/netstat -anp

$ ssh test1 sudo netstat -anp | grep -w LISTEN
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      859/master          
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      759/sshd            
tcp6       0      0 ::1:25                  :::*                    LISTEN      859/master          
tcp6       0      0 :::22                   :::*                    LISTEN      759/sshd  

These NOPASSWD: entries can also be applied to human user accounts if you consider the activity to be sufficiently low risk that the human doesn’t need to re-authenticate (the df example from earlier may apply).

Block Lists don’t work – Example of a bad setup

I was working with a team some time back who wanted to perform centralised data collection and some of the data required root level permissions. Now, naturally, I refused them ALL access; having a tool with access to every server and having unlimited root on all those servers was an unacceptable concentration of risk.

They went away and came back with the following request:

#List of commands svc_acct is not allowed to execute via sudo
Cmnd_Alias SVCNOEXEC=
adduser*,
chmod*,
chown*,
cp*,
...and so on

svc_acct ALL=NOPASSWD: ALL, !SVCNOEXEC

(In all there were 30 commands listed in the SVCNOEXEC alias).

Apart from the fact this wasn’t a valid sudoers file, these block list setups provide ZERO security in the environment. I was able to think of three approaches within a few minutes:

First obvious way to bypass those restrictions:

Run the command under /bin/sh, which wasn’t restricted:

sudo /bin/sh -c '<any_command_in_that_list>'

eg

sudo /bin/sh -c 'halt'

Second less obvious way to bypass these restrictions:

copy the file to a tmp directory and run that:

cp /sbin/halt /tmp/myprogram
sudo /tmp/myprogram

A third way

Create a wrapper and call that

echo halt > /tmp/myprogram
chmod 755 /tmp/myprogram
sudo /tmp/myprogram

I could probably come up with more esoteric variations as well (symlinks, ld.so, perl, python… even use sed to update the sudoers file, itself!)

Basically, block lists like this can be bypassed with zero effort and should not be used.

Conclusion

The secure way of setting up a sudo profile is to default-deny and then only permit the minimum necessary commands.

These commands should be restricted with options to ensure they are read-only and do not allow further privileges. So, for example, on Solaris sudo format may not be permitted because you can shell out of it. Similarly hbacmd may not be permitted without restriction because it can be used to reset HBAs, change binding rules, etc. but hbacmd ListHBAs is OK.

Of course, understanding business requirements and not blocking efficiency also needs to feed into the decision of what commands you allow an SA to run BAU.

You may decide that sudo cat is OK, trusting on the audit log to be a sufficient activity trail (perhaps with reporting triggered from it if a restricted file is accessed). That’s a definite productivity gain!

Also you may be able to use additional tools; e.g. SELinux maybe able to lock down access to secret.txt, even to users running sudo cat.

If you have any additional guidelines for sudo entry creation, please leave a message in the contents! I know I haven’t covered every scenario (e.g. wrapper scripts) because some of those could be a post in themselves!