CVE-2021-23597 (JavaScript, published 2022-02-11T17:15Z)
Description: value: This affects the package fastify-multipart before 5.3.1. By providing a name=constructor property it is still possible to crash the application. **Note:** This is a bypass of CVE-2020-8136 (https://security.snyk.io/vuln/SNYK-JS-FASTIFYMULTIPART-1290382).
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: CWE-1321 \u2014 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype.
Repository: https://github.com/fastify/fastify-multipart
Files changed:
index.js
test/legacy/append-body.test.js
test/legacy/multipart.test.js
Commit message: Merge pull request from GHSA-qh73-qc3p-rjv2



Vulnerable code:
    if (key === '__proto__') {
      mp.destroy(new Error('__proto__ is not allowed as field name'))
      if (field === '__proto__') {
        file.destroy(new Error('__proto__ is not allowed as field name'))

Fixed code:
    if (key === '__proto__' || key === 'constructor') {
      mp.destroy(new Error(`${key} is not allowed as field name`))
      if (field === '__proto__' || field === 'constructor') {
        file.destroy(new Error(`${field} is not allowed as field name`))

test('addToBody with constructor 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
    const form = new FormData()
    const opts = {
      protocol: 'http:',
      hostname: 'localhost',
      port: fastify.server.address().port,
      path: '/',
      headers: form.getHeaders(),
      method: 'POST'
    }

    const 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('constructor', 'world')
    pump(form, req, function (err) {
      t.error(err, 'client pump: no err')
    })
  })
})

test('should not allow constructor', { skip: process.platform === 'win32' }, 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.equal(err.message, 'constructor 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
    const form = new FormData()
    const opts = {
      protocol: 'http:',
      hostname: 'localhost',
      port: fastify.server.address().port,
      path: '/',
      headers: form.getHeaders(),
      method: 'POST'
    }

    const req = http.request(opts, (res) => {
      t.equal(res.statusCode, 500)
      res.resume()
      res.on('end', () => {
        t.pass('res ended successfully')
      })
    })
    const rs = fs.createReadStream(filePath)
    form.append('constructor', rs)
    pump(form, req, function (err) {
      t.error(err, 'client pump: no err')
    })
  })
})