CORS and RFC1918

A Collection of Interesting Ideas,

This version:
https://wicg.github.io/cors-rfc1918/
Editor:
(Google Inc.)

Abstract

This document specifies modifications to Fetch which are intended to mitigate the risks associated with unintentional exposure of devices and servers on a client’s internal network to the web at large.

1. Introduction

This section is not normative.

Although [RFC1918] has specified a distinction between "private" and "public" internet addresses for over a decade, user agents haven’t made much progress at segregating the one from the other. Websites on the public internet can make requests to internal devices and servers, which enable a number of malicious behaviors, including attacks on users' routers like those documented in [DRIVE-BY-PHARMING] (and, more recenly, [SOHO-PHARMING] and [CSRF-EXPLOIT-KIT]).

Here, we propose a mitigation against these kinds of attacks that would require internal devices to explicitly opt-in to requests from the public internet.

1.1. Goals

The overarching goal is to prevent the user agent from inadvertantly enabling attacks on devices running on a user’s local intranet, or services running on the user’s machine directly. For example, we wish to mitigate attacks on:

1.2. Examples

1.2.1. Secure by Default

MegaCorp Inc’s routers have a fairly serious CSRF vulnerability which allows their DNS settings to be altered by navigating to http://admin:admin@router.local/set_dns and passing in various GET parameters. Oh noes!

Happily, MegaCorp Inc’s routers don’t have any interest in requests from the public internet, and didn’t take any special effort to enable them. This greatly mitigates the scope of the vulnerability, as malicious requests will generate a CORS-preflight request, which the router ignores. Let’s take a closer look:

Given https://csrf.attack/ that contains the following HTML:

<iframe href="https://admin:admin@router.local/set_dns?server1=123.123.123.123">
</iframe>

router.local will be resolved to the router’s address via the magic of multicast DNS [RFC6762], and the user agent will note it as private. Since csrf.attack resolved to a public address, the request will trigger a CORS-preflight request:

OPTIONS /set_dns?... HTTP/1.1
Host: router.local
Access-Control-Request-Method: GET
Access-Control-Request-External: true
...
Origin: https://csrf.attack

The router will receive this OPTIONS request, and has a number of possible safe responses:

  • If it doesn’t understand OPTIONS at all, it can return a 50X error. This will cause the preflight to fail, and the actual GET will never be issued.

  • If it does understand OPTIONS, it can neglect to include an Access-Control-Allow-External header in its response. This will cause the preflight to fail, and the actual GET will never be issued.

  • It can crash. Crashing is fairly safe, if inelegant.

1.2.2. Opting-In

Some of MegaCorp Inc’s devices actually need to talk to the public internet for various reasons. They can explicitly opt-in to receiving requests from the internet by sending proper CORS headers in response to a CORS-preflight request.

When a website on the public internet makes a request to the device, the user agent determines that the requestor is public, and the router is private. This means that requests will trigger a CORS-preflight request, just as above.

The device can explicitly grant access by sending the right headers in its response to the preflight request. For the above request, that might look like:

HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: https://public.example.com
Access-Control-Allow-Methods: GET
Access-Control-Allow-Credentials: true
Access-Control-Allow-External: true
Vary: Origin
Content-Length: 0
...
MegaCorp Inc. runs an internal link shortening service at https://go/, and its employees often email such links to each other. The email server is hosted at a public address in order to ensure that employees can work even when they’re not at the office. How considerate!

Clicking http://go/* links from https://mail.mega.corp/ will trigger a CORS-preflight request, as it is a request from a public address to a private address:

OPTIONS /short-links-are-short-after-shortening HTTP/1.1
Host: go
Access-Control-Request-Method: GET
Access-Control-Request-External: true
...
Origin: https://mail.mega.corp

In order to ensure that employees can continue to navigate such links as expected, MegaCorp chooses to allow external requests:

HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: https://mail.mega.corp
Access-Control-Allow-Methods: GET
Access-Control-Allow-Credentials: true
Access-Control-Allow-External: true
Vary: Origin
Content-Length: 0
...

MegaCorp’s leak-prevention department is worried, though, that this access will allow external folks to read the location of any redirect that the shortener would return. They’re more or less resigned to the fact that https://go/shortlink will leak, but would be sad indeed if the target (https://sekrits/super-sekrit-project-with-super-sekrit-partner) leaked as well.

MegaCorp’s shortlink engineers are careful to avoid this potential failure by returning CORS headers only for the preflight. The "real" navigation doesn’t require CORS headers, and they don’t actually want to support cross-origin requests as being CORS-same-origin:

// Request:
GET /short-links-are-short-after-shortening HTTP/1.1
Host: go
Access-Control-Request-External: true
...
Origin: https://mail.mega.corp

// Response:
HTTP/1.1 301 Moved Permanently
...
Location: https://sekrits/super-sekrit-project-with-super-sekrit-partner

The navigation will proceed normally, but mail.mega.corp won’t be considered CORS-same-origin with the response.

2. Framework

An IPv4 address is a private address if it matches the private address space defined in Section 3 of [RFC1918], a local address if it matches the "loopback" space (127.0.0.0/8) defined in section 3.2.1.3 of [RFC1122] or the "link-local" space (169.254.0.0/16) defined in [RFC3927], and a public address otherwise.

An IPv6 address is a local address if it matches the "Unique Local Address" prefix (fc00::/7) defined in Section 3 of [RFC4193] or the "link-local" prefix (fe80::/10) defined in section 2.5.6 of [RFC4291], and a public address otherwise.

What does "private" mean in the context of IPv6? <https://github.com/mikewest/cors-rfc1918/issues/3>

A request (request) is an external request if any of the following are true, and an internal request otherwise:

  1. request’s current url’s host maps to a private address, and request’s client’s address space is "public".

  2. request’s current url’s host maps to a local address, and request’s client’s address space is either "public" or "private".

2.1. Additional CORS Headers

The Access-Control-Request-External indicates that the request is an external request.

The Access-Control-Allow-External indicates that a resource can be safely shared with external networks.

2.2. The treat-as-public-address Content Security Policy Directive

The treat-as-public-address directive instructs the user agent to treat a document as though it was served from a public address, even if it was actually served from a private address. That is, it is a mechanism by which private documents may drop the privilege to contact other private documents without a preflight.

The directive’s syntax is described by the following ABNF grammar:

directive-name  = "treat-as-public-address"
directive-value = ""

This directive has no reporting requirements; it will be ignored entirely when delivered in a Content-Security-Policy-Report-Only header, or within a meta element.

This directive’s initialization algorithm is as follows. Given a Document or global object (context), a Response (response), and a policy (policy):

  1. Set context’s address space to "public" if policy’s disposition is "enforce".

2.3. Feature Detection

To determine the address space in which a context finds itself, a simple enum value is added to Document and WorkerGlobalScope:

enum AddressSpace { "local", "private", "public" };

partial interface Document {
  readonly attribute AddressSpace addressSpace;
};

partial interface WorkerGlobalScope {
  readonly attribute AddressSpace addressSpace;
};

Both attributes' getters return the value of the corresponding Document or WorkerGlobalScope's address space property.

3. Integrations

This section is non-normative.

This document proposes a number of modifications to other specifications in order to implement the mitigations sketched out in the examples above. These integrations are outlined here for clarity, but the external documents are the normative references.

3.1. Integration with Fetch

This document proposes a few changes to Fetch, with the following implications:

  1. Requests whose client’s address space is "local" are unchanged from status quo. They may continue to make requests to public, private, and local addresses as they do today.

    Chris Palmer suggests that we might want to change the proposal such that private services must always opt-in to being contacted by anyone other than themselves. That is, we’d force a preflight for all cross-origin requests to private servers, whether they come from public addresses, or private addresses. <https://github.com/wicg/cors-rfc1918/issues/1>

  2. Requests whose client’s address space is "private" are allowed to fetch resources from private and public addresses as they do today, but may only request local resources if their client is a secure context and a CORS-preflight request to the target origin is successful.

  3. Requests whose client’s address space is "public" are allowed to fetch resources from public addresses as they do today, but may only request private and local resources if their client is a secure context and a CORS-preflight request to the target origin is successful.

Note: UAs must not allow non-secure public contexts to request resources from private addresses, even if the private server would opt-in to such a request via a preflight. Making requests to private resources presents risks which are mitigated by ensuring the integrity of the client which initiates the request. In particular, network attackers should not be able to trivially exploit an endpoint’s consent to a non-secure origin.

To those ends:

  1. The HTTP fetch algorithm should be adjusted to ensure that a preflight is triggered for all external requests. This might be as simple as changing the current "HTTP(S) scheme" block of the switch statement to the following:

    1. If request is an external request, return the result of performing an HTTP fetch using request with the CORS-preflight flag set.

      Otherwise, return the result of performing an HTTP fetch using request with the CORS-preflight flag unset.

    Note: We do not set the CORS flag in this case, as we’re dealing with either a same-origin request that has been tainted by something like a §5.3 DNS Rebinding attack, a navigation, or a no-cors request. The preflight alone should be enough to mitigate the risks of such requests.

    Note: This will require a preflight for every request initiated from a public address that targets a private address. This includes navigations.

  2. The CORS-preflight fetch algorithm should be adjusted to append an Access-Control-Request-External header for preflights triggered by external requests. For instance, the following could be executed after the current step 5:

    1. If request is an external request:

      1. If request’s client is not a secure context, return a network error.

        Note: We don’t need to touch the network to know that this request will be denied.

      2. Set "Access-Control-Request-External" to "true" in preflight’s header list.

  3. The CORS-preflight fetch algorithm should be further adjusted to ensure that consent is explicitly granted via an appropriate "Access-Control-Allow-External" header in the response. For instance, the following could be executed before the current step 10:

    1. If request is an external request:

      1. Let external be the result of extracting header list values given "Access-Control-Allow-External" and response’s header list.

      2. If external is not "true", return a network error.

  4. Finally, to mitigate the impact of DNS rebinding attacks (see §5.3 DNS Rebinding), the CORS-preflight cache should be adjusted to distinguish between request types. For example, we could:

    1. Add "request network type" to each cache entry, and change the cache match algorithm to take it into account.

    2. Populate request network type according to the nature of request in steps 7.12 and 7.14 of the current CORS-preflight fetch algorithm.

    3. Change the cache match algorithm to take

[FETCH] does not yet integrate the details of DNS resolution into the Fetch algorithm, though it does define an obtain a connection algorithm which is a good start. We’ll need to think about things like Happy Eyeballs [RFC6555] to make sure that we perform the right checks depending on the IP address we actually connect to. For the moment, this document assumes that connection information is available in the HTTP fetch algorithm. That might not be a reasonable assumption, in which case we’ll need to revisit all of this.

3.2. Integration with WebSockets

This document proposes a few changes to WebSockets, with similar implications to the above.

  1. The establish a WebSocket connection algorithm should be adjusted to ensure that the endpoint is warned about external requests. For instance, the following could be executed after the current step 7:

    1. If request is an external request, then append Access-Control-Request-External/"true" to request’s header list.

  2. Further, the same algorithm should be adjusted to check the response for an opt-in. For instance, the following could be executed after the current step 12:

    1. If request is an external request, then:

      1. Let external be the result of extracting header list values given "Access-Control-Allow-External" and response’s header list.

      2. If external is not "true", return a network error.

Look into this. Adding headers to the handshake seems reasonable, but we might need to go beyond that and require a preflight. Not sure what the capabilities of the GET are, and how much control the caller has.

3.3. Integration with HTML

To support the checks in [FETCH], we store an address space on both Document and WorkerGlobalScope objects, which is set as follows during Document and Worker initialization:

  1. Set the address space to "local" if the the resource used to instantiate the Document or Worker was was delivered from a local address, or from a URL whose scheme is "file" (see §4.1 Where do file URLs fit?).

  2. Set the address space to "private" if the the resource used to instantiate the Document or Worker was was delivered from a private address.

  3. Set the address space to "public" if the the resource used to instantiate the Document or Worker was was delivered from a public address.

Assuming that example.com resolves to a public address (say, 123.123.123.123), then the Document created when navigating to https://example.com/document.html will have its address space set to "public".

If, on the other hand, example.com resolved to a local address (say, 127.0.0.1), then the Document created when navigating to https://example.com/document.html will have its address space set to "local".

4. Implementation Considerations

4.1. Where do file URLs fit?

It isn’t entirely clear how file URLs fit into the public/private scheme outlined above. It would be nice to prevent folks from harming themselves by opening a malicious HTML file locally, on the one hand, but on the other, code running locally is somewhat outside of any sane threat model.

For the moment, let’s err on the side of treating file URLs as local, as they seem to be just as much a part of the local system as anything else on a loopback address.

Reevaluate this after implementation experience.

5. Security and Privacy Considerations

5.1. User Mediation

The proposal in this document only ensures that the device consents to access from the public internet. Users agents SHOULD ensure that the user consents to such access as well, as it might be in their interests to deny such access, even though the device itself would allow it.

This mediation could be done via an explicit permission grant, via some sort of pairing ceremony a la PAKE, or any other clever interface which the user agent might devise.

5.2. Mixed Content

Note that the CORS restrictions added by the proposal in this document do not obviate mixed content checks [MIXED-CONTENT]. Developers who wish to fetch private resources from public pages MUST ensure that the connection is secure. This might involve a solution along the lines of [PLEX], or we might end up inventing a new way of ensuring a secure connection to devices (perhaps the pairing ceremony hinted at above, or one of the ideas floated in [SECURE-LOCAL-COMMUNICATION]?). In either case, consenting to access by sending proper CORS is necessary, but not sufficient.

Note: Doing something like the proposal here would make me more comfortable with relaxing the mixed content restrictions that prohibit unencrypted connections to loopback addresses. Right now, those aren’t really subject to the kinds of concerns that drive most of [MIXED-CONTENT]'s decisions, but I’m reluctant to change Chrome’s implementation without some protection for the local server and user.

5.3. DNS Rebinding

The mitigation described here operates upon the IP address which the user agent actually connects to when loading a particular resource. This check MUST be performed for each new connection made, as DNS rebinding attacks may otherwise trick the user agent into revealing information it shouldn’t.

The modifications to the CORS-preflight cache are intended to mitigate this attack vector.

5.4. Scope of Mitigation

The proposal in this document merely mitigates attacks against private web services, it cannot fully solve them. For example, a router’s web-based administration interface must be designed and implemented to defend against CSRF on its own, and should not rely on a UA that behaves as specified in this document. The mitigation this document specifies is necessary given the reality of private web service implementation quality today, but vendors should not consider themselves absolved of responsibility, even if all UAs implement this mitigation.

6. IANA Considerations

The Content Security Policy Directive registry should be updated with the following directives and references [RFC7762]:

treat-as-public-address

This document (see §2.2 The treat-as-public-address Content Security Policy Directive)

7. Acknowledgements

Conversations with Ryan Sleevi, Chris Palmer, and Justin Schuh helped flesh out the contours of this proposal. Hopefully they won’t hate it too much. Mathias Karlsson has the dubious honor of being the straw that broke the camel’s back, and Brian Smith’s contributions to the resulting thread were useful, as always.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSP3]
Mike West. Content Security Policy Level 3. URL: https://www.w3.org/TR/CSP3/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[MIXED-CONTENT]
Mike West. Mixed Content. URL: https://www.w3.org/TR/mixed-content/
[RFC1122]
R. Braden, Ed.. Requirements for Internet Hosts - Communication Layers. October 1989. Internet Standard. URL: https://tools.ietf.org/html/rfc1122
[RFC1918]
Y. Rekhter; et al. Address Allocation for Private Internets. February 1996. Best Current Practice. URL: https://tools.ietf.org/html/rfc1918
[RFC3927]
S. Cheshire; B. Aboba; E. Guttman. Dynamic Configuration of IPv4 Link-Local Addresses. May 2005. Proposed Standard. URL: https://tools.ietf.org/html/rfc3927
[RFC4193]
R. Hinden; B. Haberman. Unique Local IPv6 Unicast Addresses. October 2005. Proposed Standard. URL: https://tools.ietf.org/html/rfc4193
[RFC4291]
R. Hinden; S. Deering. IP Version 6 Addressing Architecture. February 2006. Draft Standard. URL: https://tools.ietf.org/html/rfc4291
[RFC7762]
M. West. Initial Assignment for the Content Security Policy Directives Registry. January 2016. Informational. URL: https://tools.ietf.org/html/rfc7762
[SECURE-CONTEXTS]
Mike West; Yan Zhu. Secure Contexts. URL: https://w3c.github.io/webappsec-secure-contexts/
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/

Informative References

[AVASTIUM]
Avast: A web-accessible RPC endpoint can launch 'SafeZone' (also called Avastium), a Chromium fork with critical security checks removed.. URL: https://code.google.com/p/google-security-research/issues/detail?id=679
[CSRF-EXPLOIT-KIT]
Kafeine. An Exploit Kit dedicated to CSRF Pharming. URL: http://malware.dontneedcoffee.com/2015/05/an-exploit-kit-dedicated-to-csrf.html
[DRIVE-BY-PHARMING]
Sid Stamm; Zulfikar Ramzan; Markus Jakobsson. Drive-By Pharming. URL: https://www.symantec.com/avcenter/reference/Driveby_Pharming.pdf
[PLEX]
Filippo Valsorda. How Plex is doing HTTPS for all its users. URL: https://blog.filippo.io/how-plex-is-doing-https-for-all-its-users/
[RFC6555]
D. Wing; A. Yourtchenko. Happy Eyeballs: Success with Dual-Stack Hosts. April 2012. Proposed Standard. URL: https://tools.ietf.org/html/rfc6555
[RFC6762]
S. Cheshire; M. Krochmal. Multicast DNS. February 2013. Proposed Standard. URL: https://tools.ietf.org/html/rfc6762
[SECURE-LOCAL-COMMUNICATION]
Minutes from 'Secure communication with local network devices': TPAC, 2015. URL: http://www.w3.org/2015/10/28-local-minutes.html
[SOHO-PHARMING]
Team Cymru. SOHO Pharming. URL: https://www.team-cymru.com/ReadingRoom/Whitepapers/2013/TeamCymruSOHOPharming.pdf
[TREND-MICRO]
TrendMicro node.js HTTP server listening on localhost can execute commands. URL: https://code.google.com/p/google-security-research/issues/detail?id=693

IDL Index

enum AddressSpace { "local", "private", "public" };

partial interface Document {
  readonly attribute AddressSpace addressSpace;
};

partial interface WorkerGlobalScope {
  readonly attribute AddressSpace addressSpace;
};

Issues Index

What does "private" mean in the context of IPv6? <https://github.com/mikewest/cors-rfc1918/issues/3>
Chris Palmer suggests that we might want to change the proposal such that private services must always opt-in to being contacted by anyone other than themselves. That is, we’d force a preflight for all cross-origin requests to private servers, whether they come from public addresses, or private addresses. <https://github.com/wicg/cors-rfc1918/issues/1>
[FETCH] does not yet integrate the details of DNS resolution into the Fetch algorithm, though it does define an obtain a connection algorithm which is a good start. We’ll need to think about things like Happy Eyeballs [RFC6555] to make sure that we perform the right checks depending on the IP address we actually connect to. For the moment, this document assumes that connection information is available in the HTTP fetch algorithm. That might not be a reasonable assumption, in which case we’ll need to revisit all of this.
Look into this. Adding headers to the handshake seems reasonable, but we might need to go beyond that and require a preflight. Not sure what the capabilities of the GET are, and how much control the caller has.
Reevaluate this after implementation experience.