Secure coding practices are hard. This blog post presents couple of secure coding practices for modern web development.
Coders are usually good at their jobs. They dedicate hours and hours to tune their skills. Little does a typical coder know how they should be coding more securely. When Secure Coding Practices are introduced, people are amazed to find out in how many ways malicious attacker can do harm in their system. This is because coders usually see security as firewalls and user credentials. There’s much more to it than that.
Here’s presentation of layers in modern web application security:
This is not a perfect presentation of security, but gives a rough idea what’s what. You’re sitting inside a really big bubble of layers. You are the first link in the chain. Don’t screw it up.
Snapshot of security threats
Evil Regex in event loop
Node.js is well known event-driven I/O server-side JavaScript environment. It’s very fast and powerful, but it’s got a caveat: If you make http or other operation synchronically, it will prevent all other requests from executing. Usually these situations are handled automatically in various libraries so you really don’t have to think about it too much.
There’s still one nasty situation which can block event loop even though it looks quite innocent:
console.time( 'taketime' ); /^(([a-z])+.)+[A-Z]([a-z])+$/.test( 'aaaaaaaaaaaaaaaaaaaa!' ); console.timeEnd( 'taketime' ); |
This example introduces Regular Expression Denial of Service (ReDoS) attack. The more letter ’a’ you give, the more you have to wait. You can even try this in your browser JavaScript console.
Let’s look at a typical email-validation (email-validation with regular expression itself is a waste of time, since you can’t handle all the cases.):
console.time( 'taketime' ); /^^([a-zA-Z0- 9 ])(([-.]|[_]+)?([a-zA-Z0- 9 ]+))*(@){ 1 }[a-z0- 9 ]+[.]{ 1 }(([a-z]{ 2 , 3 })|([a-z]{ 2 , 3 }[.]{ 1 }[a-z]{ 2 , 3 }))$/.test( 'aaaaa!' ); console.timeEnd( 'taketime' ) |
Email-validation above introduces attacking surface for Regular Expression Denial of Service (ReDoS) attack.
Something to look at that will enable ReDoS attack surface:
- Grouping with repetition, ( )+
- Inside repeated group
- repetition, ( a+ )+
- alternation with overlapping, ( a|aa )+
- Inside repeated group
There are some tools to test whether your regex expression is vulnerable:
More dirty details about this can be read in https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS.
HTTP Parameter pollution
HTTP Parameter pollution is quite overlooked subject. Usually we just get values from request and use them as such. There’s still a caveat that can cause Node.js to crash if there’s no exception mechanism attached.
// POST firstname=Seppo&firstname=Seppo req.body.firstname // => ["Seppo", "Seppo"] |
By default you were expecting just a string with firstname, but instead you got an array. After you start doing some operations expecting string, you might get type errors (trim() can cause this).
In the worst case you could end up with some really strange looking values in your schemaless NoSQL database.
More details: https://www.owasp.org/index.php/Testing_for_HTTP_Parameter_pollution_(OTG-INPVAL-004)
Mapping and validating request values
Validating may sound simple, but there’s more to it than checking if something is string, number or email. If your system has some prefilled values in eg. a dropdown menu, you should always make sure that the user has inserted a valid value. This means that you must always use surrogate structure to get final value for your operations.
// POST position=coder const position = positions[req.body.position]; // can be id, number or even the same value. fetchJobs(position); |
Example above shows that the job position data structure has the final value that should be used in the function call. Never trust the user input.
More information on data validation: https://www.owasp.org/index.php/Data_Validation
Don’t show your internals
This is too obvious to notice in the day to day life. Most of the coders know that throwing any exception or error trace to the user is bad. Some think it’s ugly, some think it’s revealing too much.
How far we can take this subject? Put on the hat of a attacker and consider what these points reveal about the system:
- ID 1432 does not exist
- This gives a hint what type of format your ID’s are. Also now you know the field name.
- table ’users’ does not exist
- Do not tell if a table does not exist. Now the attacker knows your table name.
- user ’Seppo Sorsa’ does not exist
- Never reveal if a user does not exist in your system.
- password for ’Seppo Sorsa’ is incorrect
- Now the attacker knows that this user does exist in the system.
- user does not exist
- Same as above
- incorrect password
- Same as above
Better alternatives
- An error occurred.
- User does not exist or password incorrect
- Error 3451, Please use this as a reference when contacting customer support.
- (Then give list of error codes and explanations to customer support.)
These seem like minor things, but all these reveal attacking surface to the malicious attacker. So they get a starting point where dig deeper.
More information about attacking surface: https://www.owasp.org/index.php/Attack_Surface_Analysis_Cheat_Sheet
NodeGoat learning environment
There’s simple learning environment for Node.js where you can learn OWASP TOP10 security risks: https://www.owasp.org/index.php/OWASP_Node_js_Goat_Project
What you need is: Docker (https://docs.docker.com/installation/) and Docker Compose (https://docs.docker.com/compose/install/)
1.Get sources
git clone https: //github.com/OWASP/NodeGoat.git cd NodeGoat |
2.Change the db config in config/env/development.js
to point to the respective Docker container.
db: "mongodb://mongo:27017/nodegoat" , |
3. Build and run
docker-compose build docker-compose up |
4. Open up learning tutorial and have fun!
http: //localhost:4000/tutorial |