Closed Bug 1855345 (CVE-2023-6208) Opened 1 year ago Closed 11 months ago

On X11, any page can freely write to the primary selection

Categories

(Core :: DOM: Selection, defect)

All
Linux
defect

Tracking

()

VERIFIED FIXED
121 Branch
Tracking Status
firefox-esr115 120+ verified
firefox118 --- wontfix
firefox119 + wontfix
firefox120 + verified
firefox121 + verified

People

(Reporter: u734064, Assigned: masayuki)

References

()

Details

(Keywords: reporter-external, sec-moderate, X11, Whiteboard: [adv-main120+][adv-esr115.5+][reporter-external] [client-bounty-form] [verif?])

Attachments

(6 files, 1 obsolete file)

In Firefox running on X11, any script from any page can write to the
primary selection -- which on X11 is the main and most used copy-paste
mechanism.

No "secure context", "transient activation", extra permissions or any other
prerequisites (as required by the much less valuable Clipboard.writeText())
are needed.

This basically means that any visited page can take over the victim's system
the first time she tries to paste something in the terminal.

With prior knowledge of the user's workflow and preferences, devising a
succesful attack is a matter of minutes. Getting something that works
reliably for most Linux/Users users is more complicated but still orders
of magnitude easier than exploiting some memory corruption or similar bug.

=== Example ===

Load the following html snippet inside a Firefox running on top of Xorg
(Xorg, not Wayland, see below why). The protocol (http:, file:, etc) or
whether this runs inside an iframe does not matter.

Then switch to a gnome-terminal or xterm window, and press the middle
button of the mouse or shift-Insert to paste the selection.

Every 500ms you'll get a new text made up of the current time and the
page's url.

------------------x-------------------
<!doctype html>
<title>write to the primary selection</title>
intentionally left blank
<script>
function setXPrimary(text){
let d = document.body.appendChild(document.createElement('pre'));
d.style.fontSize = 0;
setTimeout(function(){
d.textContent = text;
getSelection().selectAllChildren(d);
d.remove();
}, 0);
}

setInterval(function(){
setXPrimary(Date.now() / 1000 + ' ' + document.location)
}, 500);
</script>
------------------x-------------------

=== NON-mitigating factors ===

  1. Wayland only allows the window having the keyboard focus to write to
    the primary selection. This complicates things a bit, but not for much; I
    think it's possible to detect via timing DOM calls, etc. whether the main
    firefox window has acquired the focus and only then write the selection,
    even if it's not the tab or iframe the script runs in that has the focus.

  2. The "bracketed paste" feature of the terminal and shell (the default
    in recent bash) means that you cannot just use "setXPrimary('rm -fr ~\n')"
    and be done: it's the user who has to press the Enter for it to run.

However, even naive workarounds like

setXPrimary("\n" + "echo reboot" + "\n".repeat(100) +
	" ".repeat(30) + "\n".repeat(100))

which will confuse the user with a screen like

dummy@ohzd:~$ echo first
first
dummy@ohzd:~$ true
dummy@ohzd:~$
#	<-- cursor here, most users will hit Enter to get a new prompt

will do for it most of the time.

Flags: sec-bounty?
Attached file example.html

looks like the example html snippet was mangled in the description; here is the same example, as an attachment

I'll start this off in DOM Selection, but maybe there's a Widget GTK component to this.

Group: firefox-core-security → dom-core-security
Component: Security → DOM: Selection
Product: Firefox → Core
OS: Unspecified → Linux
Hardware: Unspecified → All

We should probably only set the primary clipboard if there's a user interaction or so, perhaps?

(In reply to Emilio Cobos Álvarez (:emilio) from comment #3)

We should probably only set the primary clipboard if there's a user interaction or so, perhaps?

Yes, that seems the most plausible option. It'd also be interesting to know what other browsers do here...

watch -n 1 xsel may be useful.

Different behavior was reported for HTMLInputElement.setSelectionRange(). I wonder why Selection.selectAllChildren() would be different.

With MOZ_DISABLE_CONTENT_SANDBOX=1 mozregression --launch 2021-01-01, I see similar behavior but the selection is often being set to empty, for reasons I do not know.

Chromium at least tests for user-input.

Odd that sites have wanted this behavior for a long time but haven't discovered it.

(In reply to :Gijs (he/him) from comment #4)

(In reply to Emilio Cobos Álvarez (:emilio) from comment #3)

We should probably only set the primary clipboard if there's a user interaction or so, perhaps?

Yes, that seems the most plausible option. It'd also be interesting to know what other browsers do here...

I would very much prefer to have it as an option of Clipboard.writeText() or a separate method that I could catch and block from an extension's content script with exportFunction(). That would also be more straightforward to use from apps like editors and such.

Having the primary selection set as a hidden side effect of using the Selection API is still icky, even if gated by user interaction checks or similar.

(In reply to Karl Tomlinson (:karlt) from comment #5)

With MOZ_DISABLE_CONTENT_SANDBOX=1 mozregression --launch 2021-01-01, I see similar behavior but the selection is often being set to empty, for reasons I do not know.

That's probably because of some race triggered by my badly over-engineered example (sorry).

Here's a simplified example which should work more reliably in all versions from at least firefox-42.0 through the lates nightly (120.0a1).

<!DOCTYPE HTML>
<html lang=en>
<title>write to the primary selection (#2 take)</title>
<pre id=pre style=font-size:0></pre>
intentionally left blank
<script>
setInterval(function(){
        pre.textContent = Date.now() / 1000 + ' ' + document.location + '\n';
        getSelection().selectAllChildren(pre);
}, 500)
</script>
Attached file simplified example

I'll post a patch for just avoiding that only Selection.selectAllChildren can update the clipboard. I think that we should discuss better handling in bug 1567160.

Assignee: nobody → masayuki
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true

(In reply to turistu from comment #6)

Having the primary selection set as a hidden side effect of using the Selection API is still icky, even if gated by user interaction checks or similar.

What do you think should happen instead?

If a website has a "select all" button (or context menu entry...) next to a rich text editor (contenteditable), that uses the selection API to select text, wouldn't the expected behaviour on X11 and similar be that this makes it to the primary selection?

(In reply to :Gijs (he/him) from comment #11)

(In reply to turistu from comment #6)

Having the primary selection set as a hidden side effect of using the Selection API is still icky, even if gated by user interaction checks or similar.

What do you think should happen instead?

Highlight the text without putting it into the primary selection.

AFAICS, there are 4 ways out of this.

  1. Only allow the Selection API functions when called as a result of user action (the so called "transient activation") and only from secure contexts. Totally unacceptable as it would break a lot of sites which are using the Selection API just for highlighting search results and other text (instead of inserting <span> elements or other ugly kludge).

  2. Allow the Selection API to be used at any time, but only set the primary selection when some conditions like "transient activation", https, etc are met. A script could call addRange() and selectAllChildren() in the hope that that would also copy the text to the clipboard, but would have no way to know if that actually happened at all (unlike with Clipboard.writeText(), which returns a promise). Writing non-visible or non-highlighted text to the selection would require sordid hacks like transparent elements. Malware would still be able to abuse it to write to selection something else than what the user has selected. Extensions would not be able to block them without breaking things in the same way 1. would.

  3. Allow the Selection API to be used at any time (and continue to work as a precursor to windows-style Ctrl-C and execCommand('copy') as it does now) but without affecting the primary selection in any way. Implement copying to the primary selection either as an option of Clipboard.writeText(), a separate Clipboard API function (subject to the same or more stringent conditions than writeText()), or make writeText() set both X_PRIMARY and X_CLIPBOARD by default. For most Linux/BSD users, the "Copy" buttons will instantly start to work on most sites (or could be easily made to) in the same way "Copy outerHTML" does in the firefox's own web dev pane. Extensions would be able to selectively block that API from a content script with exportFunction().

  4. Keep things as they are and just document them (so that people know what to expect and make their own assessment).

If a website has a "select all" button (or context menu entry...) next to a rich text editor (contenteditable), that uses the selection API to select text, wouldn't the expected behaviour on X11 and similar be that this makes it to the primary selection?

Yes it would be nice it that just worked as expected, but letting that work via 2. is too dangerous (yet not at all reliable!), while 3. would easily allow to achieve that in a simple and obvious way.

The security considerations should be the same as for the regular clipboard API write: only from a secure context, only with a user activation.

Keywords: sec-moderate

(In reply to Daniel Veditz [:dveditz] from comment #13)

The security considerations should be the same as for the regular clipboard API write: only from a secure context, only with a user activation.

I find the sec-moderate label COMPLETELY INNACURATE -- for a bug which allows any visited site to simply take over the user's machine without them even having to interact in any way with the site.

Also the linux-only tag: this actually works on *BSD and Solaris too.

And also, because the way it's used and the way it works, writing to the primary selection on X11/Unix is much more dangerous than writing to the clipboard on Windows or Mac.

(In reply to Karl Tomlinson (:karlt) from comment #5)

watch -n 1 xsel may be useful.

FWIW, the Xfixes X11 extension allow you to monitor selection changes in real time, no need to poll for it every second or so.

(In reply to turistu from comment #14)

I find the sec-moderate label COMPLETELY INNACURATE

Sorry for the misspelling, so that should've read INACCURATE

the way it works, writing to the primary selection on X11/Unix

Please do not adjust the sec severity flags yourself.

Needinfo'ing Dan for consideration of comment #14 re: sec-severity.

Flags: needinfo?(dveditz)

(In reply to :Gijs (he/him) from comment #17)

Please do not adjust the sec severity flags yourself.

And why not, if the interface allows it?

I've just corrected a bogus flag. It's not like I can take over your "processes" or force you to do anything by just pushing buttons here (or could I?).

The descriptions of the severity flags say:

sec-moderate Vulnerabilities which can provide an attacker additional information or positioning that could be used in combination with other vulnerabilities.
sec-high Obtain confidential data from other sites the user is visiting or the local machine, or inject data or code into those sites, requiring no more than normal browsing actions. Exploitable web vulnerabilities that can lead to the targeted compromise of a small number of users.
sec-critical Exploitable vulnerabilities which can lead to the widespread compromise of many users.

This is a bug where a random page from a background tab can run arbitrary code on the user's computer (possibly with root privileges, if the user is into sudo or doas), without the user having to interact with the page in any way. There are systems (Alpine Linux, OpenBSD, etc) where even the cartoon version of the exploit (writeXPrimary('rm -fr ~\r')) works 100% in their default configuration.

Bypassing hurdles like the bracketed-paste feature of readline/bash or zsh requires more skill than that, but it's still easy. If you think that that is a big hurdle, or that the lack of such of such feature qualifies itself as a vulnerability -- i.e. each program (but firefox itself, of course) should treat any user input as potentially hostile, remote-controlled data, then please state it clearly to have it on record here.

Needinfo'ing Dan for consideration of comment #14 re: sec-severity.

No info is forthcoming. There are 10 days already with 0 technical engagement (besides the odd vacuous statement) from your part. It looks more and more like sharing this info with you was a horrible terrible mistake from my part.

(In reply to turistu from comment #12)
> AFAICS, there are 4 ways out of this.
> 
> 3. Allow the Selection API to be used at any time (and continue to work as a precursor to windows-style Ctrl-C and `execCommand('copy')` as it does now) but **without affecting the primary selection in any way**. Implement copying to the primary selection either as an option of `Clipboard.writeText()`, a separate Clipboard API function (subject to the same or more stringent conditions than `writeText()`), or make `writeText()` set both `X_PRIMARY` and `X_CLIPBOARD` by default. For most Linux/BSD users, the "Copy" buttons will instantly start to work on most sites (or could be easily made to) in the same way  "Copy outerHTML" does in the firefox's own web dev pane. Extensions would be able to *selectively* block that API from a content script with `exportFunction()`.

Just for the record, here is a patch implementing this.

The first part remove javascript's ability to indirectly mess with the system's clipboard. AFAIK this should only affect `Selection.selectAllChildren()`; other functions of the Selection API ('.addRange()`, etc) or `HTMLInputElement.setSelectionRange()` were **already broken**, so this does not really change anything, beside fixing the vulnerability. The only code that this would break is probably my own little extension.

The second parts adds an option to use the primary selection to `Clipboard.write()` and `.writeText()`. Sample usage is:
```
navigator.clipboard.writeText(some_text, {useX11Primary: true})
```
Should you take this seriously, I can do the same for `.read()`, and also write some tests and such.
```

```

Needinfo'ing Dan for consideration of comment #14 re: sec-severity

I obviously don't understand how the X11 Primary Clipboard is used well enough to rate this.

Flags: needinfo?(dveditz)
Keywords: sec-moderate

While it is true that if the attacker pulls it off, they can do basically anything, it does require some specific steps from the user, which reduces the danger a bit. The user has to go to a console and hit paste (and possibly enter), while the attacker payload is still in the buffer.

If the user goes to hit paste in the console, they'd presumably be hitting copy themselves at some point for the text they want, which will replace the attacker payload. If we assume they aren't selecting text themselves from the attacker page (because if they were, the ability to bypass the user interaction requirement would matter less), then that means the attacker page is more likely to be in the background, and we throttle setInterval and other ways to run script when a page is in the background (to reduce energy usage), so it would be harder for the page to re-overwrite the buffer during that time.

Keywords: sec-moderate

(In reply to Andrew McCreight [:mccr8] from comment #22)

While it is true that if the attacker pulls it off, they can do basically anything, it does require some specific steps from the user,

Yes, the most specific step of which is using firefox at all

which reduces the danger a bit. The user has to go to a console and hit paste (and possibly enter), while the attacker payload is still in the buffer.

There's of course no clipboard "buffer" whatsoever on X11. Neither for the primary nor for the clipboard selection.

If the user goes to hit paste in the console, they'd presumably be hitting copy themselves at some point for the text they want, which will replace the attacker payload

What are you talking about "going to paste in the console" and "hitting copy"? Have you even read my bug report? Are you even using an X11 system at all?

The user is happily working in their terminal or editor, and then --without having to do anything out of the ordinary-- she gets powned by a page from a background tab from a minimized firefox window which has hijacked the selection just in between a copy and a paste. That's it.

then that means the attacker page is more likely to be in the background, and we throttle setInterval and other ways to run script when a page is in the background (to reduce energy usage)

Throttling does not matter at all -- in fact, trying to hijack the selection faster than 500ms or so would be counterproductive, because the user will immediately notice that she's not able to select anything. Unlike firefox, most X11 program will de-highlight the text upon losing the primary selection's ownership. And 500ms-3s is exactly the sweet spot between selecting and pasting for most users.

But all that's about my silly example, which was only meant as an illustration. But there is at least one terminal emulator with at least a couple thousand users where even karlt's example (watch -n1 xsel) is more than enough to run arbitrary code on the user's machine.

so it would be harder for the page to re-overwrite the buffer during that time.

Yes, keep pounding on that "buffer". Maybe it will come into existence by just believing in it.

I for one am done with this here.

(Sorry for the delay to put the patch, my Linux environment was broken, and not yet recovered/rebuilt.)

Severity: -- → S2

It looks like the original reporter of this bug has posted this vulnerability publicly, on October 17.

[Tracking Requested - why for this release]: sec-moderate, but the issue has been made public and there's a clear path to fixing this, so we want to get it out in a timely fashion.

Group: dom-core-security → core-security-release

Even though there is a reasonable usabe like bug 1567160, we should
ensure that AutoCopyListener never update clipboard when the selection
change is caused by a Selection API call.

(I'll be offline Thu and Wed. Therefore, feel free to steal and/or land the patch. I think that it's enough safe to uplift even into ESR.)

Attached file loremipsum.html

A repro for mstange's question for macOS behavior in Phab:

  1. Open the attached file
  2. Select some random text in the lorem ipsum paragraph
  3. Soon after that consetetur will be selected by the script
  4. Tap "Firefox Nightly -> Services -> Look Up in Dictionary"
  5. See which text shows up

Nightly: consetetur
The patch with JS_REASON: the random text you selected before the selection change

Pushed by masayuki@d-toybox.com:
https://hg.mozilla.org/integration/autoland/rev/88e0043c5aa4
Make `AutoCopyListener` ignore any selection changes caused by Selection API r=karlt,smaug

Comment on attachment 9359698 [details]
Bug 1855345 - Make AutoCopyListener ignore any selection changes caused by Selection API r=karlt!

Beta/Release Uplift Approval Request

  • User impact if declined: The "primary" buffer is automatically updated by JS with a Selection API, Selection.selectAllChildren().
  • Is this code covered by automated tests?: No
  • Has the fix been verified in Nightly?: No
  • Needs manual test from QE?: Yes
  • If yes, steps to reproduce: 1. Run Firefox on Linux
  1. Type a word in the search bar or URL bar then, select it
  2. Open the testcase
  3. Middle click in the search bar or URL bar

Then, the middle click should paste the word you selected in the search bar or URL bar. Note that if you touch the test case, e.g., click in the test case, the primary buffer is still updated even after the patch is applied.

  • List of other uplifts needed: None
  • Risk to taking this patch: Low
  • Why is the change risky/not risky? (and alternatives if risky): The patch makes AutoCopyListener do nothing if it comes from a Selection API call. If some web apps use this path for updating the primary buffer without any malice, the feature would be broken, but it should be restored once bug 1567160 is fixed later.
  • String changes made/needed:
  • Is Android affected?: Yes

ESR Uplift Approval Request

  • If this is not a sec:{high,crit} bug, please state case for ESR consideration: This bug is really annoying because web apps can change the primary buffer content without users' operation. And this issue has already been published.
  • User impact if declined: The "primary" buffer is automatically updated by JS with a Selection API, Selection.selectAllChildren().
  • Fix Landed on Version:
  • Risk to taking this patch: Low
  • Why is the change risky/not risky? (and alternatives if risky): The patch makes AutoCopyListener do nothing if it comes from a Selection API call. If some web apps use this path for updating the primary buffer without any malice, the feature would be broken.
Attachment #9359698 - Flags: approval-mozilla-release?
Attachment #9359698 - Flags: approval-mozilla-esr115?
Attachment #9359698 - Flags: approval-mozilla-beta?
Flags: qe-verify+
Status: ASSIGNED → RESOLVED
Closed: 11 months ago
Resolution: --- → FIXED
Target Milestone: --- → 121 Branch
QA Whiteboard: [qa-triaged]

I am attempting to understand how to reproduce/verify this, but I am having trouble with it; @Masayuki Nakano can you clear up some confusion related to the steps in the uplift request?

  1. Regarding step 2: does it matter how the word is selected? Is primary selection not done a certain way? (CTRL+A? double-click? triple-click?)
  2. Regarding step 3: Which test case should we use? example.html or another?
  3. how do you suggest opening the test case? (I think we should definitely not copy-paste it considering it might erase the current primary selection) Does it matter?
  4. Regarding step 4: Should we perform the middle-click in a new tab? Or over the test case's URL?
    Thank you for the help!
Flags: needinfo?(masayuki)

Comment on attachment 9359698 [details]
Bug 1855345 - Make AutoCopyListener ignore any selection changes caused by Selection API r=karlt!

Approved for 120.0b4

Attachment #9359698 - Flags: approval-mozilla-beta? → approval-mozilla-beta+

(In reply to Daniel Bodea [:danibodea] from comment #32)

  1. Regarding step 2: does it matter how the word is selected? Is primary selection not done a certain way? (CTRL+A? double-click? triple-click?)

Either can update the primary selection. It's currently intended only for user interaction.

  1. Regarding step 3: Which test case should we use? example.html or another?

Attachment 9355227 [details] which is the original test case of this report. The script in the test case should not update selection when user does not interact with the document. In other words, currently, we unintentionally allow it updates primary selection when user mouse up on the document. It's traditional issue and out of scope of this bug.

  1. how do you suggest opening the test case? (I think we should definitely not copy-paste it considering it might erase the current primary selection) Does it matter?

Just open the test case into the new (active) tab. Either is fine if you don't break the primary selection. I used the history to open it.

  1. Regarding step 4: Should we perform the middle-click in a new tab? Or over the test case's URL?

Middle-click in the search bar or URL bar. Before applying the patch, the test case updates primary selection even during inactive. The patch avoids web apps to update primary selection with any API calls. However, the selection changes during mouse button down and mouse button up is used at mouse up because it's just not cared by the current design. Therefore, if you click in the test case, you keep seeing unexpected primary selection changes.

Sorry for my unclear comment and confused you.

Flags: needinfo?(masayuki)

Comment on attachment 9359698 [details]
Bug 1855345 - Make AutoCopyListener ignore any selection changes caused by Selection API r=karlt!

Approved for 115.5esr

Attachment #9359698 - Flags: approval-mozilla-esr115? → approval-mozilla-esr115+

Verified as fixed on Ubuntu 20.04 x64.

Setting Fx119 to wontfix and will reject the release-uplift request.
This will ride the trains with Fx120 and 115.5esr

Comment on attachment 9359698 [details]
Bug 1855345 - Make AutoCopyListener ignore any selection changes caused by Selection API r=karlt!

See Comment 39

Attachment #9359698 - Flags: approval-mozilla-release? → approval-mozilla-release-

Verified as fixed on Firefox 115.5.0esr(Treeherder build) on Ubuntu 20.04 x64.

Status: RESOLVED → VERIFIED
QA Whiteboard: [qa-triaged]
Flags: qe-verify+

I guess the reporter removed themselves from the Bug Bounty program by deleting their account

Flags: sec-bounty? → sec-bounty-
Whiteboard: [reporter-external] [client-bounty-form] [verif?] → [adv-main120+][adv-esr115.5+][reporter-external] [client-bounty-form] [verif?]
Attached file advisory.txt
Attachment #9364415 - Attachment description: advsiory.txt → advisory.txt
Attached file advisory.txt (obsolete) —
Attachment #9364421 - Attachment is obsolete: true
Alias: CVE-2023-6208

Bulk-unhiding security bugs fixed in Firefox 119-121 (Fall 2023). Use "moo-doctrine-subsidy" to filter

Group: core-security-release
Flags: sec-bounty-hof+
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: