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
must implement or embed authentication

Controller

• 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 4. $_COOKIE 5. $_SERVER 6. $_ENV $message = $this->getRequest()->message;

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
improved

• avoid mt_rand() • more entropy

• but it is safe enough
(for now)

/** * Generate CSRF token * */ protected function _generateHash() { $this->_hash = md5( mt_rand(1,1000000) . $this->getSalt() . $this->getName() . mt_rand(1,1000000) ); $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' /* own name */ 'name' /* own storage */ 'save_path' /* XSS hardening */ 'cookie_httponly' /* short lifetime */ 'gc_maxlifetime' )); Zend_Session::start(); ?> => => => => => true, 'mySSL', '/sessions/mySSL', true, 15 * 60

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); } } ?>
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •  37

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

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

Sign up to vote on this title
UsefulNot useful