Closed Bug 1752888 Opened 2 years ago Closed 2 years ago

Confirming install prompt for trusted addon may execute arbitrary privileged code instead

Categories

(Toolkit :: Add-ons Manager, defect, P1)

defect

Tracking

()

VERIFIED FIXED
98 Branch
Tracking Status
firefox-esr91 --- unaffected
firefox96 --- disabled
firefox97 blocking disabled
firefox98 blocking verified
firefox99 --- verified

People

(Reporter: arminius, Unassigned)

References

(Regression)

Details

(5 keywords)

Attachments

(5 files)

An attacker-controlled site may prompt the user to install an official, signed addon which instead installs a rogue extension, allowing code execution with system principal privileges.

The attack is facilitated by the new auto-download/-open behavior since FF 97, and how addon integrity is verified during installation. I could repro it on {Linux,Windows} x Firefox {Beta,Nightly} with default settings.

Proof of concept

The attached GIF shows the UX of the attack on Ubuntu + Firefox Beta 97.0b9.

  • The user visits https://attacker.example/poc.html.

  • An installation prompt for the trusted "Mozilla Rally" extension pops up.

  • The user accepts, causing the browser to execute a JS payload with chrome privileges.

Note that the PoC uses a service worker which requires HTTPS. Also, you may need to increase delays between the attack steps on slower systems, although the PoC seemed reliable on my end.

I'll follow up with more details.

Flags: sec-bounty?
Attached file Proof of concept files

Rundown of the PoC:

  • A download stream is opened with the target file name addon.xpi and the full content of a signed addon from AMO (here, Mozilla Rally) is written to it. The download stream is not closed yet, so that the browser will hold it as a partial download in addon.xpi.part. A bunch of null bytes are then added to ensure it's being flushed to disk.

  • Next, an SVG image with embedded script code is downloaded. As is default behavior since FF 97, the file downloads and opens automatically in a new tab from within the user's download dir.

  • The SVG's script triggers an addon installation of addon.xpi.part which pops up the confirmation dialog.

  • The download stream for addon.xpi is now closed, causing the browser to rename addon.xpi.part to addon.xpi.

  • Now, a fake addon package is downloaded as addon.xpi.part, thus taking the place of the file that the current install dialog refers to. By pre-caching the download, this switch can be done sufficiently fast.

  • If the user now confirms the dialog, the browser installs and executes the fake addon (while still referring to the previously parsed manifest) without re-confirming integrity.

For seamless installation, the fake addon is based on a copy of the original. To gain system privileges it takes advantage of the original addon's experiment_apis manifest entry. The referenced API implementation (here, in core-addon/FirefoxPrivilegedApi.js) is simply replaced with a JS payload which then executes with full permissions.

[Tracking Requested - why for this release]: I'm not familiar enough with this code to assess the report, but it sounds like a potentially bad security regression.

Flags: needinfo?(gijskruitbosch+bugs)

(In reply to Arminius (Armin Ebert) from comment #1)

Thanks for the clear explanation. It's midnight here and so I don't have time to reproduce myself, but I have some questions to get us going...

  • Next, an SVG image with embedded script code is downloaded. As is default behavior since FF 97, the file downloads and opens automatically in a new tab from within the user's download dir.

Can you clarify why you think this is new since 97? SVGs have opened in the browser since bug 1639067 (Firefox 81), AIUI.

  • The SVG's script triggers an addon installation of addon.xpi.part which pops up the confirmation dialog.

Dan or Mike, do you have any intuitions as to how much would break if we started rendering file: SVGs loaded as toplevel docshell items as image documents (ie without running script), and/or how "web"-incompatible that would be?

  • The download stream for addon.xpi is now closed, causing the browser to rename addon.xpi.part to addon.xpi.

FWIW, this behaviour has always existed on macOS... so it's not clear to me that this is really new for 97, though the scope may have been more limited before)

  • Now, a fake addon package is downloaded as addon.xpi.part, thus taking the place of the file that the current install dialog refers to. By pre-caching the download, this switch can be done sufficiently fast.

Would using a strong random number generator for the part file name help against this attack?

  • If the user now confirms the dialog, the browser installs and executes the fake addon (while still referring to the previously parsed manifest) without re-confirming integrity.

This seems like a serious issue with the add-on install process, especially if it applies special mozilla privileges to non-mozilla add-ons. Rob/Shane/Luca/Tomislav?

Flags: needinfo?(tomica)
Flags: needinfo?(rob)
Flags: needinfo?(mozilla)
Flags: needinfo?(mixedpuppy)
Flags: needinfo?(lgreco)
Flags: needinfo?(gijskruitbosch+bugs)
Flags: needinfo?(dveditz)
Flags: needinfo?(armin)

There are at least two terrible things that jump out at me right off the bat in addition to the TOCTOU problem with addon signature verification.

  1. we're downloading things to a predictable name. What happened to the random bit?

  2. We've eliminated the human from the "convince someone to download evil document; and then convince them to open it to let them loose in your download directory" attack and let web sites completely automate this. Luckily we've stopped treating "same directory" as same-origin for files, but there are still "cross-site" features that can be used with predictable file names (script, images, InstallTrigger...)

Those need to be fixed in addition to the addon-manager. That needs to either lock the file and hold on to it once the signature verification process is started, or operate on a copy of the file in a secret location (temp random directory or file name). The latter isn't the best, but might be easier to do since our process is designed around downloading a copy of a .xpi from the web into a temporary location. We shouldn't skip that just because the file starts local.

This is reproducible with the POC files in 97, with browser.download.improvements_to_download_panel set to true (default in 97). I cannot repro in 96, and if I flip the pref to false, I cannot get it to happen in 97.

In terms of issues in the addon manager with the install flow:

a) InstallTrigger is being used, and is not requiring user interaction to call it, we should probably change that, though changing that might break legit some use case (I kind of dont care).
b) Verification is happening before moving the file into the user profile, it should probably be staged first to move it out of the downloads dir.

Flags: needinfo?(mixedpuppy)
Severity: -- → S1
Status: UNCONFIRMED → NEW
Ever confirmed: true
Priority: -- → P1

[edit: midair, mostly saying similar things as Dan and Shane]

These are the main steps copied from PoC:

// Start extension download. This creates a `.part` file in the download dir
download(`/${filename}?fetch=real.xpi&control`)
// Wait until `.part` file contains entire extension
await waitForMessage('stream.sent');
// Download helper SVG which auto-opens and prompts to install the `.part` file as extension
download(URL.createObjectURL(svg), 'installer.svg')
// Allow some time for install prompt to pop up. Increase as necessary
await sleep(1);
// Close stream, so the download completes and `.part` file disappears
registration.active.postMessage('stream.close');
// Download fake extension in place of previous `.part` file
download(`/${filename}.part?fetch=fake.xpi`)
// If the user now confirms the dialog, they are installing the fake addon

The main trigger here is that the SVG's script starts an install from a local xpi file with a known name in the same directory:
InstallTrigger.install([{URL: "${filename}.part"}])

Would using a strong random number generator for the part file name help against this attack?

It looks like yes that would impede the main vector here, though we have a number of other things we should improve as well.

We should obviously confirm that the addon.xpi is not swapped between the prompt and installation. Another could probably be disallowing InstallTrigger from file:// URIs.

Flags: needinfo?(tomica)

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

Can you clarify why you think this is new since 97? SVGs have opened in the browser since bug 1639067 (Firefox 81), AIUI.

Downloading and opening SVGs with zero user interaction by default seems to have been switched on from 97 via bug 1733587.

  • The download stream for addon.xpi is now closed, causing the browser to rename addon.xpi.part to addon.xpi.

FWIW, this behaviour has always existed on macOS... so it's not clear to me that this is really new for 97

True, I didn't mean to suggest it's new behavior. In the PoC I'm just using it as a vehicle to replace/modify a file at a known location.

Would using a strong random number generator for the part file name help against this attack?

Yes, a known file location is crucial for the attack. FWIW, possibly the original file name could still be prefixed in a format like ${original}.${random}.part (e.g. addon.xpi.ABCDEYXZ.part) so the user is able recognize which download it belongs to.

Flags: needinfo?(armin)

There's also supposed to be a prompt for an un-whitelisted site attempting an install, but apparently this is suppressed in the case of a trusted addon. That means we have to download the file and check the signature first before we decide this site isn't allowed to do this.

The trustedness of the addon doesn't mean the site can't be an asshole about pestering you to install it, and maybe they've found an exploitable bug in it we don't know about yet. Or a clever string of bad choices as in this case. If a site isn't whitelisted for an install we should not let them even start an install until the user says it's OK (my preference would be to require the user to manually manage the exception list in settings, but I know I'm on the losing end of that one)

We should start spinning these things into individual sub-bugs that this one depends on.

Regressed by: 1733587

In Firefox 96 and before, while the prompt is open waiting for me to approve the download the file name is "SjdmchmY.svg.part", so we do still have the "random part name" code in there somewhere.

Flags: needinfo?(dveditz)
Depends on: 1752996
Depends on: 1752998
Depends on: 1752999
Depends on: 1753004
Has Regression Range: --- → yes
Flags: needinfo?(mozilla)

For those who can't see the dependencies, we're starting to split this up into individual bugs to fix. Since this is supposed to be RC week for Firefox 97 we won't be able to fix all of them, or maybe any of them, and get it safely tested to land into a Release Candidate. Release Managers are wanting us to flip the pref to disable the new download behavior for one more release to buy time. No bug filed for that yet, I don't think.

bug 1752979: TOCTOU flaw when verifying addon signatures: actual install might have different content
bug 1752996: Ensure all .part files have a random component
bug 1752998: Require user interaction for InstallTrigger
bug 1752999: Consider blocking InstallTrigger / add-on install from file:
bug 1753004: Do not automatically open SVG files and run script in them from file:/// URLs

Depends on: 1753096

Clearing needinfos, as Luca and I have been working on this in the past day.

I have elaborated on the TOCTOU in https://bugzilla.mozilla.org/show_bug.cgi?id=1752979#c1

Flags: needinfo?(rob)
Flags: needinfo?(lgreco)

Sorry for this last-minute workload. Could I be CC'ed on the split-offs to keep following the process?

(In reply to Arminius (Armin Ebert) from comment #13)

Sorry for this last-minute workload. Could I be CC'ed on the split-offs to keep following the process?

Thank you for testing on Beta and reporting this when you did! Much better that we were able to address this prior to the feature shipping rather than after :)

(In reply to Arminius (Armin Ebert) from comment #13)

Could I be CC'ed on the split-offs to keep following the process?
(FTR, dveditz went through and CCed the reporter on the related bugs)

Bug 1752979 has landed in version 98, which fixes the primary cause of the privilege escalation.
Bug 1753096 was previously uplifted to 97 to disable the change that offered an entry point to start the exploit chain.

There are bugs related to InstallTrigger, but these changes are not required since the behavior is comparable to installs from websites.

I do recommend looking into ways to prevent auto-opening scriptable content (e.g. SVG - bug 1753004) before enabling the downloads panel feature, as that could be dangerous. Although file://-resources have unique origins by default, the behavior can be controlled by prefs (security.fileuri.strict_origin_policy). If that pref is set, automatically opening downloaded files with scripting functionality allows drive-by downloads to read all local files.

Thanks again Armin for your high-quality report.

Status: NEW → RESOLVED
Closed: 2 years ago
Resolution: --- → FIXED
Target Milestone: --- → 98 Branch
Group: firefox-core-security → core-security-release
No longer depends on: 1752998
See Also: → 1752998
Flags: sec-bounty? → sec-bounty+
QA Whiteboard: [post-critsmash-triage]
Flags: qe-verify+

Hello,

I’m attempting to verify the fix using the PoC attachment from Comment 1 to see if it reproduces on the fixed versions, however I’m facing some troubles with the “Makefile” file from the PoC.

I’ve installed “Chocolatey” on my Windows 10 machine and then the “make” package via “choco install make”. Running “make -v” shows that I indeed have “make” installed.
Next I’ve opened an elevated Windows PowerShell from the PoC folder and ran “make” without specifying any arguments. I get the below error and I’m not sure what I’m supposed to do next.

“wget -O real.xpi https://addons.mozilla.org/firefox/downloads/file/3892089/mozilla_rally-1.4.1buildid20220111.145016-fx.xpi
process_begin: CreateProcess(NULL, wget -O real.xpi https://addons.mozilla.org/firefox/downloads/file/3892089/mozilla_rally-1.4.1buildid20220111.145016-fx.xpi, ...) failed.
make (e=2): The system cannot find the file specified.
make: *** [Makefile:9: real.xpi] Error 2”

Some more detailed STR on how to verify the fix as well a solution in regards to the above error would be appreciated. Thank you !

Attached file fake.xpi

Pre-generated fake.xpi file (generated on a Linux system using the Makefile from the POC files attached as https://bugzilla.mozilla.org/attachment.cgi?id=9261609).

hi Alex,
I suspect that the error raised by executing the Makefile on windows may be due to the fact that wget isn't installed and it can't be executed successfully to download the real xpi file (which is then patched to generate the fake.xpi file).

The resulting fake.xpi file as generated on a Linux system (where I have made sure wget was installed) should work just fine on windows with the rest of the STR, and so in comment 18 I've attached you one I generated locally.

Let me know if there is anything else missing to let you QA verify the issue on windows.

Flags: needinfo?(alexandru.cornestean)
Attached file poc_premade.zip

(Thanks for the quick handling and bounty grant!)

In case it's most straightforward for QA, I've attached the original archive after make has been run as poc_premade.zip. It comes with both XPIs, so you can skip the make step when reproducing.

Hello and thank you all for the additional info and help !

Verified the fix on the latest Nightly (99.0a1/20220214213941) and Beta (98.0b4/20220213185901) under Windows 10 x64 and Ubuntu 16.04 LTS.

Following the instructions in the README and opening the poc.html page will immediately trigger the download of 3 files to the downloads directory– an addon-”random_numbers”.xpi, an addon-”random_numbers”.xpi.part and an installer.svg. The installer.svg opens a new tab and attempts an add-on install. The installation is not performed and an error doorhanger is displayed stating: “<<\Path>> This add-on could not be installed because of a filesystem error”.
The console logs the following: example -> “1644919552248 addons.xpi WARN XPI file C:\Users\alexandru.cornestean\Downloads\addon-0.5359906155297055.xpi.part does not exist .”

Confirmed with Luca these are the expected results of the fix.

For further details, see the attached video.

Status: RESOLVED → VERIFIED
Flags: needinfo?(alexandru.cornestean)
Attached video 2022-02-15_11h03_54.mp4
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: