Skip to content

Commit

Permalink
fix: prevent object prototype poisoning (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
simoneb authored Mar 8, 2021
1 parent 438762f commit 2f3ade7
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,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.
- `disableTimestampEncoding`, a boolean that when set disables the encoding of Dates into the [timestamp extension type](https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type). 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 @@ -14,7 +14,9 @@ function msgpack (options) {
options = options || {
forceFloat64: false,
compatibilityMode: false,
disableTimestampEncoding: false // if true, skips encoding Dates using the msgpack timestamp ext format (-1)
disableTimestampEncoding: false, // if true, skips encoding Dates using the msgpack timestamp ext format (-1)
// options.protoAction: 'error' (default) / 'remove' / 'ignore'
protoAction: 'error'
}

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

return {
encode: buildEncode(encodingTypes, options.forceFloat64, options.compatibilityMode, options.disableTimestampEncoding),
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 @@ -14,7 +14,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 @@ -361,6 +361,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()
})

0 comments on commit 2f3ade7

Please sign in to comment.