Simple Ways to Add Security to Web Development

Nitish Tiwari

Issue #238, February 2014

Can't afford to lose time in code refactoring for security? Why not make everything secure in the first place? Read on!

As a software developer myself, I have seen developers rushing to finish the feature they are assigned to, with little or no consideration for security in the code—no security guidelines, no coding standards, just a mad dash to finish the feature. Next comes the security review, in which the software obviously fails, and then comes the security-hardening phase.

Although trying to improve code's security obviously is a nice thing to do, the time when it commonly is done is often in the final code development phase, and as with the basic nature of software development, changing the code almost always leads the software away from maturity. So, the software that has almost ended its development phase is again pushed to instability during the security-hardening phase. Is this really necessary?

Why can't developers make the code secure in the first place? What can be done to make developers more aware of application security policies, so they are more informed and alert when they develop their next application? In this article, I discuss how developers can do so effectively.

One simple way is to change developers' coding styles and make them write code that is inherently secure. Also, following simple policies related to application security can make a lot of difference. This is sometimes not a very easy thing to do, but if the practices to follow are simple and easy to adopt, it is not very difficult.

Let's look at some security concerns/flaws typically found in software and the corresponding security mechanisms and policies that can be applied to counter them. These mechanisms generally can be implemented in all programming languages and follow the OWASP code development guidelines. But, for the sake of open-source culture, I use PHP as the language for the examples in this article.

SQL Injection

Let's start with the most famous of the lot. It is also one of the most widely used and one of the most simple for unleashing attacks on the Web. What many people don't know, however, is that it's easy to prevent as well. Let's first consider what an SQL injection attack is.

Suppose you have a text box in your application for a user name field. As the user fills it in, you take the data to the back end and fire a query to the database—something like this:


<Input Type = "Text" value ="username" name = "username">

<?php $username = $_POST['username']; ?>

Then, the SQL query:

SELECT * FROM table WHERE name = '" + $username + '"

A simple way to attack this system would be to type “'” or “'1'='1” in the text box. The resulting database query now will be:

SELECT * FROM table WHERE name = '  ' or  '1'='1'

As you can see, this condition always is true and when executed, the query will just split out all the rows in the table. This was a simple example, but in real-life scenarios, such attacks are very severe and can take down a whole application in a few seconds, because they are targeted directly at the database.

So, how can you prevent this? Simple logic is that instead of passing the input taken from the front end directly, it should be checked thoroughly and only then sent to the database as a part of the query. Here are the most common and effective ways to do that:

Parameterized Queries: such queries result in exactly the same way as normal SQL queries, but the difference is that here you need to define the SQL code first and then pass the parameters to the query later. So, even if someone tries to attack by passing malicious data to the query, the query searches for the exact match of whatever is sent as input. For example, if someone tries to pass ' or '1=1 as the data, the query will look up the DB for a literal match of the data.

Here is an example of how to write parameterized queries in PHP (see your programming language manual for more about parameterized queries):

/* Prepared statement, stage 1: prepare */
if (!($stmt = $mysqli->prepare("INSERT INTO test(id) VALUES (?)"))) {
 echo "Prepare failed: (" . $mysqli->errno . ") " . $mysqli->error;
}

/* Prepared statement, stage 2: bind and execute */
$id = 1;

if (!$stmt->bind_param("i", $id)) {
    echo "Binding parameters failed: (" . $stmt->errno . ") " .
$stmt->error;
}

if (!$stmt->execute()) {   
    echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
}

So, the next time you need to look up the database, use a parameterized query for it. But beware, this approach has a downside as well. In some cases, doing this can harm performance, because parameterized queries need server resources. In situations where an application is performance-critical, there are other ways to counter SQL injection attacks.

Stored procedures: this is another commonly used method for countering SQL injection attacks. It works the same as parameterized queries, the only difference being that the procedure or the method is itself stored in the database and called by the application when required. Here's how to write a stored procedure in PHP for MySQL:

/* Create the stored procedure */
if (!$mysqli->query("DROP PROCEDURE IF EXISTS p") ||
    !$mysqli->query("CREATE PROCEDURE p(IN id_val INT) 
     ↪BEGIN INSERT INTO
test VALUES(id_val); END;")) {
    echo "Stored procedure creation failed: (" . $mysqli->errno . ") " .
$mysqli->error;
}

/* Call the stored procedure */
if (!$mysqli->query("CALL p(1)")) {
    echo "CALL failed: (" . $mysqli->errno . ") " . $mysqli->error;
}

This approach is equally effective in preventing SQL injection as the parameterized queries method I mentioned earlier, so you can decide which is better for your situation.

Escaping user supplied input: in this approach, user input is manually (or sometimes with the help of DBMS escaping mechanisms) escaped for valid strings, thus minimizing any chance of SQL injection attacks. Although it is bit weaker than other approaches, it can be useful in cases where you want better performance or are rewriting legacy code and want to finish with lesser effort.

PHP provides an automatic input escape mechanism called magic_quotes_gpc that you can use before sending the input to the back end. But, it would be better to use the escaping mechanism provided by your database, because in the end, the query comes to the database, and the database will know better about what is a valid query. MySQL provides the mysql_real_escape_string() method to escape the input. Check your database documentation to find which escape function is supported.

Session Handling

As soon as legitimate users log in to a site with their credentials, a session is started and maintained until they log out. The problem begins when someone impersonating a genuine user tries to sneak in. Obviously, the results can be very severe—users' money or even their identities can be stolen. Let's explore how you can change your coding style so that the session is handled safely.

Session management implementation: you always should use the built-in session management feature that comes out of the box with your Web development framework. Not only does this save critical development time and cost, it generally is safer as well, because many people are using and testing it.

Another thing to take care of while implementing session management is keeping track of what method the application uses to send/receive the session ID. It may be a cookie or URL rewriting or a mixture of both, but you generally should limit it and accept the session ID only via the mechanism you chose in the first place.

Cookie management: whenever you plan to use cookies, be aware that you are sending out data about the user/session, which potentially can be intercepted and misused. So, you need to be extra careful while handling cookies. Always add the cookie attributes securely with HttpOnly, because this ensures that the cookie always is sent only via an HTTPS connection and doesn't allow scripts to access the cookie. These two attributes will reduce the chances of cookies being intercepted.

Other attributes like domain and path always should be set to indicate to the browser where exactly the cookie should be sent, so that it reaches only the exact destination and not anywhere else.

Last but definitely not least, the expire and max age attributes should be set in order to make the cookie nonpersistent, so it is wiped off once the browser instance is closed.

Session expiry management: a timeout should be enforced over and above the idle time out, so that if users intend to stay longer, they should authenticate themselves again. This generates a new session ID, so attackers have less time to crack the session ID.

Also, when the session is being invalidated, special care should be taken to clear the browser data and cookie, if used. To invalidate a cookie, set the session ID to empty and the expires date to a past date. Similarly, the server side also should close and invalidate the session by calling proper session handling methods, for example session_destroy()/unset() in PHP.

Web Service Security

In layman's terms, a Web service can be defined as a software system designed to support communication between two electronic devices over the Internet. In real-life scenarios, however, things are not that simple. As the Internet grows, the threat of misuse of these services grows. Let's look at some important tips you should keep in mind while developing Web services.

Schema validation: whatever SOAP payloads you expect your Web service to handle, they should be validated against the associated XML schema definition. The XSD should at least define the maximum length and character set of every parameter allowed in the Web service. Also, if you expect a fixed-format parameter, such as e-mail ID, phone numbers and so on, define the validation pattern in the XSD.

XML denial of service prevention: denial of service attacks try flooding the Web server with a large number of requests so that it eventually crashes. To protect your Web service from such attacks, be sure to optimize the configuration for maximum message throughput and limit SOAP message size. Also, validate the requests against recursive or oversized payloads, because such payloads generally can be malicious.

Safe URL Redirects

Many times you will need to redirect to a new page based on user input. But, the redirects can take a dangerous turn if not done properly. For example, if attackers get to redirect the application to a URL of their choice, they then can launch a phishing attack and compromise user data. A safe URL redirect is one where you have hard-coded the URL in the code like this:


<?php header("Location: http://www.mywebsite.com") ?>

Cases like the following where you have to go to a URL passed by the user should be avoided:


<?php $url = $_GET['inputURL'];
header("Location: " . $url); ?>

If you can't avoid this, keep reading.

Don't use the URL as the input: even if you want to redirect to a URL based on user input, it is better not to allow the user to enter the URL. Instead, you can use other input mechanisms like a button or a direct link. This way you can prevent users from entering any random link.

Validate the input before the redirect: in cases where you simply can't avoid user input, make sure you validate the input against exactly the same site or a list of hosts or a regex. Also notify users with an indication of the site they are being redirected to.

Cross-Site Scripting

Such attacks are targeted to the end user's browser. A common way to attack is to inject a malicious script, in the form of JavaScript, Flash or even HTML to a genuine Web site. When the user accesses that site, the browser has no clue whether script is genuine and just executes it. Such scripts, once executed, can access the cookies, session data or other sensitive information stored in the browser, because they are sent via a genuine Web site that the user tried to access.

To counter such attacks/injections in your Web site, OWASP suggests treating the Web pages as templates with certain slots for the untrusted data. For example, say you are creating a home page, and on the top left, you have a slot for the user name, which is retrieved by the application and displayed to the user while the Web page renders. These slots can be for one of several components—for example, JavaScript, HTML or CSS. For each component, there are preventive measures that help make sure others can't inject their code. Let's look at all the rules.

First, you need to define the slots that should be present in the Web page. Then, make sure you don't allow any untrusted data into the document, unless it is within one of the slots you already defined.

Untrusted data in HTML tags and attributes: when you need to insert untrusted data in HTML tags like div, p, b, td and so on, make sure it is escaped before being used. This way, even if attackers somehow manage to send in their code, escaping the untrusted data will ensure that the code doesn't cause much harm. For example, characters like <, >, & and so on should be changed to &lt, &gt and &amp, respectively. Also attribute fields in HTML tags like name, id, width and so on sometimes can be required to take variable values, so you may need to pass untrusted data to such fields. In this case, make sure to escape the data before using it as well. Take a look at the ESAPI reference implementation of HTML entity escaping and un-escaping. Here is a sample usage of the API:

String safe = ESAPI.encoder().encodeForHTMLAttribute( 
 ↪request.getParameter("input" ) );

Untrusted data in CSS: in situations where you need to put untrusted data in CSS, it is important to take care that it is done only in a property value and nowhere else. Especially when you need to pass a URL, check if it starts with “http”. Then, except for alphanumeric characters, escape all the other characters with an ASCII value less than 256 (the ESAPI also supports CSS escaping and un-escaping):

String safe = ESAPI.encoder().encodeForCSS( 
 ↪request.getParameter( "input" ) );

Untrusted data in an HTTP GET parameter: URLs like “http:www.mysite.com/value=data” can be possible targets of attack if the GET parameter is not being escaped before execution. In such cases, make sure to escape all characters with an ASCII value less than 256 with the %HH escaping format. The ESAPI for URL validation looks like this:

String safe = ESAPI.encoder().encodeForURL( 
 ↪request.getParameter( "input" ) );

Untrusted data in JavaScript data values: if you want to place untrusted data in JavaScript, the only safe place is inside the quoted data value. Because otherwise it is very easy to change the execution context and execute the statement, which actually is meant to be data. Again, the escaping mechanism to follow is the same as in previous cases—that is, escape all the characters with an ASCII value less than 256. The ESAPI for this is:

String safe = ESAPI.encoder().encodeForJavaScript( 
 ↪request.getParameter("input" ) );

Conclusion

Finally, it is best to acquaint yourself with the OWASP security guidelines if you are a Web developer or plan to become one. This not only will save you a lot of work in design changes and security hardening, but it also makes sure your end users' data and identity are safe.

Nitish Tiwari is based out of Bangalore, India. He currently is working as a developer in a FOSS-based startup. He also helps enterprises implement open-source tools based on their needs. In his free time, he likes to try out and test open-source tools. You can reach him at nitish.tiwari@technocube.in.