©2014 Google
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.
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.
<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>
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.
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:
writeonly
attributewriteonly
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
content attribute.Copy/paste this IDL for button, keygen, object, select, and textarea.
partial interface HTMLFormElement { attribute boolean writeonly; };
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.
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.
<input>
element behaviorExpand 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:
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
).
InvalidStateError
exception. [HTML5]
Note: Setting these attributes is unaffected.
selectionStart
and selectionEnd
IDL
attributes will throw an InvalidStateError
exception. [HTML5]
Note: Setting these attributes is unaffected.
keydown
, keyup
, or
keypress
events on input. [DOM-LEVEL-3-EVENTS]
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.
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:
FormData
object’s
get()
method, it MUST return null
.
FormData
object’s
getAll()
method, it MUST return the empty sequence.
Content-Type
from an opaque FormData
object formdata, first
the following steps:
send()
method,
Fetch’s Request
constructor,
or
Fetch’s Response
constructor
then skip the next step and proceed executing the algorithm.
Content-Type
algorithm on a new (empty) FormData
object.
Monkey-patching! Hooray! Though, of course, that’s pretty much this whole strawman...
FormData
constructorInsert the following steps before step 3 of
the FormData
constructor’s algorithm:
true
, then set fd’s opaque flag.
FormData
’s get()
Redefine FormData
’s
get()
method as follows:
null
.null
if no such
entry
exists.
FormData
’s getAll()
Redefine FormData
’s
getAll()
method as follows:
Request
objectsAdd a new opaque request flag to
Fetch’s Request
objects.
This flag is unset unless otherwise specified.
Request
’s constructorAdd the following step after step 3 of step 17 of
Fetch’s Request
constructor:
body
member is a FormData
object whose opaque flag is set, set r’s opaque
flag.
Body
’s consume body
Insert the following step after step 2 of step 3 of
Body
’s as generic
algorithm:
Redefine the FormData
case of Fetch’s
extract a byte stream and Content-Type
algorithm as follows:
send()
method
or
Fetch’s Request
constructor
then:
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.
Figure out the right way to monkey-patch Service Worker’s Handle a Fetch algorithm to do the right thing with opaque requests.
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.
This section is non-normative.
In order to mitigate the risk of content injection attacks which steal user credentials, we recommend that authors:
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.
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 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:
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.
partial interface HTMLInputElement { attribute boolean writeonly; }; partial interface HTMLFormElement { attribute boolean writeonly; };