From d96da7197b42eaa8dbc29ae825cc7e34c539cb05 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 17 Feb 2020 10:34:27 +0100 Subject: [PATCH] Explicit handle __proto__ fields. --- index.js | 10 ++++- package.json | 1 + test/append-body.test.js | 46 ++++++++++++++++++++++ test/multipart.test.js | 84 ++++++++++++++++++++++++++++++++++++---- 4 files changed, 132 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 51340ee1..a1261a35 100644 --- a/index.js +++ b/index.js @@ -47,6 +47,10 @@ function attachToBody (options, req, reply, next) { }, options) mp.on('field', (key, value) => { + if (key === '__proto__') { + mp.destroy(new Error('__proto__ is not allowed as field name')) + return + } body[key] = value }) } @@ -142,7 +146,7 @@ function fastifyMultipart (fastify, options, done) { }) stream.on('finish', function () { - log.debug('finished multipart parsing') + log.debug('finished receiving stream, total %d files', files) if (!completed && count === files) { completed = true setImmediate(done) @@ -162,6 +166,10 @@ function fastifyMultipart (fastify, options, done) { log.debug({ field, filename, encoding, mimetype }, 'parsing part') files++ eos(file, waitForFiles) + if (field === '__proto__') { + file.destroy(new Error('__proto__ is not allowed as field name')) + return + } handler(field, file, filename, encoding, mimetype) } diff --git a/package.json b/package.json index 3485dc33..40e451fb 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "form-data": "^3.0.0", "pre-commit": "^1.2.2", "pump": "^3.0.0", + "readable-stream": "^3.6.0", "snazzy": "^8.0.0", "standard": "^14.0.2", "tap": "^12.7.0", diff --git a/test/append-body.test.js b/test/append-body.test.js index 06f845db..f4492cbd 100644 --- a/test/append-body.test.js +++ b/test/append-body.test.js @@ -621,3 +621,49 @@ test('addToBody option does not change behaviour on not-multipart request', t => }) }) }) + +test('addToBody with __proto__ field', t => { + t.plan(3) + + const fastify = Fastify() + t.tearDown(fastify.close.bind(fastify)) + + const opts = { + addToBody: true, + onFile: (fieldName, stream, filename, encoding, mimetype) => { + t.fail('there are not stream') + } + } + fastify.register(multipart, opts) + + fastify.post('/', function (req, reply) { + t.fail('should not be called') + }) + + fastify.listen(0, function () { + // request + var form = new FormData() + var opts = { + protocol: 'http:', + hostname: 'localhost', + port: fastify.server.address().port, + path: '/', + headers: form.getHeaders(), + method: 'POST' + } + + var req = http.request(opts, (res) => { + t.equal(res.statusCode, 500) + res.resume() + res.on('end', () => { + t.pass('res ended successfully') + }) + }) + + form.append('myField', 'hello') + form.append('__proto__', 'world') + pump(form, req, function (err) { + t.error(err, 'client pump: no err') + }) + }) +}) diff --git a/test/multipart.test.js b/test/multipart.test.js index 22cbb2e4..0f86e0ba 100644 --- a/test/multipart.test.js +++ b/test/multipart.test.js @@ -7,10 +7,12 @@ const multipart = require('..') const http = require('http') const path = require('path') const fs = require('fs') -const pump = require('pump') const concat = require('concat-stream') -const Readable = require('stream').Readable -const Writable = require('stream').Writable +const stream = require('readable-stream') +const Readable = stream.Readable +const Writable = stream.Writable +const pump = stream.pipeline +const eos = stream.finished const crypto = require('crypto') const filePath = path.join(__dirname, '../README.md') @@ -81,7 +83,7 @@ test('should parse forms', function (t) { }) test('should call finished when both files are pumped', function (t) { - t.plan(8) + t.plan(10) const fastify = Fastify() t.tearDown(fastify.close.bind(fastify)) @@ -100,10 +102,14 @@ test('should call finished when both files are pumped', function (t) { function handler (field, file, filename, encoding, mimetype) { const saveTo = path.join(os.tmpdir(), path.basename(filename)) - pump(file, fs.createWriteStream(saveTo), function (err) { + eos(file, function (err) { t.error(err) fileCount++ }) + + pump(file, fs.createWriteStream(saveTo), function (err) { + t.error(err) + }) } }) @@ -281,6 +287,7 @@ if (!process.env.TRAVIS) { const fastify = Fastify() const hashInput = crypto.createHash('sha256') + let sent = false t.tearDown(fastify.close.bind(fastify)) @@ -303,6 +310,13 @@ if (!process.env.TRAVIS) { pump(file, hashOutput, new Writable({ objectMode: true, write (chunk, enc, cb) { + if (!sent) { + eos(hashInput, () => { + this._write(chunk, enc, cb) + }) + return + } + t.equal(hashInput.digest('hex'), chunk.toString('hex')) cb() } @@ -327,9 +341,7 @@ if (!process.env.TRAVIS) { n = total } - // poor man random data - // do not use in prod, it can leak sensitive informations - var buf = Buffer.allocUnsafe(n) + var buf = Buffer.alloc(n).fill('x') hashInput.update(buf) this.push(buf) @@ -337,6 +349,8 @@ if (!process.env.TRAVIS) { if (total === 0) { t.pass('finished generating') + sent = true + hashInput.end() this.push(null) } } @@ -364,3 +378,57 @@ if (!process.env.TRAVIS) { }) }) } + +test('should not allow __proto__', function (t) { + t.plan(5) + + const fastify = Fastify() + t.tearDown(fastify.close.bind(fastify)) + + fastify.register(multipart, { limits: { fields: 1 } }) + + fastify.post('/', function (req, reply) { + t.ok(req.isMultipart()) + + const mp = req.multipart(handler, function (err) { + t.is(err.message, '__proto__ is not allowed as field name') + reply.code(500).send() + }) + + mp.on('field', function (name, value) { + t.fail('should not be called') + }) + + function handler (field, file, filename, encoding, mimetype) { + t.fail('should not be called') + } + }) + + fastify.listen(0, function () { + // request + var form = new FormData() + var opts = { + protocol: 'http:', + hostname: 'localhost', + port: fastify.server.address().port, + path: '/', + headers: form.getHeaders(), + method: 'POST' + } + + var req = http.request(opts, (res) => { + t.equal(res.statusCode, 500) + res.resume() + res.on('end', () => { + t.pass('res ended successfully') + }) + }) + var rs = fs.createReadStream(filePath) + form.append('__proto__', rs) + // form.append('hello', 'world') + // form.append('willbe', 'dropped') + pump(form, req, function (err) { + t.error(err, 'client pump: no err') + }) + }) +})