8 Critical Security Issues to Avoid in Your Website
A web application, being exposed to the web, is far more prone to be attacked than other kinds of applications. Building a secure web application is of prime importance — a hacked website costs money and time, impacts your business prospects and harms your customers.
Building a secure web application is not difficult at all. The security issues that arise are often due to simple mistakes that can be easily avoided.
In this article, we’re going to discuss the most common vulnerabilities found in web applications, and how to prevent them.
Issue #1: Cross site scripting (XSS)
Let’s have a look at a simple demonstration of XSS attack. Let’s say, we have a file named
demo.php with the following content:
Hello <?php echo $_GET['name']; ?>
Note how the script displays user input without filtering it. You can inject arbitrary scripts by navigating to an URL like:
Typing the above URL in a browser would result in:
Obviously, a real attacker would do something more malicious, such as injecting a script which steals the user’s cookies. The attacker could then send it to users, who are compromised by opening the link.
This attack could appear in other forms too. Consider a comment form on a website: an attacker could submit a script as a comment. This script will now be executed by anyone who happens to visit the page.
The proper way to prevent XSS is to convert characters such as
> to their respective character entities, such as
>. If you’re storing the data as with the comment form example, you should only do this while displaying the page. You should not store the data in the entity-encoded form.
If you’re using PHP, you can use
htmlspecialchars() for this purpose. If we rewrite our above PHP script like so:
Hello <?php echo htmlspecialchars($_GET['name']); ?>
The attack will no longer work, and instead display:
If you’re using a templating engine or a framework, this is already handled for you.
The XSS prevention cheat sheet by OWASP covers this in great detail, including cases where conversion to HTML entities may not be enough.
Issue #2: SQL injection
SQL injection occurs when data entered by the user is put into an SQL statement directly. The user-entered data could contain SQL statements, thus allowing the attacker to bypass authentication, steal information and modify your databases.
Consider the login form of a website. To check for successful authentication, a website could do something along the lines of:
result = db.query("SELECT * FROM users WHERE username = '" + request.post("username") + "' AND password = '" + request.post("password") + "'")
For a normal user, the SQL query would look like:
SELECT * FROM users WHERE username = 'admin' AND password = '$tr0ngpa$$w0rd';
However, an attacker could enter an username such as
hacker' OR 1 = 1 --. This results in the following query being executed:
SELECT * FROM users WHERE username = 'hacker' OR 1 = 1 -- AND password = 'password';
In this case, the attacker is able to bypass authentication entirely, by changing the query that was executed.
The proper way to prevent a SQL injection attack is to use parameterized queries. It is a way of executing database queries where the data and the query are kept separate, thus preventing this problem entirely. Most database drivers have this feature, so you should switch to it already if you’ve been using string concatenation instead.
In addition, if you use an ORM (Object-Relational Mapper) for accessing the database, you should also be fine. Most ORMs use parameterized queries to generate their underlying queries.
Issue #3: Problems in authentication and session management
Developing your own authentication system could give rise to a number of security issues. While discussing all of them would be outside of the scope of this article, here are the most common problems:
- Not preventing username enumeration: When there is an authentication failure, the error should not suggest whether the username or password was incorrect — instead, simply say something like “Incorrect username or password.” This prevents attackers from finding out which users have accounts on your service.
- Not regenerating sessions: When an user is logged in or logged out, you should regenerate the session identifier associated with the user, in order to prevent a “session fixation” attack. Suppose, an attacker logs into a website from a public computer, notes the session identifier, and then logs out. When another user logs in from the same computer, the same identifier is being reused. Thus, the attacker can send requests from another computer with the same identifier to determine if another user has logged in. This allows for a complete compromise of the user’s account.
- Allowing unlimited login attempts: You should place limits on the number of times an user can try logging in within a certain period of time. Otherwise, your web application would be vulnerable to brute force attempts. The most user-friendly way is to require users to fill in a CAPTCHA before logging in if they’ve already tried doing so too many times. In other high-security applications, you could lock out IPs or user accounts if you’ve had too many attempts.
- Not checking the use of insecure passwords: Many web applications do not have rules in place to prevent users from keeping insecure passwords, such as the infamous
123456. While most users find complicated password rules frustrating, you can add simple checks like rejecting passwords that match the 1000 most common ones, or passwords that are the same as the user’s username or email address.
Issue #4: Proper storage of passwords
Storing sensitive information such as passwords in plaintext is one of the worst mistakes that a developer can make. This is because if your database is stolen (such as through SQL injection or by an insider), the passwords will be served on a silver platter to the attacker. Since there are many users who reuse passwords across websites, this could lead to all of their accounts being hacked.
Many websites suggest using a cryptographic hashing function, however, even this is insecure — they are too fast to provide sufficient resistance to an attacker who is trying to iterate across millions of passwords to see if one of them matches the hashed password.
The proper way to store passwords is by using a strong password hashing function, such as bcrypt, along with a “salt” added to it. A “salt” is a random value that is added while hashing the password. When the password is stored, the hash is stored along with it, in plaintext. The salt simply serves to change the hash, so that even if two users chose the same password, the hashed password would vary. Thus, an attacker would be unable to tell if two users passwords are the same, and make educated guesses as to what the password may be.
For more details, take a look at this video Marty Weiner presenting at QCon:
Issue #5: Cross Site Request Forgery (CSRF/XSRF)
A CSRF attack involves making an user perform actions on a website without their knowledge.
Consider a banking website where people can make money transfers. An attacker notices the fact that money transfers make use of the following URL:
Let’s say, the attacker, Eve wants to steal money from a person, Bob. Bob makes financial transactions all the time, and he has the bank’s website open most of the time. Eve sends a mail to Bob, which contains a link containing pictures of cute cats. However, in addition, to all those pictures, the page also contains the following HTML:
When the browser tries to fetch this URL, it ends up performing the money transfer, with Bob being none the wiser.
The easiest way to prevent CSRF attacks is by checking the Referer/Origin headers of the request. If they don’t match the name of your website, deny the request.
Another popular prevention technique is to initiate sessions for every user who happens to visit the website. On the server, a long, random string of characters is generated for every session, which is called the “CSRF token”. When a form is presented to the user that performs a sensitive action, this token is embedded as a hidden field:
<input type="hidden" name="csrf_token" value="cas11L2bcutotsSjnp2a5mOveGI">
When the form is submitted, the value of the hidden field and the value stored on the server is compared. If they do not match, the request is rejected.
If you’re using a framework, this should be automatically handled for you with just a few lines of code.
Issue #6: Failure to restrict URL access
There are some URLs in a web application which should be restricted to a certain set of users. For example, only administrators should be able to access administration pages; users should not be able to change other’s profile information, and so on. If these pages can be accessed by other categories of users, then the website is vulnerable.
When building a web application, you must be careful so as not to allow unauthorised users access to resources that are not meant for them.
Issue #7: Unvalidated redirects
Redirections are commonplace on websites. If the redirection occurs on the basis of a parameter present in the HTTP request, it could be misused for various purposes.
For example, there are many websites which redirect an user to a web page after performing an action. For example, many websites allow the user to return to the same page after logging in — and in order to do so, they use URLs of the form:
An attacker could replace the
redirect parameter with something like:
While this seems far more innocuous than the other vulnerabilities, it can still be useful for deceiving users. For example, it could be used to redirect the user to a phishing website after logging in. Most users would have a hard time detecting it. After all, they did enter their username and password on the original website!
Preventing this attack is easy. If the redirection parameter contains an URL which doesn’t point to your website, don’t redirect them to that page.
Issue #8: Uploading server-executable scripts
If users can upload files through your web application, you must take care so as to not allow uploading of PHP and CGI scripts. An attacker could simply upload a PHP script, and then access it, thereby gaining access to your server.
You can prevent this attack from happening in a variety of ways. Two of them are as follows:
- Restricting the types of files that the user can upload: Say, you’re building a website for uploading images. You can check the MIME-type of the uploaded file. You should not rely upon the file extension or the MIME-type value provided by the browser. Instead, you should check the actual structure of the file to see if it’s an image. In PHP, you can use
finfo_file(). If it isn’t an image, you can reject it.
- Disabling scripts in the upload directory: You can configure your server to exclude the upload directory from executing CGI and PHP scripts. This will allow users to even upload such scripts. When the user tries to visit the URL to the uploaded script, they will be able to see the contents as-is, just like a text file.
However, if you’re using something like NodeJS or Django to build your web applications, and/or you don’t have PHP or CGI enabled on your server, you won’t need to check for this.
Security is an aspect which if ignored can cause serious repercussions. If you’ve read through all these steps and taken care of the issues involved, your web application is now more secure!