Friday, January 25, 2013

How to secure HTML hidden input fields

A hacker can access hidden fields just as easily as querystring values by using an intercepting proxy (or any number of tools). I dont think there is anything wrong with using hidden fields as long as they aren't used for anything sensitive and you validate them like you would any other value from the client.

Hidden input fields are neither hidden, nor unchangeable. Once data has left the realm of the web server, it should be treated as possibly contaminated and potentially dangerous. Hidden fields are no exception. So the best solution is encryption and hashing.

Encryption and hashing to hidden input fields:

the necessary steps needed to be taken to implement the solution are as follows:

  1. Hash the original value 
  2. Encrypt the original value 
  3. Send the encrypted value along with the hash digest 
  4. Upon receiving back the data, decrypt the encryption, hash the result and compare it to the received hash digest to ensure the value remained unchanged 
Encryption-decryption in PHP using MCRYPT extension:

Here is the function description and parameters:

string mcrypt_encrypt ( string $cipher , string $key , string $data , string $mode [, string $iv ] )

The list of parameters that we are going to use is as follows:

  • $cipher – this takes one of the predefined constants MCRYPT_ciphername, in our case MCRYPT_BLOWFISH 
  • $key – in order to generate a 128-bit key, we can use any string and hash it with the 128-bit md5 function – md5(‘demo_text’). This would produce the following key: 6cf50d50aa1133458a0c24fffa658565
  • $data – this the message/value that needs to be encrypted, in our case we will encrypt the super secret message – ‘Bing is actually famous, check in Google’ 
  • $mode – one of the MCRYPT_MODE_modename constants, in our case MCRYPT_MODE_CFB$iv – initialisation vector that needs to be the same size as the block size of the cipher. Blowfish has a 64-bit block size. However, we do not have to remember that. We can use the mcrypt_get_iv_size() function to get the necessary size. 

This is how the final implementation of mcrypt_encrypt() looks like:

<?php
 $key = md5(‘demo_value’);
 $original_value = ‘demo_value1’;
 $iv = substr(md5(‘demo_value2’),0,mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CFB));
 $encrypted_value = base64_encode(mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $original_value, MCRYPT_MODE_CFB, $iv));
?>

We need to encode the output of the mcrypt_encrypt() function with base64_encode(). This encodes the binary data with MIME base64 so it survives transportation when it travels from and to the web server. Otherwise, the data might (most probably will) arrive corrupted and this would break the decryption process. When the data arrives back at the server base64_decode() is used to decode the data back to the original output of mcrypt_encrypt().

The decryption process is carried out in a similar way, this time using the mcrypt_decrypt() function, which takes the same parameters as mcrypt_encrypt(). Note: that the $encrypted_value has to be decoded first with base64_decode().


Hashing in PHP:

PHP provides several hashing options, most common (and strongest) of which are md5() and sha1(). The former generates a 128-bit hash, whereas the latter returns a 160-bit hash. Although that might sound like a lot of bits, modern hardware can generate hashes of this length pretty quickly (hundreds and even thousands per second).

This way, using a dictionary (not only words, but also alpha-numeric combinations), an attacker can keep generating hashes until a collision appears – meaning he can find the original value that generated your hash. One solution to this weakness is simply using stronger hashing functions (more bits) in order to make brute force collision detection computationally infeasible.

However, those functions will also put computational pressure on web servers and ultimately make web pages slow. On the other hand, as of this moment md5() and sha1() are the strongest functions implemented for PHP. Therefore, a technique can be applied to those functions that makes them stronger – adding salts. Salts are random bits of string with sufficient length that are added to the hash. This results in a different hash generated each time from the same input value.

Consequently, we need to know the salt value later in order to regenerate the same hash for the data verification. This might sound a bit confusing at first, however, looking at an example should clear the confusion. For our hashing process, we will use the 160-bit sha1() function. This is how a plain normal hashing in PHP looks like:

//this would produce the followin hash – 3251d0991b55202abe9ede887efab662d3d57399 sha1(‘my_value’);


Now let’s fortify this hash mixing it with a salt:

$original_value = ‘my_string’; 
$salt = substr(md5(uniqid(rand(), true)), 0, 10); 
$hashed_value = $salt . substr(sha1($salt . $original_value),-10);

The algorithm uses the functions rand(), uniqid(), md5() and substr() to create a unique salt with length of 10. Then comes the algorithm for hashing – we concatenate the salt to the original string, create a 160-bit hash, take 10 characters of the resulting string (just so the final hash is not too long) and attach the salt to the beginning. This way, later we can extract the value of the salt again knowing it is the first 10 characters.

Mixing it all together with hidden input fields

 Now it is time to put everything together. The starting point is just a very simple mark-up page with a form containing one hidden field and a submit button. We will create two php documents.

One is form.php where the encryption and the initial hashing will take place. The other is process_form.php where the form will post to and where decryption and verification will take place.

 This is the mark-up we are going to start with:


<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8"/>
        <title>Simple Form</title>
    </head>
    <body>
        <form action="process_form.php" method="post">
            <input type="hidden" name="demo_value" value="demo_value2"/>
            <input type="submit" value="Submit form"/>
        </form>
    </body>
</html>

Simple Form
Below is the modified version (document form.php) with added encryption and hashing. Note that there is an additional hidden field just for the hashed value. That is not necessary and is done just for clarity. The hashed value can be concatenated to the encrypted value.


<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8"/>
        <title>Simple Form</title>
    </head>
    <body>
        <?php
        $key = md5(‘demo_value1’);
        $original_value = ‘demo_value2’;
        $iv = substr(md5(‘demo_value3’),0,mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CFB));
        $encrypted_value = base64_encode(mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $original_value, MCRYPT_MODE_CFB, $iv));
        //Now create a decent hash with a 10 character salt
        $salt = substr(md5(uniqid(rand(), true)), 0, 10);
        $hashed_value = $salt . substr(sha1($salt . $original_value),-10);
        ?>
        <form action="process_form.php" method="post">
            <input type="hidden" name="encrypted_value" value="<?php echo $encrypted_value; ?>"/>
            <input type="hidden" name="hashed_value" value="<?php echo $hashed_value; ?>"/>
            <input type="submit" value="Submit form"/>
        </form>
    </body>
</html>


And finally, below is the processing script (document process_form.php). First a check is made to make sure that the form has been posted properly. Then the encrypted value is decrypted using the same $key and $iv. Then the $salt is extracted from the posted hashed value (the first 10 characters). A new hash is created from the decrypted value and the salt using the same algorithm. Finally this new hash is compared to the original one posted by the form. If they match, this means the data came back valid and unchanged and further processing can continue (in this case just print). Otherwise, something went wrong and the data was corrupted, either intentionally or not.


<?php
if (!isset($_POST[‘encrypted_value’]) || !isset($_POST[‘hashed_value’])) {
    die(‘Submission failed’);
} else {
    $key = md5(‘demo_value1’);
    $iv = substr(md5(‘demo_value2’),0,mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CFB));
   $decrypted_value = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, base64_decode($_POST[‘encrypted_value’]), MCRYPT_MODE_CFB, $iv);
    //Now let’s recreate the hash
    //We can extract the salt from the original hash
    $salt = substr($_POST[‘hashed_value’], 0, 10);
    $new_hashed_value = $salt . substr(sha1($salt . $decrypted_value), -10);
    if($new_hashed_value == $_POST[‘hashed_value’]){
        //This is where you process further the validated value. In our case just print
        print $decrypted_value;
    }
    else{
        //The hidden value has been changed and is therefore invalid
        print ‘The message is corrupted’;
    }
}
Disqus Comments