× {{alert.msg}} Never ask again
Receive New Tutorials
GET IT FREE

Tutorial: Building Modern & Secure PHP Applications

– {{showDate(postTime)}}
Codementor PHP Expert and Book Author Ben Edmunds

Codementor PHP expert Ben Edmunds joined us for an Office Hour session to share some of his knowledge in PHP security. Ben Edmunds is the co-host of PHP Town Hall and the creator of Ion Auth, a CodeIgnitor authentication library for security. Ben has also authored the book “Building Secure PHP apps”. 

The text below is a summary done by the Codementor team and may vary from the original video and if you see any issues, please let us know!

Topics that we’ll be covering are as follows:

  • Exceptions
  • Closures
  • Namespaces
  • Statics
  • Shorty Array Syntaxes
  • PDO for SQL and databases
  • Security
  • Legit Tools


A Quick Introduction

To better follow the security tips later, there are a few core concepts to understand, and here is a quick overview of what modern PHP is.

Exceptions

Exceptions are when something bad happens

To catch an exception, a try catch block is recommended. Instead of returning a mere error code, the block will return an exception message about what went wrong. The method will save programmers from having to constantly do a trial and error to test how things respond.

try {
//your code goes here
}
catch (Exception $e) {
die($e->getMessage()); }

An example of a try catch block. The real key is the exception object returned, as it can be different exception objects and does not have to be a generic one.

Closures

In previous years with PHP, functions had to be named. Now, like Javascript, PHP now also supports closures, or anonymous functions.

Route::get(‘/', function(){
return View::make(‘index');
});

A laravel style example of a closure. In the example above, the return index is passed for just a one-time use.

What should be noted is there is some difference in the scoping between PHP and Javascript closures. In Javascript, $this constantly changes based on the scope of how deep callbacks are, but PHP doesn’t have that issue. Instead, in PHP there is the “use” command, which gives access to external variables inside callbacks.

Namespaces

Namespaces are logical groups or containers for similar objects. When using two different libraries that share the same class name and will interfere with each other as well as make things confusing, namespaces should be used.

namespace Illuminate\Console;
class Command
{
//...


use Illuminate\Console\Command;

In this example, the command class will be defined as a part of the illuminate\console namespace. At the bottom is a command to begin using a namespace, and after using it one can then call their class as they normally do.

Namespaces can go as deep as a coder wants, and the idea behind namespaces is to put pieces of code under one container that serves as a separator and also makes it easier to figure out what will come out. For example, when injecting different things into the controller, namespaces makes it possible to check back and make sure what class is used (e.g. the DB class, the illuminate DB class or some other DB class someone else put in, etc.)

Statics

The use of statics is controversial in the PHP world because of how they were often used inappropriately. However, a good situation to use statics is when there are random related functions that don’t need object-oriented inheritance. Static functions are self-contained and don’t need much of a scope.

A good example of when to use statics is in a form helper with no form object to call on. A form helper would consist of shortcuts to creating a form, inputs, etc., which is just calling functions inside the view to build out the html. A bad example is to use statics to retain state, i.e. building an object to add and remove things from where the scope and state of an object is retained through time. Although there are ways to hack around this issue such as using statics as a shortcut to IoC, the general consensus is to stick with the convention to avoid confusing other programmers who may be sharing the project.

Class Route {
public static function get() {
//...
}

Route::get();

The double colon in the example is what will make it a static call instead of a class call, which uses an arrow. In this static example, the code directly calls on the parent class without having to define a variable to stick in.

Furthermore, there is no $this object inside of a static, and there’s a difference in the scoping of variables depending on whether they are defined inside or outside of a static, whether they’re part of the parent class or not, etc. Thus, it may be worthwhile to read up on how scoping works inside statics.

Short Array Syntax

Although rather inconsequential, this neat new function is a blessing to the lazy coders as well as those who are used to coding with the Javascript language. Basically, instead of having to code the following line:

$array = array(
0 => ‘value1’,
1 => ‘value2’,
);

One can now just use brackets like this:


$array = [
0 => ‘value1’,
1 => ‘value2’,
];

PDO

PDO has been slowly gaining popularity in the recent two year, so it is now normal to see it used. It’s a nice class and a great way of handling database abstractions, as it gives a good database layer that works across other databases. For example, since it PDO is cross system, both PostresSQL and MySQL have the same API for interacting with PDO.

The systems that support PDO are:

  • MS SQL
  • MySQL
  • Oracle
  • PostgreSQL
  • SQLite
  • CUBRID
  • Firebird
  • Informix
  • ODBC & DB2
  • 4D
Furthermore, PDO promotes safe binding and will automatically handle a lot of SQL injection attacks.
$stmt = $db->prepare(‘
SELECT * FROM users WHERE id=:id
’);
$stmt->bindParam(‘:id’, $id);
$stmt->execute();

The :id in the example is a placeholder for a variable, and the statement bind command gets the id variable before it executes.

At first it may seem stupid to do the extra work of retrieving the id variable instead of sticking it into the query in the example above. However, the difference is that doing so will automatically escape the value for you. The code will also run through a different execution loop, so in a WHERE or WHERE, WHERE and WHERE, the bind parameter will not allow anything to break out of the tree it should be in. In contrast, a straight SQL query that does not escape properly will be mistaken as being over, and users would be deleted. As this sort of execution can be stopped by Bind the Parameter, it is highly recommended to use it, as it makes life easier and codes safer. The language can handle security, so there is no need to be self-reliant.

Security

OWASP, the authority on PHP security issues, has a top ten list, which is a great resource to check out.

After the overview, hopefully you now have a better idea about what modern PHP is, and will have an easier time understanding the biggest security issues it currently faces:

  • SQL injection
  • HTTPS
  • Password hashing
  • Authentication
  • Safe defaults
  • XSS & CSRF

SQL Injection

A SQL injection takes data and manipulates the id, and this sort of attack is usually seen in forum posts. Some assume that, since a post has a hidden ID variable fill and a hidden input with the ID, a user can’t change the post ID since it is hidden. However, the assumption is wrong, as anyone can copy the form, inspect it in their web browser, and change anything they want before they submit it.

Thus, any input from a user cannot be trusted and could be bad, malicious, or incorrect data.

An example of the problems that may arise when users input incorrect data can be examined through the case where a database using an input field with VarChar(10). If input is not properly checked, a user who sends something over the maximum length of 11 characters will not have their input recognized on IE6. It may be helpful to think about whether the data is expected or valid and then try to handle them through validation, typecasting, trimming, or the like. Another thing to keep in mind is MySQL will just truncate the characters that exceed the maximum length, but the PostgresSQL will give back an exception, which will eventually give false data. Therefore, it is always better to handle any kind of restrictions on data in the code when possible to prevent invalid data from getting into the database.

Escaping input before it goes into the database will protect the database from SQL injection. Escaping output is also important, as one never knows what a user will submit (e.g. html, javascript, etc) and what will happen when the input is displayed directly back to the page. HTML entities will be the go-to function 90% of the time for cleaning data up before it is put back onto the screen. And, as mentioned previously, PDO makes it extremely easy to bind parameters.

//escaping input
$stmt->bindParam(‘:id’, $id);

//escaping output
htmlentities($_POST[‘name’]);

Therefore, the bind parameter for PDO will handle SQL injections for the programmer. If using a different or older library, PHP still has built-in functions for escaping. In conclusion, simply use built-in functions to deal with SQL injections, as the PHP developers will think of more use-cases and hit more different issues in servers than a single programmer.

HTTPS/SSL

OAuth 2.0 now requires HTTPS as well

For safe end-to-end encryption, HTTPS/SSL will be the easiest and most secure option. It will encrypt traffic across the wire and who is the sender and who is the receiver. Anything that goes through the “pipe” once the connection is made will be secure, as it will be encrypted and no one can look in on it. As certificates are required, it is important to make sure both sides have valid certificates, though it could be expensive depending on where one gets the certificates. For example, the average is probably around $1000 for a certificate, but many shared hosts such as DNSimple will have special plans and offer $20/month for an SSL certificate. Naturally it will be safer to buy from the certificate authority as it would be possible to transfer between servers or DNS hosts, but if saving money is more important, then it would be a good idea to check offers made by one’s host. Another solution is to create a certificate and ask users to download it to their computer, but the method is not recommended, as good users know not to trust random authorities from websites they don’t know.

Authentication

Problem arises when a login screen does not check whether a user is qualified to enter the administrator panel and simply assume a user to be an administrator if they manage to get in. This makes a site vulnerable to a malicious user who guesses the administrator login url or finds a way to scrape it somehow, who then consequently find a way to get into the administrator panel. A method to counter this issue is through using a constructor where the administrator panel is the controller.

//authentication - access control if (!$user->inGroup(‘admin’)) {
return ‘ERROR YO’;
}

Pull in the current user through the constructor and check to see if they have authorized access to the controller. If the user does not have authorized access, return some type of error, redirect them to an error page, or take them back to where the user should be. Try to check their access as soon as possible instead of waiting until right before the view is displayed, as they may still be able to access the admin panel. Therefore, the sooner an unauthorized user is taken out of the execution path, the safer it will be for future changes.

Another issue with login many will miss is brute forcing. If there is no protection against brute forcing, it is trivial to break anyone’s account, as up to 90% of users would use the same password everywhere, and worse those passwords might be on the list of the most popular passwords used. Therefore, if a site has no check against brute forcing, someone can run a brute force login tool against the site and the hacker would be in users’ accounts in minutes or hours. The simplest way to prevent brute forcing will be to track login attempts within a certain period of time.


//authentication - brute force if ($user->loginAttempts > 5) {
return ‘CAUGHT YA’; }

For example, limit the number of login attempts to 5 tries in 10 minutes or whatever is reasonable.

Curbing the number of login attempts with a big enough period would slow down anyone trying to hack into a website, making it too much of a cost for hackers to continue. A good way to do that is to keep an IP list and a username or an email list, as this way it will be clear when the same IP tries to hit several different user accounts, and it would be easier to stop that IP. It should be noted, however, that in a company this method should be approached with more care, as many users in the same building would share the same IP because they all route through one public IP to get out of the building. If that is a concern, it will be a good idea to raise the limit for login attempts to a much higher account.

Safe Password Hashing

PHP programmers no longer need to worry about encryptions such as md5 or SHA1, as PHP now includes a safe, built-in password hashing, which means it will use the newest, most secure, or most accepted hashing algorithm. PHP will provide a common API to use now and also in the future for password hashings, which includes functions to verify the correct password.

//safe password hashing password_hash($_POST['pass']);

//password verification password_verify($_POST['pass'], $u->pass);

The top will be used before the password sorting database, while the bottom line will be the login function.

In the example above, whatever the user types into the login screen will be passed, and then the password will be pulled out of the database to see whether the two match. As this would be a hash stored into the database, it would not be pulling text password. Furthermore, as there will be a unique salt in the algorithm for every user, different users with same passwords would still have different hashes. When verifying, the database will pull the salt back out and rehash the password with the same salt.

Safe Defaults

A common problem many have encountered is when they define and use a variable direct that starts as a certain value but ends up going through a different controller method, ending up with a value that is not what was expected. Therefore, it is best to define what the default value should be as soon as possible, ideally at the top of controllers.

//safe defaults
class Your Controller {
protected $var1 = ‘default value’;

function __construct() { ... } }

If more mutations or work are needed, those should be done in the constructor. As soon as the default values can be certified, it will prevent a loop from happening.


//safe defaults
$something = false;

foreach ($array as $k => $v) { $something = $v->foo;
if ($something == ‘bar’) { ... }
}

This foreach is vulnerable, since if for some reason the initialization has not be hit because it has been accessed through a different path, the logic would be thrown into a loop as the value was expected to be false by default.

When defining defaults, try to be more explicit, since this will make it easier for other developers in the same project to understand what the default value should be without having to know what is going on in the foreach.

XSS

Cross-site scripting is a common hack that exploits URL that is not properly escaped.

http://www.yourSite.com/?page_num=2&per_page=50

A simple paging URL. If someone can guess the URL and changes it to page_num=2&action=delete and the page takes it for some reason, however….

It is important to escape URLs properly, since anyone can watch the URL, see what is happening in the URLs, and eventually use them to perform certain actions. An example of a potential threat of is a malicious user creating a test account, deletes the account, and figures out the URL to do so. In a non-persistent XSS attack, the malicious user then generates the URL to delete a user account with someone else’s user ID, somehow sends the URL to the user either through a link on another site or from an email. The unknowing user, who is still logged in with a valid account and session data, clicks on the link and ends up deleting their own account.

In a persistent XSS attack, the URL is saved into the website’s server and may be presented as a post with HTML or Javascript. For example, it could be a facebook status, in which a user comes back to look at a page with that status and ends up with something nefarious.

XSS can usually be protected with the following method:


< h1 >Title< /h1 >
Hello htmlentities($name)?>

Anything generated by a client or user should be escaped before they are displayed back.

Cross Site Request Forgery

CSRF is similar to XSS attacks, but slightly different. A way to understand CSRF is it could be a form that responds to anything. If there is a post/put/update/delete action, it does not necessarily respond to http verbs, but instead the actual action that is performed.

If data is being changed, it needs to be behind a form in a system with a token that can only be used once—also known as nonces. This would ensure the action was initiated by the user and came from a form recently supplied by the website.

function generateCsrf() {
$token = mcrypt_create_iv(
16, MCRYPT_DEV_URANDOM); Session::flash('csrfToken', $token);
return $token;
}

if (
$_POST['token'] == Session::get(‘csrfToken')
) {... }

The example above shows how to use tokens, in which the code will be used as a hidden field in a form. It will generate a unique token, save it into the session, and then return it so it can be put into the form. After the form is posted, the token can then be checked if it matches the one stored in the site’s sessions.

Basically, nonces make it so forms can be only good once. However, it could cause problems if there are two forms on a page or there is AJAX in the page. The simplest solution is to store an array of valid tokens for a session, and then have the garbage collection routine delete the tokens after some seconds or minutes. (E.g. Having all tokens good for five minutes before they expire.)

Legit Tools

Ruby had cool tools for a long time, but PHP finally has some legit tools as well, and Ben shared his thoughts about them with us.

Built-in Web Server

There is now a built-in server for local development where sites can be tested by running php –S in the host, which will be served out into a port. To see the site, just go into a browser and type in localhost:8000. However, this method is not resilient and therefore it is not recommended for going into production with, but it is a great way to test something locally. Furthermore, the built-in web server also makes it easy to switch between projects without having to worry about keeping a list of host entries.

Composer

Composer is finally a good, secure package manager for PHP with a sane format.

// composer.json

{
"require": {
"stripe/stripe-php": "dev-master", "twilio/sdk": "dev-master"
} }

All one needs to do is to define their composer.json file, what packages should be pulled in, and then a couple simple commands to get set up and going.

$ php composer.phar update
$ php composer.phar install

There is a simple updating or installation command for any changes since the composer was last used. There are also advanced uses, such as locking files to keep the exact system versions in place to be properly gotten to the servers and the rest of one’s team.

Furthermore, composer also has an autoloader built in.

$client =
new Services_Twilio($sid, $tkn);

$client->account ->messages
->sendMessage(...)

The autoloader is a great addition, as it will know how to look up a file based on the way things are namespaced. Thus, the PHP coder no longer has to worry about figuring out the whole path down inside a certain class into a certain library he or she has never used.

Unit Testing

There are a lot of tools coming out for unit testing (at last!), and here is just a very small selection of the more popular ones:

In conclusion, all these tools are to make sure the program does what it is expected to.


class ApiAuthTest extends PHPUnit_Framework_TestCase {
public function testVerify() {

$auth = new apiAuth(); $this->assertTrue($auth->verify());
$ phpunit tests

PHPUnit 3.3.17 by Sebastian Bergmann. Time: 0.01 seconds
OK (1 tests, 1 assertions)

A simple test case to verify an API authentication. Here it is expected for the response of the function to be true, whereas in the case where it is expected to be false, AssertFalse should be used instead. It is also possible to assert the expected response to be of a certain value.

The point of a unit test is to ensure every component in a code behaves properly to valid and invalid data, and also returns what is expected in each case.

Other tools that will likely be seen the most will be for acceptance testing, which confirms that not only do individual components work, but the entire system together works. Therefore, acceptance testing will usually be done through a browser or a command line tool to check the interactions in the system and whether the final output turns out as what is expected.

To run the tests, simply run the PHPUnit test, as it will go through and run all the tests in the test folder. At the end it would give an output of how many tests and assertions there are as well as if there were any errors.

Resources

PHP.net is the best resource for looking up functions, methods, or anything built into the language.

PHPtheRightWay is a website and recently a book, which is highly recommended. It’s created by John Lockart, who also created the Slim Framework for PHP, and the PHP community has gotten together to work on this resource to let people know what modern PHP looks like.

Recommended Modern Frameworks

Building Secure PHP Apps

And finally, of course Ben’s book is also a great resource. It’s a short read around 200 pages.

The topics covered here are explained in much more detail, and you can use the coupon code “codementor” to get $3 off!


Other posts in this series with Ben Edmunds:


Ben Edmunds

Need Ben’s help? Book a 1-on-1 session!

View Ben’s Profile

or join us as an expert mentor!



Author
Ben Edmunds
Ben Edmunds
active leader, developer, and speaker in various development communities. PHP/JS expert.
Ben Edmunds leads development teams to create cutting-edge web and mobile applications. He is an active leader, developer, and speaker in various development communities, especially the CodeIgniter...
Hire the Author

Questions about this tutorial?  Get Live 1:1 help from PHP experts!
Sukhrobbek Gayratov
Sukhrobbek Gayratov
5.0
Senior Full Stack Engineer
Experienced software architect and developer with over a decade of expertise in building cloud-based applications. I specialize in .NET...
Hire this Expert
Ali
Ali
5.0
DevOps Engineer and Software Developer
12+ years of professional experience in the field I only express interest when I'm 100% sure I can help you out. If I don't manage to do that,...
Hire this Expert
comments powered by Disqus