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.
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).
A typical CAPTCHA test works using the following algorithm:
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).
In Zend Framework 3, there are several CAPTCHA types available (they all belong
to the Zend\Captcha
component):
Dumb. This is a very simple CAPTCHA algorithm which requires that site user enter the word letters in reverse order. We will not consider this type in details here, because it provides too low protection level.
Image. A CAPTCHA algorithm distorting an image with addition of some noise in form of dots and line curves (figure 11.1, a).
Figlet. An unusual CAPTCHA type using FIGlet program instead of an image distortion algorithm. The FIGlet is an open-source program which generates the CAPTCHA image of many small ASCII letters (figure 11.1, b).
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.
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.
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):
type
key of the array (line 4), as usual, may either be a fully qualified class name of the element
(Zend\Form\Element\Captcha
) or its short alias ("captcha").name
key (line 5) is the value for the "name" attribute of the HTML form field.options
key contains the options for the attached CAPTCHA algorithm.
The class
key (line 9) may either contain the full CAPTCHA class name (e.g. Zend\Captcha\Image
)
or its short alias (e.g. "Image"). Other, adapter-specific, options may be added to the key
as well. We will show how to do that a little bit later.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.
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:
the class
parameter (line 21) should be either the fully qualified CAPTCHA adapter
class name (\Zend\Captcha\Image
) or its short alias (Image
).
the imgDir
parameter (line 22) should be the path to the directory where to save
the generated distorted images (in this example, we will save the images to the
APP_DIR/public/img/captcha directory).
the suffix
parameter (line 23) defines the extension for a generated image
file (".png" in this example).
the imgUrl
parameter (line 24) defines the base part of the URL for opening generated
CAPTCHA images in a web browser. In this example, site visitors will be able to access CAPTCHA
images using URLs like "http://localhost/img/captcha/<ID>", where ID is a unique ID of certain
image.
the imgAlt
parameter (line 25) is an (optional) alternative text to show if CAPTCHA
image can't be loaded by the web browser (the "alt" attribute of <img>
tag).
the font
parameter (line 26) is the path to the font file. You can download a free TTF font,
for example, from here. In this example, we use Thorne Shaded
font, which we downloaded and put into the APP_DIR/data/font/thorne_shaded.ttf file.
the fsize
parameter (line 27) is a positive integer number defining the font size.
the width
(line 28) and height
parameters (line 29) define the width and height (in pixels)
of the generated image, respectively.
the expiration
parameter (line 30) defines the expiration period (in seconds)
of the CAPTCHA images. Once an image expires, it is removed from disk.
the dotNoiseLevel
parameter (line 31) and lineNoiseLevel
parameter (line 32) define
the image generation options (dot noise level and line noise level, respectively).
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.
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>
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:
the class
parameter (line 11) should be either the full CAPTCHA adapter
class name (\Zend\Captcha\Figlet
) or its short alias (Figlet
).
the wordLen
parameter (line 12) defines the length of the secret word to be generated.
the expiration
parameter (line 13) defines the CAPTCHA expiration period (in seconds).
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.
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>
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 illustrates an example CSRF attack on a payment gateway website:
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.
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.
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.
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.
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:
For certain form, generate a random sequence of bytes (token) and save it server-side in PHP session data.
Add a hidden field to form and set its value with the token.
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.
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).