- From: Artur Janc <aaj@google.com>
- Date: Sat, 4 Nov 2017 00:40:12 +0100
- To: WebAppSec WG <public-webappsec@w3.org>
- Message-ID: <CAPYVjqo0R06FVNQ8opdvPNTdo=rzrHEh+5yR7PYkjOX1nc9pZQ@mail.gmail.com>
Hey folks, One of the long-standing topics of discussion in this group is the idea of suborigins, and the question of how they might help web authors build more secure applications. There have been some lucid arguments for the utility of the concept in the past, which I tried to distill down to a few core ideas (somewhat independent of the current spec / Chrome prototype) with the hope that it might help with the discussion next week: https://docs.google.com/document/d/1sDVPKmhAdS4aioEguOUOneQkrHoXvZrHUEJEJuWYETI/edit If you have time before TPAC, please take a look and feel free to leave comments on the doc. I'm also pasting the text below for easier reading. Have a good weekend, -Artur *** 1. Introduction It is a great irony of the web platform that its most damaging vulnerability -- XSS, which allows full code execution in the context of the vulnerable origin -- is also the easiest one for developers to introduce. In complex application ecosystems XSS can dwarf other bug classes by an order of magnitude [1,2]; this happens in large part because the most common programming tasks, including displaying text or navigating to another page [3], lead to a vulnerability unless special care is taken to properly handle any untrusted data used in such a context [4]. Historically, one of the most effective approaches for containing potentially vulnerable code in complex applications has been privilege separation, or, reducing the amount of highly trusted code in the system. Focus on such separation has been a common theme among engineers building some of the Internet's most security-critical infrastructure including Bernstein's lessons from developing qmail [5], Provos' privilege separated OpenSSH [6], and the multi-process architecture [7] and ongoing efforts to implement site isolation in Chrome [8]. Like other complex software, modern web applications are often composed of a large number of components developed by dozens of engineers, including third-party libraries written by hundreds of contributors; it is not uncommon for a web application codebase to comprise of millions of lines of code and dependencies [9]. Currently, all such code executes with the full privileges of the hosting origin, and so an XSS bug in any part of the application has complete access to all data and functionality available to scripts in that origin. Due to the linkability inherent to the web, any such flaw can be exploited against authenticated users by pointing them at a vulnerable endpoint. There is no compelling reason to allow each line of code executing in a given origin to access all resources available across that origin. The current lack of security separation in web applications is purely a legacy consequence of developers needing a place under which to map their HTML pages and code, combined with the absence of a way to isolate application components. Suborigins [10, 11] address this problem by providing authors with a lightweight mechanism to separate web application code into individual components, while allowing each component to operate with minimal disruption. In this model, endpoints hosted in suborigins could only be affected by XSS vulnerabilities in the same component, rather than by bugs elsewhere in the origin. 2. Use cases of suborigins In practice, even moderately complex web applications include many different components, including: - Application-specific endpoints corresponding to functionality visible to users. For example, a bulletin board application will typically have a listing of posts, a comment thread for each post, a page to compose a new message, an interface allowing users to log in, and a settings page to manage the user's profile information. - Server-side modules to handle common important actions, e.g. login via OAuth or single sign-on mechanisms [12], or allowing users to upload files [13]. Such modules typically require mapping to the application's URL hierarchy upon installation. - RESTful API endpoints serving data needed to dynamically construct parts of the UI, often returning data formatted as JSON [14] or XML, or even partial HTML templates fetched from the server as a response to a user action and subsequently rendered to the user. - Administrative interfaces accessible to application owners or superusers, e.g. the /admin UI on a blog [15]. Large organizations frequently also expose debugging and other application management endpoints, restricted to developers and internal users. - Client-side modules to allow developers to use widgets such as rich text editors [16] or to embed online ads [17]. This typically requires unpacking third-party code, including scripts and HTML, and hosting it in the application's origin. - Static HTML pages such as a product overview, privacy policy, terms of service page, a marketing subsite encouraging users to sign up for the application, or other auxiliary content. In ecosystems where legacy HTML pages are co-hosted alongside authenticated applications, DOM XSS bugs in static content can provide an effective avenue for attacks on sensitive user data [18]. - Exception responses triggered by various error conditions (invalid request parameters, lack of access to a given resource, etc). Such application components are generally independent from one another, may be written using different languages and development frameworks, and -- particularly in the case of functionality tangential to the main purpose of the application -- often include legacy code incompatible with modern security best practices. More worryingly, even seemingly simple content can pose a disproportionate risk of introducing XSS bugs: error pages typically eschew the protections of HTML template systems and generate responses by concatenating strings (a common anti-pattern responsible for XSS), and static HTML pages tend to load complex JS libraries, allowing client-side logic to introduce hard to spot DOM XSS vulnerabilities [19]. The key insight of suborigins is that such application components are logically distinct enough that developers may often separate them into different security principals without affecting existing functionality. In particular, in this model high-value endpoints could be isolated to protect the most sensitive parts of the application, without requiring changes to unrelated code exposed in the origin. 3. Goals of suborigins The sole objective of suborigins is to provide privilege separation between client-side code executing in the same web origin to protect sensitive functionality from XSS vulnerabilities in unrelated components. To achieve this, a complementary design goal for suborigins is a focus on facilitating adoption in web applications. As a defense-in-depth mechanism, suborigins must be readily deployable in a number of scenarios to ensure their use by web authors and security engineers. While a discussion of the full implications of these considerations is out of scope here, they influence the design goals of suborigins in two important ways: - Suborigins should provide "bi-directional" protection so that they can be used to isolate an application in a suborigin from malicious scripts in the main origin. In contrast to other features such as sandbox frames or CSP this allows protecting sensitive parts of the application instead of attempting to enumerate and contain all potential sources of maliciously injected code in the origin. - Adopting suborigins should require minimal changes to application code and, in common scenarios, be possible solely by adding server-side middleware to set the Suborigin header and properly handle any CORS requests from the same suborigin. This means that suborigins should be able to obtain access to some capabilities granted to the origin as long as this doesn't conflict with the goal of protecting the user from XSS. For example, in some cases a suborigin may need to be able to inherit browser permissions (e.g. for location or fullscreen access) or origin state, such as cookies, if allowed by the developer in the suborigin configuration [20]. It is worth noting that the threat model of suborigins explicitly excludes other security risks such as compromise of the server-side infrastructure, malicious insiders, or other web security flaws such as CSRF or clickjacking. 4. Shortcomings of current web isolation mechanisms The draft of the suborigin specification offers a detailed discussion [21] of the difficulties of using sandbox iframes and hostname separation as a mechanism to isolate code; we offer only a short summary of the main issues. Sandbox iframes -- or sandbox documents, if the designation is delivered in a CSP header -- are meant to contain wholly untrusted content and isolate it from the rest of the hosting origin. They disable some crucial web features (client-side storage, permissions), make it purposefully difficult to communicate with other content in the same origin (they send CORS and postMessage as the "null" origin), and propagate sandbox restrictions to all sub-frames and newly opened windows. Perhaps most importantly, they require placing all potentially untrusted, less-privileged or vulnerable content in sandboxes, which is not practical for most applications. An alternative approach of splitting a web application across multiple hostnames can be tantalizing, but it runs into several practical problems which prevent deployment in most common scenarios. This includes the necessity to create additional subdomains and re-organize the application so that each logical component is only accessible on its own subdomain, leading to an inflexible design intrinsically tied to the domain's DNS structure. Migration of application code is hindered by the lack of access to existing cookies and origin permissions, inability to use common origin-based APIs (e.g. history.pushState) and incompatibility with existing links and bookmarks. Most damningly, even if a developer can separate her application into functional components and expose them in subdomains, each subdomain will likely still have to host many unrelated features (error handlers, SSO endpoints, static content belonging to the given part of the application), reducing the security value of such a scheme. The paucity of existing examples of applications which split their core components across hostnames is evidence of the difficulty of employing this approach. Content Security Policy, currently the most prominent XSS mitigation, is largely complementary to suborigins: it may prevent code execution in the event of a markup injection flaw, but it doesn't protect an application from vulnerabilities in other components exposed in the same origin. Existing platform mechanisms are useful purpose-specific tools, but they stop short of enabling generic privilege separation on the web. 5. Parting thoughts Suborigins are fairly uniquely positioned to allow web authors to introduce meaningful protections against XSS in their applications. They introduce an implementation of privilege separation to the web platform, while providing allowances for real-world behaviors which reduce the restrictions on existing applications without eliminating the security benefits. We believe that implementing suborigins as a first-class feature of the web platform can add an important missing primitive and have a positive long-term impact on the state of web security. 6. References [1] https://twitter.com/jvehent/status/911192609699373056 [2] Google VRP data from 2012-2016 shows XSS to be >60% of high-risk bugs, amounting to 100+ XSS issues in sensitive origins each year. [3] Markup injection and navigational XSS (via javascript: URIs) are two major root causes of XSS on the web. Common sources of XSS have historically included the following patterns: - Generating server-side responses without properly escaping untrusted data. - Generating markup on the client-side and passing it to an unsafe DOM API (Element.innerHTML, document.write, and their library wrappers such as jQuery.html) without properly escaping untrusted data. - Allowing navigation to untrusted URLs when generating links in server-side code (<a href="javascript:evil()">) or when performing client-side navigations using an unsafe DOM API (window.open, location.href = ...). - Removing default escaping applied by the framework or template system, usually under a mistaken assumption that data is fully application controlled (Django |safe, Closure templates |noAutoescape, bypassSecurityTrustAs* functions in Angular) - Applying escaping incorrectly (e.g. HTML-escaping in a JS inline event handler), or forgetting to escape all metacharacters required by a given document context. - Use of untrusted data in calls or assignments to any of the ~50 other DOM execution sinks, e.g. setTimeout, eval, ScriptElement.src, FormElement.action, etc. Most server-side frameworks and JavaScript libraries also introduce their own APIs which result in unsafe behaviors equivalent to the patterns above. [4] There are some successful examples of web applications which have significantly reduced their susceptibility to XSS, including Facebook, GitHub and users of certain internal frameworks at Google. They have usually done so by imposing constraints on web application authors to prevent them from using unsafe native web APIs, e.g. requiring them to use safe wrappers around dangerous DOM methods, and allowing server-side responses to be generated only by template systems which guarantee proper escaping of interpolated data. In all cases this required building layers of technical and process safeguards (e.g. enforcing the compilation of all JS and preventing the use of dangerous methods; requiring security code reviews to allow exceptions) to work around the inherent unsafety of the web platform. [5] https://cr.yp.to/qmail/qmailsec-20071101.pdf, Sections 2.3, 5.1-4 [6] http://www.citi.umich.edu/u/provos/ssh/privsep.html http://www.citi.umich.edu/u/provos/papers/privsep.pdf, Section 3 [7] https://www.chromium.org/developers/design-documents/multi-process-architecture [8] https://www.chromium.org/developers/design-documents/site-isolation [9] https://www.wired.com/2015/09/google-2-billion-lines-codeand-one-place/ [10] https://w3c.github.io/webappsec-suborigins/ [11] http://www.chromium.org/developers/design-documents/per-page-suborigins [12] https://django-oauth-toolkit.readthedocs.io/en/latest/tutorial/tutorial_01.html [13] https://www.npmjs.com/package/express-fileupload [14] https://codex.wordpress.org/Administration_Screens [15] https://scotch.io/tutorials/build-a-restful-json-api-with-rails-5-part-one [16] http://cdn.ckeditor.com/4.4.8/full-all/samples/replacebyclass.html [17] https://support.google.com/richmedia/answer/117857?hl=en [18] Data from surveys of vulnerabilities at Google indicates that legacy code is disproportionately likely to contain XSS bugs. One prominent example was a decade-old “Google Zeitgeist” static page hosted under a sensitive domain, which suffered from a DOM XSS bug which affected a large number of other applications. [19] https://bugs.chromium.org/p/chromium/issues/detail?id=651822 [20] https://w3c.github.io/webappsec-suborigins/#unsafe-cookies [21] https://w3c.github.io/webappsec-suborigins/#intro
Received on Friday, 3 November 2017 23:40:59 UTC