Mitigating malware risks with SELinux

Mihail Milev
14 min readFeb 2, 2021

--

I am writing this blog post, because I quite often hear from friends and colleagues two things:
- I’ve disabled SELinux
- I don’t know what I need SELinux on my desktop for

And since usually my follow-up questions end in lengthy and probably annoying for my friends discussions, I decided to write this small blog post to show a real-life scenario, where SELinux could help, and to also demonstrate how (almost) easy it is to configure SELinux.

I haven’t found many simple SELinux tutorials. There are a lot of great documents about SELinux on the Internet and I encourage everyone to go and search online for them. I don’t want to write another such document. I want to keep it simple and on a level as high as possible, so that even non-geeks could follow.

Real-life scenario

During 2020 the cyber security world saw a huge rise in malware attacks. For the ones, who don’t know what this is, it is basically a malicious software, which when run on your computer opens a back door for the author of the malware to do additional harm — scrape for data, perform local network searches for more important data, or even encrypt your data and ask you to pay some amount of money (usually in Bitcoins) in order to get the decryption key.

Linux, although not so hardly hit, is also not left alone and is a potential target for such malicious software, as it can be read in this Forbes article and in this ZDNet article, and at many other pages. Security researchers anticipate, that for example ransomware will rise even further in 2021 and 2022, as written on Computer Weekly and Cybercrime Magazine.

Although people may think, that getting hit by malware is hard, malicious parties have managed to install malware using some sophisticated 0-day vulnerabilities in browsers. Usually in such cases it is enough to just visit a malicious link and sometimes the user doesn’t even know, that the link was clicked, because links could get clicked automatically via JavaScript for example.

Now, although the link I’ve supplied above is for Windows, there is no guarantee, that our favorite Linux web browsers, don’t have some kind of vulnerability, which could be exploited. A malicious party could use such 0-day exploits to install malware on your machine and try to gather as much data as possible. In a corporate environment, your machine could be the starting point for lateral movement, which could give the malicious party even more data.

If the system is setup correctly, malware won’t be able to cause a lot of damage, since normal users have extended privileges only for their home folders. And the root user is usually blocked, so people don’t actually browse the Internet as root. This is a good start, but not enough as your account may still hold some important data — authentication tokens, passwords, secret documents, etc. All this is data, which could cause a lot of damage, when falling in the wrong hands.

But how could SELinux help?

Normally, Linux uses file privileges to limit access. If I type “ls -la” on my Fedora 33 machine (updated to the latest packages as of time of writing), I will get a screen similar to this:

The first column shows the rights for each entry — (r)ead, (w)rite and e(x)ecute for each of the three groupings — owner, group and others. The exact owner and group of the entry are listed in the third and fourth columns.

This rudimentary system helps to control the access between different users on the same machine. If you want to read, write or execute a file system entry, you, or the group you belong to, need to have the corresponding rights for this file system entry.

When you run an executable, the process runs with your username and group. There are some cases, where this is not valid (search for SUID and SGID for example), but we’ll skip these. Since processes, run by you, use your identifications, they also have access to everything you have access to. And since a malware will run with your system ID, it will have access to all your personal files.

To limit this, RedHat-based systems come pre-installed, pre-configured and automatically started with SELinux. SELinux is a actually a kernel extension, which offers RBAC (role-based access control) to what has access to what. SELinux defines a context for each executable file and its processes, and a context for each object in the system — users, files, folders, sockets (which btw. are also files), etc. Then SELinux defines policies, which define which source context is allowed or denied to access which target context. SELinux has nothing to do with the file system privileges, which means, that even if you have for example read rights on a file, a process you execute may still be denied read access to a file.

Let’s take your private SSH key file. This file in my Fedora 33 is located under “~/.ssh/id_rsa”. If a malware can read it, then the malicious party could connect to each server, where your public key is authorized — perfect for lateral movement. But if you set a specific context to this file and allow only the ssh (and for example ssh-keygen) executable, through its own context to use this file, then a malware, which by default is being executed in your user context, will not be allowed to read them. This approach can be used also for other important files, thus limiting the amount of harm a malware could do.

Getting dirty

First of all, always check what the “starting position” is. Let’s see what the SELinux context of a newly created user is. Now, again I remind you, this is on a freshly installed, fully updated Fedora 33. Type the “id” command:

The context is shown as the most right part of the output. A context in SELinux is always defined by four parts delimited by a colon. The four parts are:

{user}:{role}:{type}:{level}

The level part could also be separated in two by a colon: {level}:{category}.

I won’t delve into details for each part as there are great documents explaining them. For us, the most important part is the “type”. In our case, the newly created user called “secure_one” is of type “unconfined_t” (note that although “_t” at the end is not obligatory, people tend to add it, so that it is clear, that it is a type). “Unconfined” is exactly what it states — no limits (almost). According to SELinux this user may touch all other SELinux types. There are some small limitations to this, but we won’t delve into them. For more information about these limitations, visit the RedHat “Confined and Unconfined Users” documentation. I will just cite a small paragraph out of it:

If an unconfined Linux user executes an application that SELinux policy defines as one that can transition from the unconfined_t domain to its own confined domain, the unconfined Linux user is still subject to the restrictions of that confined domain. The security benefit of this is that, even though a Linux user is running unconfined, the application remains confined. Therefore, the exploitation of a flaw in the application can be limited by the policy.

Let’s see what SELinux context files in our home folder have:

As you can see, the only difference from the previous “ls” command is to add a big “Z” to the options. Actually a lot of commands in RedHat Linux allow you to pass a big “Z” as an option, which lists the SELinux context for the object of interest. Try it yourself for example with “ps”.

On the screenshot the context for each file can be seen. All objects in the home folder have a type of “user_home_t”, the “object” role and the “unconfined” user. As it can be expected, since the “secure_one” user is an unconfined user, it is allowed to access all these files above.

Before we continue, we need to install some packages:

These packages will provide the following executables, which we’ll need later:
- sesearch
- seinfo
- checkmodule
- semodule_package
- semodule
- semanage

Once the packages are installed, we can start getting dirty with SELinux. Remember every process that a user starts is executed in the same context as the user itself, except if there is a special rule for that executable. So, if our user is an unconfined one, then every process we start is also running in an unconfined context, thus can read everything. In order to limit this, we need to modify the user’s context. Let’s see where our context comes from. Execute the following command as root:

By default there are two login types — root and default. The latter is for all users which don’t have an explicit entry in this table.

To check what SELinux users are available, we do this again as root:

Just as a side note, here you can see that “unconfined” could be not completely “unconfined”, because for example “root” has more roles than “unconfined”.

From the list, it is clear that SELinux users such as “xguest”, “guest” or “user” have less rights than the “unconfined” user. From experience I know, that “guest” is really limited (can’t even establish connections on the SSH port). Therefore I usually suggest to get “user”. To modify the context of an existing user, without touching all the other users, do the following as root:

Now, when the user logs out and in again:

It is now in the “user_u:user_r:user_t:s0” context. Every new file the user creates will be in the context “user_u:object_r:user_home_t:s0”. Since old files are in the “unconfined_u” context, they’ll also be still writable by the user.

In order to see if a user has access to a specific file (or actually a more right expression: if a source type has access to a target type), we can use the following command as root (you may omit the “fold” command, I did it for the screenshot):

Each of these lines is called “Access Vector”. The syntax can be described (very simplified) like follows:

allow [source_type] [destination_type]:[object_type] { actions_list }

On several lines above you’ll see, that the source “user_t” has action allowance for “open”, “read”, “write”, etc. to destination type “user_home_type:file”. That’s why the user is still allowed to work on the old files.

Let’s generate this user’s private SSH key:

Let’s list it and also try to read it:

If we leave it like this, every process started from us (knowingly or not) can read our private key. But if we change the type of this file, then this won’t be the case. Now, there are a lot of types per default in SELinux. To list part of them, type as root:

On this system there are 4956 types. Surely we can reuse one of them, but we could also create our own. To add a new type, this type has to be compiled into the kernel. And as it is usual, to compile something, you need source code. Start by creating a new folder and then creating a new file inside it called for example “ultra_secure_file_type.te” (“te” is the file extension for “type enforcement” files). This file could have the following contents:

The first line defines the module name and its version. The second line defines the exact “naming” of the file type. The require block includes attributes, which we’ll use to set the new type with and the last line assigns the included attributes to the new file type. This file has to be compiled. Execute the following commands as root:

In the same folder, there should be a file called “ultra_secure_file_type.pp”. This is a SELinux compiled file, which we could install into SELinux by using the following command as root:

After installing the module, check with the second command if the new file type is registered. Once SELinux knows about this new type, we can assign it to a file. There are two ways to do this — a temporary (until next context restore) and a permanent one. Let’s start with the temporary one. Type the following commands as root:

Now, if the user tries to read the file:

This also means, that a malicious software run on behalf of the user won’t be able to read it. But also the SSH command won’t be able to get its data:

Let’s check in which context the SSH executable will run. Execute as root:

The type is defined as “ssh_exec_t”. But this is not going to be the process’ context type. That is because of a SELinux mechanism called transitioning. The mechanism is perfectly described on this page in this image. Simply explained, depending on which context executes which target context, the resulting process will transition in some other context/domain. Let’s see in which context ssh will transition based on our starting context “user_t”. Start as root:

The resulting SSH process will run in the “ssh_t” context type. This allows us to create a new rule, which could allow this context type to access our new “ultra_secure_file_t”. Let’s create another “.te” file (we could insert this definition to our previous SELinux module, but I prefer splitting them):

Once the above commands are executed as root, we should have an access vector for SSH to access the new file type, but the user would still not be able to read it:

Well, such server doesn’t exist in my network, but there was no “Permission denied” message. This means, SSH was successful on reading the private key.

Permanently change a file/folder’s context

If root re-writes the contexts, the private key will be again readable by the user:

To make the context permanent issue the following commands as root:

As it can be seen, the “semanage fcontext” command takes a regular expression as a destination for the rule. Although here we wanted to modify only the private key (public keys may and should be readable by all), I decided to show this approach, so that it is clear that rules may be applied to whole paths in a very flexible manner. To list all existing “fcontext” rules, do as root:

Modifying executables’ contexts

As shown above, we could modify the context of an executable. To do this, we need to define a new transition. A transition instructs SELinux to move one process from one domain into another upon execution. Here are the contents of a sample .te file:

Let’s modify the standard python executable. Issue these commands as root:

And now let’s run this as the normal user, which has “user_r” role:

Let’s check its context as root:

Let’s try to open the private key inside python:

The result is valid — if you look at the definition of the new domain, we’ve never allowed it to access files of context type “ultra_secure_file_t”. Let’s add some more lines to the module for the new domain and file types:

Don’t forget also to include the “dir” class and its “search” attribute. Include also the two types “ultra_secure_file_t” and “user_home_dir_t”.

Once the module is recompiled and installed, python can successfully read our private key:

You can use this method to limit access to valuable files, so that only specific programs can access them.

Mitigating ransomware

Ransomware is on the rise. There are two aspects, when we try to mitigate its impact on our system:
- stealing the data
- encryption of data

When the malicious party has access to your system, they usually first steal the data, e.g. copying it to their own machines. This way, if you have some confidential information, spicy photos, private keys, etc. they could extort you about it. Against this aspect, all written above should help. If you put all important data — your crown jewels as it is often called — in special folders, which are secured using SELinux as described above, the ransomware should not be able to get this data. Allow access to this data only to applications, which you know need it. For example confidential documents could be accessed only by LibreOffice executables, PDFs by Okular, etc.

Secondly malicious parties will encrypt your data, so there is no way for you to recover important information. Securing your files like described above with SELinux could help, but this depends on the algorithm of the ransomware. What definitely would help is a backup. But if you just copy your files to another disk, the ransomware could encrypt the data also there. In order to prevent this, secure the whole backup disk with SELinux as described above and let only the backup program have access to the disk.

Thank you!

Thanks to all friends and the great Red Hat Accelerators community, who read this article and pointed out mistakes and corrections!

Disclaimer

While I am not a representative of Red Hat and my views about Red Hat are my own, I am a member of Red Hat Accelerators community which gives me a connection to Red Hat and through which I engage with other Red Hat Accelerators.
Learn more about the Red Hat Accelerators

--

--

Mihail Milev
Mihail Milev

Written by Mihail Milev

Electronics and DevOps engineer, Linux geek, passionate about cyber security