Write-only Form Elements

A Collection of Interesting Ideas, 15 October 2014

This version:
https://mikewest.github.io/credentialmanagement/writeonly/
Version History:
https://github.com/mikewest/credentialmanagement/commits/master/writeonly/index.src.html
Editor:
(Google Inc.)

Abstract

This specification describes how a website can ask a user agent to deny read access to form elements, which can mitigate the risk of credential leakage via cross-site scripting attacks.

Table of Contents

1. Introduction

This section is not normative.

A user’s credentials are valuable, and are often the key target of phishing and content injection attacks.

Users can defend themselves from the former threat by using a credential manager which enables the generation of strong, unique passwords for all the sites they visit, and which only provides those credentials to the website they’re tied to. If a user relies on a credential manager to keep track of her passwords, and the credential manager refuses to fill them into forms on unexpected origins, phishing becomes significantly more difficult.

Content injection, on the other hand, can turn the credential manager against the user, as demonstrated in "Automated Password Extraction Attack on Modern Password Managers" [LUPIN]. If an attacker can inject a form into a website and the credential manager can be tricked into filling it, the user’s credentials will be available to the attacker directly via DOM APIs or indirectly via form submission to a malicious endpoint. The latter risk can be mitigated via the Content Security Policy form-action directive [CSP2], but the former is a real problem. If passwords are a simple "value" accessor away, users are at risk.

It is tempting to simply block read access to password fields, but many websites do interesting things with password fields. They increasingly read credential values in order to sign a user in via XMLHttpRequest, for instance, rather than submitting a form. A blanket restriction is therefore unlikely to be web-compatible.

Instead, this document proposes an opt-in mechanism by which a website can choose to deny itself read access to the values of particular form fields. User agents could (and should!) allow credential managers to help users sign-in by writing values into form fields, but those values would remain opaque to the website’s JavaScript.

1.1. Examples

1.1.1. Sign-in Form

<form action="/signin-endpoint" method="POST">
  <input type="text"
         autofill="username"
         name="username">
  <input type="password"
         writeonly
         autofill="current-password"
         name="password">
  <input type="submit">
</form>

1.1.2. Content Security Policy

Content-Security-Policy: form-writeonly current-password new-password;

Note: A real page’s policy should also include form-action and connect-src directives to mitigate the risk of exfiltration.

2. Preventing DOM access to form fields

Developers may opt-into preventing DOM access to form fields by setting a writeonly attribute on specific form or form-associated elements, or by using the form-writeonly Content Security Policy directive to prevent DOM access to any and all form-associated elements based on their autocomplete attributes. Both mechanisms are described below:

2.1. The writeonly attribute

writeonly is a boolean attribute that controls whether or not an element’s value may be read directly via the value IDL attribute, or inferred via the constraint validation API [HTML5].

partial interface HTMLInputElement {
    attribute boolean writeonly;
};
writeonly
This attribute reflects the value of the element’s writeonly content attribute.

Copy/paste this IDL for button, keygen, object, select, and textarea.

partial interface HTMLFormElement {
    attribute boolean writeonly;
};
writeonly
This attribute reflects the value of the element’s writeonly content attribute.

If the writeonly attribute is set for an input element input, then set input’s write-only value flag to true.

If the writeonly attribute is set for a form element form,then set the write-only value flag to true for all submittable elements whose form owner is form.

Note: Removing the writeonly attribute will not clear an element’s write-only value flag. Once that flag is set for an element, it cannot be cleared.

2.2. The form-writeonly Content Security Policy directive

The form-writeonly directive specifies a policy which affects how the user agent interprets input elements in a protected resource. The syntax for the name and value of the directive are described by the following ABNF grammar [ABNF]:

directive-name  = "form-writeonly"
directive-value = "" / *WSP [ autocomplete-token *[ 1*WSP autocomplete-token ] *WSP ]
autocomplete-token = <any valid autofill detail tokens>

When enforcing the credentials directive, the user agent MUST set the protected resource’s forced write-only types to '*' if the directive’s value is empty, or to the list of autofill detail tokens specified by the directive’s value.

2.3. <input> element behavior

Expand this to include all submittable elements.

If an input element input’s write-only value flag is true, or if input’s Document's forced write-only types is '*', or if input’s autocomplete attribute contains one or more values which are also contained in input’s Document's forced write-only types, then the following restrictions apply:

  1. When setting input’s autocomplete IDL attribute [HTML5], input’s write-only value flag MUST also be set to true.

    Note: This prevents attackers from bypassing the CSP directive by changing the element’s autocomplete value from current-password to something unprotected (like section-fake).

  2. Getting input’s value, valueAsNumber, and valueAsDate IDL attributes will throw an InvalidStateError exception. [HTML5]

    Note: Setting these attributes is unaffected.

  3. Getting input’s selectionStart and selectionEnd IDL attributes will throw an InvalidStateError exception. [HTML5]

    Note: Setting these attributes is unaffected.

  4. input is considered barred from constraint validation.
  5. The user agent MUST NOT fire keydown, keyup, or keypress events on input. [DOM-LEVEL-3-EVENTS]
  6. FormData objects constructed from the form which contains input are opaque, as described in §2.4 Opaque FormData objects

Is this more or less exhaustive? I’m sure I’m missing some clever ways of reading the value.

2.4. Opaque FormData objects

FormData objects have a opaque flag, unset by default, and set only if the object is constructed from a form containing one or more input elements whose write-only value flag. Opaque FormData objects return null and the empty sequence when their get() and getAll() methods are executed, respectively. Further, data from opaque FormData objects can only be extracted in the context of executing XMLHttpRequest’s send() method.

Opaque FormData objects have the following properties:

  1. Whenever the user agent would execute an opaque FormData object’s get() method, it MUST return null.
  2. Whenever the user agent would execute an opaque FormData object’s getAll() method, it MUST return the empty sequence.
  3. Whenever the user agent would extract a byte stream and Content-Type from an opaque FormData object formdata, first the following steps:
    1. If the extraction algorithm is being executed in the context of XMLHttpRequest’s send() method, Fetch’s Request constructor, or Fetch’s Response constructor then skip the next step and proceed executing the algorithm.
    2. Otherwise, abort the extraction algorithm and return the result of executing the extract a byte stream and Content-Type algorithm on a new (empty) FormData object.
  4. ISSUE: Determine the right spec text to impart the following: When a request is handed off to Fetch, mark it as opaque if its' body is populated from an opaque FormData object, or if it is the result of a form submission with opaque contents.

2.4.1. Algorithm Modifications

Monkey-patching! Hooray! Though, of course, that’s pretty much this whole strawman...

2.4.1.1. XHR: FormData constructor

Insert the following steps before step 3 of the FormData constructor’s algorithm:

  1. Unset fd’s opaque flag.
  2. If form is given, then:
    1. Let controls be a list of all the submittable elements whose form owner is form.
    2. For each element field in controls:
      1. If field’s write-only value flag is true, then set fd’s opaque flag.
    2.4.1.2. XHR: FormData’s get()

    Redefine FormData’s get() method as follows:

    1. If the opaque flag is set, return null.
    2. Otherwise, return the value of the first entry whose name is name, and null if no such entry exists.
    2.4.1.3. XHR: FormData’s getAll()

    Redefine FormData’s getAll() method as follows:

    1. If the opaque flag is set, return the empty sequence.
    2. Otherwise, return the values of all entries whose name is name, in list order, and the empty sequence if no such entry exists.
    2.4.1.4. Fetch: Request objects

    Add a new opaque request flag to Fetch’s Request objects. This flag is unset unless otherwise specified.

    2.4.1.5. Fetch: Request’s constructor

    Add the following step after step 3 of step 17 of Fetch’s Request constructor:

    1. If init’s body member is a FormData object whose opaque flag is set, set r’s opaque flag.
    2.4.1.6. Fetch: Body’s consume body

    Insert the following step after step 2 of step 3 of Body’s as generic algorithm:

    1. If object’s opaque request flag is set, set stream to an empty byte sequence.
    2.4.1.7. Fetch: Extract a byte stream

    Redefine the FormData case of Fetch’s extract a byte stream and Content-Type algorithm as follows:

    1. If object’s opaque flag is not set, or if the extraction algorithm is being executed in the context of XMLHttpRequest’s send() method or Fetch’s Request constructor

      then:

      1. Push the result of running the multipart/form-data encoding algorithm, with object as form data set and with utf-8 as the explicit character encoding, to stream.
      2. Set Content-Type to `multipart/form-data;boundary=`, followed by the multipart/form-data boundary string generated by the multipart/form-data encoding algorithm.
    2. Otherwise, set Content-Type to text/plain;charset=UTF-8.

      Note: In this case (e.g. object is opaque and the algorithm isn’t being executed as a result of XHR.send()), stream will remain an empty byte stream.

    2.4.1.8. Service Worker: Handle a Fetch

    Figure out the right way to monkey-patch Service Worker’s Handle a Fetch algorithm to do the right thing with opaque requests.

3. Security Considerations

3.1. Sandboxing autofilled credential access

This section is non-normative.

Some user agents are implemented in such a way as to render websites via low-privilege processes that don’t have direct access to the network. Network requests are mediated by a more privileged parent process, which can protect against a number of threats which arise if a clever attacker can exploit bugs in the user agent to corrupt the website’s process.

Even if we prevent DOM-level access to input fields, the form field is somehow represented in memory that’s accessible to that child process. It is quite conceivable that an attacker could exploit browser bugs which corrupt the child process in such a way as to expose a user’s credentials.

Marking fields as write-only might mitigate this risk in the case of autofilled credentials. Since the webpage does not need access to a user’s actual password, the parent process can generate a nonce when autofilling credentials, and ask the child process to fill the password field with that value instead of the actual password. Then, when the child process asks the parent process to POST a form, the parent process can replace occurrences of the nonce in the POST’s body with the actual password for a given credential.

This approach means that the actual credentials are never provided to the high-risk child process, which means that an attacker would have to break out of the child’s sandbox in order to steal credentials.

A more rigorous examination of this approach is undertaken in "Protecting Users Against XSS-based Password Manager Abuse" [STOCK]. Their use of a browser extension is instructive: there’s no reason that this scheme couldn’t be implemented by a third-party credential manager, as long as it has access to the data POSTed from one page to another.

4. Authoring Considerations

This section is non-normative.

In order to mitigate the risk of content injection attacks which steal user credentials, we recommend that authors:

  1. Set a Content Security Policy [CSP2] on every page of their sites (including error pages, etc.), and ensure that the policy contains both form-action, connect-src and form-writeonly directives. The first will protect against autofilled form submissions which exfiltrate data to an attacker’s origin, the second against XHR-based submissions, and the last against DOM-level content injection attacks.
  2. Always place sign-in forms on TLS-protected pages, and always submit such forms to TLS-protected endpoints. If credentials are sent in the clear, they are trivially vulnerable to theft.

5. Acknowledgements

This proposal is heavily inspired by Jacob S Hoffman-Andrews' comments on public-webapps@, as well as Brian Smith’s response. Anne van Kesteren helped immensely with the Fetch integration.

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.

References

Normative References

[ABNF]
Dave Crocker; Paul Overell. Augmented BNF for Syntax Specifications: ABNF. RFC. URL: http://www.ietf.org/rfc/rfc5234.txt
[CSP2]
Mike West; Adam Barth; Dan Veditz. Content Security Policy Level 2. LCWD. URL: https://w3c.github.io/webappsec/content-security-policy/
[DOM-Level-3-Events]
Gary Kacmarcik; et al. Document Object Model (DOM) Level 3 Events Specification. 25 September 2014. WD. URL: http://www.w3.org/TR/DOM-Level-3-Events/
[HTML5]
Robin Berjon; et al. HTML5. LCWD. URL: http://w3.org/TR/HTML5/
[rfc2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: http://www.ietf.org/rfc/rfc2119.txt

Informative References

[LUPIN]
Raul Gonzalez; Eric Y. Chen; Collin Jackson. Automated Password Extraction Attack on Modern Password Managers. URL: http://arxiv.org/pdf/1309.1416.pdf
[STOCK]
Ben Stock; Martin Johns. Protecting Users Against XSS-based Password Manager Abuse. URL: https://www.ben-stock.de/wp-content/uploads/asiacss2014.pdf

Index

IDL Index

partial interface HTMLInputElement {
    attribute boolean writeonly;
};

partial interface HTMLFormElement {
    attribute boolean writeonly;
};

Issues Index

Copy/paste this IDL for button, keygen, object, select, and textarea.
Expand this to include all submittable elements.
Is this more or less exhaustive? I’m sure I’m missing some clever ways of reading the value.
Monkey-patching! Hooray! Though, of course, that’s pretty much this whole strawman...
Figure out the right way to monkey-patch Service Worker’s Handle a Fetch algorithm to do the right thing with opaque requests.