Piwik Security checklist: Make your Piwik code secure

This page aims to provide a quick reference on how to secure your PHP code, when developping Piwik Plugins

Secure your software against direct access

The files of your plugins will usually be called by Piwik. Piwik is a wrapper around your software, it provides many useful features like user authentication and so on. Since developers usually test their plugins only through Piwik, they tend to forget about the possibility of calling files directly. Instead of calling your plugin by

http://yoursite.com/piwik/index.php?module=YourPlugin

crackers also might try to use

http://yoursite.com/piwik/plugins/YourPlugin/YourPlugin.php

As you can see, the PHP file will be executed directly, without Piwik as a wrapper around it. Now, if your file only contains some classes or functions, but does not execute any code, there is nothing wrong about that:

Example class

<?php
 class myClass {
     [SomeFunctionsHere]
 }
 function myFunction() {
     [SomeCodeHere]
 }
?>

The cracker would just see an empty page when accessing your file directly. But if that PHP file actually executes anything, he would probably see a bunch of error messages, revealing important details of your system. Under some circumstances, he might also be able to execute any code he wants to, on your system!

Conclusion: To make your plugin secure against direct access, insert this code line into the beginning of every PHP file that executes code:

<?php
// no direct access
defined('PIWIK_INCLUDE_PATH') or die('Restricted access');

// your code here

This is a MUST for every file that executes PHP code. If you are in doubt whether your file executes code, do use this line!

ALWAYS load the GET/POST/etc. with Piwik::getRequestVar()

To be protected against Cross Site Scripting (XSS) you need to load all external variables using the function Piwik::getRequestVar().

Example: If you have a URL that looks like

piwik/index.php?module=Home&date=yesterday

If you want to read the $_GET['date'] value, don't read it directly but use Piwik::getRequestVar('date')

Use the Piwik functions & bind parameters to execute SQL queries

SQL injections make it possible for attackers to modify certain unsafe SQL queries, your script executes, in such a way that it could alter data in your database or give out sensible data to the attacker. That is because of unvalidated user input.

Take a look at this code:

<?php
    $idsite = $_GET['value'];
    Piwik_Query( "SELECT * FROM ".Piwik::prefixTable('site')." WHERE idsite = $idsite" );

An attacker could hand over a string like '1 OR 1', the query results in "SELECT * FROM piwik_site WHERE idsite = 1 OR 1", thus returning all rows from piwik_site. I'm not going more into detail here, as SQL injections are covered quite well on the web. Please take a look at the resources listed at the bottom of this post.

To execute safely SQL queries in Piwik, you have to bind all the parameters passed to the SQL queries. Use the function helper and populate the $parameters array for each value in the SQL query:

  • function Piwik_Query( $sqlQuery, $parameters = array())
  • function Piwik_FetchAll( $sqlQuery, $parameters = array())
  • function Piwik_FetchOne( $sqlQuery, $parameters = array())

Example of bind parameters with Piwik_FetchOne

<?php
 $feedburnerFeedName = Piwik_FetchOne('SELECT feedburnerName 
                                       FROM '.Piwik::prefixTable('site').
                                      ' WHERE idsite = ? and name = ?', 
                                      array( Piwik_Common::getRequestVar('idSite'), Piwik_Common::getRequestVar('name') ) 
                     );

Secure your software against remote file inclusion

Consider the following code:

<?php
// $lib_dir is an optional configuration variable
include($lib_dir . "functions.inc");

or worse still:

<?php
// $page is a variable from the URL
include($page); 

The user could set the $lib_dir or $page variables and include files such as /etc/passwd or remote files such as http://www.example-hacker-website.com/whatever.php with malicious code. This malicious code could potentially delete files, corrupt databases, or change the values of variables used to track authentication status.

When using functions such as readfile, fopen, file, include, require using user data, you must be careful!

Possible solutions

  • Are you sure you really need to use a user value to include a new file?
  • If yes, check the file name against a list of valid file names. For example,
    <?php
          $valid_pages = array(
            "apage.php"   => "",
            "another.php" => "",
            "more.php"    => "");
    
          if (!isset($valid_pages[$page])) {
            // Abort the script
            die("Invalid request");
          }
    
  • If you must really use a variable from the browser, check the variable's value using code like the following:
    <?php
          if (!(eregi("^[a-z_./]*$", $page) && !eregi("\\.\\.", $page))) {
            // Abort the script
            die("Invalid request");
          }
    

Other tips

  • Make sure that accessing your files directly doesn't execute any function that could have an impact.
  • Use .php extension for all your PHP scripts
  • Avoid executing php code using one of the following functions: eval(), preg_replace(), exec(), passthru(), system(), popen()
  • Make sure your code doesn't rely on register_globals set to On, which should be the case because PHP5 has register_globals = Off by default

References