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

fix: prevent object prototype poisoning #97

Merged
merged 1 commit into from
Mar 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ options:

- `forceFloat64`, a boolean to that forces all floats to be encoded as 64-bits floats. Defaults to false.
- `compatibilityMode`, a boolean that enables "compatibility mode" which doesn't use str 8 format. Defaults to false.
- `protoAction`, a string which can be `error|ignore|remove` that determines what happens when decoding a plain object with a `__proto__` property which would cause prototype poisoning. `error` (default) throws an error, `remove` removes the property, `ignore` (not recommended) allows the property, thereby causing prototype poisoning on the decoded object.

-------------------------------------------------------
<a name="encode"></a>
Expand Down
6 changes: 4 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ function msgpack (options) {

options = options || {
forceFloat64: false,
compatibilityMode: false
compatibilityMode: false,
// options.protoAction: 'error' (default) / 'remove' / 'ignore'
protoAction: 'error'
}

function registerEncoder (check, encode) {
Expand Down Expand Up @@ -68,7 +70,7 @@ function msgpack (options) {

return {
encode: buildEncode(encodingTypes, options.forceFloat64, options.compatibilityMode),
decode: buildDecode(decodingTypes),
decode: buildDecode(decodingTypes, options),
register: register,
registerEncoder: registerEncoder,
registerDecoder: registerDecoder,
Expand Down
13 changes: 12 additions & 1 deletion lib/decoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function IncompleteBufferError (message) {

util.inherits(IncompleteBufferError, Error)

module.exports = function buildDecode (decodingTypes) {
module.exports = function buildDecode (decodingTypes, options) {
return decode

function getSize (first) {
Expand Down Expand Up @@ -358,6 +358,17 @@ module.exports = function buildDecode (decodingTypes) {
var valueResult = tryDecode(buf, offset)
if (valueResult) {
key = keyResult.value

if (key === '__proto__') {
if (options.protoAction === 'error') {
throw new SyntaxError('Object contains forbidden prototype property')
}

if (options.protoAction === 'remove') {
continue
}
}

result[key] = valueResult.value
offset += valueResult.bytesConsumed
totalBytesConsumed += (keyResult.bytesConsumed + valueResult.bytesConsumed)
Expand Down
49 changes: 49 additions & 0 deletions test/object-prototype-poisoning.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict'

var test = require('tape').test
var msgpack = require('../')

test('decode throws when object has forbidden __proto__ property', function (t) {
const encoder = msgpack()

const payload = { hello: 'world' }
Object.defineProperty(payload, '__proto__', {
value: { polluted: true },
enumerable: true
})

const encoded = encoder.encode(payload)

t.throws(() => encoder.decode(encoded), /Object contains forbidden prototype property/)
t.end()
})

test('decode ignores forbidden __proto__ property if protoAction is "ignore"', function (t) {
const encoder = msgpack({ protoAction: 'ignore' })

const payload = { hello: 'world' }
Object.defineProperty(payload, '__proto__', {
value: { polluted: true },
enumerable: true
})

const decoded = encoder.decode(encoder.encode(payload))

t.equal(decoded.polluted, true)
t.end()
})

test('decode removes forbidden __proto__ property if protoAction is "remove"', function (t) {
const encoder = msgpack({ protoAction: 'remove' })

const payload = { hello: 'world' }
Object.defineProperty(payload, '__proto__', {
value: { polluted: true },
enumerable: true
})

const decoded = encoder.decode(encoder.encode(payload))

t.equal(decoded.polluted, undefined)
t.end()
})