Symfony 1.2, la guía definitiva

7.5. Mecanismo de escape

Cuando se insertan datos generados dinámicamente en una plantilla, se debe asegurar la integridad de los datos. Por ejemplo, si se utilizan datos obtenidos mediante formularios que pueden rellenar usuarios anónimos, existe un gran riesgo de que los contenidos puedan incluir scripts y otros elementos maliciosos que se encargan de realizar ataques de tipo XSS cross-site scripting). Por tanto, se debe aplicar un mecanismo de escape a todos los datos mostrados, de forma que ninguna etiqueta HTML pueda ser peligrosa.

Imagina por ejemplo que un usuario rellena un campo de formulario con el siguiente valor:

<script>alert(document.cookie)</script>

Si se muestran directamente los datos, el navegador ejecuta el código JavaScript introducido por el usuario, que puede llegar a ser mucho más peligroso que el ejemplo anterior que simplemente muestra un mensaje. Por este motivo, se deben aplicar mecanismos de escape a los valores introducidos antes de mostrarlos, para que se transformen en algo como:

&lt;script&gt;alert(document.cookie)&lt;/script&gt;

Los datos se pueden escapar manualmente utilizando la función htmlspecialchars() de PHP, pero es un método demasiado repetitivo y muy propenso a cometer errores. En su lugar, Symfony incluye un sistema conocido como mecanismo de escape de los datos que se aplica a todos los datos mostrados mediante las variables de las plantillas. El mecanismo se activa mediante un único parámetro en el archivo settings.yml de la aplicación.

7.5.1. Activar el mecanismo de escape

El mecanismo de escape de datos se configura de forma global para toda la aplicación en el archivo settings.yml. El sistema de escape se controla con 2 parámetros: la estrategia (escaping_strategy) define la forma en la que las variables están disponibles en la vista y el método (escaping_method) indica la función que se aplica a los datos.

En principio, lo único necesario para activar el mecanismo de escape es establecer para la opción escaping_strategy el valor on en vez de su valor por defecto off, tal y como muestra el listado 7-44.

Listado 7-44 - Activar el mecanismo de escape, en frontend/config/settings.yml

all:
  .settings:
    escaping_strategy: on
    escaping_method:   ESC_SPECIALCHARS

Esta configuración aplica la función htmlspecialchars() a los datos de todas las variables mostradas. Si se define una variable llamada prueba en la acción con el siguiente contenido:

$this->prueba = '<script>alert(document.cookie)</script>';

Con el sistema de escape activado, al mostrar esta variable en una plantilla, se mostrarán los siguientes datos:

echo $prueba;
=> &lt;script&gt;alert(document.cookie)&lt;/script&gt;

Además, todas las plantillas tienen acceso a una variable llamada $sf_data, que es un objeto contenedor que hace referencia a todas las variables a las que se les ha aplicado el mecanismo de escape. Por lo tanto, se puede acceder al valor de la variable prueba mediante la siguiente instrucción:

echo $sf_data->get('prueba');
=> &lt;script&gt;alert(document.cookie)&lt;/script&gt;

Nota El objeto $sf_data implementa la interfaz Array, por lo que en vez de utilizar la sintaxis$sf_data->get('mivariable'), se puede obtener la variable mediante $sf_data['mivariable']. Sin embargo, no se trata realmente de un array, por lo que no se pueden utilizar funciones como por ejemplo print_r().

La variable $sf_data también da acceso a los datos originales o datos en crudo de la variable. Se trata de una opción muy útil por ejemplo cuando la variable contiene código HTML que se quiere incluir directamente en el navegador para que sea interpretado en vez de mostrado (solo se debería utilizar esta opción si se confía plenamente en el contenido de esa variable). Para acceder a los datos originales se puede utilizar el método getRaw().

echo $sf_data->getRaw('prueba');
=> <script>alert(document.cookie)</script>

Si una variable almacena código HTML, cada vez que se necesita el código HTML original, es necesario acceder a sus datos originales, de forma que el código HTML se interprete y no se muestre en el navegador. Por este motivo el layout por defecto utiliza la instrucción $sf_data->getRaw('sf_content') para incluir el contenido de la plantilla, en vez de utilizar directamente el método $sf_content, que provocaría resultados no deseados cuando se activa el mecanismo de escape.

Cuando el valor de la opción escaping_strategy es off, la variable $sf_data también está disponible, pero en este caso siempre devuelve los datos originales de las variables.

Nota La versión 1.0 de Symfony define otros dos valores para la opción escaping_strategy. El valor bc se convierte en el valor off, mientras que el valor both se convierte en on. Si utilizas cualquiera de esos valores, la aplicación no deja de funcionar, pero en los archivos de log se muestra un mensaje de error.

7.5.2. Los helpers útiles para el mecanismo de escape

Los helpers utilizados en el mecanismo de escape son funciones que devuelven el valor modificado correspondiente al valor que se les pasa. Se pueden utilizar como valor de la opción escaping_method en el archivo settings.yml o para especificar un método concreto de escape para los datos de una vista. Los helpers disponibles son los siguientes:

  • ESC_RAW: no modifica el valor original.
  • ESC_SPECIALCHARS: aplica la función htmlspecialchars() de PHP al valor que se le pasa.
  • ESC_ENTITIES: aplica la función htmlentities() de PHP al valor que se le pasa y utiliza la opción ENT_QUOTES para el estilo de las comillas.
  • ESC_JS: modifica un valor que corresponde a una cadena de JavaScript que va a ser utilizada como HTML. Se trata de una opción muy útil para escapar valores cuando se emplea JavaScript para modificar de forma dinámica el contenido HTML de la página.
  • ESC_JS_NO_ENTITIES: modifica un valor que va a ser utilizado en una cadena de JavaScript pero no le añade las entidades HTML correspondientes. Se trata de una opción muy útil para los valores que se van a mostrar en los cuadros de diálogo (por ejemplo para una variable llamada miCadena en la instrucción javascript:alert(miCadena);).

7.5.3. Aplicando el mecanismo de escape a los arrays y los objetos

No solo las cadenas de caracteres pueden hacer uso del mecanismo de escape, sino que también se puede aplicar a los arrays y los objetos. El mecanismo de escape se aplica en cascada a todos los arrays u objetos. Si la estrategia empleada es on, el listado 7-45 muesta el mecanismo de escape aplicado en cascada.

Listado 7-45 - El mecanismo de escape se puede aplicar a los arrays y los objetos

// Definición de la clase
class miClase
{
  public function pruebaCaracterEspecial($valor = '')
  {
    return '<'.$valor.'>';
  }
}

// En la acción
$this->array_prueba = array('&', '<', '>');
$this->array_de_arrays = array(array('&'));
$this->objeto_prueba = new miClase();

// En la plantilla
<?php foreach($array_prueba as $valor): ?>
  <?php echo $valor ?>
<?php endforeach; ?>
 => &amp; &lt; &gt;
<?php echo $array_de_arrays[0][0] ?>
 => &amp;
<?php echo $objeto_prueba->pruebaCaracterEspecial('&') ?>
 => &lt;&amp;&gt;

De hecho, el tipo de las variables en la plantilla no es el tipo que le correspondería a la variable original. El mecanismo de escape "decora las variables y las transforma en objetos especiales:

<?php echo get_class($array_prueba) ?>
=> sfOutputEscaperArrayDecorator
<?php echo get_class($objeto_prueba) ?>
=> sfOutputEscaperObjectDecorator

Esta es la razón por la que algunas funciones PHP habituales (como array_shift(), print_r(), etc.) no funcionan en los arrays a los que se ha aplicado el mecanismo de escape. No obstante, se puede seguir accediendo mediante [], se pueden recorrer con foreach y proporcionan el dato correcto al utilizar la función count() (aunque count() solo funciona con la versión 5.2 o posterior de PHP). Como en las plantillas los datos (casi) siempre se acceden en modo solo lectura, la mayor parte de las veces se accede a los datos mediante los métodos que sí funcionan.

De todas formas, todavía es posible acceder a los datos originales mediante el objeto $sf_data. Además, los métodos de los objetos a los que se aplica el mecanismo de escape se modifican para que acepten un parámetro adicional: el método de escape. Así, se puede utilizar un método de escape diferente cada vez que se accede al valor de una variable en una plantilla, o incluso es posible utilizar el helper ESC_RAW para desactivar el sistema de escape para una variable concreta. El listado 7-46 muestra un ejemplo.

Listado 7-46 - Los métodos de los objetos a los que se aplica el mecanismo de escape aceptan un parámetro adicional

<?php echo $objeto_prueba->pruebaCaracterEspecial('&') ?>
=> &lt;&amp;&gt;
// Las siguientes 3 líneas producen el mismo resultado
<?php echo $objeto_prueba->pruebaCaracterEspecial('&', ESC_RAW) ?>
<?php echo $sf_data->getRaw('objeto_prueba')->pruebaCaracterEspecial('&') ?>
<?php echo $sf_data->get('objeto_prueba', ESC_RAW)->pruebaCaracterEspecial('&') ?>
=> <&>

Si se incluyen muchos objetos en las plantillas, el truco de añadir un parámetro adicional a los métodos se utiliza mucho, ya que es el método más rápido de obtener los datos originales al ejecutar el método.

Advertencia Las variables de Symfony también se modifican al activar el mecanismo de escape. Por tanto, las variables $sf_user, $sf_request, $sf_param y $sf_context siguen funcionando, pero sus métodos devuelven sus datos modificados, a no ser que se utilice la opción ESC_RAW como último argumento de las llamadas a los métodos.

Truco Aunque los ataques de tipo XSS son una de las amenazas más habituales de los sitios web, no son la única. Los ataques CSRF también son muy populares, por lo que a partir de la versión 1.1 de Symfony también se ha incluido un mecanismo de protección contra los ataques CSRF. El capítulo 6 explica con más detalle el nuevo filtro CSRF.