Next Browser RCE

Table of Contents

1 Overview

Next Browser's XML-RPC was (from about 1.0 to 1.2.1) vulnerable to remote exploitation. If you use Next, please upgrade to 1.2.2 immediately.

Both the lisp core and the platform ports listen on all interfaces with no authentication. This means that anyone on your network or any site you visit can send commands over the XML-RPC. Some interesting commands in the platform port include buffer.evaluate.javascript and set.proxy. Some interesting commands in the lisp core include make.buffers and push.input.event.

2 Demos

2.1 Open A Window

The most basic exploit, this sends a single request to open a new window.

next-rce-1.gif

2.2 Basic Keypresses

A basic example to demo pressing keys. This one will open the first url selected by the search 'f'.

next-rce-2.gif

2.3 Full RCE

Uses COMMAND-EVALUATE to execute arbitrary code on the system. This also seems to cause next to hang, but the payload is already executing by the time it's hanging.

next-rce-3.gif

3 Takeaways

If you open a port, even if it's only accessible from your own machine, treat it as if it's open to the world. See this blog post for more information.

If you find these kind of things worrying, install a uMatrix-style plugin. Such a plugin in a strict mode will mitigate some of these attacks.

4 Methodology

This is the first proper security issue that I have worked on in the wild, so I decided to document my methodology. We were initially tipped off from a quote on this page:

XML-RPC can be used over HTTP sockets, and this is what we do. This comes with a nice side effect: we don't even need to run the separate parts on the same machine, they can be connected remotely over the Internet!

The security-sensitive part, i.e. the renderer, is contained into a relatively simple executable. It's possible to start this executable within a container, so that security issues with the renderer (or, who knows, with the GUI toolkit) won't ever reach beyond the boundaries of the RPC calls.

My personal effort was then focused towards the next core XML-RPC server, as that seemed the most likely to be vulnerable. After a bunch of tweaking, I eventually was able to fire the make.buffers rpc call over a local client, and then over a local js browser, and lastly a remote website, validating the approach.

Finding how to turn this access into RCE was a bit tricky. The commands offered by the core didn't seem very useful at first, mainly due to not fully understanding them. I then pivoted to trying to exploit the platform port, but I quickly found that:

  1. It was unstable, closing whenever any invalid arguments were passed to it.
  2. It offered much more in terms of api functionality, but it wouldn't be a good target if we're aiming for RCE due to sandboxing. It might be possible to get XSS or some man in the middle attack, but that wasn't my goal.
  3. Snooping on calls from core to the platform port is harder, as in order to do that, you need to kill the platform port (at which point, you wouldn't have any window to control).

I then decided to forcibly kill the core (which seemed to leave the platform port running as if nothing had happened). I then put an echo server on the same port and was able to immediately see all requests being made by the port. After finding out that the port sent keystrokes to the core, and that there was a default command called COMMAND-EVALUATE, the rest was trivial.

In retrospect, I would have liked to have handled disclosure a bit better. It's probably a good idea to give core developers a heads up as soon as you even think there's an issue, to maximize the time they have to fix it. I spent a lot of time sitting on this issue, leaving people vulnerable for longer than they should have been.

4.1 Further Work and Mitigation

  • Next still listens over the network on all interfaces. Ideally there would be nothing listening on a port at all, and if something must be exposed, it could be exposed only on localhost. However, it looks like the host is hard-coded in s-xml-rpc:

    (sb-bsd-sockets:socket-bind socket #(0 0 0 0) port)
    

    The next developers are planning on switching to other formats for communication, which will fix this and other issues in this list.

  • Authentication tokens are passed from the core to the platform port in a less-than-ideal way. This isn't much of an issue, but is an area for improvement.
  • Authentication tokens are passed during calls as the first argument, which is prone to errors or missed validation.
  • It's too easy to start slime, which exposes a port without authentication in much the same way as Next used to. It doesn't look like this will be fixed on slime's end.

5 Credits

Thanks to:

  • Florian Bruhin (The-Compiler) for initially suspecting the vulerability, and providing advice.
  • Vasilij Schneidermann (wasamasa) for initial work on discovering the vulnerability, making the initial report, and providing advice on a fix, and reviews.
  • Pierre Neidhardt (Ambrevar) and John Mercouris (jmercouris) for finalizing, reviewing, and committing fixes.

6 Navigation

Author: Jay Kamat

Published: 2019-05-21

Emacs 25.3.1 (Org mode 9.3)