8 Practices to Secure Your Web App

Sandeep Panda

When it comes to application security, in addition to securing your hardware and platform, you also need to write your code securely. This article will explain how to keep your application secure and less vulnerable to hacking. The following are the best habits that a programmer can develop in order to protect his or her application from attack:

  • Input data validation
  • Guarding against XSS attacks
  • Guarding against CSRF attacks
  • Preventing SQL Injection attacks
  • Protecting the file system
  • Protecting session data
  • Proper error handling
  • Guarding included files

Input Data Validation

While designing your application, you should be striving to guard your app against bad input. The rule of thumb to follow is this: don’t trust user input. Although your app is intended for good people, there is always a chance that some bad user will try to attack your app by entering bad input. If you always validate and filter the incoming data, you can build a secure application.

Always validate data in your PHP code. If you are using JavaScript to validate user input, there is always a chance that the user might have turned off JavaScript in her browser. In this case your app will not be able to validate the input. Validating in JavaScript is okay, but to guard against these types of problems then you should re-validate the data in PHP as well too.

Guarding Against XSS Attacks

Cross-site scripting attack (XSS attack) is an attack based on code injection into vulnerable web pages. The danger is a result of accepting unchecked input data and showing it in the browser.

Suppose you have a comment form in your application that allows users to enter data, and on successful submission it shows all the comments. The user could possibly enter a comment that contains malicious JavaScript code in it. When the form is submitted, the data is sent to the server and stored into the database. Afterward, the comment is fetched from database and shown in the HTML page and the JavaScript code will run. The malicious JavaScript might redirect the user to a bad web page or a phishing website.

To protect your application from these kinds of attacks, run the input data through strip_tags() to remove any tags present in it. When showing data in the browser, apply htmlentities()function on the data.

Guarding Against CSRF Attacks

In a Cross Site Request Forgery (CSRF) attack, the attacker tricks the victim into loading sensitive information or making a transaction without their knowledge. This mainly occurs in web applications that are badly coded to trigger business logic using GET requests.

Ideally, GET requests are Idempotent in nature. Idempotency means the same page can be accessed multiple times without causing any side effects. Therefore, GET requests should be used only for accessing information and not for performing transactions.

The following example shows a how a poorly coded application unknowingly supports CSRF attacks:

<?php
if (isset($_REQUEST["name"], $_REQUEST["amount"])) {
    // process the request and transfer the amount from
    // from the logged in user to the passed name.
}

Let’s assume Bob wants to perform a CSRF attack on Alice, and constructs a URL like the following and sends it to Alice in an email:

<a href="http://example.com/process.php?name=Bob&amount=1000">Visit My WebSite</a>

If Alice clicks on this link, and is logged into the website already, this request will deduct $1000 from her account and transfer it to Bob’s! Alternatively, Bob can create an image link whose src attribute points to the URL.

<img src="http://example.com/process.php?name=Bob&amount=1000" width="1" height="1"/>

The browser can’t display any image as expected, but it will still make the request using the URL which will make a transaction without notifying Alice.

The solution is to process any function that changes the database state in POST request, and avoid using $_REQUEST. Use $_GET to retrieve GET parameters, and use $_POST to retrieve POST parameters.

In addition, there should be a random token called a CSRF token associated with each POST request. When the user logins into his/her account, the application should generate a random token and store it in the session. Whenever any form is displayed to the user, the token should be present in the page as a hidden input field. Application logic must check for the token and ensure that it matches the token present in the session.

Preventing SQL Injection Attacks

To perform your database queries, you should be using PDO. With parameterized queries and prepared statements, you can prevent SQL injection.

Take a look at the following example:

<?php
$sql = "SELECT * FROM users WHERE name=:name and age=:age";
$stmt = $db->prepare($sql);
$stmt->execute(array(":name" => $name, ":age" => $age));

In the above code we provide the named parameters :name and :age to prepare(), which informs the database engine to pre-compile the query and attach the values to the named parameters later. When the call to execute() is made, the query is executed with the actual values of the named parameters. If you code this way, the attacker can’t inject malicious SQL as the query is already compiled and your database will be secure.

Protecting the File System

As a developer you should always write your code in such a way that none of your operations put your file system at risk. Consider the following PHP that downloads a file according to a user supplied parameter:

<?php
if (isset($_GET['filename']) {
    $filename = $_GET['filename'];
    header('Content-Type: application/x-octet-stream');
    header('Content-Transfer-Encoding: binary');
    header('Content-Disposition: attachment; filename="' . $filename . '";');
    echo file_get_contents($filename);
}

The script is very dangerous since it can serve files from any directory that is accessible to it, such as the session directory and system directories. The solution is to ensure the script does not try to access files from arbitrary directories.

Protecting Session Data

By default, session information is written to a temp directory. In the case of a shared hosting server, someone other than you can write a script and read session data easily. Therefore, you should not keep sensitive information like passwords or credit card numbers in a session.

A good way to guard your session data is to encrypt the information stored in the session. This does not solve the problem completely since the encrypted data is not completely safe, but at least the information is not readable. You should also consider keeping your session data stored somewhere else, such as a database. PHP provides a method called session_set_save_handler() which can be used to persist data in session in your own way.

As of PHP 5.4 you can pass an object of type SessionHandlerInterface to session_set_save_handler(). Check out the PHP documentation to learn about implementing custom session persistence by implementing SessionHandlerInterface.

Proper Error Handling

It’s good to know about all the errors that occur while we’re developing an application, but when we make the application accessible to end users we should take care to hide the errors. If errors are shown to users, it may make our application vulnerable. So, the best approach is configuring your server differently for development and production environments.

In production mode we need to turn off display_errors and display_start_up_errors settings. error_reporting and log_errors should be on so that we can log errors while hiding those from end users.

You can use set_error_handler to define custom error handlers. However, it has limitations. The custom error handler bypasses the standard error handling mechanism of PHP. It cannot catch errors like E_CORE_ERROR, E_STRICT or E_COMPILER_ERROR in the same file the error handler is defined in. Furthermore, it will fail to handle errors that might occur within the handler itself.

To handle errors elegantly you should perform exception handling through try/catch blocks. Exceptions are represented by the Exception class and its subclasses. If any error occurs inside the try block you can throw an exception and process it in the catch block.

Guarding Included Files

PHP scripts often include other PHP files that contain code for things like connecting to a database, etc. Some developers give the included files an extension like .inc. Files with this extension are not parsed by PHP by default if called directly and will be served as plain text to the users. If an attacker directly accesses the include file that contains database credentials, he now has access to all of your application’s data. Always use the .php extension for included code files and keep them outside of directories directly accessible to users.

Summary

By keeping the above 8 points in mind it’s possible to secure a PHP application to a great extent. The best piece of advice by far is don’t trust user input, but also be sure to guard your file system and database as well.

Image via Fotolia

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • mohsen

    very good.
    tanks

  • http://phpdeveloper.org Chris

    One thing to note, Exceptions != Errors. You can define a custom error handler and a custom exception handler and prevent the information leakage inside there. You’ve mixed the two in the section “Proper Error Handling”. Sure, throwing exceptions is a better practice than using something like trigger_error, but they’re not handled the same way.

    Those “display errors” settings do not apply to Exception handling. Additionally, uncaught exceptions will not be handled by a custom error handler function.

    I wish you’d included code on the right way to do things instead of just the wrong way. Saying “here’s how to fix this” isn’t nearly as useful as having something showing how.

  • Patrick

    Good article. One point I would add is that passwords should be hashed before storage, preferably with Bcrypt.

  • Jason

    It’s another incomplete php article on security with incomplete advice and examples. Authors would be better tackling single security points in depth.

    Consider your xss advice. What if you want or need to allow a limited number of html tags. This is a fairly common task. What is the best practice for that?

    Further reading links to sites like phpsec should be added.

    In regards to data input. Assume any user can enter invalid input. If you are asking for a year you should be validating that the input is an integer and usually 4 characters. Using such precise parameters will limit damage elsewhere. Remember not to trust any input. POST / GET is obvious. COOKIE / SERVER etc may not be.

  • http://sharafatalee.com Sharafat Ali

    Good work. A reminder for experienced people :) and good tips for juniors.

  • Spudley

    Re sessions being in a public temp folder, and encrypting them:
    Any decent web host should have this covered for you already. It is trivial to set the PHP config to point the session storage to a folder that is private to each host, and any good sys admin should do that as a matter of routine, so if you’re with a good host, you shouldn’t be able to access other people’s sessions. Also, if your web hosting company uses the Suhosin patch for their PHP installations (and most do), your session data will already be encrypted too.
    So while you are right that sessions can be vulnerable, if you’re with a half-decent hosting company, they’re not nearly as vulnerable as you’ve made out; encrypting them may not be necessary.
    All that said, I would completely agree with not storing passwords or credit card numbers in session data. In fact, these specific data items should *never* be stored *anywhere*. CC numbers shouldn’t even be entered into your site unless you’re a bank, and passwords should always be hashed immediately on input.

  • noname

    Why htmlentities() and not just htmlspecialchars()? The topic is security, htmlspecialchars() is doing the job therefor. The job of htmlentities() is additionally to scramble a lot of more characters than useful.

  • DanilaRidzhi

    For catch all errors:
    // index.php
    ini_set(‘display_errors’, true);
    error_reporting(E_ALL | E_STRICT);
    set_error_handler(array(‘ErrorHandler’,’errorHandler’));
    register_shutdown_function(array(‘ErrorHandler’,’fatalErrorHandler’));
    ob_start();
    // some code

    // ErrorHandler.php
    class ErrorHandler
    {
    public static function errorHandler($errno, $errstr, $errfile, $errline) {
    if(error_reporting() && $errno) {
    // some output
    }
    return true;
    }
    public static function fatalErrorHandler() {
    if($error = error_get_last() AND $error['type'] & (E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR)) {
    ob_end_clean();
    self::errorHandler($error['type'], $error['message'], $error['file'], $error['line']);
    } else {
    ob_end_flush();
    }
    }
    }

  • Sourav Banik

    Please tell me exactly how to guard the php files i include??

  • http://www.scriptvenue.com Arvind

    useful article.
    Thanks

  • Voitcus

    It’s very easy to hack POST query. Just type in a notepad something like , open it in your browser and click “Submit”. It’s more difficult than changing URI to manipulate GET data, but still no much effort.