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

Explicit handle __proto__ fields. #116

Merged
merged 1 commit into from
Feb 17, 2020
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
10 changes: 9 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
}
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
46 changes: 46 additions & 0 deletions test/append-body.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})
})
84 changes: 76 additions & 8 deletions test/multipart.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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))
Expand All @@ -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)
})
}
})

Expand Down Expand Up @@ -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))

Expand All @@ -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()
}
Expand All @@ -327,16 +341,16 @@ 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)

total -= n

if (total === 0) {
t.pass('finished generating')
sent = true
hashInput.end()
this.push(null)
}
}
Expand Down Expand Up @@ -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')
})
})
})