Você está na página 1de 44

http://www.sektioneins.

de

Secure Programming with the


Zend-Framework
Stefan Esser <stefan.esser@sektioneins.de>

June 2009 - Amsterdam


Who I am?

Stefan Esser
• from Cologne / Germany

• Information-Security since 1998

• PHP Core Developer since 2001

• Month of PHP Bugs and Suhosin

• Head of Research and Development at SektionEins GmbH

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  2


Part I
Introduction

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  3


Introduction

• Using the Zend-Framework got very popular in the last years


• Growing request of security for Zend-Framework based applications
• Books/Talks/Seminars concentrate on secure programming of PHP
applications without a framework

• Using a framework requires different ways to implement protections


• Some frameworks come with their own security features

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  4


Topics

• Authentication
• Input Validation and Input Filtering
• SQL Security
• Cross Site Request Forgery (CSRF) Protection
• Session Management Security
• Cross Site Scripting (XSS) Protection

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  5


Part II
Authentication

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  6


Classic Applications vs. Zend-Framework

• Zend-Framework applications usually use a


MVC design with dispatcher Dispatcher

• Classic applications usually use neither a


MVC design, nor a dispatcher

• without dispatcher every reachable script Controller


must implement or embed authentication

• classic approach is error-prone


• often scripts exists that forget to
implement the authentication View Model

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  7


Central Authentication in Controller

• Deriving the Zend_Controller_Action


• Authentication implemented in init() method
• Attention: if a controller has an own init() method then method
of the parent class must be called
class My_Controller_Action extends Zend_Controller_Action
{
/**
* Init function
*
* First check if this is a logged in user, ...
*/
public function init()
{
$isLoggedIn = true;
try {
My_Auth::isLoggedIn();
} catch (My_Auth_UserNotLoggedInException $e) {
$isLoggedIn = false;
}
...
}

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  8


Part III
Input Validation and Input Filtering

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  9


Accessing Request Parameters (I)

• Traditionally PHP applications access user input directly


➡ $_GET, $_POST, $_COOKIE, $_REQUEST, $_SERVER, $_ENV, $_FILES

• Form of access also possible in Zend-Framework, but not usual


➡ Input validation and input filtering not directly portable from
traditional PHP applications

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  10


Accessing Request Parameters (II)

• Access via request object Zend_Controller_Request_Http


• Either via methods or magic properties
• Access is unfiltered - only raw data
• Access via magic property in the following order
1. internal parameter array
2. $_GET
3. $_POST
$message = $this->getRequest()->message;
4. $_COOKIE
5. $_SERVER
6. $_ENV

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  11


Accessing Request Parameters (III)

• function getQuery($key = null, $default = null)


• function getPost($key = null, $default = null)
• function getCookie($key = null, $default = null)
• function getServer($key = null, $default = null)
• function getEnv($key = null, $default = null)
• wrapper around $_GET / $_POST / $_COOKIE / $_SERVER / $_ENV with the
possibility to return a default value

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  12


Accessing Request Parameters (IV)

• function getParam($key = null, $default = null)


• gets parameters from the internal parameter array and from $_GET and
$_POST or returns the default value

• parameter sources can be configured ($_GET / $_POST)


• similar to $_REQUEST without $_COOKIE

• function getParams($key = null, $default = null)


• gets all parameters from the internal parameter array, $_GET and $_POST
• in case of double entries, later entries will overwrite the earlier entries

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  13


Validation with Zend_Validate

• Validators to validate parameters


• Zend-Framework comes with a set of validators
Alnum, Alpha, Barcode, Between, Ccnum, Date, Digits,
EmailAddress, Float, GreaterThen, Hex, Hostname, Iban,
InArray, Int, Ip, LessThan, NotEmpty, Regex, StringLength

<?php
$email = $this->getRequest()->getPost('email', 'none@example.com');

$validator = new Zend_Validate_EmailAddress();


if ($validator->isValid($email)) {
// email seems valid
} else {
// email seems invalid; Outputting the reasons
foreach ($validator->getMessages() as $message) {
echo "$message\n";
}
}
?>

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  14


Chaining Validators

• for complex validations own validators can be implemented


• it is however possible to combine validators in validator chains

<?php
// Creating a Validator Chain
$validatorChain = new Zend_Validate();
$validatorChain->addValidator(new Zend_Validate_StringLength(6, 12))
->addValidator(new Zend_Validate_Alnum());

// Validation of "username"
if ($validatorChain->isValid($username)) {
// "username" is valid
} else {
// "username" is invalid; Outputting the reasons
foreach ($validatorChain->getMessages() as $message) {
echo "$message\n";
}
}
?>

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  15


Filtering with Zend_Filter

• Filtering of parameters is done with filters


• Zend-Framework comes with a set of pre defined filters
Alnum, Alpha, BaseName, Callback, Decrypt, Digits, Dir,
Encrypt, Htmlentities, Int, StripNewlines, RealPath,
StringToUpper, StringToLower, StringTrim, StripTags

<?php
$message = $this->getRequest()->getPost('message', '');

$filter = new Zend_Filter_StripTags();

// remove all tags


$message = $filter->filter($message);
?>

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  16


Chaining Filters

• for complex filtering own filters can be implemented


• it is however possible to combine filters in filter chains

<?php
// Create a filter chain and add filters
$filterChain = new Zend_Filter();
$filterChain->addFilter(new Zend_Filter_Alpha())
->addFilter(new Zend_Filter_StringToLower());

// Filtering "username"
$username = $filterKette->filter($username);
?>

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  17


Inputvalidation/-filtering in Forms (I)

• ZF-Forms use validators and filters automatically


• they are attached to Zend_Form_Element objects
• and can be chained as wished
// create name element
$name = $form->createElement('text', 'name', array('size' => 40, 'maxlength' => 40));
$name->addValidator('Alpha')
->addValidator('StringLength', false, array(1, 40))
->setLabel('Name')
->setRequired(true);

// create message element


$message = $form->createElement('textarea', 'message', array('rows' => 6, 'cols' => 40));
$message->setLabel('Message')
->setRequired(true)
->addFilter('StripTags');

// create submit button


$submit = $form->createElement('submit', 'send');
$submit->setLabel('send');

// add all elements to the form


$form->addElement($name)->addElement($message)->addElement($submit);

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  18


Inputvalidation/-filtering in Forms (II)

• Form is validated in the action handler

// checking form data for validity


if (!$form->isValid($this->getRequest()->getPost()))
{
// submit varibales to view
$this->view->form = $form;
$this->view->title = "Form 1";

// stop processing
return $this->render('form');
}

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  19


Validation and Filtering with Zend_Filter_Input

• is a framework for validation and filtering complete arrays


• applies defined filter and validation ruleset to supplied data
• allows validation of all user input automatically
$filters = array(
'*' => 'StringTrim',
'month' => 'Digits'
);

$validators = array(
'month' => array(
new Zend_Validate_Int(),
new Zend_Validate_Between(1, 12)
)
);

$params = $this->getRequest()->getParams();
$input = new Zend_Filter_Input($filters, $validators, $params);

if ($input->isValid()) {
echo "OK\n";
}

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  20


Part IV
SQL Security

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  21


SQL Security - Traditionally

• Traditional PHP Applications


• use PHP‘s database extensions directly
• use their own database abstraction layer
• use PDO
• lots and lots of different escaping functions
• escaping only supports data not identifiers
• partially support for prepared statements

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  22


Databaseaccess in Zend-Framewok Applications

➡ Zend-Framework offers different APIs for handling queries


• Zend_Db
• Zend_Db_Statement
• Zend_Db_Select
• Zend_Db_Table

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  23


Zend_Db - Queries (I)

• function query($sql, $bind = array())


• uses prepared statement internally
• SQL-Injection still possible if $sql is dynamically created
• function fetchAll($sql, $bind = array(), $fetchMode = null)
• all „fetch“ methods use prepared statements internally
• SQL-Injection still possible if $sql is dynamically created

<?php
$sql = "SELECT id FROM _users WHERE lastname=? AND age=?";
$params = array('Smith', '18');
$res = $db->fetchAll($sql, $params);
?>

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  24


Zend_Db - Queries (II)

• function insert($table, array $bind)


• internally uses prepared statements
• SQL-Injection not possible

• function update($table, array $bind, $where = '')


• uses partially prepared statements
• SQL-Injection still possible if $where is dynamically created

• function delete($table, $where = '')


• SQL-Injection still possible if $where is dynamically created

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  25


Zend_Db - Escaping

• function quote($value, $type = null)


• applies the correct escaping - one function not many
• ATTENTION: also puts strings in quotes

• function quoteIdentifier($ident, $auto=false)


• applies escaping for identifiers
• a function not available to traditional PHP applications
• ATTENTION: also puts strings in quotes

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  26


Zend_Db_Select

• used to dynamically build SELECT statements


• uses partially prepared statements
• SQL-Injectionen still possible when wrongly used
• vulnerable through: WHERE / ORDER BY

// Build this query:


// SELECT product_id, product_name, price
// FROM "products"
// WHERE (price < 100.00 OR price > 500.00)
// AND (product_name = 'Apple')

$minimumPrice = 100;
$maximumPrice = 500;
$prod = 'Apple';

$select = $db->select()
->from('products',
array('product_id', 'product_name', 'price'))
->where("price < $minimumPrice OR price > $maximumPrice")
->where('product_name = ?', $prod);

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  27


Part V
Cross Site Request Forgery (CSRF) Protection

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  28


Cross Site Request Forgery (CSRF) Protection

• Protections against CSRF attacks are usually based on secret,


session depended form tokens

• Zend-Framework offers Zend_Form_Element_Hash which is a


secret token with built-in validator

• HTML forms can be secured against CSRF attacks by just adding


the form element to the form

$form->addElement('hash', 'csrf_token',
array('salt' => 's3cr3ts4ltG%Ek@on9!'));

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  29


Automatic CSRF Protection

• normally protection must be added manually


• by deriving Zend_Form it is possible to create an own form
class that automatically comes with CSRF protection

<?php
class My_Form extends Zend_Form
{
function __construct()
{
parent::__construct();
$this->addElement('hash', 'csrf_token',
array('salt' => get_class($this) . 's3cr3t%Ek@on9!'));
}
}
?>

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  30


Token Algorithm

/**
• Token algorithm could be * Generate CSRF token
*
improved */
protected function _generateHash()
• avoid mt_rand() {
$this->_hash = md5(
mt_rand(1,1000000)
• more entropy . $this->getSalt()
. $this->getName()
. mt_rand(1,1000000)
• but it is safe enough );
(for now) $this->setValue($this->_hash);
}

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  31


Part VI
Session Management Security

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  32


Session Management Configuration

• Configuration has big influence on security


• to safeguard SSL applications set the secure flag
• use an own session id for each application
• harden the session cookie against XSS with the httpOnly flag
• define the maximal lifetime
<?php
Zend_Session::setOptions(array(
/* SSL server */ 'cookie_secure' => true,
/* own name */ 'name' => 'mySSL',
/* own storage */ 'save_path' => '/sessions/mySSL',
/* XSS hardening */ 'cookie_httponly' => true,
/* short lifetime */ 'gc_maxlifetime' => 15 * 60
));
Zend_Session::start();
?>

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  33


Session Fixation and Session Hijacking

• Session Fixation
• is harder in case of session validation / strict session handling
• but is only stopped by regenerating the session id after each
change in status

Zend_Session::regenerateId();

• should be added directly into the login functionality


• Session Hijacking
• there is only one real protection - SSL
• httpOnly cookies protect against session id theft by XSS
• session validation only of limited use

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  34


Session Validation (I)

• recognizes a valid session by checking certain additional


information stored in the session

• often recommended as protection against session fixation/hijacking


- but doesn‘t make much sense

• Zend-Framework supports session validators to validate sessions


• Zend_Session_Validator_HttpUserAgent

<?php
try {
Zend_Session::start();
} catch (Zend_Session_Exception $e) {
Zend_Session::destroy();
Zend_Session::start();
Zend_Session::regenerateId();
}
Zend_Session::registerValidator(new Zend_Session_Validator_HttpUserAgent());
?>

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  35


Session Validation (II)

• Attention checking additional information can cause trouble


• User-agent HTTP header checking is dead since Internet Explorer 8
• Accept HTTP header checks have always been a problem with
Microsoft Internet Explorer

• Checking the client‘s IP address is a problem when big proxy farms


are used (big companies/ISPs)

➡ possible to limit to class C/B/A networks


➡ but useful for SSL applications

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  36


Session Validation - Validating Client‘s IP Address
<?php
class Zend_Session_Validator_RemoteAddress extends Zend_Session_Validator_Abstract
{

/**
* Setup() - this method will get the client's remote address and store
* it in the session as 'valid data'
*
* @return void
*/
public function setup()
{
$this->setValidData( (isset($_SERVER['REMOTE_ADDR'])
? $_SERVER['REMOTE_ADDR'] : null) );
}

/**
* Validate() - this method will determine if the client's remote addr
* matches the remote address we stored when we initialized this variable.
*
* @return bool
*/
public function validate()
{
$currentBrowser = (isset($_SERVER['REMOTE_ADDR'])
? $_SERVER['REMOTE_ADDR'] : null);

return $currentBrowser === $this->getValidData();


}

}
?>

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  37


Part VII
Cross Site Scripting (XSS) Protection

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  38


XSS in Zend-Framework Applications

• Symfony supports automatic output escaping


• Zend-Framework doesn‘t support such automagic
• preventing XSS is job of the programmer
• XSS occurs in the „view“ part

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"


"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><?php echo $this->title; ?></title>
</head>
<body>
<h2><?php echo $this->headline; ?></h2>
<ul>
<li><a href="<?php echo $this->link; ?>">Link 1</a></li>
</ul>
</body>
</html>

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  39


Protecting against XSS (I)

• Two alternative traditional protections


1. Encoding before echoing

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"


"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><?php echo $this->escape($this->title); ?></title>
</head>
<body>
<h2><?php echo $this->escape($this->headline); ?></h2>
<ul>
<li><a href="<?php echo urlprepare($this->link); ?>">Link 1</a></li>
</ul>
</body>
</html>

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  40


Protecting against XSS (II)

• Two alternative traditional protections


2. Encoding when assigning template variables

$entityFilter = new Zend_Filter_HtmlEntities();


$urlFilter = new My_Filter_Url();

$this->view->title = $this->escape("Page 1");


$this->view->headline = $entitiyFilter->filter($this->getRequest()->getPost('link'));
$this->view->link = $urlFilter->filter($this->getRequest()->getPost('link'));

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  41


Protecting with Zend_View_Helper

• preventing XSS is error prone - one XSS for every forgotten encoding
• automatically scanning for forgotten escaping is hard
• directly echoing variables should be forbidden (e.g. with Bytekit + pre-commit-hook)
• output only via Zend_View_Helper
• preventing XSS becomes job of Zend_View_Helper

<form action="action.php" method="post">


<p><label>Your Email:
<?php echo $this->formText('email', 'you@example.com', array('size' => 32)) ?>
</label></p>
<p><label>Your Country:
<?php echo $this->formSelect('country', 'us', null, $this->countries) ?>
</label></p>
<p><label>Would you like to opt in?
<?php echo $this->formCheckbox('opt_in', 'yes', null, array('yes', 'no')) ?>
</label></p>
</form>

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  42


Automatic Escaping by deriving Zend_View

• all output goes through Zend_View


• deriving Zend_View allows automatic encoding
• e.g. by overloading __set() and __get()
• Attention: Encoding must be context sensitive (e.g.: javascript: Links)

public function __get($key)


{
if (isset($this->_params[$key])) {
return($this->escape($this->_params[$key]));
}
return null;
}

public function __set($key, $val)


{
$this->_params[$key] = $val;
}

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  43


Thank you for listening...

Questions ?
http://www.sektioneins.de

Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  44

Você também pode gostar