Zend PHP 7 Certification – Security – Cross-Site Request Forgery

This post covers the Cross-Site Request Forgery section of the PHP Security chapter when studying for the Zend PHP 7 Certification.

Cross-Site Request Forgery (CSRF) is a type of attack that causes a malicious action to a website from a user’s browser that is running a valid session.

The attack happens when fake forms or requests are made that behave exactly as on the original website.

URLs on websites can sometimes reflect the actions that they perform. Take an example of a user that’s registered on a website that has an active subscription to a certain section of interest.

A way of deactivating the subscription could be as simple as navigating to the URL http://www.somesite.com/membership/deactivate

The problem with this is that attackers could put a link on some page or email and try to dupe the user into clicking on it. Even easier, they could get the user to go to some page of the attacker’s website which does nothing more than redirect to the deactivation URL.

In addition, the attacker could get the user to go to a page containing a broken image link to that URL.

When retrieving the URL the user’s browser will automatically send the attacker’s session cookie to the www.somesite.com domain which would detect the user is still logged in, and the user’s subscription would be deactivated.

Whilst it seems like a very simple exploit within web forms, fortunately there are simple ways of preventing these attacks.

The first solution is to add a unique key or token to each form through a hidden input field, such as the example below.

<form ...>
    <input type="hidden" name="form_key" value="<?php echo getFormKey(); ?>" />
    <!-- Some other fields below -->
</form>

The key would come from a function that randomly generates a string.

function getFormkey() {
    if (!isset($_SESSION['form_key'])) {
        $formKey = bin2hex(openssl_random_pseudo_bytes(32));
        $_SESSION['form_key'] = $formKey;
    } else {
        $formKey = $_SESSION['form_key'];
    }
    return $formKey;
}

When the form is submitted, compare the form key submitted in the form field with the form key stored in the session.

$formKey = $_REQUEST['form_key'];

if ($formKey == $_SESSION['form_key']) {
    // Okay to proceed
} else {
    // Potential CSRF attack
}

We can also use measures such as requiring the user to re-login for sensitive operations. This would mean that if an attacker tried to trick the user into navigating to the http://www.somesite.com/membership/deactivate to deactivate a subscription, the user would have to re-authenticate, would soon realise they have been redirected to this URL and therefore would not enter their login credentials, and the subscription would remain active.

Form CAPTCHAs work well as the data is generated on the client side randomly, therefore an attacker cannot guess the pattern. There is an argument for this solution not being user friendly, as users will not want to type out text or perform a calculation, however products like Google’s reCAPTCHA work well as they usually only require the user to tick a checkbox.

If you take the correct measures on your web forms, you can eliminate the risk of a CSRF attack.

View the other sections:

Note: This article is based on PHP version 7.0.