Symfony 1.0, 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 htmlentities() 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 las siguientes secciones se describen estas opciones en detalle, pero básicamente lo único necesario para activar el mecanismo de escape es establecer para la opción escaping_strategy el valor both en vez de su valor por defecto bc, tal y como muestra el listado 7-44.

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

all:
  .settings:
    escaping_strategy: both
    escaping_method:   ESC_ENTITIES

Esta configuración aplica la función htmlentities() 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;
=> &gt;&lt;script&gt;alert(document.cookie)&lt;/script&gt;

Si se activa el mecanismo de escape, desde cualquier plantilla se puede acceder a una nueva variable llamada $sf_data. Se trata de un objeto contenedor que hace referencia a todas las variables que se han modificado mediante el sistema de escape. De esta forma, también es posible mostrar el contenido de la variable prueba de la siguiente manera:

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

Truco 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().

Mediante este objeto también se puede acceder 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.

7.5.2. Opción escaping_strategy

La opción escaping_strategy determina la forma en la que se muestra el contenido de las variables en las plantillas. Sus posibles valores son los siguientes:

  • bc backward compatible mode o modo retrocompatible): el contenido de las variables no se modifica, pero el contenedor $sf_data almacena una versión modificada de cada variable. De esta forma, los datos de las variables se obtienen en crudo, a menos que se obtenga la versión modificada del objeto $sf_data. Se trata del valor por defecto de la opción, aunque se trata del modo que permite los ataques de tipo XSS.
  • both: a todas las variables se les aplica el mecanismo de escape. Los valores también están disponibles en el contenedor $sf_data. Se trata del valor recomendado, ya que solamente se está expuesto al riesgo si se utilizan de forma explícita los datos originales. En ocasiones, se deben utilizar los valores originales, por ejemplo para incluir código HTML de forma que se interprete en el navegador y no se muestre el código HTML. Si una aplicación se encuentra medio desarrollada y se cambia la estrategia del mecanismo de escape a este valor, algunas funcionalidades pueden dejar de funcionar como se espera. Lo mejor es seleccionar esta opción desde el principio.
  • on: los valores solamente están disponibles en el contenedor $sf_data. Se trata del método más seguro y más rapido de manejar el mecanismo de escape, ya que cada vez que se quiere mostrar el contenido de una variable, se debe elegir el método get() para los datos modificados o el método getRaw() para el contenido original. De esta forma, siempre se recuerda la posibilidad de que los datos de la variable sean corruptos.
  • off: deshabilita el mecanismo de escape. Las plantillas no pueden hacer uso del contenedor $sf_data. Si nunca se va a necesitar el sistema de escape, es mejor utilizar esta opción y no la opción por defecto bc, ya que la aplicación se ejecuta más rápidamente.

7.5.3. 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_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.4. 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 both, el listado 7-45 muesta el mecanismo de escape aplicado en cascada.

Listado 7-45 - El sistema 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.