EdgeMAX Websocket Denial of Service

ยท 839 words ยท 4 minute read

Both my EdgeRouter ER-X and EdgeRouter ER-6P exhibit a denial of service attack vector. It isn’t the sort of DoS that will bring down the entire router, but it will make the Web GUI non-responsive.

It all starts with the two WebSockets that are found listening on the Web GUI. These configs can be found in the /etc/lighttpd/lighttpd.conf file:

$HTTP["url"] =~ "^/ws/stats" {
	wstunnel.server = ( "" => ( ( "socket" => "/tmp/ubnt.socket.statsd" ) ) )
	wstunnel.frame-type = "text"
	server.max-read-idle = 600
	server.stream-request-body  = 2
	server.stream-response-body = 2
}

$HTTP["url"] =~ "^/ws/cli" {
	wstunnel.server = ( "" => ( ( "socket" => "/tmp/ubnt.socket.cli") ) )
	wstunnel.frame-type = "binary"
	server.max-read-idle = 600
	server.stream-request-body  = 2
	server.stream-response-body = 2
}

Let’s zoom in on the second one, the ws/cli entry. If I connect to this WebSocket, I will get a telnet login prompt for my router. I can then login with my credentials and configure the router if I wanted to.

Why does this WebSocket exist? ๐Ÿ”—

This WebSocket exists to allow an admin to pop open a command line window while logged in through the Web GUI. From there, he can continue to configure his router using the CLI. If SSH were not enabled and he were not directly in contact with the router, he can use this approach to configure the router using a shell.

The bug ๐Ÿ”—

There is a single binary on the router known as ubnt-util and it has many jobs. One of its jobs is to ensure that it can set up the telnet session through the WebSocket. When a connection is made to the ws/cli WebSocket, ubnt-util will open a telnet session to the telnet daemon that is running on 127.0.0.101:55523. Basically, you’re connecting from localhost to localhost. When this happens, telnetd will start the /bin/login process which prompts the user to login. After a successful login, you get your shell.

The problem occurs when simultaneous telnet clients are waiting for a login. Somewhere between the login session timing out with no user input and the connection closing, ubnt-util will become non-responsive. When this occurs, even though the Web GUI login page is visible and responsive, your login will not succeed and you lose access to the Web GUI. The only way to recover from this so far is to SSH into the router and kill ubnt-util. Then, the ubnt-daemon binary will restart a new ubnt-util and all is right again.

The sweet spot for making this happen is to send 10 simultaneous connections to the Web Socket and wait 60 seconds before closing each connection.

How can this be fixed? ๐Ÿ”—

I am waiting for an official response for remediation from Ubiquiti, but until then, I used lighttpd’s ACLs to block all remote access to this WebSocket.

$HTTP["url"] =~ "^/ws/cli" {
  $HTTP["remoteip"] != "192.168.1.0/24" {
    url.access-deny("")
  }
  wstunnel.server = ( "" => ( ( "socket" => "/tmp/ubnt.socket.cli") ) )
	wstunnel.frame-type = "binary"
	server.max-read-idle = 600
	server.stream-request-body  = 2
	server.stream-response-body = 2
}

In the directive above, if the remote IP is not part of the local network, then deny access to this WebSocket. You can make this as narrow or as broad as you want, but the bottom line is to disallow someone from across the globe to hose your Web GUI.

How to test if the WebSocket is open? ๐Ÿ”—

You can use the tool utelnet to check for whether your EdgeRouter has the WebSocket enabled. Utelnet will check a few things including your hostname and the approximate age of the Javascript libraries used. This can give a rough estimation of the version of EdgeOS in use. If a WebSocket is found, then you will see the hostname as well as websocket: true

Bonus bug ๐Ÿ”—

Because this WebSocket allows you to login using telnet, you can easily write a bruteforcer to guess passwords on the router. The kicker is that none of the remote IP’s are being logged on the device.

The /var/log/ubnt-daemon.log file will show this line for each disconnect and reconnect:

2024-06-26 09:56:22 cli: Error: Socket write error: write: Broken pipe

The /var/log/lighttpd/ubnt-rtr-ui.log file does not log any activity. The /var/log/lighttpd/error.log file does not log any activity.

The /var/log/auth.log file will log activity as follows:

Jun 26 10:22:18 EdgeRouter-X-5-Port login[11078]: pam_unix(login:auth): authentication failure; logname=LOGIN uid=0 euid=0 tty=/dev/pts/2 ruser= rhost=  user=admin
Jun 26 10:22:21 EdgeRouter-X-5-Port login[11078]: FAILED LOGIN (1) on '/dev/pts/2' from '127.0.0.1:35062' FOR 'admin', Authentication failure
Jun 26 10:22:25 EdgeRouter-X-5-Port login[11078]: FAILED LOGIN (2) on '/dev/pts/2' from '127.0.0.1:35062' FOR 'admin', Authentication failure
Jun 26 10:22:29 EdgeRouter-X-5-Port login[11078]: FAILED LOGIN (3) on '/dev/pts/2' from '127.0.0.1:35062' FOR 'admin', Authentication failure
Jun 26 10:22:33 EdgeRouter-X-5-Port login[11078]: FAILED LOGIN (4) on '/dev/pts/2' from '127.0.0.1:35062' FOR 'admin', Authentication failure

Bruteforcing is probably not sexy these days, but is still a valid attack vector and it would be all the more important to fix if you advertise your EdgeRouter publicly.

Additional notes ๐Ÿ”—

I have contacted Ubiquiti and it has been some time since they have responded to me on whether they think this is a significant issue or not.