Anti-XSS Response-Time Uniqueness Requirement

A Collection of Interesting Ideas,

Version History:
https://github.com/mikewest/artur-yes/commits/master/index.src.html
Issue Tracking:
GitHub
Inline In Spec
Editor:
Mike West
Participate:
File an issue (open issues)

Abstract

This document describes a mechanism which allows developers to restrict the ways in which their pages can execute script, with the goal of mitigating cross-site scripting vulnerabilities.

1. Introduction

Cross-site scripting is a problem for developers. Despite the existence of context-aware escaping mechanisms in templating systems, content injection continues to be one of of the most common attack vectors.

Content Security Policy [CSP] aims to reduce the risk of these attacks, and is widely supported by browsers. It has proven to be difficult to deploy, however, as the syntax is complicated, the overriding rules are confused, and the feature itself serves several different purposes. Recent research has shown flaws in real-world deployments result in policies that are ineffective at directly preventing cross-site scripting in the overwhelming majority of cases [CSP-IS-DEAD]. That research, and related discussions, has lead to new approaches within CSP (like those described in Content Security Policy Level 3 §strict-dynamic-usage) which seem promising, but the syntax ends up being somewhat verbose and the behavior confusing due to backwards compatibility concerns.

This document proposes a policy language focused strictly on XSS protection, with the goal of providing the same XSS mitigation guarantees as a verbose and complicated CSP, but in a pithy and comprehensible package.

MegaCorp, Inc. wishes to defend against cross-site scripting. It can do so by generating a unique string for each page load, and including that string both in an ARTUR header and in the script element’s nonce attribute, as follows:
ARTUR: { "nonce": "abcdefg" }
<!-- This script will execute. -->
<script src="script.js" nonce="abcdefg"></script>

<!-- This script won’t (no nonce). -->
<script src="script.js"></script>

2. Framework

This specification uses terminology from [RFC5234] and [HTTP-JFV] to define its syntax expectations, and relies heavily on [CSP] (which itself relies on [FETCH] and [HTML]) for enforcement.

2.1. Policy

An ARTUR policy defines a set of behaviors which are to be applied to a Window or WorkerGlobalScope.

Each policy has a nonce which is a string. Its value is the empty string unless otherwise specified.

Each policy has a hash set which is a set of strings. Its value is the empty set unless otherwise specified.

Each policy has a reporting disposition which is either "enforce" or "report". Its value is "enforce" unless otherwise specified.

Each policy has a reporting group which is a string. Its value is the string "default" unless otherwise specified.

Each policy has a eval policy which is either "block" or "unsafe-eval". Its value is "unsafe-eval" unless otherwise specified.

2.2. Algorithms

2.2.1. Obtain a set of ARTUR policies for response.

Given a response (response), this algorithm returns a sequence of ARTUR policy objects. If no valid policy is found for response, the returned sequence will be empty.

  1. Let policies be an empty sequence.

  2. Let value be the result of parsing "ARTUR" in response’s header list.

  3. If value is null, return policies.

  4. Let list be the result of executing the algorithm defined in Section 4 of [HTTP-JFV] on value. If that algorithm results in an error, return policies.

  5. For each item in list:

    1. Let policy be a new ARTUR policy.

    2. If item has a hash member whose value (value) is a sequence<DOMString>, then for each digest in value:

      1. If digest matches the ABNF grammar hash-algorithm-hash-source (as defined in [CSP]), then add digest to policy’s hash set.

    3. If item has a nonce member whose value (value) is a DOMString that matches the ABNF grammar base64-value (as defined in [CSP]), then set policy’s nonce to value.

    4. If item has a report member whose value (value) matches the token grammar (as defined in [RFC7230]), then set policy’s reporting group to value.

    5. If item has a eval member whose value (value) is a valid EvalPolicy, then set policy’s eval policy to value.

    6. Append policy to policies.

  6. Return policies.

The ARTUR HTTP response header field is the delivery mechanism for the mitigations this document offers. The header’s value is a serialized ARTUR policy represented by the following ABNF [RFC5234]:

ARTUR = json-field-value
ARTUR: { "nonce": "abcdefg", "report": "group1" }

This header will have more or less the same effect as delivering the following header:

Content-Security-Policy: script-src 'strict-dynamic' 'nonce-abcdegf';
                         object-src 'none';
                         report-to group1;

A server MAY send different ARTUR header field values with different representations of the same resource.

The header’s value is interpreted as an array of JSON objects, as described in Section 4 of [HTTP-JFV]. Detailed parsing instructions are found in §2.2.1 Obtain a set of ARTUR policies for response., but the syntax boils down to a representation of the following dictionary type:

enum EvalPolicy {
  "block",
  "unsafe-allow"
};

dictionary ArturPolicy {
  EvalPolicy eval = "unsafe-allow";
  sequence<DOMString> hash;
  DOMString nonce;
  DOMString report = "default";
  boolean reportOnly = false;
};

The following subsections define the behavior of each of the known members the header’s value defines. Future versions of this document may define additional members; user agents MUST ignore unknown members when parsing the header.

3.1. The eval member

The eval member defines the page’s policy towards eval(). For increased security, developers can set the value to "block", which causes eval() to throw a SecurityError rather than executing a string as code. It is set to "unsafe-allow" by default in order to keep policies simple, as we’ve seen widespread-enough usage of eval() to make it hard to deny by fiat.

MegaCorp, Inc. has a number of libraries which require eval() for various purposes. They can allow such execution by sending an appropriate eval member along with their ARTUR policy:
ARTUR: { "hash": [ "sha256-abcd...", "sha256-zyx...", "eval": "unsafe-allow" ] }

3.2. The hash member

The hash member defines a set of digests which specify specific script content which is allowed to execute on a given page.

If present, the member’s value MUST be a sequence of DOMStrings, and each MUST adhere to the hash-source grammar specified in Content Security Policy Level 3 §framework-directive-source-list. Any item in the sequence that does not match that grammer MUST be ignored.

MegaCorp, Inc. wishes to ensure that injected script elements do not execute. They can do so by executing a secure hash function (like those specified in [SHA2]) on each script they wish to execute, and including the resulting list of digests in an ARTUR header. These digests either match the inline content of a script element or event handler, or match a script element’s integrity attribute:
ARTUR: { "hash": [ "sha256-abcd...", "sha256-zyx..." ] }

...

<script src="script.js" integrity="sha256-abcd..."></script>
<script>
  // Content which hashes to abcd... goes here.
</script>
    
<a onclick="// Content which hashes to zyx... goes here.">Click!</a>

Matching inline event handlers requires some changes to CSP. <https://github.com/w3c/webappsec-csp/issues/13>

Matching external scripts requires solidifying CSP’s SRI integration. <https://github.com/w3c/webappsec-csp/issues/78>

3.3. The nonce member

The nonce member defines a cryptographic nonce ("number used once") which can be used to ensure that only script elements whose nonce attributes contain a specific value are allowed to execute script for a given page.

If present, the member’s value MUST be a DOMString, and MUST adhere to the base64-value grammar specified in Content Security Policy Level 3 §framework-directive-source-list. If the member’s value does not match that grammar, the value MUST be ignored.

MegaCorp, Inc. wishes to ensure that injected script elements do not execute. They can do so by generating a unique string for each page load, and including that string both in an ARTUR header and in the script element’s nonce attribute, as follows:
ARTUR: { "nonce": "abcdefg" }

...

<script src="script.js" nonce="abcdefg"></script>

3.4. The report member

The report member defines the reporting group to which violation reports will be sent [REPORTING].

The member’s value MUST be a DOMString. If an empty string is provided, reporting will be disabled.

Note: Reporting to the group named "default" is enabled unless otherwise specified.

MegaCorp, Inc. wishes to receive violation reports when script execution is blocked by the ARTUR policy it specifies. It can do so by sending a report member along with the policy, and specifying a reporting endpoint, as follows:
ARTUR: { "nonce": "abcdefg", "report": "group1" }
Report-To: { "url": "https://example.com/report", "group": "group1", "max-age": 10886400 }

3.5. The reportOnly member

The reportOnly member defines whether the policy specified will actually block script execution, or whether it will operate in "report-only" mode, sending violation reports up to a server-side collector while allowing script execution.

MegaCorp, Inc. wishes to receive violation reports when script execution would be blocked by the ARTUR policy it specifies while it verifies that its nonce-generation code is working correctly. It can do so by sending report and reportOnly members along with the policy, and specifying a reporting endpoint, as follows:
ARTUR: { "nonce": "abcdefg", "report": "group1", "reportOnly": true }
Report-To: { "url": "https://example.com/report", "group": "group1", "max-age": 10886400 }

4. Integrations

The ARTUR header is little more than syntactic sugar on top of existing processing algorithms for [CSP]. This section explains the ways in which the ARTUR policy is enforced for a given context:

4.1. Integration with Fetch

  1. When executing Content Security Policy Level 3 §set-response-csp-list (currently as step 3.3.4 of HTTP fetch, and step 12 of HTTP-network fetch), Fetch should also execute the algorithm defined in §4.1.1 Populate response’s CSP list from its ARTUR policy..

Upstream this once we decide it’s sane.

4.1.1. Populate response’s CSP list from its ARTUR policy.

Given a response (response), this algorithm modifies its CSP list to enforce the ARTUR policy it specifies.

  1. For each policy in the result of executing §2.2.1 Obtain a set of ARTUR policies for response. on response:

    1. If policy’s nonce is an empty string and policy’s hash set is empty, then skip the remaining substeps and move to the next policy.

    2. Assert: policy’s nonce is either an empty string or matches the base64-value grammar specified in [CSP]. Likewise, policy’s hash set is either empty or all its values match the grammar "hash-algorithm-hash-source". Further, policy’s reporting group is a string that matches the token grammar defined in [RFC7230].

    3. Let csp be a new Content Security Policy with an empty directive set, and a disposition of "enforce" if policy’s reporting disposition is "enforce", and "report" otherwise.

    4. Let script-src be the string "'strict-dynamic'".

    5. If policy has a eval policy of "unsafe-allow", then append "'unsafe-eval'" to script-src.

    6. If policy’s nonce is not an empty string, append to script-src the result of replacing "%s" in the string "'nonce-%s' " with policy’s nonce.

    7. For each digest in policy’s hash set:

      1. Append to script-src the result of replacing "%s" in the string "'%s' " with digest.

    8. Add a new directive to csp’s directive set whose name is "script-src", and whose value is script-src.

    9. Add a new directive to csp’s directive set whose name is "object-src", and whose value is "'none'".

      This probably needs to be somehow configurable.

    10. If policy has a reporting group (group) which is not an empty string, then add a directive to csp whose name is "report-to", and whose value is group.

    11. Append policy to response’s CSP list.

5. Implementation Considerations

5.1. Error Messages

The ARTUR mechanism is defined almost entirely in terms of [CSP]. This is intentional, as it means that user agents can easily reuse all the existing machinery they’ve built up for that purpose. It might be confusing for developers, however, especially if, in the distant future, CSP becomes an esoteric thing that no one uses on its own. Error messages, in particular, might be confusing if they contain the synthesized CSP headers rather than the headers the developer actually sent.

That is, violation report console messages might look something like "Refused to execute inline event handler because it violates the following Content Security Policy directive: 'script-src 'strict-dynamic' 'nonce-abcdefg' ...". That is technically accurate (the best kind of accurate), but not particularly helpful.

User agents SHOULD ensure that the source of the policy is clear when triggering violations.

Perhaps we should even extend the SecurityPolicyViolationEvent and corresponding violation report?

6. Acknowledgements

This proposal arose from a conversation in the WebAppSec meetings at TPAC 2016. Thanks to Artur Janc for being a good sport about the original name (the lovingly backronymed ""), to Richard Barnes for being appropriately skeptical at the value provided, and to Sebastian Lekies and Krzysztof Kotowicz for being CSP gadflies in the most honorably Socratic tradition.

Conformance

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSP]
Mike West. Content Security Policy Level 3. 13 September 2016. WD. URL: https://w3c.github.io/webappsec-csp/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[HTML]
Ian Hickson. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[HTTP-JFV]
Julian Reschke. A JSON Encoding for HTTP Header Field Values. URL: http://httpwg.org/http-extensions/jfv.html
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[RFC5234]
D. Crocker, Ed.; P. Overell. Augmented BNF for Syntax Specifications: ABNF. January 2008. Internet Standard. URL: https://tools.ietf.org/html/rfc5234
[RFC7230]
R. Fielding, Ed.; J. Reschke, Ed.. Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing. June 2014. Proposed Standard. URL: https://tools.ietf.org/html/rfc7230
[WebIDL-1]
Cameron McCormack; Boris Zbarsky. WebIDL Level 1. 15 September 2016. PR. URL: https://heycam.github.io/webidl/

Informative References

[CSP-IS-DEAD]
Lukas Weichselbaum; et al. CSP Is Dead, Long Live CSP! On the Insecurity of Whitelists and the Future of Content Security Policy. URL: https://research.google.com/pubs/pub45542.html
[REPORTING]
Ilya Grigorik; Mike West. Reporting API 1. URL: https://wicg.github.io/reporting/
[SHA2]
FIPS PUB 180-4, Secure Hash Standard. URL: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
[SRI]
Devdatta Akhawe; et al. Subresource Integrity. 23 June 2016. REC. URL: https://w3c.github.io/webappsec-subresource-integrity/

IDL Index

enum EvalPolicy {
  "block",
  "unsafe-allow"
};

dictionary ArturPolicy {
  EvalPolicy eval = "unsafe-allow";
  sequence<DOMString> hash;
  DOMString nonce;
  DOMString report = "default";
  boolean reportOnly = false;
};

Issues Index

Matching inline event handlers requires some changes to CSP. <https://github.com/w3c/webappsec-csp/issues/13>
Matching external scripts requires solidifying CSP’s SRI integration. <https://github.com/w3c/webappsec-csp/issues/78>
Upstream this once we decide it’s sane.
This probably needs to be somehow configurable.
Perhaps we should even extend the SecurityPolicyViolationEvent and corresponding violation report?