Using WebRTC ICE Servers for Port Scanning in Chrome

To everything (TURN! TURN! TURN!)

Jacob Baines
Tenable TechBlog
6 min readDec 20, 2019

--

Using the browser to scan a LAN isn’t a new idea. There are many implementations that use XHR requests, websockets, or plain HTML to discover and fingerprint LAN devices. But in this blog, I’ll introduce a new scanning technique using WebRTC ICE servers. This technique is fast and, unlike the other methods, bypasses the blocked ports list. Unfortunately, it only works when the victim is using Chrome.

You can skip my explanation and go straight to the code or the demo page. Otherwise, let’s start with a proof of concept video. Here I am scanning my 192.168.88.0/24 network.

Network tab visible so you can see no requests are logged.

What’s an ICE Server?

As I said, the scanning technique uses WebRTC ICE servers. An ICE server is a STUN or TURN server considered by a WebRTC RTCPeerConnection for self discovery, NAT traversal, and/or relay. A list of servers can be passed into the RTCPeerConnection’s constructor. Here’s an example constructor being provided one of Google’s public STUN servers:

var rtc = new RTCPeerConnection({
iceServers:[{“urls”:”stun:stun.l.google.com:19302”}]
});

When the above RTCPeerConnection enters the ICE gathering state it will attempt to connect to the provided server.

Protocols Matter

ICE servers can be bound to either UDP or TCP ports. However, unless instructed otherwise, Chrome appears to only attempt communication over UDP. Below is a Wireshark screenshot of the packets Chrome sends to a non-existent TURN server. Everything is UDP.

You can force Chrome to reach out over TCP if you know something about the ICE server URLs. The URLs passed to the RTCPeerConnection’s constructor must conform to RFC 7064 (STUN) or RFC 7065 (TURN). The TURN URI scheme follows:

https://tools.ietf.org/html/rfc7065#section-3.1

Most important for scanning purposes is the optional “?transport=” field. Chrome can be forced to use ICE over TCP by using a TURN URI that ends with “?transport=tcp”.

We now have a way to initiate a TCP connection with any IP and port we choose. However, since almost all the hosts we’ll scan won’t be TURN servers, how can we determine if a host is alive or not?

Determining If a Host Is Alive

The following JSFiddle generates 256 TURN URI in order to find an active address in the range of 192.168.[0–255].1

An address is determined to be “active” when an icecandidateerror event is generated. That’s it. Chrome will generate the error event if the host rejects the connection in some form. Ideally, via RST or a quick rejection after Chrome sends the initial message. Although the server could just hold the connection open and the error event would take ~30 seconds to generate.

But that is why the JSFiddle uses port 445 to scan. The SMB implementations I’ve run into complete the TCP handshake and then close the connection after Chrome’s non-SMB traffic. 445 is also ideal since it’s more likely to pick up Windows boxes.

The event is not generated if Chrome sees no response. That could be due to a firewall ignoring unwelcome inbound requests. Or it could actually be that there is no host available.

The only edge case I ran into is when an ICMP response is sent in reply. That causes Chrome to generate an icecandidateerror that is indistinguishable from actually active hosts.

How Does Port Scanning Work?

This JSFiddle scans 192.168.88.1 on ports 21, 22, 23, 25, 53, 80, 443, 445, 5900, and 8080

On my local network this is the result:

It’s a research device. Don’t worry about it.

The script is able to categorize ports as “Open” or “Closed”, again, due to the way Chrome generates icecandidateerror events. Every icecandidateerror has a hostCandidate variable. Any ICE server that completed the TCP three way handshake will have the local IP and port listed in the hostCandidate (e.g. 192.168.88.x:51688). ICE servers that couldn’t be reached generate hostCandidates in the form of “0.0.0.x:0”. Therefore, it’s trivial to determine if a port is open or not.

Console log from scanning active hosts on my home 192.168.88.0/24

This Only Works in Chrome?

I have not been able to recreate this in any other browser. Other browsers don’t seem to have implemented onicecandidateerror. It also appears that the implementation is fairly new in Chrome since MDN shows “No support”:

MDN notes that this page was last updated on March 18, 2019.

Other browsers seem less thrilled about my usage of RTCPeerConnection as well. While Chrome is happy to accept 255 different ICE servers, Firefox gets all uppity if you offer more than two.

About the Proof of Concept Code

A semi-recent development in Chrome is that they’ve taken measures to fix the leak of local addresses via WebRTC. When the “Experimental” feature “Anonymize local IPs exposed by WebRTC” flag is enabled, Chrome will try to use an mDNS .local hostname instead of the local IP.

This is a really good change, in my opinion. Even I, a person who does very little web research, have utilized the WebRTC IP leak in a published exploit. I imagine it’s been quite useful for people that actually do that sort of thing.

Regardless, the proof of concept takes that into consideration and, if it can’t obtain an IPv4 address, it’ll search for an active IP somewhere on 192.168.[0–255].1. If you are on a different private subnet, then the proof of concept will still attempt to scan 127.0.0.1.

Is This a Vulnerability?

Initially, I felt this was a vulnerability. The attacker (arguably) bypasses Chrome’s restricted ports list and is able to map the victim’s LAN. Which, in my opinion, is a combination of CWE-184, CWE-284, and CWE-200. However, Google appears to consider this a “fingerprinting” issue. They specifically note under “Network configuration fingerprints”:

With active probing, the list of open ports on the local host indicating other installed software and firewall settings on the system. Unruly actors may also be tempted to probe the systems and services in the visitor’s local network; doing so directly within the browser will circumvent any firewalls that normally filter out unwanted incoming traffic.

And according to Google, fingerprinting is a privacy issue and not a vulnerability.

Although we do not consider fingerprinting issues to be security vulnerabilities, we do now consider them to be privacy bugs

Are There Mitigations?

Yes. There are a variety of Chrome extensions that claim to disable WebRTC entirely. You can also choose to use another browser.

Every piece I publish has been diligently combed through by my wonderful colleagues. Not only do they provide thoughtful feedback, but they also point out the many mistakes that I inevitability work into my writing. I owe them all a great debt of gratitude. Especially, on this occasion, I owe a huge thank you to David Wells for finding a glaring error in the original version of this write up. Thank you, David!

--

--