PHP Security Best Practices

This document serves the standardization of programming concepts in PHP. The aim is to enable interoperability of components and to provide a common technical basis for implementation of proven concepts for optimal programming and testing practices



Object-oriented programming (OOP)

Object-oriented programming allows function grouping and variable scoping which improves application security. Grouping functions in a logical way makes code easier to debug and maintain. While this standard does not require OOP, we strongly recommend OOP as a best practice.

Model-view-controller (MVC)

One style of object-oriented programming, Model-View-Controller (MVC), defines three types of classes:

  • Model, a type of file that stores functions for data interaction (e.g., with the database, flat files, off-site APIs, etc.);
  • View, a type of file that stores a template for the outcome of a page;
  • Controller, a type of file that stores functions for handling the browser request, deciding which model to use, querying it, manipulating the received data, and passing it to the view.

Web Root --\
           |
           |-- includes --\
           |              |- images
           |              |- js
           |              \- css
           |-- models
           |-- controllers
           \-- views

Includes

Javascript, CSS and other non-PHP libraries. This is also a good place to include common header files like those for PHP-CAS, variable files, etc. The recommended structure for and PHP files would be {filename}.inc.php

  • Files in these locations should be prefaced with a redirection header to prevent possible accessing of files.
<?php
if ($_SERVER['PHP_SELF'] == "whateverthisfilename.php")
{
    header("Location: /");
}

Models

Whenever data needs to be retrieved, manipulated or deleted this should always be done by a model. A Model is a representation of some kind of data and has the methods to change them. For example: you never put SQL queries in a Controller, those are put in the Model and the Controller will call upon the Model to execute the queries. This way if your database changes you won't need to change all your Controllers but just the Model that acts upon it.

  • Files in these locations should be prefaced with their focus and then have {Model.php}. For example: If you have an SQL Model. It would be SQLModel.php
<?php
namespace SQLModel;
class QueryManager extends \SQLModel {
    public static function get_results()
    {
        // Database interactions
    }
}
?>

While models can be used with any type of data storage we'll focus here on usage with SQL because that's the most common usage. Almost always your models will have at least all CRUD methods: create, read, update, delete (or variations on those).

Controllers

Controllers are classes that can be reached through the URL and take care of handling the request. A controller calls models and other classes to fetch the information. Finally, it will pass everything to a view for output. The index.php file below, initiates the request for a CAS Ticket if there is not already one and then shows the template file or View: _TPLDASHBOARD

<?php

require ("includes/variables.inc.php");
include(_SMARTY_DIR_ . '/Smarty.class.php');
$smarty = new Smarty();
$smarty->setTemplateDir(_SMARTY_TEMPLATE_DIR_)
    ->setCompileDir(_SMARTY_TEMPLATE_COMPILE_DIR_)
    ->setCacheDir(_SMART_CACHE_DIR_);
require ("includes/cas-config.inc.php");
$smarty->assign("sPageTitle",   _TITLE_DASHBOARD_PAGE_);
$smarty->assign("sPageHeader",  _HEADER_DASHBOARD_PAGE_);
$smarty->assign("sPanelHeader", '');
$smarty->display(_TPL_DASHBOARD_);

Views

Views are files that present data to the browser. These files enable the separation of logic and presentation for your application. Views are typically html, javascript, or css but can contain variables passed into them from the controller. We recommend the use of the smarty template engine to separate PHP code from the PHP logic. The above excerpt of a Smarty template would be an example of a view.


Frameworks

The PHP programming language is great for code development but when mixed with HTML, the syntax of PHP statements can be a mess to manage. Smarty makes up for this by insulating PHP from the presentation with a much simpler tag-based syntax. The tags reveal application content, enforcing a clean separation from PHP (application) code. No PHP knowledge is required to manage Smarty templates.

Smarty Framework

In cases where efficient template management is crucial, or the case where web designers (not PHP developers) are managing templates, the strengths of the tag-based template syntax are quickly realized. If the project size scales to hundreds or thousands of templates, template inheritance keeps template maintenance streamlined. What little Smarty adds technically (a tag-based syntax + 1-time compile step) is easily overcome by the time saved with template maintenance.

Installation

It can be obtained from smarty


Naming Conventions

Convention For Convention Example
Class / Function Name - Begin with an uppercase letter.
- If the class belongs to a hierarchy, reflect this in the naming by using an underscore.
- Avoid abbreviations
- Use a descriptive name
UserProfile
SpaceSurvey
SpaceSurvey_Room_Mgmt
Class Variables and Methods
(All Scopes)
Named using “bumpy case” or “camel case”
Lower case word followed by words with their first letter capitalized
$userLastLoggedIn
getLastLoggedIn()
Constants All uppercase
Use underscores to separate words
Prefix constant names with the uppercase name of the class or package
_DB_USERNAME
_DB_ENCRYPTEDPASS

Syntax

Code Comments

This header should predicate all PHP documents telling the reader what this file does and what its purposes are.

<?php
/**
 * Short description for file/class
 *
 * Long description for file/class (if any)...
 *
 * @category   CategoryName
 * @package    PackageName
 * @author     Original Author <author@example.com>
 * @author     Another Author <another@example.com>
 * @copyright  1997-2005 The PHP Group
 * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
 * @version    Release: @package_version@
 * @link       http://pear.php.net/package/PackageName
 * @see        NetOther, Net_Sample::Net_Sample()
 * @since      Class available since Release 1.2.0
 * @deprecated Class deprecated in Release 2.0.0
 */

Function Header

Each function should have a preface that defines what variables are passed to it and what their use is going to be.

/******************************************************************************
 * SQLServer_DB constructor.
 * @param string $sDBUserName       MSSQL database user
 * @param string $sDBPassword       MSSQL database password
 * @param string $sDBName           MSSQL database name
 * @param string $sDBServerName     MSSQL database host
 ******************************************************************************/
public function __construct( $sDBUserName, $sDBPassword, $sDBName, $sDBServerName)
{
    $this->sDBUserName    = $sDBUserName;
    $this->sDBPassword    = $sDBPassword;
    $this->sDBName        = $sDBName;
    $this->sDBServerName  = $sDBServerName;
​
    $this->resSQLSERVER = $this->sql_server_connect();
}

Single Line comments

Singe line comments will should be no more than a single line of text, maximum two lines. Beyond that a multi-line code block should be used.

$this->satellite_init_get_options($sAPIURL);
$sHostData = curl_exec($this->resAPI);
​
// We should get a 200 code here or return an error
$iCode = curl_getinfo($this->resAPI, CURLINFO_HTTP_CODE);

In-Line comments should NEVER be used as they make code difficult to read.

public function sampleFn($param /* String */, $myvar = FALSE /* Boolean */) {

If you need to explain what a variable does use a function header.

Multi-line Comment

/*
 * This is a multi-line comment;
 * this should describe what is
 * happening, in a chunk.
 */

Code Formating

  • Do not obfuscate code.
  • Indent code and use white space for readability
  • Adhere to Allman Style (BSD Style) formatting
  • Use an IDE (integrated development environment) such as VSCode

Sample:

while (x == y)
{
    something();
    somethingelse();
}

finalthing();

Security

Never trust user-entered data

Assume all data is user-entered data and escape data immediately when it needs to be escaped, not earlier. This avoids both unescaped data and redundant escaping (e.g., \\').

For example, when escaping data for a SQL query, escape everything, and escape during the concatenation, not before:

<?php

$sIPAddressRegex = "/" . "\A(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\z" . "/";

var_dump($_GET);
// Assume the following are the names of REQUIRED fields submitted from a form
$aFilterArgs = array(
    "firstname" => FILTER_SANITIZE_STRING,
    "lastname"  => FILTER_SANITIZE_STRING,
    "email"     => FILTER_SANITIZE_STRING,
    "age"       => array( 'filter' => FILTER_VALIDATE_INT, 'options' => array('min_range' => 10, 'max_range' => 100) ),
    "host_ip"   => array( 'filter' => FILTER_VALIDATE_REGEXP, 'options' => array('regexp'  => $sIPAddressRegex) ),
    "text_data" => FILTER_SANITIZE_SPECIAL_CHARS
);

/**********************************************
 * If data was submitted from a form using the
 * method GET then the constant used would be
 * INPUT_GET
 **********************************************/
$aFilteredFormData = filter_input_array (INPUT_POST, $aFilterArgs);

foreach ($aFilteredFormData as $key => $value)
{
    /*****************************************************
     * Since all fields for this example are required,
     * the only reason for an empty field would be either
     * an error of some kind or invalid data submitted
     ****************************************************/
    if ( $value == "" )
    {
        die("Error: invalid input in " . $key . " has been detected and has a null value!");
    }
}

$query = "SELECT 'col1', 'col2' FROM 'tbl' WHERE 'col3' = '". $aFilteredFormData['firstname'] ."' AND 'col4' = '". $aFilteredFormData['lastname'] ."'";

?>

Data integrity

Do not rely solely on client-side (JavaScript) validation as users can easily circumvent this. Include a server-side version of any data validation.

Also, to ensure data integrity, consider including constraints in the database (i.e., foreign keys, unique constraints) to reduce the chances that invalid data reaches your application through others means (e.g., direct database manipulation, improper code).

Borrowed code

Many developers take advantage of the abundance of free and open-source code on the internet. However, these scripts can create security vulnerabilities because of a) the large number of people working on them; b) the availability of the code to anyone who wishes to view it; and c) the sometimes heavy use of these scripts on many sites, resulting in more incentive for hackers to find holes in them.

Do not waste time reinventing the wheel (as often in-house renditions can have many of the same security issues). But stay alert and always watch for any released security vulnerabilities for the code you use and always patch your code with the supplied updates or patch it yourself and share your changes with the code community.

Encoding Passwords

PHP Encryption

php-encryption

Assuming you have a working installation of Composer you will need to install the module

[user@host html]$ composer require defuse/php-encryption

NOTE: The following files will need to be located in the application root to make use of the Crypto libraries.

To Generate an encrypted password you will first need to generate a key.

Create a file called create_key.php and use the following code.

<?php
require "vendor/autoload.php";

use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;

$key = Key::createNewRandomKey();

$fp = fopen ('/var/www/key.txt', "w");
fputs($fp, $fp);

Execute the application. You will see output similar to the folowing:

[user@host html]$ php generate_password.php
#!/usr/bin/env php
string(168) "def5020012e7b4da8f426ab05d9d359308e86cd30010604c66936b3a12dbdf08a90b9fc63ce7c090f5598dea94c5b6d99e3e85e8bf90f5fa8bfa7490ee3cf503cce441026c82c1011d0e1eb8c120612637503676"
[user@host html]$

The text in the quotes after string(168) is your encrypted password

Main Password Respository

File: api_access_vars.inc.php

Location: Outside of the web project

Contents:

<?php
// Should be the name of the Gridmaster alias
define('_INFOBLOX_SERVER_', 'infoblox.uits.uconn.edu');
define('_INFOBLOX_WAPI_VERSION', 'v2.7.3');
define('_INFOBLOX_USERNAME_', 'some.service.account');
define('_INFOBLOX_ENC_PASSWORD_', 'def5020012e7b4da8f426ab05d9d359308e86cd30010604c66936b3a12dbdf08a90b9fc63ce7c090f5598dea94c5b6d99e3e85e8bf90f5fa8bfa7490ee3cf503cce441026c82c1011d0e1eb8c120612637503676');