Resources

People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!

Privilege escalation with polkit on Linux- 527

Kevin Backhouse - Github Security LabsPosted 4 Years Ago
  • Polkit is a system service installed by default on most Linux distributions. It's used when determining if a user has access to something, requires a password or something else. Besides graphical sections, pkexec can be used as an alternate to sudo.
  • The dbus-send command can also be used to trigger polkit. This tool can manually be used to simulate D-Bus messages that the graphical interface would use to send.
  • The architecture uses a daemon to communicate with the privilege side of things. This daemon takes requests from the Authentication Agent and dbus-send, which properly forward them to accounts-daemon and polkit. The dbus-daemon enables all four processes to communicate securely, making it a key part to the puzzle.
  • When creating a new user, the flow is shown below:
    1. dbus-send sends a request to the account-daemon. This is funneled through the dbus-daemon first. This includes a special bus id from the sender.
    2. The accounts daemon asks polkit if the connection id is authorized for the create user action.
    3. polkit asks dbus-daemon for the UID of the connection. The daemon sends back a list of admins who can perform the action or a 0 if the user is root.
    4. The agent asks for a username/password to see if the action is allowed. This is sent from the authentication agent to polkit.
    5. If polkit approves this, the action is performed.
  • With the flow above, there is a bug when killing the debus-send process. In step 3 (from above), polkit asks the debus-daemon about a particular UID. But, if the process is deleted, then UID does not exist. Although this should return an error message, this gets handled in the worst way possible!
  • In step 3, if the UID is 0 (signalling root), then no other checks for authentication are performed. When the UID does not exist, 0 is also returned! This means that the killed process is the same as root!
  • Besides racing the kill the process before the lookup, there are also multiple checks on the UID. So, the proper code path needs to be hit, which takes a few tries when racing it.
  • The vulnerable code path sets an error flag and returns a TRUE/FALSE value. The bug is that TRUE is returned instead of FALSE, but sets the error flag. The developers fixed the code by returning FALSE instead of TRUE.
  • In order to a Linux distro to be vulnerable, the error flag needs to be ignored. This was the case in Ubuntu, Debian and Red Hat Linux.
  • The bug was not some crazy memory corruption bug. Instead, it was a logic bug that had existed within the code for 7 years. This is a fascinating example that security bugs will not never go away, even when memory corruption bugs are long gone.