In a recent academic publication titled Hardening Firefox against Injection Attacks (to appear at SecWeb – Designing Security for the Web) we describe techniques which we have incorporated into Firefox to provide defense in depth against code injection attacks. Within this blogpost we are going to provide insights into the described hardening techniques at a technical level with pointers to the actual code implementing it. Note that links to source code are perma-linked to a recent revision as of this blog post. More recent changes may have changed the location of the code in question.
Background on Privilege Separation in Firefox
The security architecture for separating web content and privileged (chrome) content within Firefox builds upon (1) the security boundaries provided by process separation and (2) the security boundaries provided by context separation.
- The privileged chrome process is able to perform any action the end user has access to. In contrast, the lesser privileged content process (used for loading web content) is sandboxed and restricted from almost all access to Operating System features or user privileges.
- The security context of the User Interface and privileged chrome code within Firefox is represented by the System-Principal. In contrast, the security context of untrusted web content builds upon the security context represented by a Content-Principal. (Please find details on the different kinds of Principals within Firefox in our recent post: Understanding Web Security Checks in Firefox).
The two privilege distinctions – a privileged parent process and other lesser privileged child processes; and the privileged System Principal Context and the lesser privileged Content and Null Principal Contexts – both describe mechanisms of privilege distinction within Firefox. However, the actual mechanism by which privilege is separated is generally unimportant in the context of privilege escalation attacks, which our hardening prevents.
While process-based separation is a fairly generic architecture, Principal-based separation is specific to Gecko – the rendering engine powering Firefox. Nonetheless, tricking system-principled code into doing your bidding is just as alarming as a cross-process attack. It is considered an injection attack and exposes a significant security bypass.
Securing about: pages
Firefox ships with a variety of built-in pages, commonly referred to as about: pages. Such about: pages allow the user to view internal browser information or change settings.
Further, we verify that any newly added about: page within Firefox exposes a strong CSP by consulting the function AssertAboutPageHasCSP(). This function basically acts as a commit guard to our codebase and ensures that no about: page makes it into the Firefox codebase without a strong CSP.
Before we started to protect about: pages with a CSP we faced a bug where text and markup controlled by a web application was reused in a permission prompt, which led to a Universal Cross-Site Scripting (UXSS) attack in the browser interface (CVE-2018-5124). These scripts run with elevated privileges that get access to internal APIs and can result in a full system compromise. What raises the severity of such bugs is the high-level nature of the vulnerability and the highly deterministic nature of the exploit code which allowed comparably trivial exploitation.
Restricting Loads in Privileged Contexts
Higher privileged contexts within Firefox have access to APIs that are capable of modifying the browser settings and its user interface. Most prominently, about:config, which exposes an API to inspect and update preferences and settings which allows Firefox users to tailor their Firefox instance to their specific needs and desires. Being able to set arbitrary preferences is powerful enough that we treat it as a full security bypass.
We ensure that privileged APIs (such as those that allow setting preferences) are restricted to HTML documents and scripts that are part of the Firefox source code. More precisely, system privileged code within Firefox should never need to load remote web content, because all the user interface code is packaged and shipped with the browser itself.
In addition to fixing the specific problem, and as a defense in depth mechanism, we started to enforce additional restrictions to eliminate that entire class of security problems. By adding and consulting the function CanLoadInParentProcess() Firefox is effectively not allowing documents with a scheme other than chrome:, about: or resource: to load in the parent process. Adding this additional restriction limits document loads to the aforementioned schemes and therefore prevents any remote web document loads in the parent process.
To systematically restrict all resource loads (document and subresource loads) into system privileged context or the parent process all loads consult the function CheckAllowLoadInSystemPrivilegedContext(). Since Firefox enforces a Security by Default loading mechanism for all resource loads we build our monitoring system on top of these efforts which allows us to enforce runtime assertions for every resource load. This security mechanism ensures that Firefox can only load resources into system privileged context or the parent process if the resource is packaged and shipped with the browser, hence eliminating an entire set of privilege escalation attacks.
Restricting Eval in System Privileged Contexts
To provide a defense in depth mechanism against string injection attacks which would turn into executable code at runtime, we removed all instances of eval() in system privileged code. By consulting the function IsEvalAllowed() at runtime we ensure that all calls to eval() are blocked in system privileged contexts.
Removing all occurrences of eval() in system privileged contexts reduces the attack surface of arbitrary code execution and hence provides an additional layer of security against code injection attacks.
Restricting access through X-Ray Vision
Though Firefox has been shipping with a hardening mechanism known as X-Ray Vision for a multitude of years, X-Ray Vision builds the foundation for the hardening work we are presenting in this blogpost. Not having X-Ray vision would render all additional hardening efforts obsolete, hence it’s worth sketching out fundamentals about that security mechanism within this blogpost.
The fundamental principle of X-Ray Vision is that dynamic content-controlled data is only exposed through well-defined access points for a given object type. When a privileged script accesses an object, it sees only the native version of the object. If any properties of the object have been redefined by web content, it sees the original implementation, not the redefined version.
Adding layers of security allows us to harden the codebase of Firefox against all injection attacks we are not aware of or may get introduced in the future. We encourage contributors, hackers, and bug bounty hunters to verify there are no flaws in our design. While the provided mechanisms provide a solid line of defense we would like to call out that bypassing any of the presented defense in depth mechanisms is eligible for a bug bounty.