The folks at RisingStack have published a really good article on security in Node.js applications and this checklist is meant to complement it with specifics for API development using the express framework.
- [ ] Secure headers: use helmet, especially to set the Strict Transport Security header which will keep all your connections on HTTPS. Also see here on how to setup https using a free certificate from letsencrypt.
- [ ] Log all errors but don't expose stacktraces to the client.
- [ ] Rate limit api calls to protect against DoS attacks. Can use expres-rate-limit.
- Sanitize all user input
- [ ] Sql injection: use prepared statements in favor of concatenating user input. For e.g.
can be hijacked toapp.get('/', function(req, res) { Promise.using(getSqlConnection(), function(connection) { var sql = 'SELECT * from users where id = "' + req.query.username + '"'; return connection.queryAsync(sql, [id]) .then(function(rows, cols) { return rows; }); }); });
/?username=anything%22%20OR%20%22x%22%3D%22x
which results in the following sql query being executed:select * from users where id = "anything" OR "x"="x"
. This will always result in true and return data for all the users in the system. This can be further extended to cause a lot more damage. -
[ ] XSS: prevent the ability of an attacker to inject arbitary code into your application by sanitizing user input. For e.g. the following end point which accepts user input
can then be hijacked to create a url as followsapp.get('/', function(req, res) { var html = 'Hello ' + req.query.username; res.send(html); });
/?username=%3Cbody%20onload%3Dalert(%27test1%27)%3E
. This link can then be sent to unsuspecting users of your website and have arbitary code being executed on their machine. See here for more types of XSS attacks and examples. -
[ ] Command injection: for example, a url like
https://example.com/downloads?file=user1.txt
could be turned intohttps://example.com/downloads?file=%3Bcat%20/etc/passwd
. -
[ ] MongoDb query injection: similar to sql injection but using MongoDb's special operators instead. As an example consider the following end point
where sending inapp.post('/', function (req, res) { db.users.find({username: req.body.username, password: req.body.password}, function (err, users) { // TODO: handle the rest }); });
will result in a successful match. Use mongo-express-sanitize to sanitize all user input.POST http://target/ HTTP/1.1 Content-Type: application/json { "username": "vic@smalldata.tech", "password": {"$gt": ""} }
- [ ] Regex Denial of Service: a situation where user inputted regex can lead to blocking the event loop and a hanging application. See here for examples.
- [ ] Sql injection: use prepared statements in favor of concatenating user input. For e.g.
- [ ] Use TLS for all connections. Also see here on how to setup https using a free certificate from letsencrypt.
- [ ] Keep dependencies updated to stay ahead of any security issues. Use
npm audit
to check dependencies for security vulnerabilities. Another great platform for open source projects is snyk.io. - [ ] Check for permissions at every step of the API chain: for e.g.
GET /users/:userId/contacts/:contactId
should not assume that the userId authenticated for the request is also authorized to make this call. Check thatrequest.params.userId === request.authenticatedUserId
orisAuthorized(authenticatedUserId, {userId: authenticatedUserId, resource: 'CONTACTS'}
. - [ ] Don't block the event loop: as an example parsing json is not a free operation and can potentially block the event loop for large json files (> 1Mb). Note that using the
bodyparser
module globally will give you a default maximum of 100kb for json payloads. It is efficient to only use it for routes which require it.
Please note that this checklist is meant to be used as a reference for further study. It is by no means an exhaustive list of all potential security issues. See also the web developer security checklist. Additions and comments are welcome.