Summary of the OWASP Top Ten Vulnerabilities

The Open Web Application Security Project maintains a list of the top ten vulnerabilities ranked by their respective risks. These ranks are based on how easy it is for an attacker to exploit it, how common the exploit can be found in the wild, how easily an attacker can detect it, and the amount of damage that a successful attack can potentially cause.

OWASP Top Ten

A1 - Injection Attacks

This vulnerability is a high risk due to it’s ease of exploit-ability and severe technical impact; depending on the particular queries or execution context used data can be destroyed or extracted from a database or remote code execution can occur on the application server with the rights of the user the application is running under.

Server Side JavaScript Injection

If your application uses user gathered data as input to any of the following functions: eval() setTimeout() setInterval() and Function() then you are providing a vector for a potential attacker to inject code which can be executed on your server.

Dangers that can result from this for of injection attack are numerous and varied, an attacked could cause a denial of service by process.exit() or process.kill(process.pid). The attacker could also attempt to access the contents of the server side file-system with something like: res.send(require(fs).readDirSync().toString());

This can be mitigated through the use of server side validation of user inputs. If working with JSON data do not use eval() to process it, and instead use JSON.parse() to de-serialize the data. For other types of data make sure to parse them with the appropriate data type parser such as parseInt().

Generally you’ll want to give careful consideration to any instance where you choose to execute dynamic code.

SQL Injection

For applications that are backed with an SQL server to provide data persistence you need to be careful around any unguarded data being send as parameters to queries.

The following example demonstrates an opportunity for an SQL injection attack

conn.query("SELECT * FROM users WHERE username = '" + user + "' AND password = '" + pass + "'", function(err, rows){});

If the attacker were to use a username of whatever' OR 1;-- it will comment out the password requirement and also return all records from the users table.

This can be avoided by escaping any strings being passed into queries:

conn.query("SELECT * FROM users WHERE username = '" + conn.escape(user) + "' AND password = '" + conn.escape(pass) + "'", function(err, rows){});

however a better approach is to use parameterized queries such as:

conn.query("SELECT * FROM users WHERE username = ? AND password = ?", [user, pass], function(err, rows){});

This will automatically escape the user and pass parameters and increases readability and maintainability.

NoSQL Injection

On a similar vein to the above there are situations where using a NoSQL database will also result in an opportunity to steal data.

collection.find({'username': user, 'password': pass}).toArray(function(err, docs){});

If an attacker were to use a password of {$gt:''} then it would be interpreted as find all users with a password greater than an empty string. Thus returning all user accounts.

To combat against this form of injection you can explicitly state the $eq operator in your query.

collection.find({'username': { $eq: user }, 'password': { $eq: pass } }).toArray(function(err, docs){});

Which will prevent the attacker from overriding the implied equals operator.

You can also protect against NoSQL injection by JavaScript escaping the input data so that the database’s parser doesn’t use it as an executable part of the query.

It is important to remember that all user input should be assumed to be hostile. Even legitimate users might unintentionally provide dangerous input such as a randomly generated password from a password manager.

A2 - Broken Authentication and Session Management

This form of attack is usually the result of negligence on the part of the developer either by failing to guard the user’s password through encryption, choosing predictable session IDs or not refreshing the session once the user has re-authenticated.

Authentication Practices

Using a library like bcrypt or scrypt is always a good idea, not only because it will provide salting and hashing for your passwords, but it also uses a hashing algorithm that intentionally takes a long time (approximately a second). This delay is not so long that a user would find it undesirable, but long enough that it will help thwart attempts to guess user passwords.

Your application should also enforce password complexity requirements so that if hashed user passwords get leaked then the key-space that an attack will have to brute force is as large as possible. Using upper and lower case characters, numbers, and symbols as well as defining a minimum password length are all going to make guessing the passwords more difficult.

A practice I have noticed on login and sign-up pages lately is an attempt to block people from pasting into the username and password fields of these forms. This might seem like it would provide another layer of security except that an attacker will not be attempting to brute force the form directly. Therefore this measure only serves to frustrate those users that prefer to use a password manager. This will lead them to choose a password that is easier for them to remember and type thus causing them to pick a weaker password than they otherwise would have.

When an authentication attempt fails it is advised that the response shouldn’t give any hint which field it was that caused the failure. A generic message to the effect of “The username or password entered was invalid” is preferable to a more specific message stating that the account does not exist or that the password doesn’t match the one on file for that account.

Session Management

If your application manages sessions it should not use predictable session IDs such as an auto-incrementing integer field, nor should it expose the session ID in the URL as even legitimate users might inadvertently copy and paste the URL to send it to a friend leading to the friend unknowingly assuming the senders session.

The application should enforce a sensible timeout for all sessions so that if a user fails to log out on a public terminal it will limit the time that an attacker has to resume the session. On a similar note, all temporary tokens should also enforce appropriate timeouts whether they are for password reset, email confirmation or any other tasks as those tokens are frequently as important as the user’s password.

When sending a session ID via a cookie it is important that the cookie is only sent using HTTPS, which can be ensured using the secure flag on the cookie when it is created. It is also worthwhile to prevent the cookie from being accessed by scripts with the HTTPOnly HTML Header.

Finally make sure that when a user logs out that the session is destroyed on the server and the session cookie is destroyed on the client machine.

A3 - Cross Site Scripting

Cross Site Scripting (XSS) is a security flaw caused by an application accepting user input that is then sent to other clients without first ensuring the content is safe. This can be a problem for any site that allows user generated content publishing.

This form of attack can be protected against by ensuring all user input is validated and sanitized before it is sent out to any clients. The encoding that should be used is dependent on the context in which the content is going to be used.

If the content is intended to be used as HTML Entity data, that is, anything in within a predefined HTML tag such as <div>CONTENT HERE</div>, then you’ll need to HTML Escape it by converting &, <, >, “, ‘, and / so that the attacker cannot break out of the current context and switch to an executable context like inserting a tag which would get executed by the JavaScript engine once it is loaded into the DOM.

When inserting content into attribute value fields <div attr=CONTENT HERE></div> make sure to escape all non-alphanumeric characters with ASCII values less than 256 with the &#xHH; format. It is important in this instance to also encode spaces as developers frequently do not wrap the attribute’s value in quotes.

If you are accepting user generated content that gets added to a block of JavaScript then similar to the HTML attribute value escaping above you should be careful to escape all non-alphanumeric characters in Unicode escaping \uXXXX or HTML escaping &#xHH. You will also need to ensure that any variable values are quoted.

It is important that you take care to implement the escaping on both the server and client side depending on where the rendering is taking place as the XSS can be successfully exploited on the client regardless of where the malicious content gets inserted into the document.

As a fun little exercise Google put up a game to educate people about XSS XSS-Game.

A4 - Insecure Direct Object References

This attack surface is exposed by sending internal references to objects to the client side such as database IDs or filenames. If you must send internal identifies to the client it is especially important to validate the user is authorized to view the content. If the references are predictable such as auto-incrementing database IDs an attacker can modify the reference to gain access to resources they weren’t intended to see.

To protect against this make sure that every access a user makes is properly authorized, and make use of indirect references specific to the user or session instead of the direct object references. When populating a drop down list of options you can use an index into the list to denote the users selection instead of the ID from the database row and map that back to the ID on the server after form submission.

A5 - Security Misconfiguration

This is a broad category of scenarios that range from the operating system being insufficiently patched to leaving default usernames and passwords configured on services to closing holes in firewalls.

Prevention methods stem from server maintenance and documentation. There should be a repeatable process used to configure different environments such that only the TCP or UDP ports that are needed are left open, that any administration pages are turned off, and default admin usernames and passwords are changed.

The goal here is to reduce the attack surface as much as possible so that you only need to focus on your own application’s security and not that of everything else running on the application server.

For example by default MongoDB is configured to not require a username and password, and many people forget to change that before exposing it to the Internet. Over 680TB of Data Exposed in MongoDB Instances

Other configuration tasks that should be considered are disabling settings that leak information about what libraries and applications you are using in your stack such as preventing stack traces from being displayed to the user or disabling the X-Powered-By HTTP Header.

A6 - Sensitive Data Exposure

Sensitive data is generally any uniquely identifying information or personal information about your users, this includes things like passwords, credit card or other financial information, and medical information. What is considered sensitive information is very application specific, but the means of dealing with it should be similar regardless of what you decide is sensitive.

Once you have identified what information is sensitive every effort should be made to ensure that it is encrypted both while at rest in your database or on your file system, and also when in transit. Making sure that your application uses HTTPS at all times is important, as is ensuring the data you are storing is encrypted with a modern and secure encryption algorithm.

Backups and temporary copies of the data should also be encrypted, as well as having a key management process in place to prevent the encryption keys falling into the wrong hands.

It is worth noting that automated database encryption schemes are only useful in scenarios where attackers are attempting to access the stored data from outside the database. That means if you have an SQL injection vulnerability then the data will be decrypted automatically on retrieval. Therefore sensitive data should be encrypted and decrypted at the application layer in addition to any automatic at rest encryption that the database might provide.

A7 - Missing Function Level Access Control

Often times applications will have multiple levels of users (administrators, normal users, people not authenticated, etc.) as such there are areas that only certain classes of users should have access to.

Enforcement of access controls should begin with everything disabled and only add exceptions as needed. Testing is important to ensure that if navigating around as a privileged user and then switching to a non-privileged account that administrative or other privileged areas are not still accessible.

Frequently efforts will be made to hide links to the restricted areas, but the presentation layer alone is insufficient for handling access control. Likewise you cannot rely on information that is solely provided by the client side such as session tokens or user IDs, and instead need to use trusted data from persistent storage on the server side.

A8 - Cross Site Request Forgery

Cross Site Request Forgery (CSRF) is an attack where by an attacker crafts a page on another domain that is intended to make requests against your application with a valid user’s authenticated session.

For example if your application were to use a GET request to make changes such as transferring money from one account to another https://your-domain/transfer?amount=30&destination=evil_agent and made no attempt to validate that the request came from someone using your system then the attacker could inject that as a src for an image on a malicious site. If your user were then to be tricked into loading that malicious page while they had an active session with your application it could be mistakenly interpreted as a legitimate request.

At first glance it would seem switching all state changing requests to POST/PUT/PATCH requests instead of GETs would alleviate the issue, however that alone is not sufficient to verify the user is actively and legitimately interacting with your application. While this is a good practice to get into as GET requests should always be idempotent it does little to mitigate the dangers of CSRF.

There are two steps that will need to be taken to properly verify that a request is both for and from your application. The first is to validate that the request is coming from and going to its intended destination. The second is to generate and verify random synchronizer tokens.

In order to verify the request isn’t coming from an attackers site you should check the source origin of the request by gathering the Origin HTTP Header (required to be present if using HTTPS) or the Referrer Header. In the unlikely event that neither of those headers are present you should probably chose to block and log the request.

Once you have the above source origin you’ll need to verify that it matches the origin for your application. This can either be collected from the Host HTTP Header or if your application is running behind a reverse proxy or load balancer the X-Forwarded-Host header should contain the intended target origin as well.

With both Target and Source Origins available you’ll need to verify they match each other, and provided that they do you’ll know the request is coming from your application.

The second method is to create and verify randomly generated CSRF Tokens. These can either be stored in the users session or if you aren’t maintaining sessions you can also use a double submit cookie by sending the CSRF Token in the cookie and have the form submit return it as a request parameter as well. The same origin policy in browsers prevents another site from modifying the contents of another site’s cookies.

Additional HTTP Headers can also be set to ensure that the request is from your application because another restriction provided by the same origin policy is that additional headers can only be added to requests on the client side using JavaScript, and then only if that code is running on the same origin.

A9 - Using Components with Known Vulnerabilities

This should be common sense but it is important to keep libraries and other supporting infrastructure up to date. Often applications will have a security news-feed or mailing list to disclose vulnerabilities as they are discovered and patched.

One can avoid this by writing every component to their application themselves, but that is hardly feasible. Often times it is difficult to keep on top of this sort of thing, but it is important to do frequent audits of your applications dependencies in order to ensure that those dependencies are up to date.

If there are instances where you can’t update things in a timely fashion due to dependency conflicts or similar it should be documented what components are out of date and a plan should be put together for how to upgrade them or patch them to resolve the disclosed vulnerability.

For NodeJS and the ExpressJS web framework there are advisories available at NodeJS Vulnerability Feed and ExpressJS Security Updates.

A10 - Unvalidated Redirects and Forwards

This is similar to an injection attack in that it is caused from a lack of escaping user gathered content before including it into an HTTP redirection or forward.

It is important whenever you are going to formulate a response with an HTTP Status in the 3xx’s that you verify the URL is either not built from content generated by the end user or if it is that you properly URL encode it.

If exploited and attacker can make use of this vulnerability to create a legitimate looking phishing page that a user could land on thinking it is safe because the URL is on your domain such as https://example.com/redirect.jsp?url=phishing.bad

Share Comments
comments powered by Disqus