How to securely upload files in Node with Express?
Asked Answered
L

2

6

There are many articles, tutorials, and questions about file uploads in node but mostly they are for beginners and none of them fully explains how to secure file uploads for production. I have tried very hard to find a complete answer on how to do it, but it was not successful.

Below is an explanation of my findings.

  1. Limit file size on uploads:

    app.use(express.limit('4mb'));
    
  2. Limit file uploads to only certain routes: I can't get this to actually work but here is what I have tried:

    Replace:

    app.use(express.bodyParser());
    

    with

    app.use(express.json());
    app.use(express.urlencoded());
    

    and add the multipart middleware to every upload route:

    app.post('/upload', express.multipart(), uploadController.uploadPhoto);
    

    This part doesn't work, but the upload works fine if I leave express.bodyParser(). So what am I doing wrong?

  3. Checking uploaded file type before saving upload to disk:

    I couldn't figure this part out, but a suggestion was to write a custom middleware that uses formidable to parse file uploads and trying to resize the file before it is saved (assuming that it is an image) using a library like image magic. The suggestion was that this would make the image safe and ensure that it is actually an image (because the process would fail if it is not an image).

    This would only work with images though, so it is not a complete solution.

    How can I implement this? Any example code?

Is there anything else that I am missing for uploads to be safe?

Lodgment answered 22/1, 2013 at 14:9 Comment(4)
3 would be the way to go, assuming that 2 is not working. If it is, than that will be the ideal solution.Kelleher
From what I read 2 should work, am I doing something wrong? Also do you have any examples of how to do 3? I tried to do it and was not successful. Most of the code I found did not work with the new version of expressLodgment
Could you please create the smallest project possible using 2 and share the code, possibly on github. I will take a look at it. If it doesn't work, I will build 3 for you and share.Kelleher
I ended up using a plugin called alleup which uploads the files to tmp first and then resize them then deletes them from tmp so its not checking the file type before the file is stored. Do you think this is secure enough?Lodgment
L
2

Approach 2 actually works. The problem I had was that

app.use(passport.session());

was stopping it from working. So, if you are using passport.js for authentication this might be the issue. If you use this approach just make sure to add the security on the actual route.


I ended up using this plugin

https://github.com/tih-ra/alleup

which works great with image uploads and automatically resizes the files to multiple versions and uploads them to amazon s3. Using this plugin would be inline with using approach 3, but the files are uploaded to the tmp folder first and then deleted.

Lodgment answered 23/1, 2013 at 12:12 Comment(3)
I thought you original question was about securing the paths for file uploads. Say for example someone posts a multipart form with file attached to a path /restricted-path, what would you do then?Kelleher
well the plugin uses formidable to process file uploads and for that to work express multipart should not be used so uploads cannot be posted to to just any routes. I have another problem though that passport.session() stops formidable from triggering file events #14479843 so I either need to fix that problem or figure out how to do number 2.Lodgment
ok so I figured out that app.use(passport.session()) is what makes number 2 not work. I think it is something related to passport.session() needing to be called after the multipart and not before. I am still not sure how to do that on a route by route basesLodgment
D
1

I am using multiparty for uploading (and streaming) files.

var form = new multiparty.Form();

To 1:

form.on('progress', function (bytesReceived) {
  if (262144000 < bytesReceived) {
   abortConnection('filesizeexeeded');
  }
});

implement your own abort Connection function; e.g.:

function abortConnection(reason) {
  res.writeHead(413, { 'Connection': 'close' });
  return res.end(reason);
}

warning: the browser will most probably retry the upload (up to 4 times). I am using a websocket connection to cancel the upload on the client side.

To 2: (use multiparty)

To 3: I created a gist that shows how to check the mime-type on the fly using mmmagic.

If you are using passport in combination with multiparty you might find this useful:

https://github.com/jaredhanson/passport/pull/106#issuecomment-14188999

Doggerel answered 20/1, 2014 at 14:35 Comment(1)
Seems good but how do you get the filestream when you are using multipaty? I have had this problem a long time ago and I still can't find an answer.Hurryscurry

© 2022 - 2024 — McMap. All rights reserved.