secure programming with the zend framework
DESCRIPTION
Stefan Esser's "Secure Programming with the Zend Framework" slides from Dutch PHP Conference 2009TRANSCRIPT
Secure Programming with the Zend-FrameworkStefan Esser <[email protected]>
June 2009 - Amsterdam
http://www.sektioneins.de
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
2
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
Part IIntroduction
3
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
4
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
Topics
• Authentication
• Input Validation and Input Filtering
• SQL Security
• Cross Site Request Forgery (CSRF) Protection
• Session Management Security
• Cross Site Scripting (XSS) Protection
5
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
Part IIAuthentication
6
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
Classic Applications vs. Zend-Framework
• Zend-Framework applications usually use a MVC design with dispatcher
• Classic applications usually use neither a MVC design, nor a dispatcher
• without dispatcher every reachable script must implement or embed authentication
• classic approach is error-prone
• often scripts exists that forget to implement the authentication
7
Controller
View Model
Dispatcher
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
8
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 •
Part IIIInput Validation and Input Filtering
9
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
10
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
11
$message = $this->getRequest()->message;
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
12
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
13
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
14
<?php$email = $this->getRequest()->getPost('email', '[email protected]');
$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 •
Chaining Validators
• for complex validations own validators can be implemented
• it is however possible to combine validators in validator chains
15
<?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 •
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
16
<?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 •
Chaining Filters
• for complex filtering own filters can be implemented
• it is however possible to combine filters in filter chains
17
<?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 •
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
18
// 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 •
Inputvalidation/-filtering in Forms (II)
• Form is validated in the action handler
19
// 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 •
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
20
$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 •
Part IVSQL Security
21
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
22
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
Databaseaccess in Zend-Framewok Applications
➡Zend-Framework offers different APIs for handling queries
• Zend_Db
• Zend_Db_Statement
• Zend_Db_Select
• Zend_Db_Table
23
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
24
<?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 •
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
25
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
26
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
27
// 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 •
Part VCross Site Request Forgery (CSRF) Protection
28
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
29
$form->addElement('hash', 'csrf_token', array('salt' => 's3cr3ts4ltG%Ek@on9!'));
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
30
<?phpclass 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 •
Token Algorithm
• Token algorithm could be improved
• avoid mt_rand()
• more entropy
• but it is safe enough (for now)
31
/** * 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 •
Part VISession Management Security
32
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
33
<?phpZend_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 •
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
34
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
35
<?phptry { 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 •
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
36
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
Session Validation - Validating Client‘s IP Address
37
<?phpclass 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 •
Part VIICross Site Scripting (XSS) Protection
38
Stefan Esser • Secure Programming with the Zend-Framework • June 2009 •
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
39
<!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 •
Protecting against XSS (I)
• Two alternative traditional protections
1. Encoding before echoing
40
<!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 •
Protecting against XSS (II)
• Two alternative traditional protections
2. Encoding when assigning template variables
41
$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 •
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
42
<form action="action.php" method="post"> <p><label>Your Email:<?php echo $this->formText('email', '[email protected]', 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 •
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)
43
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 •
Thank you for listening...
Questions ?http://www.sektioneins.de
44