Hello, and welcome back to CS631 "Advanced Programming in the UNIX Environment". This is our second video covering how we can restrict processes, and today we'll revisit the mechanism by which we can change our effective UID, what pitfalls might await us there, and what additional security mechanisms we have. These mechanisms include file flags, certain mount options, and a mechanism called "securelevels". --- As we saw in our last video, POSIX ACLs can allow us to better control which users can access which files. Or rather, we can control which processes, when running with a given user's effective UID, can access which files. Recall also from Week 03, segment 3, our discussion around the use of effective and real UIDs and how we can change or elevate privileges. A common example of using this mechanism are daemons that need super-user privileges initially, such as - to bind a privileged port or use raw sockets for ICMP, - to actually handle logins and to then _down_grade privileges - or to raise or changing privileges for the duration of a whole login session or a single command. --- But each of these approaches has some pitfalls, too. - A setuid program needs to be very carefully written to only raise or use the raised privileges when needed and to avoid allowing the user to escape and run any other programs. That is often more difficult than it sounds. - This also depends on the correct installation and configuration of the binary, which is something the author of the program has no influence on. What's more, setting the setuid bits on an executable allows anybody who can execute the file to run it with the owner's privileges, meaning there is no fine grained control here. We've also seen examples of changing our (effective) UID when using e.g., su(1). But being able to run su(1) - relies on the password of that user and - grants you full access to everything that user controls. That is, you can only allow another user to su(1) and thus to do anything and everything as the target user, but not restrict it to a subset of functionality or commands. To overcome some of these limitations as well as to provide a bit more control, logging, and additional protections around the changing of eUIDs or running programs with the privileges of another user, many Unix systems use the sudo(8) utility. Although not part of every Unix system, it is the most commonly used mechanism here, allowing for fine-grained control and careful definitions of which user may execute which commands assuming which other user's eUIDs. However, sudo(8) turns out to be a complex beast, and configuring it correctly and securely isn't easy. - All too often, trying to restrict privileges to a set of commands results in the user being able to break out of one of them, gaining more elevated privileges than intended. Because requiring additional authentication for the execution of the commands can be cumbersome especially for automated jobs, - people often times simply disable them and, quite frequently, people are not aware of what additional privileges they are granting by accident. Let's illustrate this problem by example: --- Let's create two files over here: one file that we intend to share with 'fred', and one file that we intend to keep private. The permissions on the files look like so. Now, we will grant sudo(8) privileges to 'fred' to edit files as the user 'jschauma': If you're not familiar with the syntax for the sudoers file, it consists of statements that specify which user may invoke which commands as which other user, and our addition here simply states that 'fred' may run '/usr/bin/vi' as user 'jschauma' without providing a password. Ok, so let's see. Here's fred trying to edit our file. Hmm, that didn't work - what are the permissions again? Oh, we can't even look into the directory -- the read and execute permissions are not set for 'others'. Now let's use sudo(8) instead: There we go - we can now edit the file. Hooray. But now we have a problem: we granted 'fred' the permission to run 'vi' as 'jschauma', so of course 'fred' can simply run the same command and give it the private file instead. This is an example of overly broad sudo privileges, where we intended to only grant one type of access, but received implied broader access. Ok, let's prevent 'fred' from accessing the file by removing permissions from it. We, as the owner, can chmod(1) it as needed when we want to access it, but 'fred' can't, since 'fred' can only run 'vi' as 'jschauma'. There we go - we foiled 'fred's plan to access this file, while still allowing him access to the shared file. But 'fred' is no dummy. With powers to edit files as 'jschauma', 'fred' can now edit our shell startup file. Here - let's create a backdoor when user 'jschauma' logs in the next time: we copy a regular shell and make it setuid 'jschauma'. Because we're sneaky, we also then ensure that this command is removed from the startup file so we have left no trace of our evil intrusion. With that line at the top of the .shrc file, all we have to do is sit back and wait. Nothing happens for the user while they're logged in, but if the user logs in anew -- simulated here by creating a new screen window, then our commands get executed, and... yep, we didn't leave any evidence. So now 'fred' can find the shell in /tmp, and run it to become 'jschauma'. Oh, wait, what's that? Why am I still 'fred'? Well, it turns out that most unix shells have a check to see if the effective uid matches the real user ID, and if they don't match, the shell sets the eUID to the real UID. But if we pass the '-p' flag, then we get privileged mode, where this check is not performed and... yep, we are 'jschauma' with a real UID of 1001 and an effective UID of 1000. So now... we can simply chmod the permissions... ...write data to the file... and then undo the permissions change again to cover up our tracks. Here we confirm that the file was changed. Blast! Ok, so let's try to be a bit smarter and not let 'fred' edit just any old file. Instead, we restrict 'fred' to _only_ edit the file we want him to have access. There. Now 'fred' can't edit any other files, not our shell rc file, and not the target file he wants to access. As before, 'fred' _can_ edit the shared file. But unfortunately, 'vi' allows you to read and write files, so we can simply slurp in the file we care about. Trying to write, however, we run into a problem. Permission denied, because of the permissions. But here's another thing 'vi' lets you do: it let's you shell out and run commands. Here, if we run 'whoami', we see that we are... 'jschauma'. That is, vi runs as 'jschauma', and so any commands invoked from within vi are run as 'jschauma', too! So... hey, can I run a shell from within the editor? Look at that, I can! Cool, then I can fix the permissions... write my data... and quickly let the user know that I pwned them. Alright, I hope this illustrates some of the pitfalls with using sudo(8). Many other commands besides editors allow the user to find ways to run other commands, so it's often best to assume that unless you're _really_ careful, giving another user sudo(8) access to some commands may imply giving them full access. So what other ways do we have to restrict a user? Besides permissions, there also --- are file flags. That is, we have additional ways beyond simple permissions to restrict access to a file. The chflags(2) syscall shown here comes in the three versions we're used to -- chflags operating on a file, lchflags operating on a symlink, and fchflags operations on a file descriptor. If your file system supports them, then these "file flags" or "file attributes", provide a way to, for example, indicate that a file may not be changed at all, _even by the owner and not even root_. Such a file is called immutable; if this flag is set, then only root can unset the flag. The flags are: - user append only - user immutable - system append only - system immutable --- Here's what this looks like in practice: Consider this new file. Of course I can overwrite the file. But now let's set the 'uappnd' flag, making the file append-only. Now, if we try to open the file in regular write mode, this will fail. But appending data will work. That is, we can force O_APPEND mode on the file level for all access to the file. To see these file flags, we pass the '-o' flag to ls(1). Let's see what options are available. Some of these options apply to system backup operations; the other ones are those we've just seen a minute ago. Let's create a second file... ...and mark it as system immutable, meaning the owner can't modify the file and even root can't! Note that a file being immutable extends to it being removed, even though we normally think of unlinking as affecting the directory only. This is because unlinking still modifies the meta data of the file -- by decrementing the link count! And as we can see here, even root can't remove the file! But of course root can undo the change and unset the file flag. File flags can also be set on directories. Now we can manipulate the file... but we can't remove it, because now we'd be modifying the directory it's in. Likewise for renaming the file. Ok, so with a file marked as system immutable, even root can't change it, but this is a change that's applied only on a per-file level. Let's take a look at ways to apply restrictions on a file system level instead: --- Here, we are operating on a second file system, mounted under /mnt. We have a trivial program that simply illustrates how the setuid bit works by printing the effective and real user ID. So let's create a setuid executable, owned by 'nobody'. When we run the program, we see that of course setuid behaved exactly as we had intended. Now suppose we want to make it impossible for any setuid executables to be run from this file system. We can apply the 'nosuid' mount option. Now running the program shows that setuid does not have any effect -- real and effective UID remain the same, that of our user running the program. [pause] But we can take it one step further: Suppose you have a partition where your web server has access to. Now if there's a vulnerability in your web server, the attacker might write an executable to the disk to invoke, so it'd be great if we could make it impossible for any programs to be executed. [continue] So let's change our mount options to be 'noexec'. Now when we try to run the program, we get a permission denied. Even root can't execute a program on this file system, since it's mounted 'noexec'. What other mount options exist? There are a number of options, some of which can improve performance of the file system, while others can help you protect the system. Here, let's give the read-only option a try. Now our file system is mounted no-exec and read-only [pause] which can be quite helpful: Consider that during the regular system operations it is unlikely that anything under, say, /usr needs to change: we could prevent any possibly compromised process -- even one running with eUID 0! -- from installing a backdoor into /usr/bin by marking the entire filesystem hierarchy as read-only. [continue] So now we can't remove any files. Not even root can. Nor can we create new files. But of course, like before, root can undo those changes. As we know, with eUID 0, a process can do anything on the system -- this is by design as well as necessity. However, we also have a need to run certain services with such elevated privileges, and yet we want to be able to limit the damage a rogue eUID 0 process can wreak. One solution to this problem are --- so-called 'securelevels', which have been around in the BSD family of Unix systems for a while, and for which e.g., Linux has later gotten support. - The superuser can raise a securelevel, but, and this is critical, even the superuser cannot lower it. - In effect, you are configuring the system such that certain things are not possible without lowering the 'securelevel' of the OS. Lowering the 'securelevel' of the OS requires a system reboot, however, so is (a) noisy and more likely to be detected, and (b) terminates any current connections you may have (thereby requiring persistent access to the system). There are four securelevels defined: - permanently insecure mode - so-called insecure mode - a "secure mode", and - a "highly secure mode" The restrictions enforced by the different securelevels are listed in the manual page secmodel_securelevel(9). - Some of the more interesting restrictions are those relating to 'append' and 'immutable' file flags as well as the ability to mount, unmount, or remount filesystems. --- Here, let's see what this looks like: As we said, we can mount a filesystem read-only, and even root can't make changes. But of course root can remount the filesystem in read-write mode, and then modify the files. It'd be nice if we could keep even root from making such changes. Here we see the different securelevels described. For example, in "secure" mode, file flags may not be removed, and kernel modules may not be loaded, and in highly secure mode you can't even mount new disks. So let's set the system immutable flag on 'newfile'... ...and mount the filesystem read-only. Now let's check what our current securelevel is via the sysctl(8) command. Ok, we're in permanently insecure mode, which is why we could make all the changes. But that also means that we can _raise_ the secure level now. If we go straight to 'highly secure' mode... ...then we can't change the flags on the file any longer. And neither can we mount the filesystem in read-write mode. We can _unmount_ the filesystem, but we can't mount it back again. In securelevel '2', there really is absolutely nothing we can do now - not even as root. So let's lower the securelevel back to '-1'. Oh, right, we can't do that, either... Once a securelevel has been set, you can only raise it, never lower it. Not even as root. The only thing you can do to undo those changes is to reboot the system... --- Ok, let's summarize what we've learned in this video. - su(1) and sudo(8) can be used to grant others the ability to run commands as another user, but it can be difficult to restrict access. - We can use "file flags" to restrict some access -- we can mark a file as being append-only or as immutable. On BSD derived systems, we use chflags(1) for this; on Linux systems, there is a chattr(1) command that acts similarly. - We can restrict entire file systems: we can mark them as not allowing execution, as not allowing setuid, or as being entirely read-only, - and we can even prevent root from undoing some of these changes through the use of securelevels. Lowering securelevels requires a reboot of the system, which is rather intrusive and not often something an attacker can sustain easily. Alright, this gets us to the end of this video. In our next video, we'll talk about restricted shells, chroot environments and jails, before we go back to looking at restrictions applied on the per-process level. Until next time - thanks for watching! Cheers!