Upload
archwisp
View
2.086
Download
2
Embed Size (px)
Citation preview
Explicit Code & QA
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
Remember, layers
Simpler is easier to test
Don’t make assumptions
Compromised browser = game over
These are not specifically security activities but they produce more stable code
Don’t rely on the server’s configuration
Your bootstrap should set the defaults for any PHP core functionality that your application is using.
These must be customizable for every environment
e.g. (dev, staging, live)
Absolute Minimum
Include paths
Error reporting
Time zones
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();
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
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);
}
Don’t use function parameter defaults
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
"There should be one-- and preferably only one --obvious way to do it." - Tim Peters
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);
}
Use explicit operators whenever possible
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");
}
This was actual production code.
Do you think this code worked as the author intended when $sincedate was set to '2011-05-31'?
Use constants for possible values
// 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);
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.
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
Simulate strong/static types
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 = "SELECT\n %s\nFROM\n %s.%s\n%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;
}
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.
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);
}
}
Use PHPMD & CodeSniffer
#!/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
This is the ./bin/codecheck script in MindFrame2
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'.
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
--------------------------------------------------------------------------------
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
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
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
ACLs
MAC
Thread limits
Unwanted services
If you’re interested in an application security career, come talk with me.