Skip to content
This repository has been archived by the owner on May 9, 2020. It is now read-only.

Commit

Permalink
Merge pull request #218 from pro-src/security_patch
Browse files Browse the repository at this point in the history
(security) Nullify VM context's prototype chain
  • Loading branch information
codemanki authored May 11, 2019
2 parents c5b576a + a10492d commit 5d92879
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 56 deletions.
71 changes: 42 additions & 29 deletions lib/sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,55 @@ const VM_OPTIONS = {
timeout: 5000
};

const VM_ENV = `
(function (global) {
const cache = Object.create(null);
const keys = [];
const { body, href } = global;
Object.defineProperties(global, {
document: {
value: {
createElement: function () {
return { firstChild: { href: href } };
},
getElementById: function (id) {
if (keys.indexOf(id) === -1) {
const re = new RegExp(' id=[\\'"]?' + id + '[^>]*>([^<]*)');
const match = body.match(re);
keys.push(id);
cache[id] = match === null ? match : { innerHTML: match[1] };
}
return cache[id];
}
}
},
location: { value: { reload: function () {} } }
})
}(this));
`;

module.exports = { eval: evaluate, Context };

function evaluate (code, ctx) {
return vm.runInNewContext(code, ctx, VM_OPTIONS);
return vm.runInNewContext(VM_ENV + code, ctx, VM_OPTIONS);
}

// Global context used to evaluate standard IUAM JS challenge
function Context (options) {
if (!options) options = { body: '', hostname: '' };

const body = options.body;
const href = 'http://' + options.hostname + '/';
const cache = Object.create(null);
const keys = [];

this.atob = function (str) {
return Buffer.from(str, 'base64').toString('binary');
};

// Used for eval during onRedirectChallenge
this.location = { reload: function () {} };

this.document = {
createElement: function () {
return { firstChild: { href: href } };
},
getElementById: function (id) {
if (keys.indexOf(id) === -1) {
const re = new RegExp(' id=[\'"]?' + id + '[^>]*>([^<]*)');
const match = body.match(re);

keys.push(id);
cache[id] = match === null ? match : { innerHTML: match[1] };
}

return cache[id];
}
};
const atob = Object.setPrototypeOf(function (str) {
try {
return Buffer.from(str, 'base64').toString('binary');
} catch (e) {}
}, null);

return Object.setPrototypeOf({
body: options.body,
href: 'http://' + options.hostname + '/',
atob
}, null);
}
4 changes: 2 additions & 2 deletions test/test-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -645,8 +645,8 @@ describe('Cloudscraper', function () {
expect(Request.firstCall).to.be.calledWithExactly(expectedParams);

const elapsed = Date.now() - start;
// Aiming to be within ~150ms of specified timeout
expect(elapsed >= 50 && elapsed <= 200).to.be.ok;
// Aiming to be within ~450ms of specified timeout
expect(elapsed >= 50 && elapsed <= 500).to.be.ok;
});

expect(promise).to.eventually.equal(requestedPage).and.notify(done);
Expand Down
29 changes: 4 additions & 25 deletions test/test-sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,74 +34,53 @@ describe('Sandbox (lib)', function () {
it('Context() should define location.reload', function () {
const ctx = new sandbox.Context();

expect(ctx.location).to.be.an('object');
expect(ctx.location.reload).to.be.an('function');

// This is a noop
ctx.location.reload();
expect(sandbox.eval('location.reload()', ctx)).to.equal(void 0);
});

it('Context() should define document.createElement', function () {
let ctx = new sandbox.Context();
let pseudoElement = { firstChild: { href: 'http:///' } };

expect(ctx.document).to.be.an('object');

expect(ctx.document.createElement).to.be.an('function');
expect(ctx.document.createElement('a')).eql(pseudoElement);
expect(sandbox.eval('document.createElement("a")', ctx)).to.eql(pseudoElement);

ctx = new sandbox.Context({ hostname: 'test.com' });
pseudoElement = { firstChild: { href: 'http://test.com/' } };

expect(ctx.document.createElement('a')).eql(pseudoElement);
expect(sandbox.eval('document.createElement("a")', ctx)).to.eql(pseudoElement);
});

it('Context() should define document.geElementById', function () {
let ctx = new sandbox.Context();

expect(ctx.document).to.be.an('object');

expect(ctx.document.getElementById).to.be.an('function');
expect(ctx.document.getElementById()).to.be.null;
expect(sandbox.eval('document.getElementById()', ctx)).to.be.null;
expect(ctx.document.getElementById('foobar')).to.be.null;

// Missing element
ctx = new sandbox.Context();
expect(sandbox.eval('document.getElementById("foobar")', ctx)).to.be.null;

// Double quotes
ctx = new sandbox.Context({ body: '<div id="test">foobar</div>' });
expect(ctx.document.getElementById('test')).eql({ innerHTML: 'foobar' });
expect(sandbox.eval('document.getElementById("test")', ctx)).eql({ innerHTML: 'foobar' });

// Single quotes
ctx = new sandbox.Context({ body: '<div id=\'test\'>foobar</div>' });
expect(ctx.document.getElementById('test')).eql({ innerHTML: 'foobar' });
expect(sandbox.eval('document.getElementById(\'test\')', ctx)).eql({ innerHTML: 'foobar' });

// Empty
ctx = new sandbox.Context({ body: '<div id="test"></div>' });
expect(ctx.document.getElementById('test')).eql({ innerHTML: '' });
expect(sandbox.eval('document.getElementById("test")', ctx)).eql({ innerHTML: '' });

// Space agnostic tests
ctx = new sandbox.Context({ body: '<div id="test">\nabc\n\n</div>' });
expect(ctx.document.getElementById('test')).eql({ innerHTML: '\nabc\n\n' });
expect(sandbox.eval('document.getElementById("test")', ctx)).eql({ innerHTML: '\nabc\n\n' });

ctx = new sandbox.Context({ body: '<div id=\'test\' > abc </div>' });
expect(ctx.document.getElementById('test')).eql({ innerHTML: ' abc ' });
expect(sandbox.eval('document.getElementById("test")', ctx)).eql({ innerHTML: ' abc ' });

ctx = new sandbox.Context({ body: 'foo="bar" id=\'test\' a=b > abc <' });
expect(ctx.document.getElementById('test')).eql({ innerHTML: ' abc ' });
expect(sandbox.eval('document.getElementById("test")', ctx)).eql({ innerHTML: ' abc ' });

// Cache test
ctx = new sandbox.Context({ body: '<div id="test">foobar</div>' });
ctx.document.getElementById('test').innerHTML = 'foo';
expect(ctx.document.getElementById('test')).eql({ innerHTML: 'foo' });
expect(sandbox.eval('document.getElementById("test")', ctx)).eql({ innerHTML: 'foo' });
expect(sandbox.eval('document.getElementById("test")', ctx)).eql({ innerHTML: 'foobar' });
});
});

0 comments on commit 5d92879

Please sign in to comment.