upgrade-nonroot privilege escalation issue?

https://xcancel.com/ProudmuslimDev/status/1878163686365544823#m

upgrade-nonroot
gives me the option to open a root shell with no auth required

Privilege escalation on Kicksecure, a step-by-step guide:

  1. Start a system update as the default user
  2. If a config file modified by the sysadmin has been updated by the pkg maintainer, select “open root shell to inspect the problem”*
  3. Congrats
2 Likes

That’s probably true.

The conditions to run into this issue are probably rare. The user must lock down user/root isolation as well as modify a package managed configuration file in /etc, later get compromised.

That tweet sounds dramatic. It assumes a concept (such as user-sysmaint-split) for meaningful user/root isolation is already a standard feature, which isn’t the case.

By the time an attacker can run this attack, the has user has usually already bigger issues. The following XKCD applies.

xkcd: Authorization

That tweet also seems to completely disregard any prior accomplishments an has the mindset “found 1 issue → trash the whole thing”, which isn’t productive.

Since user-sysmaint-split is due to become the default for new images with the next release, this report comes at a good time. That might be the end of upgrade-nonroot for account user for the foreseeable future. Other way solutions in the future, related:

How this can be fixed in a upcomming stable upgrade:

  • A) upgrade-nonroot using apt-get-noninteractive (might be unexpected); or
  • B) disable upgrade-nonroot.
2 Likes

I don’t want to assume bad faith here, but I think Proudmuslim either doesn’t know what they’re talking about, is intentionally making this sound worse than it is, or just hasn’t thought this through fully. Rather than just saying “this really isn’t the problem it looks like”, allow me to take the opportunity to use this as a small course on how to determine what is and isn’t a problem security-wise.

First off, before we even look at the problem, we have to define our threat model. What are we trying to protect? What does a compromise look like? What is necessary to cause a compromise? What needs to be done to prevent that? These are questions only each individual user can answer for themselves, since everyone is protecting something different and for different reasons. But for the most part, you can break up data that needs protected into three general zones, with some overlap:

  • Data that you need to remain private. Things like tax records, personally identifying information, passwords, secret encryption keys, chats with your friends or workplace, documents containing business trade secrets, things that could get you put in jail if you’re arrested in the wrong country like religious texts, etc.
  • Data that you need to remain unmodified. Reports for your business, bulk data that needs to be analyzed, application source code, stuff like this. Most of this stuff may be semi-public (or even entirely public), but if someone is able to modify it other than you, things could go very wrong (for instance if someone injects malicious code into a software project you’re working on).
  • Data that you need to remain accessible. Grocery lists, instructions, notes on how to perform tasks, computer programs, etc. It’s probably not the end of the world if someone managed to get ahold of any of this data, but you’d quickly find yourself in a bind if you couldn’t get access to it.

Each of these is a security problem, and these three aspects are generally referred to with the acronym CIA - Confidentiality, Integrity, and Availability. These are the things you need to look at to decide how you need to protect your data.

For me personally, I use a single-user computer. No one else has a legitimate reason to access to my device without my direct supervision, and everything done on that device is done by me or with me watching. The root account is locked since there’s no reason for anyone to log into it except for me, and I can just use sudo if I need root access. The data I need to protect is roughly:

  • Authentication credentials of various kinds (passwords, TOTP codes for 2FA, a few security keys for Matrix, my browser’s cookies, and my GPG and SSH keys). These are mostly a confidentiality concern - integrity and availability are both important but definitely secondary. It would be a mess if I lost my email password. It would be a much, much bigger mess someone got into my email.
  • Source code for a slew of projects, most of which is open-source but some of which are closed-source (internal work stuff). Confidentiality is a concern for the closed-source repos, but for the most part integrity is of the utmost concern here. I absolutely cannot afford for someone to implant malicious code into any of the repos I work on and have it look like I put it there. Availability is a low-priority issue - if I lose the repos, I can just clone them again.
  • Financial data. Confidentiality and availability are the highest priorities here, integrity is directly tied to availability here so really it’s a high priority also. Financial data is interesting because the compromise of any of the C, I, A attributes is a disaster. This is part of why there are such strong protections around it.
  • Some personal info that I care deeply about, like pictures and documents. Here integrity and availability trump everything else. Confidentiality is nice, but if it was broken I probably wouldn’t panic too much.
  • Emails. Confidentiality is the highest priority here, integrity and availability are useful but not as important. I’ll live if some emails are corrupted or if I can’t access my inbox for a few days. I’ll have some serious issues if someone snoops on all my private communications.
  • My operating system and applications. I don’t care about confidentiality here but, integrity is a critical priority because if it’s compromised, everything else can be compromised, and availability is a critical priority because if it’s compromised, I can’t work.

Now if you look at all of the above, one thing becomes very clear - if someone gets access to my user account, I’m toast long before they get root access. If someone is even able to get to the point where they can try to get a root shell, I have much bigger problems than a privilege escalation attempt. Another thing is less obvious but still important - if you want to get root access on my system, you have to get user access first. That’s because it’s a single-user system. Putting the two together, the ability to compromise my system’s root account is a total non-issue. No matter what you do to get root on my system, you’ve fully compromised me already.

Now this might just be my use case but how many people do you know who use a desktop computer? What do they do with it? Would they be compromised just by getting access to their user account, like I would? 99% of the time the answer is going to be “absolutely yes”. So for a usual desktop use case, there’s really little to no reason to protect the root account. You want to focus your efforts on keeping unauthorized people out of your user account.

So where is root access a concern? There are probably a lot of good answers here, but the three scenarios are:

  • You’re protecting a multi-user workstation. If someone can compromise one user account, they only get the data from that one user. If you want the data from all the users, you either have to compromise every single user individually, or you have to compromise root at which point everyone else is automatically compromised.
  • You’re protecting a server. If you compromise a service like a web server, you probably won’t get access to much useful data. Getting root gives you access to much, much more data, which is useful to an attacker.
  • You’re using a hypervisor, and are trying to defend against VM escapes. This may come as a surprise, but a lot of VM escape vulnerabilities are only really a problem if malware can get root within the VM first. Protecting the root account of individual VMs is therefore a form of defense-in-depth against VM escape compromises.

Are all of these scenarios important? Absolutely. But for the typical desktop user, they really don’t have to worry about this (with the exception of Qubes OS users, where the last point may be important, but even then it’s not that important).

So far I’ve defined what we’re protecting. The next step would usually be to determine how one would compromise it. I don’t want to go into extreme detail here, but short of a physical attack (someone sitting at your laptop while it’s unlocked), the most severe risk for a typical desktop use case is if someone can execute arbitrary code as my user account. If they can do that, they can read almost anything they want if my account can access it (near-total loss of confidentiality), modify almost whatever my account can access (near-total loss of integrity), and delete anything my account has write access to (total loss of availability). (The qualifier “almost” when it comes to confidentiality and integrity is because I have password protection on my GPG key, and I don’t store that password anywhere, so they wouldn’t be able to sign things with my key or read things encrypted to me with GPG.)

How do people get arbitrary code execution on your machine? There are two very common techniques:

  • Trick the user into running code the attacker provides. This is by far the most common strategy, and is sadly extremely effective. Social engineering can break even the best software-level security strategy, Users can and will click “install” on a malicious application or paste malware into their terminal. (Many OSes try to prevent this, but they either fail miserably, or the protections end up causing serious problems for the user.)
  • Provide data to the user that, when read by a vulnerable application, tricks the application into running code embedded in the data. This sounds hard (and it is), but it’s a very common way to attack businesses. The exact way in which this works is an entire field of research of its own, so suffice it to say “it can be done in a lot of situations”.

Kicksecure and Whonix attempt to make both of the above attacks very difficult:

  • To prevent social engineering, Kicksecure and Whonix provide extensive wikis to teach the user how to keep themselves secure. This mitigates social engineering attacks.
  • To prevent application compromise, Kicksecure uses extensive kernel parameters and other security-related settings to make it so that if an application is exploited, it will present a number of obstacles to the attacker that they probably weren’t expecting or can’t get around. In practice, this means that a hacked application will (hopefully) be more likely to crash than to compromise your system. Some applications use AppArmor sandboxing for additional safety, and we also do security reviews on quite a bit of code that we think might be security-sensitive.

Do we worry about protecting the root account? Yes, but in many ways it’s a secondary pursuit to the above. It’s still something we’re pursuing (we actually have a whole project dedicated to this called user-sysmaint-split that we’ve been working on like crazy lately), but it’s not the end of the world that Proudmuslim is making it sound like.

Lastly, I’d like to illustrate what it would take for the attack Proudmuslim references to actually be exploited. The attack is, in short, “run upgrade-nonroot, then instruct apt to give you a root shell if it runs into a conffile conflict.” This is not anywhere near as easy as they make it sound. For one, conffile conflicts in Debian and Kicksecure aren’t at all common unless you’re a sysadmin that messes with package conffiles. Even if you do mess with package conffiles, you’re not all that likely to run into conffile prompts since the default configuration for packages isn’t changed very often. For two, anything you could do to intentionally trigger a conffile prompt requires root access already. Either you’d have to be able to install attacker-controlled packages onto the victim’s computer (at which point, just put your malicious code in a package postinst script, boom, compromise complete), or you’d have to be able to see a particular package upgrade is coming through that would cause a conffile conflict prompt if a particular file was modified, then modify that file, and then upgrade the system. Modifying conffiles virtually always requires root access (if it doesn’t, your system is probably broken or a package on the system is very poorly designed), so if you can modify conffiles, you don’t need to bother with getting an apt conffile prompt. You have root access already.

So ultimately, this is really not hardly a problem. Is it a problem at all? I’d say yeah, sorta, and most likely we’ll fix it once we push out user-sysmaint-split. But this isn’t even remotely close to the end of the world.

If you’re worried about this issue, and want to fix it now, you can run sudo mv /etc/sudoers.d/upgrade-passwordless /etc/sudoers-d-upgrade-passwordless.old or something similar. This will prevent upgrade-nonroot (and the apt-get-update-plus command it uses in the background) from being run as root without a password.

5 Likes

I would like a better explanation and understanding of user-sysmaint-split.
Also nice work/contributions by the way.

1 Like
1 Like

the same user posted

https://xcancel.com/ProudmuslimDev/status/1901711364240068816#m

What does he mean with that? @Patrick

Like last time, I don’t want to assume bad faith, but at this point I’m really suspecting blatant trolling at this point.

First, a breakdown of what the screenshot is. They’re showing the output of the needrestart tool, which is a Perl script whose job is to find any running programs on the system that have updates on disk that require the program to be restarted for those updates to fully apply. For instance, if you have GIMP running, and the GIMP binary gets updated, you need to restart GIMP for those updates to apply. needrestart is intended to see that GIMP has been updated on disk, and then tell the user “hey, you should restart GIMP”. In actual use, needrestart is usually used on server systems so that the sysadmin knows what services they need to prepare to restart after running system updates.

The screenshot shows that there is an executable running on Proudmuslim’s system called frontend. What frontend is or what it does is not elaborated on any further, all we know is that it’s running on the system and needrestart believes it needs to be restarted for updates to apply to it. We’re then shown the sha256sum of the frontend binary itself (apparently located in Proudmuslim’s home directory). We then also see the sha256sum of the version of the frontend binary that is actively running (retrieved via some fancy tricks using the /proc virtual filesystem). The sha256sums of the two files are identical. This is supposed to imply that needrestart is broken, because it shows a program as requiring a restart when it hasn’t been modified.

So that’s what the screenshot is supposed to show. On the surface this sounds reasonable. If a program hasn’t been updated, why does it need restarted? In a simple world where each program was entirely self-contained, this would be a valid criticism and a bug that needed fixed. Linux isn’t simple though.

Most programs are not entirely self-contained, but instead depend on libraries, which are basically bundles of code that other programs can use for various reasons. For instance, say you want to handle data in your program that is formatted as XML. You don’t want to write an XML parser, because that’s a monumental task, and at the same time there are probably 20 other programs on your computer that also need to parse XML for one reason or another. What you do then is you find a library that provides XML parsing features, and you just use that in your program. When you do this, your program links against the library, which is a fancy programmer term that in essence means the library is now part of your program similar to how a device plugged into your computer is essentially part of the computer now.

When a program gets updated, the way to get those updates to apply to the running version of the program is pretty simple - just restart the program. So what happens when a library gets updated? Restart the library? That isn’t a thing, any more than restarting an SD card is a thing. You can’t really replace the in-memory library code with the new code either. (OK, you could, but… hot reload is an entire mess all of its own, let’s not go there.) The easiest solution is to restart every program that depends on the library. Any program you restart will load and use the current version of the library on disk, rather than the older, outdated version, and thus the library update is applied for that executable. Restart all of the executables that need the library, and now the library update is fully applied to all running programs.

What this means is that a program can end up not changing at all during a system update, and still need to be restarted for system updates to apply to it. This happens all the time, for all sorts of reasons. It’s just how binaries on Linux work.

On top of that, not all programs are binaries. Some of them are scripts, where you have a text description of a program that some other executable reads. That other executable (called an interpreter) then does the things the script says it should do. In this instance, if the interpreter or any of its supporting libraries are updated, the script will need restarted for the changes to apply to it in memory. needrestart is designed to detect when an interpreter is updated, and recomment that running scripts be restarted for the update to fully apply. (Oh, and to throw one more layer of complexity into the picture, scripts oftentimes depend on other scripts, and on top of that, sometimes a script can tell the interpreter to load actual libraries (not scripts) that the interpreter usually wouldn’t load. So… yeah. This is a deep rabbit hole.)

Now, is this exactly what Proudmuslim is running into? We don’t know, they didn’t give nearly enough info to tell. We’d need to know:

  • Where was the running frontend executable originally on the disk? Is the version in the home directory actually the running version, or is this an old copy and the real executable is somewhere else on the filesystem?
  • Is frontend a script or a real executable?
  • If it’s an executable, what libraries does it depend on?
  • If it’s a script, what interpreter does it need and what libraries does that interpreter depend on?
  • What exactly has been updated on their system? Did a dependency somewhere get updated that triggered the need for frontend to be restarted?

There might be even more we’d need to know, but basically, all they’ve shown here is that the contents of the running frontend executable match the contents of the frontend executable on their home directory. This says nothing about whether needrestart is wrong that frontend needs restarted or not.

Also, even if needrestart did get it wrong, needrestart isn’t a Kicksecure creation at all! It’s actually a project all of its own (GitHub - liske/needrestart: Restart daemons after library updates.), packaged for Debian (Debian -- Details of package needrestart in bookworm), and it’s not even installed on Kicksecure by default. So if Proudmuslim is running into a real bug in needrestart, they should make sure they can reproduce it in vanilla Debian, then file a bug with Debian or something. Blaming this on Kicksecure makes no sense.

2 Likes

It is wonderfully implemented in systemcheck - I added it to autostart. You can run it automatically in background mode in new version of Kicksecure - it will indicate new updates and monitor security (warn about unsafe installed packages and services)

1 Like

autostart systemcheck

4 posts were split to a new topic: Why passwordless sudo by default?