A free and open-source book on ZF3 for beginners


11.1. Form Security Elements

We will consider the usage of two form security elements provided by Zend Framework 3: Captcha and Csrf (both classes belong to Zend\Form\Element namespace). By adding those elements to your form model (and rendering them in a view template), you will make your form resistant to hacker attacks.

11.1.1. CAPTCHA

A CAPTCHA (stands for "Completely Automated Public Turing test to tell Computers and Humans Apart") is a challenge-response test used in web sites for determining whether the user is a human or a robot.

There are several types of CAPTCHA. The most widely used one requires that the user type the letters of a distorted image that is shown on the web page (see figure 11.1 for some examples).

Figure 11.1. CAPTCHA examples Figure 11.1. CAPTCHA examples

A typical CAPTCHA test works using the following algorithm:

  1. Some secret sequence of characters (word) is generated server-side.
  2. The secret word is saved in a PHP session variable.
  3. The distorted image is generated based on the secret word. The image is then displayed on the web page to site user.
  4. The site user is asked to type characters shown on the image.
  5. If the characters typed by user are the same as the secret word saved in the session, the test is considered passed.

The goal of the CAPTCHA test is to protect your form from filling and submission by an automated process (so called robot). Usually, such robots send spam messages to forums, hack passwords on site login forms, or perform some other malicious actions.

The CAPTCHA test allows to reliably distinguish humans from robots, because humans are easily able to recognise and reproduce characters from the distorted image, while robots are not (at the current stage of evolution of computer vision algorithms).

11.1.1.1. CAPTCHA Types

In Zend Framework 3, there are several CAPTCHA types available (they all belong to the Zend\Captcha component):

The Zend\Captcha component provides a unified interface for all CAPTCHA types (the AdapterInterface interface). The AbstractAdapter base class implements that interface, and all other CAPTCHA algorithms are derived from the abstract adapter class 45. The class inheritance diagram is shown in figure 11.2 below.

Figure 11.2. CAPTCHA adapter classes Figure 11.2. CAPTCHA adapter classes

45) The adapter is a design pattern that translates one interface for a class into a compatible interface, which helps two (or several) incompatible interfaces to work together. Typically, CAPTCHA algorithms have different public methods, but since they all implement AbstractAdapter interface, the caller may use any CAPTCHA algorithm in the same common manner (by calling the methods provided by the base interface).

As you can see from the figure 11.2, there is another base class for all CAPTCHA types that utilize some secret word of characters: the AbstractWord class. This base class provides methods for generating random sequence of characters and for adjusting word generation options.

11.1.1.2. CAPTCHA Form Element & View Helper

ZF3 provides the dedicated form element class and view helper class for letting you use CAPTCHA fields on your forms.

To add a CAPTCHA field to a form model, you use the Captcha class that belongs to Zend\Form component and lives in Zend\Form\Element namespace.

The Captcha element class can be used with any CAPTCHA algorithm (listed in the previous section) from Zend\Captcha component. For this purpose, the element class has the setCaptcha() method which takes either an instance of a class implementing Zend\Captcha\AdapterInterface interface, or an array containing CAPTCHA configuration 46. By the setCaptcha() method, you can attach the desired CAPTCHA type to the element.

46) In the latter case (configuration array), the CAPTCHA algorithm will be automatically instantiated and initialized by the factory class Zend\Captcha\Factory.

You add the Captcha element to a form model as usual, with the add() method provided by the Zend\Form\Form base class. As usual, you can pass it either an instance of the Zend\Form\Element\Captcha class or provide an array of configuration options specific to certain CAPTCHA algorithm (in that case, the element and its associated CAPTCHA algorithm will automatically be instantiated and configured by the factory class).

The code example below shows how to use the latter method (passing a configuration array). We prefer this method because it requires less code to write. It is assumed that you call this code inside of form model's addElements() protected method:

<?php
// Add the CAPTCHA field to the form model
$this->add([
  'type'  => 'captcha',
  'name' => 'captcha',
  'options' => [
    'label' => 'Human check',
    'captcha' => [
      'class' => '<captcha_class_name>', //
      // Certain-class-specific options follow here ...
    ],
  ],
]);

In the example above, we call the add() method provided by the Form base class and pass it an array describing the element to insert (line 3):

For generating the HTML markup for the element, you may use the FormCaptcha view helper class (belonging to Zend\Form\View\Helper namespace). But, as you might learn from the previous chapter, typically you use the generic FormElement view helper instead, like shown in the code below:

<?= $this->formElement($form->get('captcha')); ?>

It is assumed that you call the view helper inside of your view template.

Next, we provide two examples illustrating how to use different CAPTCHA types provided by ZF3: the Image and Figlet. We will show how to add a CAPTCHA field to the feedback form that we used in examples of the previous chapters.

11.1.1.3. Example 1: Adding Image CAPTCHA to the ContactForm

Image CAPTCHA requires that you have PHP GD extension installed with PNG support and FT fonts.

To add the Image CAPTCHA to your form model, call the form's add() method as follows:

<?php
namespace Application\Form;
// ...

class ContactForm extends Form
{
    // ...    
    protected function addElements() 
    {
        // ...         
       
        // Add the CAPTCHA field
        $this->add([
            'type'  => 'captcha',
            'name' => 'captcha',
            'attributes' => [
            ],
            'options' => [
                'label' => 'Human check',
                'captcha' => [
                    'class' => 'Image',
                    'imgDir' => 'public/img/captcha',
                    'suffix' => '.png',
                    'imgUrl' => '/img/captcha/',
                    'imgAlt' => 'CAPTCHA Image',
                    'font'   => './data/font/thorne_shaded.ttf',
                    'fsize'  => 24,
                    'width'  => 350,
                    'height' => 100,
                    'expiration' => 600, 
                    'dotNoiseLevel' => 40,
                    'lineNoiseLevel' => 3
                ],
            ],
        ]);
    }
}

Above, the captcha key of the configuration array (see line 20) contains the following parameters for configuring the Image CAPTCHA algorithm attached to the form element:

To render the CAPTCHA field, add the following lines to your contact-us.phtml view template file:

<div class="form-group">
  <?= $this->formLabel($form->get('captcha')); ?>
  <?= $this->formElement($form->get('captcha')); ?>
  <?= $this->formElementErrors($form->get('captcha')); ?>
  <p class="help-block">Enter the letters above as you see them.</p>
</div>

Finally, create the APP_DIR/public/img/captcha directory that will store generated CAPTCHA images. Adjust directory permissions to make the directory writeable by the Apache Web Server. In Linux Ubuntu, this is typically accomplished by the following shell commands (replace the APP_DIR placeholder with the actual directory name of your web application):

mkdir APP_DIR/public/img/captcha

chown -R www-data:www-data APP_DIR

chmod -R 775 APP_DIR

Above, the mkdir command creates the directory, and chown and chmod commands set the Apache user to be the owner of the directory and allow the web server to write to the directory, respectively.

Now, if you open the "http://localhost/contactus" page in your web browser, the CAPTCHA image will be generated based on a random sequence of letters and digits saved in session. You should see something like in the figure 11.3 below.

Figure 11.3. Image CAPTCHA Figure 11.3. Image CAPTCHA

When you fill the form fields in and press the Submit button, the letters entered into the Human check field will be transferred to server as part of HTTP request. Then, on form validation, the Zend\Form\Element\Captcha class will compare the submitted letters to those stored in PHP session. If the letters are identical, the form is considered valid; otherwise form validation fails.

Once the PHP renderer processes the view template, it generates HTML markup for the CAPTCHA element as shown below:

<div class="form-group">
  <label for="captcha">Human check</label>
  <img width="350" height="100" alt="CAPTCHA Image" 
       src="/img/captcha/df344b37500dcbb0c4d32f7351a65574.png">
  <input name="captcha[id]" type="hidden" 
         value="df344b37500dcbb0c4d32f7351a65574">
  <input name="captcha[input]" type="text">                              
  <p class="help-block">Enter the letters above as you see them.</p>
</div>

11.1.1.4. Example 2: Adding a FIGlet CAPTCHA to the ContactForm

To use the FIGlet CAPTCHA element with your form, replace the form element definition from the previous example with the following code:

<?php
// Add the CAPTCHA field
$this->add([
	'type'  => 'captcha',
	'name' => 'captcha',
	'attributes' => [                                                
	],
	'options' => [
		'label' => 'Human check',
		'captcha' => [
			'class' => 'Figlet',
			'wordLen' => 6,
			'expiration' => 600,                     
		],
	],
]);

Above, the captcha key of the configuration array (see line 10) contains the following parameters for configuring the Figlet CAPTCHA algorithm attached to the form element:

Now, open the "http://localhost/contactus" page in your web browser. Once that is done, you should see a page like in the figure 11.4 below.

Figure 11.4. FIGlet CAPTCHA Figure 11.4. FIGlet CAPTCHA

Once the PHP renderer processes the view template, it generates HTML markup for the CAPTCHA element like shown below:

<div class="form-group">
  <label for="captcha">Human check</label>            
    <pre> 
 __   _    __   __   _    _      ___     _    _    __   __  
| || | ||  \ \\/ // | \  / ||   / _ \\  | || | ||  \ \\/ // 
| '--' ||   \ ` //  |  \/  ||  | / \ || | || | ||   \ ` //  
| .--. ||    | ||   | .  . ||  | \_/ || | \\_/ ||    | ||   
|_|| |_||    |_||   |_|\/|_||   \___//   \____//     |_||   
`-`  `-`     `-`'   `-`  `-`    `---`     `---`      `-`'   
                                                           
</pre>
<input name="captcha[id]" type="hidden" 
       value="b68b010eccc22e78969764461be62714">
<input name="captcha[input]" type="text">                              
<p class="help-block">Enter the letters above as you see them.</p>
</div>

11.1.2. CSRF Prevention

Cross-site request forgery (CSRF) is a kind of hacker attack which forces the user's browser to transmit an HTTP request to an arbitrary site. Through the CSRF attack, the malicious script is able to send unauthorized commands from a user that the website trusts. This attack is typically performed on pages containing forms for submission of some sensitive data (e.g. money transfer forms, shopping carts etc.)

To better understand how this attack works, take a look at figure 11.5.

Figure 11.5. A CSRF attack example Figure 11.5. A CSRF attack example

Figure 11.5 illustrates an example CSRF attack on a payment gateway website:

  1. You log into your account at payment gateway web site https://payment.com. Please note that the SSL-protected connection (HTTPS) is used here, but it doesn't protect from such kind of attacks.

  2. Typically, you set check on the "Remember Me" check box of the login form to avoid entering user name and password too often. Once you logged in to your account, your web browser saves your session information to a cookie variable on your machine.

  3. On the payment gateway site, you use the payment form https://payment.com/moneytransfer.php to buy some goods. Please note that this payment form will later be used as a vulnerability allowing to perform the CSRF attack.

  4. Next you use the same web browser to visit some website you like. Assume the website contains cool pictures http://coolpictures.com. Unfortunately, this web site is infected by a malicious script, masqueraded by an <img src="image.php"> HTML tag. Once you open the HTML page in your web browser, it loads all its images, thus executing the malicious image.php script.

  5. The malicious script checks the cookie variable, and if it presents, it performs the "session riding" and can act on behalf of the logged in user. It is now able to submit the payment form to the payment gateway site.

The above described CSRF attack is possible it the web form on the payment gateway site does not check the source of the HTTP request. The people who maintain the payment gateway site must put more attention in making its forms more secure.

To prevent CSRF attacks to a form, one has to require a special token with the form, as follows:

  1. For certain form, generate a random sequence of bytes (token) and save it server-side in PHP session data.

  2. Add a hidden field to form and set its value with the token.

  3. Once the form is submitted by the user, compare the hidden value passed in the form with the token saved server-side. If they match, consider the form data secure.

If a malicious user will try to attack the site by submitting the form, he will not be able to put right token in the form submissions, because the token is not stored in cookies.

11.1.2.1. Example: Adding a CSRF Element to Form

In Zend Framework 3, to add a CSRF protection to your form model, you use the Zend\Form\Element\Csrf form element class.

The Csrf element has no visual representation (you will not see it on the screen).

To insert a CSRF element to your form model, add the following lines in its addElements() method:

// Add the CSRF field
$this->add([
  'type'  => 'csrf',
  'name' => 'csrf',
  'options' => [                
    'csrf_options' => [
      'timeout' => 600
    ]
  ],
]);

Above, we use the Form's add() method (line 2), to which we pass a configuration array describing the CSRF element. The element will be automatically instantiated and initialized by the factory.

In line 3, we specify the class name for the CSRF element. This either may be the fully qualified class name (Zend\Form\Element\Csrf) or a short alias ("csrf").

In line 4, we set the "name" attribute for the element. In this example, we use "csrf" name, but you may use any other name, on your choice.

In line 6, inside of csrf_options array, we specify the options specific to Zend\Form\Element\Csrf class. We set the timeout option to 600 (look at line 7), which means the CSRF check expires in 600 seconds (10 minutes) after form creation.

To render the CSRF field, in your view template .phtml file, add the following line:

<?= $this->formElement($form->get('csrf')); ?>

When the PHP renderer evaluates the view template, it generates the HTML markup for the CSRF field like shown below:

<input type="hidden" name="csrf" value="1bc42bd0da4800fb55d16e81136fe177"> 

As you can see from the HTML markup code above, the form now contains a hidden field with a randomly generated token. Since the attacker script doesn't know this token, it won't be able to submit its correct value, thus the CSRF attack becomes prevented.

What happens if CSRF element validation fails?

If during the form validation the CSRF check fails, the form is considered invalid and the user will see it again to fix input errors, but he won't see the error message for the CSRF element (we don't want hackers to know for sure what's wrong with the form).


Top