Re: [webcomponents] Proposal for Cross Origin Use Case and Declarative Syntax

Hi Ryosuke,

Can you help me understand what security properties your proposal
achieves and how it achieves them?  I spent some time thinking about
this problem a couple of years ago when this issue was discussed in
depth, but I couldn't come up with a design that was simultaneously
useful and secure.

For example, your proposal seems to have the vulnerability described below:

== Trusted container document ==

<link rel="import"
href="https://untrusted.org/untrusted-components.html"
importcomponents="name-card">
<body>
<name-card ></name-card>

== untrusted-components.html ==

<template defines="name-card" interface="NameCardElement">
Name: {{name}}<br>Email:{{email}}
</template>
<script>
NameCardElement.prototype.created = function (shadowRoot) {
  var victim = shadowRoot.ownerDocument;
  var script = victim.createElement("script");
  script.textContent = "alert(/hacked/);";
  victim.body.appendChild(script);
};
</script>

Maybe I'm not understanding your proposal correct?  If this issue is
indeed a vulnerability with your proposal, I have no doubt that you
can modify your proposal to patch this hole, but iterating in that way
isn't likely to lead to a secure design.

Thanks,
Adam


On Fri, Nov 8, 2013 at 11:24 AM, Ryosuke Niwa <rniwa@apple.com> wrote:
> Hi all,
>
> We have been discussing cross-orign use case and declarative syntax of web
> components internally at Apple, and here are our straw man proposal to amend
> the existing Web Components specifications to support it.
>
> 1. Modify HTML Imports to run scripts in the imported document itself
> This allows the importee and the importer to not share the same script
> context, etc…
>
> 2. Add “importcomponents" content attribute on link element
> It defines the list of custom element tag names to be imported from the
> imported HTML document.
> e.g. <link rel="import" href="~" importcomponents="tag-1 tag-2"> will export
> custom elements of tag names "tag-1" and "tag-2" from ~.  Any name that
> didn't have a definition in the import document is ignored (i.e. if "tag-2"
> was not defined in ~, it would be skipped but "tag-1" will be still
> imported).
>
> This mechanism prevents the imported document from defining arbitrary
> components in the host document.
>
> 3. Support "static" (write-once) binding of a HTML template
> e.g.
> <template id=cardTemplate>Name: {{name}}<br>Email:{{email}}</template>
> <script>
> document.body.appendChild(cardTemplate.instantiate({name: "Ryosuke Niwa",
> email:"rniwa@webkit.org"}));
> </script>
>
> 4. Add “interface" content attribute to template element
> This content attribute specifies the name of the JavaScript constructor
> function to be created in the global scope. The UA creates one and will be
> used to instantiate a given custom element.  The author can then setup the
> prototype chain as needed:
>
> <template defines="name-card" interface="NameCardElement">
> Name: {{name}}<br>Email:{{email}}
> </template>
> <script>
> NameCardElement.prototype.name = function () {...}
> NameCardElement.prototype.email = function () {...}
> </script>
>
> This is similar to doing:
> var NameCardElement = document.register(’name-card');
>
> 5. Add "defines" content attribute on HTML template element to define a
> custom element
> This new attribute defines a custom element of the given name for the
> template content.
> e.g. <template defines="nestedDiv"><div><div></div></div></template> will
> let you use <nestedDiv></nestedDiv>
>
> We didn’t think having a separate custom element was useful because we
> couldn’t think of a use case where you wanted to define a custom element
> declaratively and not use template by default, and having to associate the
> first template element with the custom element seemed unnecessary
> complexity.
>
> 5.1. When a custom element is instantiated, automatically instantiate
> template inside a shadow root after statically binding the template with
> dataset
> This allows statically declaring arguments to a component.
> e.g.
> <template defines="name-card">Name: {{name}}<br>Email:{{email}}</template>
> <name-card data-name="Ryosuke Niwa" data-email="rniwa@webkit.org”>
>
> 5.2. When a new custom element object is constructed, "created" callback is
> called with a shadow root
> Unfortunately, we can't let the author define a constructor because the
> element hadn't been properly initialized with the right JS wrapper at the
> time of its construction.  So just like we can't do "new HTMLTitleElement",
> we're not going to let the author do an interesting things inside a custom
> element's constructor.  Instead, we're going to call "created" function on
> its prototype chain:
>
> <template defines="name-card" interface="NameCardElement">
> Name: {{name}}<br>Email:{{email}}
> </template>
> <script>
> NameCardElement.prototype.name = function () {...}
> NameCardElement.prototype.email = function () {...}
> NameCardElement.prototype.created = function (shadowRoot) {
>     ... // Initialize the shadowRoot here.
> }
> </script>
>
> This is similar to the way document.register works in that document.register
> creates a constructor automatically.
>
> 6. The cross-origin component does not have access to the shadow host
> element, and the host document doesn’t have access to the element object.
> When member functions of the element is called, “this” object will be
> undefined.  This is necessary because exposing the object to a cross-origin
> content will result in tricky security issues, forcing us to have proxy
> objects, etc…
>
> Inside the document that imported a component, the element doesn’t use the
> prototype defined by the component as that exposes JS objects cross-origin.
> e.g. even if LikeButtonElement was defined in
> facebook.com/~/like-button.html, the document that uses this component
> wouldn’t see the prototype or the constructor.  It’ll be HTMLUnknownElement.
> (We could create a new custom element type such as
> HTMLCrossOriginCustomElement if think that’s necessary).
>
> 7. Expose shadow host’s dataset on shadow root
> This allows the component to communicate with the host document in a limited
> fashion without exposing the element directly.
>
> This design allows us to have an iframe-like boundary between the shadow
> host (custom element itself) and the shadow root (implementation details),
> and address our cross-origin use case elegantly as follows:
>
> rniwa.com/webkit.html
> ---------------------------------
> <!DOCTYPE html>
> <html>
> <head>
> <link rel=import href="https://webkit.org/components.html"
> defines="share-button like-button">
> </head>
> <body>
> <like-button data-url="https://build.webkit.org/">Like
> build.webkit.org</like-button>
> </body>
> </html>
>
> webkit.org/components.html
> ---------------------------------
> <template defines="like-button" interface="LikeButtonElement">
> <!-- implicitly does
> shadowRoot.appendChild(myTemplate.instantiate(shadowHost.dataset)); -->
> <form ...>
> <input type=hidden value="{{url}}">
> <button type=submit>Like!</button>
> </form>
> <script>
> LikeButtonElement.prototype.created = function (shadowRoot) {
>     shadowRoot.query('form').onsubmit = function () {
>         // ...
>     }
> }
> </script>
> </template>
>
> - R. Niwa

Received on Monday, 11 November 2013 07:57:56 UTC