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

Invalid csrf token when on multipart/form-data #53

Closed
mk0y opened this issue Jan 2, 2016 · 12 comments
Closed

Invalid csrf token when on multipart/form-data #53

mk0y opened this issue Jan 2, 2016 · 12 comments

Comments

@mk0y
Copy link

mk0y commented Jan 2, 2016

When I'm using formidable and upload image fields in forms with enctype=multipart/form-data, I'm getting "invalid csrf token" error message. When I remove enctype from the form, it works.

Any ideas?

Thanks

My middlewares:

    app.use(expressSession({                                                                                                                                                                                                  
         cookie: {                                                                                                                                                                                                                  
             maxAge: 60000                                                                                                                                                                                                          
         },                                                                                                                                                                                                                         
         secret: '...',                                                                                                                                                                                      
         resave: true,                                                                                                                                                                                                              
         saveUninitialized: true                                                                                                                                                                                                    
    }));

    app.use(bodyParser.json());                                                                                                                                                                                               
    app.use(bodyParser.urlencoded({extended: false}));                                                                                                                                                                        

    app.use(cookieParser('...'));                                                                                                                                                                      

    app.use(require('csurf')());                                                                                                                                                                                              
    app.use(function(req, res, next) {                                                                                                                                                                                        
          res.locals._csrfToken = req.csrfToken();                                                                                                                                                                                   
          next();                                                                                                                                                                                                                    
    });


    //                                                                                                                                                                                                                             
    // 404                                                                                                                                                                                                                         
    //                                                                                                                                                                                                                             
    app.use(function (err, req, res, next) {                                                                                                                                                                                  
         res.status(400);                                                                                                                                                                                                           
         res.send(err.message);                                                                                                                                                                                                     
    });
@mk0y
Copy link
Author

mk0y commented Jan 3, 2016

I solved this but adding:

action="?_csrf={{_csrfToken}}"

to the form. Removed the hidden field for csrf token.

@mk0y mk0y closed this as completed Jan 3, 2016
@EthanRBrown
Copy link
Owner

Hm, that's interesting...the hidden field should have done the trick, though your fix is also valid. I'll look into this....

@jason-ca
Copy link

jason-ca commented Apr 3, 2016

Experiencing the same problem, tried both Formidable and Multer. The problem also appears only with multipart/form-data forms in my case. I confirm Markzero's fix works, the difference with my code is that I do app.use(csrf({cookie: true})); It's interesting why hidden field doesn't work. Can the problem lie within csurf or formidable/multer when they interact with multipart/form-data?
As I understand, the fix is not very clean - https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-include-csrf-token-in-action

@jason-ca
Copy link

jason-ca commented Apr 4, 2016

Ok, I've found the solution here - https://github.com/expressjs/csurf/issues/58 (the issue is also mentioned here - https://github.com/expressjs/csurf/issues/15).
I will clarify it here for people coming over from Google, as it took me some time to understand it, try a few options from there and properly implement it. (I'm a newbie).
Despite the above Markzero's fix with query parameter works, as I mentioned, it's not really safe, follow the link to Spring.io above to see why.
Ssso, for Multer to work with csurf, you will need to place multer middleware above csrf middleware.
You could individually pass middlewares (without app.use(...) to your app.post, like app.post(..., multer, csrfProtection ...), but I've got the impression it doesn't really work, may be I did something wrong.
Anyways, the below code is a bit sloppy, also due to tabulation, but suffices for illustration purposes and is an example of what really works:

Your app.js, a basic picture upload app:

var express = require('express'),
      cookieParser = require ('cookie-parser'),
      hbs = require('hbs'),
      csrf = require('csurf'),
      multer = require('multer'),
      app = express();

app.engine('html', hbs.__express);

app.set('view engine', 'html');

app.use(express.static('public')); //Don't forget to have /public folder or whatever you want to use for uploads

app.use(cookieParser('secret'));

app.use(multer({dest: 'public/'}).single('file')); //Beware, you need to match .single() with whatever name="" of your file upload field in html
app.use(csrf({cookie: true})); //So here follows csurf, _after_ multer

app.get('/', function(req,res){
    res.render('main', {_csrfToken: req.csrfToken(), layout: false});
});

app.post('/', function(req, res) {
    res.render('main', {_csrfToken: req.csrfToken(), img: req.file.filename, layout: false}); // I assume I upload a picture, thus, {img:...}. Also, I'm passing csrf token here as well, so that one is able to upload again after having done so already - if not, it will report invalid csrf token, so we need it since the server would have regenerated it after the first upload.
});

app.listen(5000);

console.log('Servo started on :5000');

Your main.html in /views:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
  <title>Testing multer and csurf</title>
</head>
<body>
  <h1>Testing multer and csurf</h1>
  <div style="border: 1px solid black; padding: 5px;">
        <form action="/" method="post" enctype="multipart/form-data">
                        <input type="hidden" name="_csrf" value="{{_csrfToken}}">
                <input type="file" name="file" accept="image/*">
                <button type="submit" id="file_button">Upload it</button>
        </form>
  </div>
  <img style="width: 30%; margin: 20px" src="{{img}}">
</body>
</html>

Now, you can check if it works by removing hidden input from your html and trying to upload something, it will fail complaining about invalid token.
Good luck!

@folamy
Copy link

folamy commented Dec 13, 2016

@jason-ca ....Thank you so much........ it actually works

@takinola
Copy link

The only issue with @jason-ca's solution is that multer will still upload the files if the CSRF fails (because multer processing happens before the CSRF check is performed).

@wajihkm
Copy link

wajihkm commented Dec 27, 2017

I think the problem is that:
"multipart/form" send chunks of data so at the server you can't access the body inputs directly,
you must wait till the end of uploading so you can access all the body fields.

@itsgratien
Copy link

still i got the same issues, invalid csrf token

@Brondchux
Copy link

@markzero you are still saving lives in 2019, thank you !

@gregbown
Copy link

@tamb
Copy link

tamb commented Jan 16, 2020

A possible crumby work around is adding ?_csrf=mytoken to the action of the form

@Khaos66
Copy link

Khaos66 commented Feb 15, 2020

I found that using express-fileupload over multer works better. This module supports uploads to memory, so no files are written before the CSRF token is validated. (Only good for small files, of course!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests