Skip to content

Commit bf18acc

Browse files
authored
use in to check for prototype violation (#484)
* use in to check for prototype violation * add additional prototype checks
1 parent 109a116 commit bf18acc

File tree

2 files changed

+137
-3
lines changed

2 files changed

+137
-3
lines changed

index.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ const secureJSON = require('secure-json-parse')
1717

1818
const kMultipart = Symbol('multipart')
1919
const kMultipartHandler = Symbol('multipartHandler')
20-
const getDescriptor = Object.getOwnPropertyDescriptor
2120

2221
const PartsLimitError = createError('FST_PARTS_LIMIT', 'reach parts limit', 413)
2322
const FilesLimitError = createError('FST_FILES_LIMIT', 'reach files limit', 413)
@@ -249,7 +248,7 @@ function fastifyMultipart (fastify, options, done) {
249248

250249
function onField (name, fieldValue, fieldnameTruncated, valueTruncated, encoding, contentType) {
251250
// don't overwrite prototypes
252-
if (getDescriptor(Object.prototype, name)) {
251+
if (name in Object.prototype) {
253252
onError(new PrototypeViolationError())
254253
return
255254
}
@@ -295,7 +294,7 @@ function fastifyMultipart (fastify, options, done) {
295294

296295
function onFile (name, file, filename, encoding, mimetype) {
297296
// don't overwrite prototypes
298-
if (getDescriptor(Object.prototype, name)) {
297+
if (name in Object.prototype) {
299298
// ensure that stream is consumed, any error is suppressed
300299
sendToWormhole(file)
301300
onError(new PrototypeViolationError())

test/multipart-security.test.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,141 @@ test('should not allow __proto__ as field name', function (t) {
104104
})
105105
})
106106

107+
test('should not allow toString as field name', function (t) {
108+
t.plan(4)
109+
110+
const fastify = Fastify()
111+
t.teardown(fastify.close.bind(fastify))
112+
113+
fastify.register(multipart)
114+
115+
fastify.post('/', async function (req, reply) {
116+
t.ok(req.isMultipart())
117+
118+
try {
119+
await req.file()
120+
reply.code(200).send()
121+
} catch (error) {
122+
t.ok(error instanceof fastify.multipartErrors.PrototypeViolationError)
123+
reply.code(500).send()
124+
}
125+
})
126+
127+
fastify.listen({ port: 0 }, async function () {
128+
// request
129+
const form = new FormData()
130+
const opts = {
131+
protocol: 'http:',
132+
hostname: 'localhost',
133+
port: fastify.server.address().port,
134+
path: '/',
135+
headers: form.getHeaders(),
136+
method: 'POST'
137+
}
138+
139+
const req = http.request(opts, (res) => {
140+
t.equal(res.statusCode, 500)
141+
res.resume()
142+
res.on('end', () => {
143+
t.pass('res ended successfully')
144+
})
145+
})
146+
form.append('toString', 'world')
147+
148+
form.pipe(req)
149+
})
150+
})
151+
152+
test('should not allow hasOwnProperty as field name', function (t) {
153+
t.plan(4)
154+
155+
const fastify = Fastify()
156+
t.teardown(fastify.close.bind(fastify))
157+
158+
fastify.register(multipart)
159+
160+
fastify.post('/', async function (req, reply) {
161+
t.ok(req.isMultipart())
162+
163+
try {
164+
await req.file()
165+
reply.code(200).send()
166+
} catch (error) {
167+
t.ok(error instanceof fastify.multipartErrors.PrototypeViolationError)
168+
reply.code(500).send()
169+
}
170+
})
171+
172+
fastify.listen({ port: 0 }, async function () {
173+
// request
174+
const form = new FormData()
175+
const opts = {
176+
protocol: 'http:',
177+
hostname: 'localhost',
178+
port: fastify.server.address().port,
179+
path: '/',
180+
headers: form.getHeaders(),
181+
method: 'POST'
182+
}
183+
184+
const req = http.request(opts, (res) => {
185+
t.equal(res.statusCode, 500)
186+
res.resume()
187+
res.on('end', () => {
188+
t.pass('res ended successfully')
189+
})
190+
})
191+
form.append('hasOwnProperty', 'world')
192+
193+
form.pipe(req)
194+
})
195+
})
196+
197+
test('should not allow propertyIsEnumerable as field name', function (t) {
198+
t.plan(4)
199+
200+
const fastify = Fastify()
201+
t.teardown(fastify.close.bind(fastify))
202+
203+
fastify.register(multipart)
204+
205+
fastify.post('/', async function (req, reply) {
206+
t.ok(req.isMultipart())
207+
208+
try {
209+
await req.file()
210+
reply.code(200).send()
211+
} catch (error) {
212+
t.ok(error instanceof fastify.multipartErrors.PrototypeViolationError)
213+
reply.code(500).send()
214+
}
215+
})
216+
217+
fastify.listen({ port: 0 }, async function () {
218+
// request
219+
const form = new FormData()
220+
const opts = {
221+
protocol: 'http:',
222+
hostname: 'localhost',
223+
port: fastify.server.address().port,
224+
path: '/',
225+
headers: form.getHeaders(),
226+
method: 'POST'
227+
}
228+
229+
const req = http.request(opts, (res) => {
230+
t.equal(res.statusCode, 500)
231+
res.resume()
232+
res.on('end', () => {
233+
t.pass('res ended successfully')
234+
})
235+
})
236+
form.append('propertyIsEnumerable', 'world')
237+
238+
form.pipe(req)
239+
})
240+
})
241+
107242
test('should use default for fileSize', async function (t) {
108243
t.plan(4)
109244

0 commit comments

Comments
 (0)