When you insert dynamic data in a template, you must be sure about the data integrity. For instance, if data comes from forms filled in by anonymous users, there is a risk that it may include malicious scripts intended to launch cross-site scripting (XSS) attacks. You must be able to escape the output data, so that any HTML tag it contains becomes harmless.
As an example, suppose that a user fills an input field with the following value:
<script>alert(document.cookie)</script>
If you echo this value without caution, the JavaScript will execute on every browser and allow for much more dangerous attacks than just displaying an alert. This is why you must escape the value before displaying it, so that it becomes something like this:
<script>alert(document.cookie)</script>
You could escape your output manually by enclosing every unsure value in a call to htmlspecialchars()
, but that approach would be very repetitive and error-prone. Instead, symfony provides a special system, called output escaping, which automatically escapes every variable output in a template. It is activated by a simple parameter in the application settings.yml
.
7.2.1. Activating Output Escaping
Output escaping is configured globally for an application in the settings.yml
file. Two parameters control the way that output escaping works: the strategy determines how the variables are made available to the view, and the method is the default escaping function applied to the data.
Basically, all you need to do to activate output escaping is to set the escaping_strategy
parameter to on
instead of its default value off
, as shown in Listing 7-44.
Listing 7-44 - Activating Output Escaping, in frontend/config/settings.yml
all:
.settings:
escaping_strategy: on
escaping_method: ESC_SPECIALCHARS
This will add htmlspecialchars()
to all variable output by default. For instance, suppose that you define a test
variable in an action as follows:
$this->test = '<script>alert(document.cookie)</script>';
With output escaping turned on, echoing this variable in the template will output the escaped data:
echo $test;
=> <script>alert(document.cookie)</script>
In addition, every template has access to an $sf_data
variable, which is a container object referencing all the escaped variables. So you can also output the test variable with the following:
echo $sf_data->get('test');
=> <script>alert(document.cookie)</script>
Tip The $sf_data object implements the Array interface, so instead of using the $sf_data->get('myvariable')
, you can retrieve escaped values by calling $sf_data['myvariable']
. But it is not a real array, so functions like print_r()
will not work as expected.
$sf_data
also gives you access to the unescaped, or raw, data. This is useful when a variable stores HTML code meant to be interpreted by the browser, provided that you trust this variable. Call the getRaw()
method when you need to output the raw data.
echo $sf_data->getRaw('test');
=> <script>alert(document.cookie)</script>
You will have to access raw data each time you need variables containing HTML to be really interpreted as HTML. You can now understand why the default layout uses $sf_data->getRaw('sf_content')
to include the template, rather than a simpler $sf_content
, which breaks when output escaping is activated.
When escaping_strategy
is off
, $sf_data
is still available, but it always returns raw data.
Tip Symfony 1.0 had two other possible values for escaping_strategy
. bc
now fallbacks to off
, and both
now fallbacks to on
. Using any of these values still work, but will log an error.
7.2.2. Escaping Helpers
Escaping helpers are functions returning an escaped version of their input. They can be provided as a default escaping_method
in the settings.yml
file or to specify an escaping method for a specific value in the view. The following escaping helpers are available:
ESC_RAW
: Doesn't escape the value.ESC_SPECIALCHARS
: Applies the PHP functionhtmlspecialchars()
to the input.ESC_ENTITIES
: Applies the PHP functionhtmlentities()
to the input withENT_QUOTES
as the quote style.ESC_JS
: Escapes a value to be put into a JavaScript string that is going to be used as HTML. This is useful for escaping things where HTML is going to be dynamically changed using JavaScript.ESC_JS_NO_ENTITIES
: Escapes a value to be put into a JavaScript string but does not add entities. This is useful if the value is going to be displayed using a dialog box (for example, for amyString
variable used injavascript:alert(myString);
).
7.2.3. Escaping Arrays and Objects
Output escaping not only works for strings, but also for arrays and objects. Any values that are objects or arrays will pass on their escaped state to their children. Assuming your strategy is set to on
, Listing 7-45 demonstrates the escaping cascade.
Listing 7-45 - Escaping Also Works for Arrays and Objects
// Class definition
class myClass
{
public function testSpecialChars($value = '')
{
return '<'.$value.'>';
}
}
// In the action
$this->test_array = array('&', '<', '>');
$this->test_array_of_arrays = array(array('&'));
$this->test_object = new myClass();
// In the template
<?php foreach($test_array as $value): ?>
<?php echo $value ?>
<?php endforeach; ?>
=> & < >
<?php echo $test_array_of_arrays[0][0] ?>
=> &
<?php echo $test_object->testSpecialChars('&') ?>
=> <&>
As a matter of fact, the variables in the template are not of the type you might expect. The output escaping system "decorates" them and transforms them into special objects:
<?php echo get_class($test_array) ?>
=> sfOutputEscaperArrayDecorator
<?php echo get_class($test_object) ?>
=> sfOutputEscaperObjectDecorator
This explains why some usual PHP functions (like array_shift()
, print_r()
, and so on) don't work on escaped arrays anymore. But they can be still be accessed using []
, be traversed using foreach
, and they give back the right result with count()
(count()
works only with PHP 5.2 or later). And in templates, the data should be read-only anyway, so most access will be through the methods that do work.
You still have a way to retrieve the raw data through the $sf_data
object. In addition, methods of escaped objects are altered to accept an additional parameter: an escaping method. So you can choose an alternative escaping method each time you display a variable in a template, or opt for the ESC_RAW
helper to deactivate escaping. See Listing 7-46 for an example.
Listing 7-46 - Methods of Escaped Objects Accept an Additional Parameter
<?php echo $test_object->testSpecialChars('&') ?>
=> <&>
// The three following lines return the same value
<?php echo $test_object->testSpecialChars('&', ESC_RAW) ?>
<?php echo $sf_data->getRaw('test_object')->testSpecialChars('&') ?>
<?php echo $sf_data->get('test_object', ESC_RAW)->testSpecialChars('&') ?>
=> <&>
If you deal with objects in your templates, you will use the additional parameter trick a lot, since it is the fastest way to get raw data on a method call.
Caution The usual symfony variables are also escaped when you turn on output escaping. So be aware that $sf_user
, $sf_request
, $sf_param
, and $sf_context
still work, but their methods return escaped data, unless you add ESC_RAW
as a final argument to their method calls.
Tip New in symfony 1.2: Even if XSS is one of the most common exploit of websites, this is not the only one. CSRF is also very popular and symfony provides a simple way to protect your applications. Read the sidebar named "The CSRF Filter" in Chapter 6 for more information.