Sunday, March 5, 2017

How can PHP json_encode prevent XSS (Cross-Site Scripting) attacks?

Cross-Site Scripting attack (also known as XSS attack) is a technique that can be used by a malicious script, to perform actions in a website as if the user were performing them.

Lets say a server is answering to, assigning the value of the get parameter some_number to a variable $variable. Somewhere in the response the following script is used to render the page:
    var data = {
        param: <?php echo $variable ?>

An attacker using a different site ( could have a script that, without the user's consent, makes a request to'||alert('hello')||'1.

This means that the attacker could run any script inside the domain, as if it was the user. If website1 is an online shop for example, and the user is logged in, it would be possible to place an order in the name of the user.

One simple way of preventing such kind of attack for this particular situation is to use PHP json_encode. The code could be replaced by this:
    var data = <?php
        echo json_encode([
            'param' => $variable

It is interesting to note that using json_encode also prevents the contents of the variable from breaking your HTML structure. For example, if the value of $variable is '</script><div>hello</div>', with the first code, PHP would render the following output:
    var data = {
        param: </script><div>hello</div>

This results in a JavaScript syntax error, and a malformed HTML document.

When using json_encode, PHP automatically escapes the character '/', and that makes the browser interpret the data as a JavaScript string:
    var data = {"param":"<\/script><div>hello<\/div>"};

So, besides making the code look better in some cases, using json_encode can prevent some XSS attacks, and prevent your HTML code from being corrupted.

Wednesday, June 22, 2016

Where to put story points of partially finished stories?

You probably experienced some situation where your team couldn't finish all the stories you had committed to do for some sprint. If this was the case, you probably had some stories that were partially or even very close to be finished. Then the question arises: to which sprint should the points of this story be credited?

Possible commonly suggested approaches are:

  1. All the story points go the sprint where the story got completely finished.
  2. The story gets splitted, an estimation of the remaining work is made, and the points are assigned partially to two sprints.
  3. The points of the unfinished story are not counted at all.
  4. The story is re-estimated considering only the work that is still left, which is likely to be a smaller number. Only the new estimation is taken into account.

There is no correct answer. It all depends on how the team interprets and uses the story points. It is important though to choose one approach and stick to it.

Approach 1 is the one that I find more appropriate, although it tends to cause a bigger variation in the amount of store points per sprint. Approach 2 tends to be problematic, because all stories should have business value individually, and splitting stories is not always straightforward. The reason I don't like approaches 3 and 4 is because I think story points should be used by the team to estimate their velocity. When you decide to trash the points of partially finished stories, you end up not being able to predict the velocity very well.

For example, let's say that the team has three stories in the backlog, with estimated points A) 3, B) 7 and C) 5. In the first sprint the team managed to finish A, and almost finished B. So they take the rest of B and C to the second sprint. At the end of the second sprint all the stories are finished. If the team chooses approach 3, in this case they finished 3 story points in the first sprint, and 5 in the second sprint. This would mean that the team velocity is 4 points per sprint, when their actual speed, considering the two sprints, was almost twice that value. Approach 4 would have slightly different results, but the consequences are basically the same.

These differences are not so big in real cases, because you end up having more than one story per sprint, and if your stories are small enough, the remaining story points are negligible in comparison to the team velocity.

So, the best option is to discuss with your team, decide how you want to use the outcome of the story points, and choose the approach which fits best for you.

Tuesday, December 8, 2015

Disable all community and local (non-core) modules in Magento

Sometimes when something is not working properly on a Magento shop, it is very useful to disable some modules, to see if one of them is causing the problem. Magento has a standard feature, that allows you to disable all the local modules, editing the local.xml, finding "<disable_local_modules>false</disable_local_modules>" and replacing false with true.

This feature is nice, but it doesn't help in two situations:
  • when you want to deactivate community modules also. In this case, you can use something as described in this blog post.
  • when you want to disable all the modules but then re-enable a few of them.
For the second situation, I wrote a simple shell script that creates a "modules deactivator" under app/etc/modules, which disables all the non-core modules. After the script finishes, you can go to this "modules deactivator" XML, and enable the modules you want, so you can test each module individually.

The script consists of two files: a bash script, and a PHP script, used to get the modules that should be deactivated.

This is the bash script:
echo "<?xml version=\"1.0\"?>" > $MODULES_DEACTIVATOR_FILE
echo "<config>" >> $MODULES_DEACTIVATOR_FILE
echo " <modules>" >> $MODULES_DEACTIVATOR_FILE
for m in $($PHP_BIN $SCRIPT_FOLDER/active_non_core_modules.php)
echo " <active>false</active>" >> $MODULES_DEACTIVATOR_FILE
echo " </modules>" >> $MODULES_DEACTIVATOR_FILE
echo "</config>" >> $MODULES_DEACTIVATOR_FILE

And this is the PHP script:
$modules = Mage::app()->getConfig()->getNode('modules')->asCanonicalArray();
$modules = array_filter($modules, function ($module) {
return $module['codePool'] != 'core' && $module['active'] === 'true';
echo(implode(PHP_EOL, array_keys($modules)));

To use them, just save the two scripts in the same folder (the PHP script must be named active_non_core_modules.php), and then run the bash script, from the Magento root folder.

Tuesday, October 13, 2015

Download modules from Magento Connect with a single click

If you are a developer and work with Magento, it is very likely that you don't use Magento Connect to download modules. Magento Connect was made to simplify the process of installing modules for people that don't have a solid background on development, which makes it potentially harmful.

There are a few websites which allow you to download a module through them, entering the extension key, or searching through a list of modules like these:

I read this article that explains how to download a module, manually generating the URL of the module package, based on the data found on the module page. Using this information I wrote this small script, that when executed on a Magento Connect module page, opens a new window that causes the module package download to start:

var lastVersion = jQuery(".extension-version li")


var magentoConnectKey = jQuery("#extension-key-input").val();
var moduleName = magentoConnectKey.replace(/.*\/([^/]*)/, "$1");
    magentoConnectKey + "/" +
    lastVersion + "/" +
    moduleName + "-" +
    lastVersion + ".tgz");

What the code does is quite clear: it just clicks on some elements, gets the last version and the module key from the page, generates the URL and opens it on a new window.

I added this code to my favorites, and now, all I have to do to download a module is to go to the module page on Magento Connect and click the favorites button on the browser.

To add the script to the browser favorites you need to add the prefix "javascript:", so the browser interprets whatever comes next as a JavaScript code. Here is a minified vesion ready to paste:

javascript:(function(){var e=jQuery(".extension-version li").first().contents().last().text().trim();jQuery("#button-purchase-input").click(),jQuery("#connect-version-id").val(2),jQuery("#licence-agreement-checkbox").click(),jQuery("#get-extension-button-free").click();var n=jQuery("#extension-key-input").val(),t=n.replace(/.*\/([^/]*)/,"$1");"/"+e+"/"+t+"-"+e+".tgz")})();