Secure Programming with the Zend-Framework

excitingwonderlakeInternet and Web Development

Dec 13, 2013 (3 years and 8 months ago)

109 views

Secure Programming with the
Zend-Framework
Stefan Esser <
stefan.esser@sektioneins.de
>
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 I
Introduction
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 II
Authentication
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 III
Input 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'
,
'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 • 
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 IV
SQL 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 V
Cross 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
<?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 • 
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 VI
Session 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
<?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 • 
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
<?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 • 
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
<?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 • 
Part VII
Cross 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'
,
'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 • 
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