Ten Tips For Securing Your Web Applications
Web applications are often notoriously insecure. With more of us migrating to web-based technologies, ridding the web of these insecurities becomes a top priority. Here are ten tips that should help you secure your web applications.
1. Send all confidential data over a secure connection.
At the very least, send user credentials (i.e. username and password) over HTTPS. At the very most, send all data over HTTPS, especially when your apps are dealing with large amounts of personal information. There are almost no excuses for not using HTTPS these days, especially when buying an SSL certificate is so cheap. Be aware that if you choose to only send credentials over HTTPS, your web application will be susceptible to session hijacking attacks.
Never send any confidential data in an email, especially password confirmation emails. Email is not a secure method of communication, and it probably never will be (PGP is not widely used at all). When dealing with passwords, always let the user set their own, as opposed to generating it for them. That way, you do not need to send their password in an email since they already know what it is.
2. Encrypt confidential data before storing it.
If your web application stores credit card numbers of users or other confidential data, make sure that this data is encrypted in whatever storage medium you are using. If your web application needs to access this data, it should be copied and decrypted in memory, before discarding the copy. At no point should the unencrypted data be stored in some permanent location.
Additionally, the key(s) used for encryption / decryption should not be stored in the same location as the encrypted data. This is to minimize damage if the storage medium is compromised (for instance, if hackers gain access to a database containing encrypted data, the decryption key should not also be compromised).
3. Salt and hash all passwords in the database.
This is possibly one of the most important things a web application designer should implement in terms of user security, but again and again we see large organizations and companies either ignoring or misunderstanding the importance of salting and hashing passwords.
There are absolutely no excuses for not salting and hashing passwords. Your web application should never be able to retrieve a user’s password, either for a comparison or for sending to the user in case they forget it. When the user first registers, their password should be concatenated with a salt (some unique random string of characters) and then hashed with a strong hashing algorithm (SHA-256 for example). PHP has a built-in function called crypt() that supports numerous hashing methods.
4. Any alterations that increase security should be opt-out or forced.
Many web applications start off with a basic level of security, and then slowly add security as time goes on and the application becomes more popular (or, more often, in response to a security breach). Aside from the fact that this is a terrible way to approach application development, if it is done this way, any alterations to the application that increase security should be opt-out or forced upon the user.
To use a counter-example, Facebook originally worked over HTTP, with only the login being sent over HTTPS. This led to session hijacking attacks where a user on an insecure network could have their session cookie(s) stolen and used to gain access to their account. To fix this, Facebook allowed the entire site to be used over HTTPS, but had this option disabled by default. In short, they fixed the problem for those less likely to be affected by it anyway (the security conscious who don’t use insecure networks). Such an option should be forced upon users (as it does not impede on the way they use Facebook at all).
Generally speaking, if the increase in security has no effect on the usability of the application, or it fixes a critical security vulnerability, it should be forced on users with no option to opt-out. If the increase in security affects usability in some way which may annoy a large number of users, and the security issue it fixes is not considered high severity, there should be an opt-out for those users. Obvious exceptions to an opt-out would be features that require user setup (e.g. 2-step authentication).
5. Any alterations that reduce privacy should be opt-in.
A reduction in privacy happens if an alteration to the application makes some (user) information accessible to users who didn’t have access to it before, or if the application starts storing information that it wasn’t storing before. For example, if an email address on a user profile is usually hidden and an alteration is made to allow the user to display their email to other users, the email address should remain hidden until a user actively chooses to display it.
In the case of more information being stored than was before, it is perfectly acceptable to deny access to users who refuse to agree to this new storage feature (e.g. if a law is passed which requires your application to store IP addresses indefinitely). However, prior to the user agreeing to the change, the system should treat their information as it did before.
6. Password resets should depend on a security question of the user’s choosing.
If the user uses a “forgot my password” feature, the application should assume that the request is malicious and present the user with a security question (or questions) that they set up when their account was registered. Sending an email with a password reset link is not recommended, since the email account could already be compromised. Once the security questions are answered correctly, it is then acceptable to send a password reset link in an email.
Letting users themselves set the security questions on their accounts puts the security of their account in their own hands, and reduces the usage of pathetic security questions like “Mother’s maiden name?”.
7. Persistent session cookies should have a short life, but be renewed often.
Persistent session cookies (i.e. those that keep a user logged in between browser sessions) should expire after a short period of time. This reduces the likelihood of an attacker gaining a valid session cookie from an offline source and using it to hijack a user’s session.
In order to make cookies last longer without increasing the likelihood of such a session hijack, cookies should be renewed after a certain amount of time (thus extending the cookie expiration date). For example, a persistent session cookie is initially set to expire 2 weeks from when it was created, but it is renewed for another 2 weeks if the user uses the application when the cookie has less than one week until its expiry date.
8. Protect cookies from JavaScript using HttpOnly.
Setting the HttpOnly flag on cookies (theoretically) prevents JavaScript from accessing cookies completely. This means that any Cross-site Scripting (XSS) attacks will be severely limited, as they are often used to send cookies to a remote attacker. Even if you have protected against XSS attacks (see tip 9), the HttpOnly flag will protect your users against self-XSS attacks (where an attacker convinces the user to manually enter JavaScript and run it in their browser).
9. Treat all data as malicious. Validate and sanitize input. Escape all output.
If this list of tips could be reduced to just one item, it would be this one. User-entered data is something you have absolutely no control over, and it is the basis for almost every single attack on web applications. All data should be treated as malicious and handled as such.
Validate all user input against a whitelist of acceptable values (e.g. if an integer is expected, ensure that it does not have any non-numeric characters). Sanitize all input strings before sending them to a database by escaping characters like ‘ and ” (the PHP function mysqli_real_escape_string() does this for you). Using prepared statements or stored procedures instead of passing user input directly to an SQL statement will eliminate any SQL Injection vulnerabilities if they are used correctly.
Do not forget to validate lengths of inputs either. The HTML “maxlength” attribute can be easily ignored by an attacker, so always do a length check when the input reaches the application server.
When outputting user-submitted content to a web page, know exactly where it is going in the page and escape it accordingly. Escaping content intended to be placed inside HTML tags is completely different to escaping content intended to be placed inside JavaScript code.
10. Generate unique hard-to-guess tokens to be used with requests that alter values.
Cross-site Request Forgery (CSRF) is a potentially devastating attack that only requires a user to visit the attacker’s website. It affects most web applications simply because developers do not consider the trust that browsers have in their users. Unfortunately, CSRF is complicated to defend against, but such defences should be implemented if your application is to be judged as secure.
For each request that alters a value (ranging from changing a user email address to actively logging the user out), a unique and hard-to-guess token should be pre-generated and submitted with the request. Upon receipt of the request, the application should check the token to ensure it is valid before the alteration is allowed.
