Confirming install prompt for trusted addon may execute arbitrary privileged code instead
Categories
(Toolkit :: Add-ons Manager, defect, P1)
Tracking
()
People
(Reporter: arminius, Unassigned)
References
(Regression)
Details
(Keywords: csectype-priv-escalation, csectype-sandbox-escape, sec-critical)
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.
Reporter | ||
Comment 1•2 years ago
|
||
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 inaddon.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 renameaddon.xpi.part
toaddon.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.
Updated•2 years ago
|
Comment 2•2 years ago
|
||
[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.
Updated•2 years ago
|
Comment 3•2 years ago
•
|
||
(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 renameaddon.xpi.part
toaddon.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?
Comment 4•2 years ago
|
||
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.
-
we're downloading things to a predictable name. What happened to the random bit?
-
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.
Comment 5•2 years ago
|
||
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.
Updated•2 years ago
|
Comment 6•2 years ago
•
|
||
[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.
Updated•2 years ago
|
Updated•2 years ago
|
Reporter | ||
Comment 7•2 years ago
|
||
(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 renameaddon.xpi.part
toaddon.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.
Comment 8•2 years ago
|
||
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.
Comment 9•2 years ago
|
||
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.
Updated•2 years ago
|
Updated•2 years ago
|
Updated•2 years ago
|
Updated•2 years ago
|
Comment 10•2 years ago
•
|
||
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
Updated•2 years ago
|
Updated•2 years ago
|
Comment 11•2 years ago
|
||
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
Comment 12•2 years ago
|
||
Bug 1753096 has been landed for 97.0rc2, disabling the feature for this cycle.
https://hg.mozilla.org/releases/mozilla-release/rev/44702fdc700c
Reporter | ||
Comment 13•2 years ago
|
||
Sorry for this last-minute workload. Could I be CC'ed on the split-offs to keep following the process?
Comment 14•2 years ago
|
||
(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 :)
Comment 15•2 years ago
|
||
(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)
Comment 16•2 years ago
|
||
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.
Updated•2 years ago
|
Updated•2 years ago
|
Updated•2 years ago
|
Updated•2 years ago
|
Comment 17•2 years ago
|
||
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 !
Comment 18•2 years ago
•
|
||
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).
Comment 19•2 years ago
|
||
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.
Reporter | ||
Comment 20•2 years ago
|
||
(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.
Comment 21•2 years ago
•
|
||
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.
Updated•2 years ago
|
Comment 22•2 years ago
|
||
Updated•1 year ago
|
Description
•