Unix Permissions - Part 3: Devising a Security Model

| 6 min read

Introduction

In the last sections of this series, we explored various CLI applications that work with Unix permissions and learned about how the permission system really works. In this last part of the series, we will look at using those learnings to secure files and systems.

Our increased reliance on the internet and computers for sensitive data makes digital security increasingly important. We will look at how Unix permissions fall in a general threat scenario, and which threat models it can defend against, and which it is unideal to defend against.

Threat Models Unix Permissions Can Defends Against

Unix permissions can defend against attacks that target files on the file system, as that is what they are designed to protect. It can protect these files from:

  • A process or application that is malicious, buggy or compromised
  • A malicious or incompetent user who has shell access (physically or remotely via SSH)

If a file is protected with the right permissions, the malicious actor could be prevented from reading or stealing the contents of a file, modifying or overwriting the content, or deleting the file entirely.

Threat Models Where Unix Permissions Will Not Help

  • An attacker who has root shell access: root access can bypasses Unix permissions
  • A process that has root access
  • An attacker that has physical access to the hardware: With physical access, the attacker can separate the hardware from the operating system, bypassing the operating system's protections. They can connect the hardware to another computer to access the files, for example.
  • Data not on the filesystem: Traditional permissions can only protect files. Data in network transit or displayed on a graphical application displayed on a monitor are not covered.

As we can see, Unix permissions are necessary but not sufficient. There are other security layers that can be responsible for the thread models where Unix permissions are insufficient, but they are out of scope for this article.

Devising a Security Model Using Traditional Unix Permissions

Now that we have gained an understanding of Unix permissions and how to work with them, we can actually start using them! What we learned so far is sufficient for working with individual files or directories on a case-by-base basis, but what about if we take a step back and look at the big picture of securing our system?

We already have the necessary skills of interacting with the permission system, but the system is flexible enough that there are several ways of going about using traditional permissions to devise a security model for your system. This can range from giving everything permissive permissions to make matters as easy as possible without much regard to security, or we could go totally overboard while sacrificing convenience and ease of use.

I will discuss with you a security model that I recommend. It may be overkill for some, but it would still be valuable to learn, and you can feel free to adjust it yourself as you see fit, as it is easy to only partially apply this strategy.

Least Privilege Principle

The least Privilege Principle tells us that every user must only given the minimum permissions necessary for them to perform the tasks they need. This means that any malicious or compromised actor is limited in the damage they could possibly cause and the data they can access. This is what is sometimes referred to as the attack surface.

If a certain user is given more permissions that they need, there is little benefit to doing so. At the same time, it risks increasing the attack surface if this user is ever compromised. This means that reducing permissions to the minimum needed is beneficial.

Moreover, this means that having one or few users have all or most permissions is undesired. Instead, having more users, each having a smaller subset of permissions, can lead to increased security.

Imagine, for example, that a process got compromised, and the attacker can now manipulate this process. If we did not follow the principle of least privilege, the process could end up having the ability to cause a lot of damage or compromise a lot of data. The attacker can manipulate data, steal it, or control other processes. But if least privilege principle was followed, even with a compromised process, the attacker may end up with little to nothing of interest to do.

We model our system in a way such that an attacker ends up in this situation: even in the case that the attacker compromises a process somehow, the process is very limited and hence the damage they can do is limited as well. Even if we secure the process that we think it is unlikely to be compromised, we can never be sure, and there is little sacrifice in implementing least privilege.

So how exactly do we use traditional permissions to implement least privilege principle?

Assigning Users to Access Patterns

Let us start with our users. Recall that users are the elementary unit that has permissions assigned, and can request access based on those permissions. Therefore,this is the entity whose permissions we want to limit as much as possible. We associate users with certain access patterns, such as:

  • a certain program or process we expect to run, such as a web-server
  • a script that will get auto-triggered
  • a remote device or script accessing our system via SSH
  • an actual user / person

Each of those should have their own users that have only the least amount of permissions needed to perform what they need. Whenever a new user is created, you must carefully determine what accesses they need and only grant them that access.

Granting Permissions Via Groups

Now that we figured out how to distribute users, let's give them the permissions they need!

Recall that a user can gain permission to a file by being part of one of three scopes: the user owner of a file, part of the group owner of the file, or all other users.

If we want to grant a user access to a file, our natural inclination might be to just make that user the owner of the file, and define permissions as such. This model is insufficient in many cases, as there can only be one owner per file, and thus deprives us from the ability of granting access to more than one user.

It would also not be wise to rely on the "all other users" scope, as this deprives us of the granularity of choosing which users are granted access to a file. Yes, we do not want to grant access to only one user, but we also don't want to grant access to unvetted users.

This leaves us with the last scope: the group. Controlling access by modifying permissions of the file's group and adding users we want to grant permissions to said group gives us the best balance. We can control which users get access, and what access they get.

This is how we arrive to our approach: to give a user permissions to a file, they must belong to the file's group.

Modeling and Organizing Groups

If we grant a user access to a file by assigning it to the file's group, we would then organize groups by associating a group with a single category of access, or more precisely a group of files that have common access expectations.

Let's take an example. Suppose we have a directory that includes a set of private notes. To follow the model we devised, we would create a group for all of those notes. We will conveniently call it "notes":

# create the group
groupadd notes
# assign the notes directory to the notes group
chgrp notes notes/

So now, whenever we want to grant any user access to "notes", we would just add it to that group. To do this with the user "nezar":

usermod --append --groups notes nezar

and now the user nezar is granted access to notes!

Of course, we must remember to set the permissions for users and groups correctly, and to choose a good user owner for the directory:

chmod --recursive 660 notes/
chown root: --recursive notes

the above makes the user owner and the group able to read and write the files, and all other users unable to read, write or execute the files. Then, it sets root as the user owner.

And we can follow this model for all other files and directories. To summarize:

  • identify a semantic grouping of files that would have common permissions for a set of users
  • create a group with a name semantically describing that group of files, similar to what we did with our "notes"
  • assign the files to the group with chgrp or chown
  • define the permissions for the files with chmod
  • add users to the group using usermod --append --group ... to grant them access

Limitations and Alternatives of Unix Permissions

The traditional Unix permissions model is simple yet powerful. Its biggest benefit is that it is the default on most Unix-like systems, but sometimes it is too simple and limited.

For example, traditional permissions may allow us to define permissions for a group of users, but it does not allow us to set different permissions for different groups of users. We can only set different permissions for the 3 scopes granted to us: the user owner, the group, and all other users. One of those can only fit one single user, and the other grants access to a group of access without any granularity. This means we cannot define permissions for two or more specific groups of users.

Thankfully, there are alternatives. I will list some of them but not cover them in this post, but look out for future posts as I may write about them in the future!

  • POSIX Access Control Lists
  • AppArmor
  • SELinux