Code Injection in Firefox macOS desktop client
Categories
(Core :: Security: Process Sandboxing, defect, P2)
Tracking
()
People
(Reporter: wojciechregula, Assigned: haik)
References
Details
(Keywords: csectype-priv-escalation, sec-want, Whiteboard: [adv-main103-])
Attachments
(2 files)
155.68 KB,
image/png
|
Details | |
48 bytes,
text/x-phabricator-request
|
dmeehan
:
approval-mozilla-esr102+
|
Details | Review |
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
Steps to reproduce:
Vulnerability description
I've identified a code injection vulnerability in your macOS desktop client. Any malicious application, running with standard user permissions is able to exploit this vulnerability and execute code in your application's context.
Requirements
In order to exploit this vulnerability, a victim has to have a malicious application installed on the device.
Proof of Concept
To show you the impact I've prepared a proof of concept where malicious application without root permissions opens a calculator from Firefox's context.
1. Create a new dylib with following contents:
#include <Foundation/Foundation.h>
__attribute__((constructor)) static void pwn() {
NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/Applications/Calculator.app/Contents/MacOS/Calculator";
[task launch];
}
2. Compile it using gcc
gcc -dynamiclib malicious.m -o malicious.dylib -compatibility_version 10.10.10 -lobjc -framework Foundation
3. Inject to the Firefox binary
DYLD_INSERT_LIBRARIES=./malicious.dylib /Applications/Firefox.app/Contents/MacOS/firefox
4. Calculators should be run in Firefox's context (as I shown on the attached screenshot)
Actual results:
Impact
In the proof of concept I showed that any malicious application is able to execute code in Firefox's context. An attacker is able to steal browser cookies, sniff the traffic or perform actions on websites.
Expected results:
Recommendations
Assuming that the desktop client has been compiled using XCode, a developer needs to turn on "Hardened Runtime" capability making sure that Allow DYLD Environment Variables option is turned off. Another way to disallow the DYLD Environement variables is adding a _RESTRICTED segment to the application binary (this technique is used for example by Google Chrome).
References
Apple Docs - Hardened runtime entitlements
https://developer.apple.com/documentation/security/hardened_runtime_entitlements
Important notes
- Physical access is not required to exploit this vulnerability.
- Applications do not need root permission to open other applications with DYLD_INSERT_LIBRARIES environment variable - execve documentation.
Updated•5 years ago
|
Comment 1•5 years ago
|
||
Haik, from your newsgroup post about notarized builds, I'm assuming this is covered by the notarized builds work and is fixed in 69? Is that right, and if so, is there a more minimal fix we could apply to 68/esr ?
Assignee | ||
Comment 2•5 years ago
|
||
(In reply to :Gijs (he/him) from comment #1)
Haik, from your newsgroup post about notarized builds, I'm assuming this is covered by the notarized builds work and is fixed in 69? Is that right, and if so, is there a more minimal fix we could apply to 68/esr ?
Hardened Runtime and Notarization are enabled together in 69, but we are allowing the dyld environment variable entitlement. I found that we depend on these variables (e.g., @executable_path). I don't know enough about these to say how difficult it would be to change that. I'll look into this some more.
Not to say this isn't something we should fix, but if an attacker can install a malicious dylib and control the launching of the browser, presumably they could instead install a malicious version of Firefox.
Our configuration for Hardened Runtime is here:
https://searchfox.org/mozilla-central/rev/8a990595ce6d5ed07ace2d4d5d86cc69aec90bde/security/mac/hardenedruntime/production.entitlements.xml
Assignee | ||
Comment 3•5 years ago
|
||
We appreciate the bug report.
After looking into this some more, so far I can see that we depend on DYLD_INSERT_LIBRARIES and DYLD_LIBRARY_PATH. In order to set allow-dyld-environment-variables=false, we need to change that. When launching child processes, we use DYLD_INSERT_LIBRARIES[1] ourselves so that plugin child processes can load libplugin_child_interpose.dylib[2]. From the code comments, we always set it when launching a child process, but only need to use the interposer in plugin processes.
We might be able to link the parent process Firefox binary in a way that blocks DYLD_INSERT_LIBRARIES, but allow it for child processes where we can control how it is set. Some applications are using an undocumented __RESTRICT header in the binary to get dyld to ignore DYLD variables.
I was under the impression that allow-dyld-environment-variables=false also applies to the @variables listed in dyld(1), but that might be wrong. I will try to confirm that.
More investigation/testing needed.
Assignee | ||
Updated•5 years ago
|
Updated•5 years ago
|
Reporter | ||
Comment 4•5 years ago
|
||
Hey!
Known security researcher wrote yesterday an article about injecting code using DYLD_INSERT_LIBRARIES. Maybe that article will be helpful to you in resolving this issue :-) - https://theevilbit.github.io/posts/dyld_insert_libraries_dylib_injection_in_macos_osx_deep_dive/
Cheers,
Updated•5 years ago
|
Assignee | ||
Comment 5•5 years ago
|
||
(In reply to wojciechregula from comment #4)
Hey!
Known security researcher wrote yesterday an article about injecting code using DYLD_INSERT_LIBRARIES. Maybe that article will be helpful to you in resolving this issue :-) - https://theevilbit.github.io/posts/dyld_insert_libraries_dylib_injection_in_macos_osx_deep_dive/
Thanks, I'll give it a look. Addressing this is going to require some Firefox changes because Firefox specifically depends on using DYLD_INSERT_LIBRARIES in some cases. We also use it in some tests, but that shouldn't prevent us from rolling this out in production. We do want to address this, but we need to figure out the scope of the Firefox changes required and take into account the attack vector.
Once we change Firefox to not depend on DYLD_INSERT_LIBRARIES or any other DYLD environment variables, the simplest fix for us would be to set com.apple.security.cs.allow-dyld-environment-variables
to false in our Hardened Runtime entitlement list (production.entitlements.xml in the source tree). At present, we enable macOS Hardened Runtime on Firefox 69 and later, and we plan to enable this on Firefox 68 soon.
Also, starting with macOS 10.15 and Firefox 69, the dylib should need to be signed by a valid Apple Developer ID identity in order for it to be loaded. @wojciechregula, please don't feel obligated, but could you try and reproduce this on a macOS 10.15 Beta using Firefox 69 or newer?
Reporter | ||
Comment 6•5 years ago
|
||
Unfortunately, I do not have a VM with macOS 10.15 to test that. But I can verify that on the newest macOS 10.14 with Firefox 69. ;-)
Assignee | ||
Comment 7•5 years ago
|
||
(In reply to wojciechregula from comment #6)
Unfortunately, I do not have a VM with macOS 10.15 to test that. But I can verify that on the newest macOS 10.14 with Firefox 69. ;-)
I tested on macOS 10.15 and after changing the test code to use the correct Calculator path[1] on 10.15, the dylib still loads via DYLD_INSERT_LIBRARIES despite it not being signed. That need some more investigation.
- On 10.15, Calculator has been moved to /System/Applications.
Reporter | ||
Comment 8•5 years ago
|
||
(In reply to Haik Aftandilian [:haik] from comment #7)
I tested on macOS 10.15 and after changing the test code to use the correct Calculator path[1] on 10.15, the dylib still loads via DYLD_INSERT_LIBRARIES despite it not being signed. That need some more investigation.
Where did you find the information that, in the macOS 10.15, the injected dylibs have to be signed with a valid Developer ID? I only found info that the new checks will be performed on the first use when the app is quarantined. - https://developer.apple.com/videos/play/wwdc2019/701/
I took a screenshot of the new checks - https://i.imgur.com/PliPBFg.png
Did I miss something?
Assignee | ||
Comment 9•5 years ago
•
|
||
(In reply to wojciechregula from comment #8)
(In reply to Haik Aftandilian [:haik] from comment #7)
I tested on macOS 10.15 and after changing the test code to use the correct Calculator path[1] on 10.15, the dylib still loads via DYLD_INSERT_LIBRARIES despite it not being signed. That need some more investigation.
Where did you find the information that, in the macOS 10.15, the injected dylibs have to be signed with a valid Developer ID? I only found info that the new checks will be performed on the first use when the app is quarantined. - https://developer.apple.com/videos/play/wwdc2019/701/
I took a screenshot of the new checks - https://i.imgur.com/PliPBFg.png
Did I miss something?
I'm referring to Hardened Runtime restrictions on loading shared libraries. The documentation doesn't talk about injected dylibs specifically, but I was assuming the restrictions would apply to DYLD_INSERT_LIBRARIES. I don't know if Hardened Runtime is intended to prevent that or not. That's something we need to investigate.
As of 10.15, Hardened Runtime restrictions are enforced and an application signed with the "-o runtime" flag (or if its enabled with Xcode) can only load libraries signed by the same team ID as the application. The com.apple.security.cs.disable-library-validation
entitlement can be used to allow the application to load dylibs signed by other developers, but they do have to be signed.
On 10.15, applications are required to be notarized in order to launch (ignoring workarounds) and notarization is only allowed on apps with hardened runtime enabled.
On the blog post you referenced on comment 4, the author wrote they couldn't use DYLD_INSERT_LIBRARIES once they enabled hardened runtime until they signed the dylib. The error message they included has "Code has to be at least ad-hoc signed."
Assignee | ||
Comment 10•5 years ago
|
||
I tested a basic app built with Xcode 10.2.1 on 10.15 Beta 4 (19A512f) with Hardened Runtime enabled (allowing dyld variables) and I could also load an unsigned shared library using DYLD_INSERT_LIBRARIES. That behavior is consistent with what we see with Firefox on 10.15.
However, with Xcode 11.0 beta 2 (11m337n) running on 10.15 Beta 4 (19A512f), with the same test program, using DYLD_INSERT_LIBRARIES with an unsigned shared library fails. That triggers the "Code has to be at least ad-hoc signed" error message. After signing the dylib, DYLD_INSERT_LIBRARIES worked.
We might just need to use a newer codesign command to get this benefit. And this might be documented somewhere in the WWDC 2019 presentations. More investigation needed and it should eventually be its own bug.
Reporter | ||
Comment 11•5 years ago
|
||
On 10.15, applications are required to be notarized in order to launch (ignoring workarounds) and notarization is only allowed on apps with hardened runtime enabled.
OK, you are right - I missed that ;-) Hardened runtime without com.apple.security.cs.disable-library-validation
will force the valid Developer ID.
We might just need to use a newer codesign command to get this benefit. And this might be documented somewhere in the WWDC 2019 presentations. More investigation needed and it should eventually be its own bug.
That's interesting!
Assignee | ||
Updated•5 years ago
|
Assignee | ||
Comment 12•5 years ago
•
|
||
Apple recently changed the definition of com.apple.security.cs.disable-library-validation
so that it matches the behavior we observed on Mojave. And I believe the more strict behavior of Catalina Beta was changed to match the new less strict definition in Beta 4. See bug 1570840 for more information.
Once bug 1570840 lands, it should not be possible to use DYLD_INSERT_LIBRARIES to inject an unsigned library. Update: We can't set com.apple.security.cs.disable-library-validation=false
because we still need to load libraries signed by different Apple developer team ID's. More details on the bug.
We should still work to prevent using DYLD_INSERT_LIBRARIES to inject signed libraries. We need to make the necessary Firefox changes so that we can set the com.apple.security.cs.allow-dyld-environment-variables
entitlement to false (at least on production builds).
Reporter | ||
Comment 13•5 years ago
|
||
FYI - I've just confirmed that the newest Firefox 68.0.2esr has the Hardened Runtime capability turned on (flags=0x10000(runtime)).
$ codesign -d --verbose /Volumes/Firefox/Firefox.app/Contents/MacOS/firefox
Executable=/Volumes/Firefox/Firefox.app/Contents/MacOS/firefox
Identifier=org.mozilla.firefox
Format=app bundle with Mach-O thin (x86_64)
CodeDirectory v=20500 size=415 flags=0x10000(runtime) hashes=4+5 location=embedded
Signature size=9019
Timestamp=13 Aug 2019 at 19:14:23
Info.plist entries=26
TeamIdentifier=43AQ936H96
Runtime Version=10.11.0
Sealed Resources version=2 rules=13 files=95
Internal requirements count=1 size=188
Assignee | ||
Updated•5 years ago
|
Reporter | ||
Comment 14•5 years ago
|
||
Hey,
Any news? :-)
Assignee | ||
Comment 15•5 years ago
|
||
Not much to update at this time.
We should be able to set com.apple.security.cs.allow-dyld-environment-variables=false
and possibly com.apple.security.cs.disable-library-validation=false
for the parent process, but not child processes. This should be easy to test and I'll update the bug once I have some results. I originally thought hardened runtime entitlements were inherited by child processes (testing done with 10.14 suggested that), but that's incorrect. Sometimes parent process entitlements can affect child capabilities, but, from what I understand, only when the TCC subsystem is trying to decide if an operation is permitted and ends up falling back to the parent process's entitlements.
For child processes, we use one plugin-container executable for all child process types so we can't use disable-library-validation=false
or allow-dyld-environment-variables=false
yet. Once we remove Flash support from the tree, we can stop using the Mac interpose library we use and start using allow-dyld-environment-variables=false
. If we used different binaries for different child process types, we could use different entitlements and therefore use library validation. I will file a bug to use different plugin-container binaries for different child process types (web content, plugin) so we can set disable-library-validation=false
for web content.
Assignee | ||
Comment 16•5 years ago
•
|
||
I've done some testing with using separate entitlements for the parent process and plugin-container process. Setting com.apple.security.cs.allow-dyld-environment-variables=false
for the parent process works as expected and DYLD_INSERT_LIBRARIES
can not be used to inject a dylib. This only works if com.apple.security.get-task-allow=false
which is what we use for production. get-task-allow is intended to be used for debugging.
Landing this depends on bug 1593072 to update the build automation to support separate entitlement files.
Assignee | ||
Comment 18•4 years ago
|
||
Like with bug 1606464, this bug does not need to remain private. Yes it's possible to load an unsigned shared library if you have remote-code-execution or if you can launch Firefox with DYLD_INSERT_LIBRARIES on the target system, but this was typical of all apps prior to Mojave when these hardening entitlements were introduced for app use.
For tracking purposes, we have meta bug 1606778 which covers the work required to block loading of libraries not signed by Mozilla or Apple and to block the use of DYLD_INSERT_LIBRARIES in the parent process. To extend that work to content processes, we depend on bug 1593389.
Updated•4 years ago
|
Updated•4 years ago
|
Assignee | ||
Comment 20•2 years ago
|
||
Now that Flash is gone (via bug 1682030), we should be able to use allow-dyld-environment-variables=false
without depending on bug 1593389. I'll work on testing the change.
Assignee | ||
Comment 21•2 years ago
|
||
Small update. I haven't had time to make much more progress on this. I previously tested without the dyld entitlement, but ran into launch failures caused by failures to load libnss3.dylib from the .app bundle. I don't know if this is due to the use of the dyld @executable_path variable or another issue. More debugging needed.
$ ./Nightly.app/Contents/MacOS/firefox -P CodeSignTests
dyld[24028]: Library not loaded: @executable_path/libnss3.dylib
Referenced from: /Users/haftandilian/r/codesign-tree/examples/02-firefox-old/Nightly.app/Contents/MacOS/plugin-container.app/Contents/MacOS/plugin-container
Reason: tried: '/Users/haftandilian/r/codesign-tree/examples/02-firefox-old/Nightly.app/Contents/MacOS/plugin-container.app/Contents/MacOS/libnss3.dylib' (no such file), '/usr/lib/libnss3.dylib' (no such file)
[Parent 24027, IPC Launch #1] WARNING: parent MachReceivePortSendRight failed: 0x10004003 (ipc/rcv) timed out: file /Users/haftandilian/r/mc/ipc/glue/GeckoChildProcessHost.cpp:1308
[Parent 24027, IPC I/O Parent] WARNING: Failed to launch socket subprocess: file /Users/haftandilian/r/mc/ipc/glue/GeckoChildProcessHost.cpp:756
dyld[24029]: Library not loaded: @executable_path/libnss3.dylib
Referenced from: /Users/haftandilian/r/codesign-tree/examples/02-firefox-old/Nightly.app/Contents/MacOS/plugin-container.app/Contents/MacOS/plugin-container
Reason: tried: '/Users/haftandilian/r/codesign-tree/examples/02-firefox-old/Nightly.app/Contents/MacOS/plugin-container.app/Contents/MacOS/libnss3.dylib' (no such file), '/usr/lib/libnss3.dylib' (no such file)
[Parent 24027, IPC Launch #1] WARNING: parent MachReceivePortSendRight failed: 0x10004003 (ipc/rcv) timed out: file /Users/haftandilian/r/mc/ipc/glue/GeckoChildProcessHost.cpp:1308
[Parent 24027, IPC I/O Parent] WARNING: Failed to launch tab subprocess: file /Users/haftandilian/r/mc/ipc/glue/GeckoChildProcessHost.cpp:756
UNSUPPORTED (log once): POSSIBLE ISSUE: unit 1 GLD_TEXTURE_INDEX_2D is unloadable and bound to sampler type (Float) - using zero texture because texture unloadable
JavaScript error: resource:///modules/sessionstore/SessionStore.jsm, line 6291: TypeError: can't access property "sendAsyncMessage", browser.messageManager is null
dyld[24030]: Library not loaded: @executable_path/libnss3.dylib
Referenced from: /Users/haftandilian/r/codesign-tree/examples/02-firefox-old/Nightly.app/Contents/MacOS/plugin-container.app/Contents/MacOS/plugin-container
Reason: tried: '/Users/haftandilian/r/codesign-tree/examples/02-firefox-old/Nightly.app/Contents/MacOS/plugin-container.app/Contents/MacOS/libnss3.dylib' (no such file), '/usr/lib/libnss3.dylib' (no such file)
[Parent 24027, IPC Launch #1] WARNING: parent MachReceivePortSendRight failed: 0x10004003 (ipc/rcv) timed out: file /Users/haftandilian/r/mc/ipc/glue/GeckoChildProcessHost.cpp:1308
[Parent 24027, IPC I/O Parent] WARNING: Failed to launch tab subprocess: file /Users/haftandilian/r/mc/ipc/glue/GeckoChildProcessHost.cpp:756
Assignee | ||
Comment 23•2 years ago
|
||
I've got this to work on an ad-hoc package build. By starting with a package build and then changing the dylib loader paths for the plugin-container
executable and the shared dylibs using install_name_tool(1)
I get a package that launches cleanly with allow-dyld-environment-variables=false
and injection appears to be blocked. The child process needs to be changed to be @executable_path/../../../<dylib>
to reference the location of the dylibs. And the dylibs get changed to use @loader_path
instead of @executable_path
so they correctly load their dylib dependencies when loaded from the parent or child process. See examples below.
The current dyld paths are wrong for plugin-container
, but the dylibs can be loaded because the parent process sets DYLD_LIBRARY_PATH
to the directory in the bundle containing the dylibs such as /Applications/Nightly.app/Contents/MacOS
. This doesn't work with allow-dyld-environment-variables=false
because DYLD_LIBRARY_PATH is ignored.
For plugin-container, changing from
$ otool -L Nightly.app/Contents/MacOS/plugin-container.app/Contents/MacOS/plugin-container
Nightly.app/Contents/MacOS/plugin-container.app/Contents/MacOS/plugin-container:
@executable_path/libnss3.dylib (compatibility version 1.0.0, current version 1.0.0)
@executable_path/XUL (compatibility version 1.0.0, current version 1.0.0)
@executable_path/libmozglue.dylib (compatibility version 1.0.0, current version 1.0.0)
...
to
$ otool -L Nightly.app/Contents/MacOS/plugin-container.app/Contents/MacOS/plugin-container
Nightly.app/Contents/MacOS/plugin-container.app/Contents/MacOS/plugin-container:
@executable_path/../../../libnss3.dylib (compatibility version 1.0.0, current version 1.0.0)
@executable_path/../../../XUL (compatibility version 1.0.0, current version 1.0.0)
@executable_path/../../../libmozglue.dylib (compatibility version 1.0.0, current version 1.0.0)
...
And for the dylibs, using XUL as an example, changing from
$ otool -L Nightly.app/Contents/MacOS/XUL
Nightly.app/Contents/MacOS/XUL:
@executable_path/XUL (compatibility version 1.0.0, current version 1.0.0)
@executable_path/libnss3.dylib (compatibility version 1.0.0, current version 1.0.0)
@executable_path/liblgpllibs.dylib (compatibility version 1.0.0, current version 1.0.0)
@executable_path/libmozglue.dylib (compatibility version 1.0.0, current version 1.0.0)
...
to
$ otool -L Nightly.app/Contents/MacOS/XUL
Nightly.app/Contents/MacOS/XUL:
@executable_path/XUL (compatibility version 1.0.0, current version 1.0.0)
@loader_path/libnss3.dylib (compatibility version 1.0.0, current version 1.0.0)
@loader_path/liblgpllibs.dylib (compatibility version 1.0.0, current version 1.0.0)
@loader_path/libmozglue.dylib (compatibility version 1.0.0, current version 1.0.0)
...
@glandium, do you see any problem with changing the build to have the dyld loader paths setup like this for plugin-container and our dylibs?
Comment 24•2 years ago
|
||
Presumably not, but it depends what we do with non-packaged builds locally. I think we only use dist/*.app, not dist/bin, so that should be fine.
Comment 25•2 years ago
|
||
Removing the stalled
keyword, which is a security evaluation (we need more information to even characterize the bug) and not an engineering state (those would be "wontfix" or "future" or similar). The issue is described clearly.
Assignee | ||
Updated•2 years ago
|
Assignee | ||
Comment 26•2 years ago
|
||
Drop the com.apple.security.cs.allow-dyld-environment-variables entitlement to disallow use of dyld environment variables in signed production builds.
Leave the entitlement in for signed developer builds.
Firefox gtests depend on the use of DYLD_LIBRARY_PATH. However, testing infrastructure does not run gtests on signed builds and therefore gtests are not impacted by this change. gtests could be run on signed developer builds in the future which will still allow dyld environment variables after this change.
browser.production.entitlements.xml and plugin-container.production.entitlements.xml are not used, but being kept up to date.
Comment 27•2 years ago
|
||
Pushed by haftandilian@mozilla.com: https://hg.mozilla.org/integration/autoland/rev/4f741519a2d6 Code Injection in Firefox macOS desktop r=spohl
Comment 28•2 years ago
|
||
bugherder |
Assignee | ||
Comment 29•2 years ago
|
||
Comment on attachment 9279688 [details]
Bug 1562756 - Code Injection in Firefox macOS desktop r?spohl!
ESR Uplift Approval Request
- If this is not a sec:{high,crit} bug, please state case for ESR consideration: The fix provides a security improvement preventing Firefox from loading third-party and possibly malicious dylibs via DYLD environment variables.
This can be a vector for malicious dylibs to inject into Firefox and bypass macOS security controls and gain access to resources Firefox has been allowed by the user to access. For example, if Firefox has been allowed access to the camera/mic for zoom.us, a malicious dylib could be injected into Firefox and use the camera without triggering an OS permission prompt.
The fix depends on bug 1770484.
- User impact if declined: No user-visible impact. Firefox will continue to allow loading of third-party and possibly malicious dylibs.
- Fix Landed on Version: 103
- Risk to taking this patch: Low
- Why is the change risky/not risky? (and alternatives if risky): The change is only to Firefox entitlement files and blocks use of DYLD environment variables which we don't use on production builds.
Comment 30•2 years ago
|
||
Comment on attachment 9279688 [details]
Bug 1562756 - Code Injection in Firefox macOS desktop r?spohl!
Approved for ESR102.1, thanks.
Comment 31•2 years ago
|
||
bugherder uplift |
Comment 32•2 years ago
|
||
This bug in itself does not represent a bypass of any security boundaries, but presented a very useful opportunity for hardening. Flagging this accordingly.
Description
•