Article

WebUSB - How a website could steal data off your phone

Introduction

On the 5th September this year, Chrome 61 was released with WebUSB enabled as a default feature. WebUSB is a JavaScript API to allow web sites access to connected USB devices. It is aimed at scientific and industrial USB devices and does not support common devices like webcams, HIDs or mass storage devices. However many other USB devices can be accessed using the WebUSB API, and users may not realise the level of access gained whenever they grant permission to a web site. 

This blog post looks in to the capabilities of WebUSB to understand how it works, the new attack surface, and privacy issues. We will describe the processes necessary to get access to devices and how permissions are handled in the browser. Then we will discuss some security implications and shows, how a website can use WebUSB to establish an ADB connection and effectively compromise a connected Android phone.

Basics

When a USB device is plugged into the host the browser reads the descriptors sent by the device and stores it in the internal USB device storage. This process is handled by Chrome's rendering engine Blink. Logs are easily accessible at chrome://device-log (the GET parameter "refresh=1" is really useful).

According to the specification a device can explicitly announce support for WebUSB in a Platform Descriptor located in its Binary Object Store. The Platform Descriptor has the following structure:

Offset
Field
Size
Value
Description

0

bLength

1

Number

Size of this descriptor. Must be set to 24.

1

bDescriptorType

1

Constant

DEVICE CAPABILITY descriptor type.

2

bDevCapabilityType

1

Constant

PLATFORM capability type.

3

bReserved

1

Number

This field is reserved and shall be set to zero.

4

PlatformCapabilityUUID

16

UUID

Must be set to {3408b638-09a9-47a0-8bfd-a0768815b665}.

20

bcdVersion

2

BCD

Protocol version supported. Must be set to 0x0100.

22

bVendorCode

1

Number

bRequest value used for issuing WebUSB requests.

23

iLandingPage

1

Number

URL descriptor index of the device’s landing page.

A browser stores every USB device in its own device storage. Accessibility to WebUSB is determined by native driver support. On Windows every USB device that is handled by the WinUSB driver can be accessed from the browser. Other devices like mass storage devices, webcams or HIDs are inaccessible from the web because they have dedicated driver handling those devices.

According to the specification (and this blog post) the browser shows a notification as soon as a device is registered. It looks like this:

 https://developers.google.com/web/updates/images/2016-03-02-access-usb-devices-on-the-web/web-usb-notification.png

However this behaviour is not easily reproducible. We tried it on the following systems:

  • Windows 7, Chrome 61
  • Windows 10, Chrome 61
  • Debian, Chromium 60 (with chrome://flags/#enable-experimental-web-platform-features enabled)
  • Debian, Google Chrome 61
  • Arch Linux, Chromium 61
  • Arch Linux, Google Chrome 61

There's an interesting element in the Platform Descriptor called "iLandingPage". Even though the specification suggest the protocols "http://" and "https://" as prefixes it is also possible to choose an empty protocol. In this cases it should be possible to specify the protocol in the provided URL itself.

However, Chrome removed or didn't implement the ability to inject arbitrary URL prefixes. Below is a code snippet from a source file called "webusb_descriptors.cc". It parses the received description headers including the "iLandingPage". It restricts the URL prefix to "http://" and "https://".

 // Look up the URL prefix and append the rest of the data in the descriptor.
 std::string url;
 switch (bytes[2]) {
   case 0:
     url.append("http://");
     break;
   case 1:
     url.append("https://");
     break;
   default:
     return false;
 }
 url.append(reinterpret_cast<const char*>(bytes.data() + 3), length - 3);

Requesting Access to a device

A web page can request permission to a device which opens a prompt. It must specify a filter on how the available devices should be filtered. This filter can be empty allowing to let a user choose a device from all available devices. The prompt opened looks like the following:

select device

The user can see all (filtered) available devices. The devices are referred to by the product name sent by the device. If no product name is specified Chrome tries to guess an expressive name with the help of the known information about the device. It then names the device for example "unknown device from <vendor_name>". After the user selects a device and clicks "Connect" the permission to access the device is granted.

Permission Handling

Permissions are handled by Chrome's Permission API1. Once a permission is granted to a device for a web page it is granted until manually revoked by the user. The Permission API differentiates "web pages" by their origin, i.e., it considers a web page the same as another when it has matching protocol, host and port. It is not obvious how the browser uniquely identifies a device. Candidates for identification are sent by the device in its description headers. The candidates are the following:

  • GUID
  • Vendor ID
  • Product ID

Although the GUID is a unique ID it is not used for identifying the device. Below is a screenshot from the device log after plugging in and out used test device for multiple times. Each time the device has another GUID. It is however recognized and accessible without the need for further permission requests.

different guids same device

This suggests that Chrome uses a combination of Product ID and Vendor ID to identify a device.

Accessing a Device

Once a web page has been granted access to a device it can access it2. First it must open the device. Opening a device starts a session with a device and locks the device so that no other tab in the same browser session can access it. However, the same device can be opened by another web page in another browser.

To communicate with a device the browser then has to claim an interface which it wants to talk to. After claiming an interface, it is un-claimable for any other application on the host. With a claimed interface a page can talk to the endpoints specified for an interface.

Next, a page initiates a control transfer to set up the device. This basically specifies the way it wishes to communicate with the device and what the exact functionality requested is.

As soon as the device is set up, it can transfer data and do everything the interface of the USB device is capable of.

Checking Support of WebUSB

A small proof of concept tool was constructed that can easily determine if a device is supported by WebUSB. The tool tests if it is able to claim at least one interface of connected USB devices. That means that it is able to talk to the device and therefore it is supported.

The tool cannot test if a USB device is completely unsupported – there are different reasons for not being able to claim an interface. The interface could be claimed by another program or the browser might not have access rights on the system (Linux).

The tool is a simple static site. You can download it here. This is how it looks:

site layout no devices

To test support for a device, click the "Choose A Device" button to open the permission prompt. This prompt lists all available USB devices. By selecting the desired device and clicking "Connect" the tool will then open the device and iterate over every available interface and trying to claim it. The results are recorded in the table at the bottom of the page. The claimed interfaces column displays the interface number(s) that could be claimed.

both supported and unsupported device

 If you want to use the supported devices elsewhere you will need to refresh the site or close the tab.

Security Considerations

This brief look at WebUSB demonstrated that it had been built with security in mind. However, like all newly added code it enlarges the code base and therefore the attack surface of the browser. It is also a relatively new technology, therefore issues may be determined further down the line. Some initial observations were made within this area about its security posture. 

WebUSB runs in the context of Chrome's rendering engine "Blink". Thus, finding a memory corruption in WebUSB might not have a bigger impact than a memory corruption anywhere else in Blink.

A website implementing WebUSB should ensure that mitigating XSS is a priority. With an XSS exploit an attacker could have the same access as the website to connected devices without the user being aware.

The permission handling WebUSB may not be obvious to a user. The notice given to the user when a page requests access to a USB device does not contain any warnings that the site will have full, silent USB access to that device from that point onwards.

A Proof of Concept (PoC) was constructed to demonstrate this issue. In this scenario the ADB host implementation based on WebUSB was used to access a connected Android phone. Once the user accepted the request, the page uses WebUSB to retrieve all pictures from the camera folder.

With this level of access a web site is not only able to steal every readable file from the file system but it can also install an APK, get access to the camera and microphone to spy on the user and potentially escalate privileges to root.

This example is limited by its high level of user interaction and therefore the risk is substantially reduced - the user has to grant the web site permissions to their phone, activate USB debugging on their phone, and finally allow the ADB connection from the host on their phone. As of now this only works on Linux since the implementation in Windows is quite unstable. However it should serve both as an example of running complex protocols over WebUSB, and to show how one click of a WebUSB request can lead to compromise of data.

You can see the PoC in action in the video below. There are two virtual machines, the one on the left acts as the evil web server, the one on the right acts as the victim. After the site connects to the phone the ADB connection is confirmed on the phone. All taken camera pictures are then retrieved and displayed on the site. You can download the source here.

Further Research

Further research could focus on finding flaws in the implementation of WebUSB and might reveal memory corruption bugs. However, the code base is relatively small and newly written with security in mind.

Another interesting vector to investigate would be to attack Chrome with a malicious USB device. This could send malformed USB descriptors and might trigger unexpected behavior in the browser. Chrome offers the possibility to add virtual test devices for WebUSB ( chrome://usb-internals/ ) which could be helpful. This vector would require physical access to a device though, so simpler attacks make this vector a little unrealistic.

When researching WebUSB or any other new web standard like Web Bluetooth or Web NFC keep in mind that those features are changing rapidly and even information from a month ago might be outdated.

Conclusion

In general WebUSB was determined to have a good standard of security due to its limitations and restrictions during this limited review period. Supported devices are very limited, webcams, HIDs and mass storage devices are not accessible to WebUSB. However, it is an interesting technology to research further, especially when significant changes are introduced or additional functionality.

Users are advised to never give untrusted websites access to USB devices that contain any sort of sensitive data. As shown this can result in a full compromise of the device.

Credits to Robert Miller and Georgi Geshev for their great support during this research project.

1) The integration is suggested by the specification. It worked until changes were made to the API in February of 2017. As of July 2017 the support was incomplete ( https://github.com/WICG/webusb/issues/84 ).

2) This is not entirely true for Linux. As of now, 22/09/2017, the user that owns the Chrome process must have RW access to the desired USB device. Currently a udev rule has to be employed. This issue tracks the current status of a way to make it easier.