Skip to content
This repository has been archived by the owner on Jul 3, 2024. It is now read-only.

Commit

Permalink
fix: push fix for json poisoning cojs#70
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Feb 14, 2019
1 parent c6236a9 commit e820cb2
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 2 deletions.
7 changes: 5 additions & 2 deletions lib/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

const raw = require('raw-body');
const inflate = require('inflation');
const bourne = require('bourne');
const utils = require('./utils');

// Allowed whitespace is defined in RFC 7159
Expand Down Expand Up @@ -34,6 +35,8 @@ module.exports = async function(req, opts) {
if (len && encoding === 'identity') opts.length = len = ~~len;
opts.encoding = opts.encoding || 'utf8';
opts.limit = opts.limit || '1mb';
opts.protoAction = opts.protoAction || 'remove'

const strict = opts.strict !== false;

const str = await raw(inflate(req), opts);
Expand All @@ -47,13 +50,13 @@ module.exports = async function(req, opts) {
}

function parse(str) {
if (!strict) return str ? JSON.parse(str) : str;
if (!strict) return str ? bourne.parse(str, null, { protoAction: opts.protoAction }) : str;
// strict mode always return object
if (!str) return {};
// strict JSON test
if (!strictJSONReg.test(str)) {
throw new Error('invalid JSON, only supports object and array');
}
return JSON.parse(str);
return bourne.parse(str, null, { protoAction: opts.protoAction })
}
};
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"urlencoded"
],
"dependencies": {
"bourne": "^1.1.2",
"inflation": "^2.0.0",
"qs": "^6.6.0",
"raw-body": "^2.3.3",
Expand Down
22 changes: 22 additions & 0 deletions test/form.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,26 @@ describe('parse.form(req, opts)', function() {
.expect(200, done);
});
});

describe('JSON poisoning', function() {
it('remove inline __proto__ properties', function(done) {
const app = new koa();

app.use(async function (ctx) {
ctx.body = await parse.form(ctx, { returnRawBody: true });
});

const body = 'foo=bar&__proto__[admin]=true'

request(app.callback())
.post('/')
.type('form')
.send(body)
.expect(function (res) {
res.body = { isAdmin: res.body.parsed.__proto__.admin }
})
.expect({ isAdmin: undefined })
.expect(200, done);
});
});
});
85 changes: 85 additions & 0 deletions test/json.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,89 @@ describe('parse.json(req, opts)', function() {
.expect(200, done);
});
});

describe('JSON poisoning', function() {
it('remove inline __proto__ properties', function(done) {
const app = new koa();

app.use(async function (ctx) {
ctx.body = await parse.json(ctx, { returnRawBody: true });
});

const body = '{"foo": "bar", "__proto__": { "admin": true }}'

request(app.callback())
.post('/')
.type('json')
.send(body)
.expect(function (res) {
res.body = { isAdmin: res.body.parsed.__proto__.admin }
})
.expect({ isAdmin: undefined })
.expect(200, done);
});

it('remove nested inline __proto__ properties', function(done) {
const app = new koa();

app.use(async function (ctx) {
ctx.body = await parse.json(ctx, { returnRawBody: true });
});

const body = '{"user": { "name": "virk", "__proto__": { "admin": true } }}'

request(app.callback())
.post('/')
.type('json')
.send(body)
.expect(function (res) {
res.body = { isAdmin: res.body.parsed.user.__proto__.admin }
})
.expect({ isAdmin: undefined })
.expect(200, done);
});

it('error on inline __proto__ properties', function(done) {
const app = new koa();

app.use(async function (ctx) {
try {
await parse.json(ctx, { returnRawBody: true, protoAction: 'error' });
} catch (err) {
err.status.should.equal(400);
err.body.should.equal('{"foo": "bar", "__proto__": { "admin": true }}');
err.message.should.equal('Object contains forbidden prototype property');
done();
}
});

const body = '{"foo": "bar", "__proto__": { "admin": true }}'

request(app.callback())
.post('/')
.type('json')
.send(body)
.end(function() {});
});
});

it('ignore inline __proto__ properties', function(done) {
const app = new koa();

app.use(async function (ctx) {
ctx.body = await parse.json(ctx, { returnRawBody: true, protoAction: 'ignore' });
});

const body = '{ "name": "virk", "__proto__": { "admin": true } }'

request(app.callback())
.post('/')
.type('json')
.send(body)
.expect(function (res) {
res.body = { isAdmin: res.body.parsed.__proto__.admin }
})
.expect({ isAdmin: true })
.expect(200, done);
});
});

0 comments on commit e820cb2

Please sign in to comment.