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!

Oh Snap! More Lemmings (Local Privilege Escalation in snap-confine)- 796

QualysPosted 4 Years Ago
  • snap-confine is a SUID-root program installed by default on Ubuntu. This is a package manager, similar to apt, for Linux distributions. While reviewing the source code of the package manager, they were about to quit. However, they found a typo within the main function of the program. When checking for permission checks, they noticed that the real_gid of the user was being compared with the getuid function and vice versa. Because of other checks above, this was not exploitable though.
  • Again, while doing source code review, they noticed a code path where an uninitialized variable could be used. If the XDG_RUNTIME_DIR ENV variable was not set, then the contents of this buffer were passed to a helper program. However, this turned out to be unexploitable since they do not control the value and several ENV variables are cleared as defense in depth.
  • snap-confine dynamically obtains the path to snap-update-ns and snap-discard-ns by reading its own path via /proc/self/exe. If snap-confine can be hardlinked into a directory that we own, then the helper script can be set to something that we control. Since this runs as root, this is quite impactful!
  • The above is impossible to exploit in a default configuration because of the configuration fs.protected_hardlinks being set to 1. If this is set to 0, this is exploitable. After going through several issues with AppArmour profile, they eventually were able to figure out a work around to get an unconfined root shell.
  • When setting up the snap sandbox (mount namespace), it is done by creating a temporary directory at /tmp/snap.$SNAP_NAME/tmp or reuses the directory if it already exists. Once there, it bind mounts it onto /tmp inside the snap mount namespace. To prevent race conditions in this, calls are made with the O_NOFOLLOW and O_DIRECTORY flags.
  • But, they were NOT careful enough. The mount syscall will follow symlinks. To exploit this, it requires a very tight timing though. After the call to open but before the call to change the ownership (fchown), the symlink needs to be created. Then, the mount will follow the link.
  • To relibly win this race condition, the file /tmp/snap.lxd can be monitored with inotify, pinning both processes to a single CPU and lowering snaps scheduling priority. That is quite the setup to relibly win the race! I had never heard of this setup; so, this may be a good trick to use in the future.
  • What does this primitive do? Inside the snap mount namespace, an attacker can bind-mount a world-writable, non-sticky directory onto /tmp, or an attacker can bind-mount any other part of the filesystem onto /tmp. The rest of the article goes over two case studies (Ubuntu Desktop and Ubuntu Server) to get a root shell.
  • The exploitation method was done on two different Ubuntu types: Desktop and Server. The main idea is to trick the directory being used in the request to be owned by the user instead of the program. Then, since we own this directory, an attacker can add their own scripts, which will run as root by the program. This required bypassing the App Armour configurations with complain profiles (instead of stop) and abusing some defaults snap libraries.
  • While going through the attack methods above, they explored MANY different avenues that did not work out. In the process, they found bugs in libmount in the unmounting code. First, if the text  (deleted) is found in the end of a text, the text is simply removed. For instance, an attacker could mount /tmp  (deleted). When we attempt to delete this, it will actually delete /tmp. Neat!
  • The similar unmounting effect can be done in certain situations because of a string truncation issue while verifying the user id. When checking the ownership, the code strncmp(user_id, uidstr, sz) == 0 is used. The sz of the string compare is calculated from the current users ID and NOT the larger between the two. As a result, 1000 and 100 would look the same, when truncated.
  • To wrap this up, they found two more bugs within gblic. The first one is an uninitialized memory read via a strange and unlikely flow within realpath(). The second bug was an off by one buffer overflow/underflow in getcwd() IF the size of the buffer is one and the PATH of the resolving file is larger than PATH_MAX.
  • Overall, I love the in depth nature of the exploitation process. From finding the bug, the methodology behind the exploit, the failures and everything in between. These Qualys reports feel like what Security Research is all about. This is worth spending lots of time on in order to understand fully; many things had to be skipped above so that this resource was not the same length as the article!