2. So, who are you, anyway?
Bryan C. Geraghty
Security Consultant at Security PS
@archwisp
I’m a Sr. PHP developer with a systems and security
engineering background - turned application security
consultant
4. These are not specifically security activities but they produce more
stable code
5. Don’t rely on the server’s configuration
Your bootstrap should Absolute Minimum
set the defaults for any Include paths
PHP core functionality Error reporting
that your application is
Time zones
using.
These must be
customizable for every
environment
e.g. (dev, staging, live)
6. Example Bootstrap
<?php
// All environment configuration for this application is done from this file
ini_set('display_errors', 0);
ini_set('error_reporting', -1);
date_default_timezone_set('UTC');
$rootPath = dirname(__FILE__);
set_include_path(sprintf(
'%s%s%s%s',
$rootPath, PATH_SEPARATOR, $rootPath, '/third-party'
));
require_once('MindFrame2/AutoLoad.php');
MindFrame2_AutoLoad::install();
7.
8. Don’t use global constants, variables, or registries
You can’t be certain that nobody else will ever use the
same global constant name
Tracking down where a value is assigned to a global
variable is usually very difficult
Global variables remove control of that variable from the
current scope
Global registries are really just fancy global variables and
the same problems apply
9. Value Reference Rules
Values should only be referenced in one of the following
ways:
A direct reference in the current scope:
$limit = $this->_objectPool->getConfig()-
>getDefaultLimit();
A parameter:
public function getUsers($limit) {
return $this->_objectPool-
>getMapper(‘User’)->fetchAll($limit);
}
10. Don’t use function parameter defaults
"There should be one-- and preferably only one --
obvious way to do it." - Tim Peters
Reduces understanding from the caller perspective
Often add unnecessary complexity
Represents the most common use-case
Other use-cases are often ignored entirely
Changing the default breaks all implementations
Adding parameters breaks default implementations
11. Default parameter example
class User
{
public function __construct($name, $email, $status = self::STATUS_ACTIVE) {
$this->_name = $name;
$this->_email = $email;
$this->_status = $status;
}
}
function createUser($name, $email, $status) {
$user = new User($name, email);
return $this->_objectPoool->getMapper(‘User’)->create($user);
}
12. Use explicit operators whenever possible
This was actual production code.
if ($sincedate && (int)$sincedate) {
$param["UpdatedSinceDate"] =
date("Y-m-d H:i:s", strtotime(time() - ((int)$sincedate)*60*60);
} elseif ($sincedate && strtotime($sincedate)) {
$param["UpdatedSinceDate"] =
date("Y-m-d H:i:s", strtotime($sincedate));
} elseif(!isset($param["UpdatedSinceDate"])) {
exit ("Invalid param UpdatedSinceDate");
}
Do you think this code worked as the author intended when $sincedate was set to
'2011-05-31'?
13. Use constants for possible values
Constants cannot be re-defined, so using a constant for configuration limits your code
to only ever be able to run in one configuration in a given stack.
// Bad: This code cannot execute differently in the same stack
define(‘PDO_FETCH_MODE’, 2);
$dbi->fetchAll(PDO_FETCH_MODE);
// Better: The value can change but this literal value means nothing without
// referencing the documentation
$dbi->fetchAll(2);
// Best: Implemented using a meaningful constant which represents a *possible* value
$dbi->fetchAll(PDO::FETCH_ASSOC);
14. Use wrappers for super-globals
$_REQUEST, $_GET, $_SESSION, and $_FILES are the most
commonly used PHP super-globals but there are others
It’s easy to create some serious security problems
It’s best to use a wrapper for interacting with these
variables
15. Simulate strong/static types
PHP is dynamically typed, so we cannot do true strong or static typing but we can
put controls in place which provide the same protection.
private function _buildSelectDatabaseTableSql($database_name, $table_name,
array $select_data, array $order_by_columns, $offset, $limit)
{
MindFrame2_Core::assertArgumentIsNotBlank($database_name, 1, 'database_name');
MindFrame2_Core::assertArgumentIsNotBlank($table_name, 2, 'table_name');
MindFrame2_Core::assertArgumentIsInt($offset, 5, 'offset');
MindFrame2_Core::assertArgumentIsInt($limit, 6, 'limit');
$skel = "SELECTn %snFROMn %s.%sn%s%s%s%s;";
$sql = sprintf($skel,
$this->_buildSelectTableSqlFieldClause($table_name),
$this->getSharedModule()->escapeDbElementName($database_name),
$this->getSharedModule()->escapeDbElementName($table_name),
$this->_buildSelectTableSqlJoinClause(
$this->getSharedModule()->getDatabase()->getName(), $table_name),
$this->_buildSelectTableSqlWhereClause($table_name, $select_data),
$this->_buildSelectTableSqlOrderByClause(
$table_name, $order_by_columns),
$this->_buildSelectTableSqlLimitClause($offset, $limit));
return $sql;
}
16. Example Typing Assertions
public static function assertArgumentIsInt($value, $position, $name)
{
if (!is_int($value)) {
$skel = 'Expected integer value for argument #%d (%s), %s given';
$message = sprintf($skel, $position, $name, gettype($value));
throw new InvalidArgumentException($message);
}
}
public static function assertArgumentIsNotBlank($value, $position, $name)
{
if (trim($value) === '') {
$skel = 'Argument #%d (%s) cannot be blank';
$message = sprintf($skel, $position, $name);
throw new InvalidArgumentException($message);
}
}
17. Use PHPMD & CodeSniffer
This is the ./bin/codecheck script in MindFrame2
#!/bin/bash
WD="`/usr/bin/dirname $0`/../";
echo Changing to working directory: $WD;
cd $WD;
if [ -z $1 ]; then
DIR='.';
else
DIR=$1;
fi
phpmd --exclude coverage $DIR text codesize,unusedcode,naming,design
phpcs --ignore=coverage --standard=`pwd`/Standards/MindFrame2 $DIR
phpcs --ignore=coverage --report=source --standard=`pwd`/Standards/MindFrame2 $DIR
18. Example PHPMD Output
bryan@ubuntu-virtual ~/Code/MindFrame2 [130] $ ./bin/codecheck Dbms/Dbi
Changing to working directory: ./bin/../
/home/bryan/Code/MindFrame2/Dbms/Dbi/Distributed.php:128 Avoid unused local
variables such as '$partner'.
/home/bryan/Code/MindFrame2/Dbms/Dbi/Distributed.php:185 Avoid unused local
variables such as '$data'.
/home/bryan/Code/MindFrame2/Dbms/Dbi/Distributed.php:199 Avoid unused local
variables such as '$index'.
/home/bryan/Code/MindFrame2/Dbms/Dbi/Distributed.php:240 Avoid unused private
methods such as '_buildReplicaDatabaseSuffix'.
/home/bryan/Code/MindFrame2/Dbms/Dbi/Single.php:25 This class has too many
methods, consider refactoring it.
/home/bryan/Code/MindFrame2/Dbms/Dbi/Single.php:150 Avoid unused parameters such as
'$options'.
19. Example PHPCS Output
FILE: /home/bryan/Code/MindFrame2/Dbms/Dbi/Single.php
--------------------------------------------------------------------------------
FOUND 6 ERROR(S) AFFECTING 6 LINE(S)
--------------------------------------------------------------------------------
39 | ERROR | Public member variables ("_connection") are forbidden
62 | ERROR | Whitespace found at end of line
144 | ERROR | Whitespace found at end of line
160 | ERROR | Line exceeds maximum limit of 80 characters; contains 97
| | characters
195 | ERROR | Whitespace found at end of line
298 | ERROR | Line exceeds maximum limit of 80 characters; contains 81
| | characters
--------------------------------------------------------------------------------
20. Unit testing / TDD
Test critical functionality at a minimum
Add edge cases to regression tests as they are discovered
Test-driven development only works if the entire team is
dedicated to it
I personally don’t trust code that isn’t regression-tested;
even if it was written by me
21. Continuous Integration
Automated system for code analysis and regression
testing
There are a lot of continuous integration systems for PHP
If your tests aren’t automated, very few people will know
or care when they break
22. Deployment
Manually copying files is riddled with problems
Tagging releases and re-deploying is a complicated and
time-consuming process
Deployment systems automate the build and deployment
process