El tutorial Jobeet

11.9. Seguridad de los formularios

11.9.1. La magia de la serialización de formularios

Los formularios de Propel son muy fáciles de utilizar porque realizan automáticamente la mayor parte del trabajo. Si quieres serializar o guardar un formulario en la base de datos, lo único que tienes que hacer es realizar una llamada al método $form->save().

¿Cómo funciona este método? Básicamente, el método save() realiza los siguientes pasos:

  • Iniciar una transacción (porque todos los formularios de Propel anidados se guardan de una vez)
  • Procesar los valores enviados (ejecutando los métodos update_NOMBRE_COLUMNA_Column() si existen)
  • Invocar el método fromArray() del objeto Propel para actualizar el valor de las columnas
  • Guardar el objeto en la base de datos
  • Realizar la transacción

11.9.2. Características de seguridad incluidas por defecto

El método fromArray() toma un array de valores y actualiza los valores de las columnas correspondientes. ¿No es esto un posible agujero de seguridad? ¿Y si alguien trata de enviar el valor de una columna para la que no tiene autorización? ¿Podría por ejemplo modifica el valor de la columna token?

Vamos a escribir una prueba para simular el envío de una oferta de trabajo con un campo llamado token:

// test/functional/frontend/jobActionsTest.php
$browser->
  get('/job/new')->
  click('Preview your job', array('job' => array(
    'token' => 'fake_token',
  )))->

  with('form')->begin()->
    hasErrors(7)->
    hasGlobalError('extra_fields')->
  end()
;

Si envías el formulario anterior te encontrarás con un error global de tipo extra_fields. El motivo es que por defecto los formularios no permiten incluir campos adicionales en los valores enviados. Este también es el motivo por el que todos los campos del formulario deben contar con un validador asociado.

Nota También puedes probar a enviar campos adicionales directamente desde el navegador gracias a herramientas como la extensión Web Developer Toolbar de Firefox.

Si quieres deshabilitar esta medida de seguridad, modifica el valor de la opción allow_extra_fields a true:

class MyForm extends sfForm
{
  public function configure()
  {
    // ...

    $this->validatorSchema->setOption('allow_extra_fields', true);
  }
}

La prueba ahora sí que pasa satisfactoriamente, pero el valor del campo token se ha eliminado de los valores del campo. Así que todavía no es posible saltarse esta medida de seguridad. No obstante, si realmente quieres pasar ese valor, puedes establecer la opción filter_extra_fields a false:

$this->validatorSchema->setOption('filter_extra_fields', false);

Nota Las pruebas creadas en esta sección son sólo para mostrar algunas de las opciones disponibles en el framework. Deberías borrarlas del proyecto Jobeet porque las pruebas no deben validar opciones de Symfony.

11.9.3. Protección frente a ataques XSS y CSRF

Durante el primer día creamos la aplicación frontend con el siguiente comando:

$ php symfony generate:app --escaping-strategy=on --csrf-secret=Unique$ecret frontend

La opción --escaping-strategy activa la protección frente a ataques de tipo XSS. Esto significa que por defecto las plantillas aplican el mecanismo de escape a los valores de todas las variables. Si tratas por ejemplo de incluir código HTML en la descripción de una oferta de trabajo, verás que cuando Symfony muestra los detalles de la oferta, las etiquetas se ven tal y como están escritas y no se interpretan como etiquetas HTML.

Por su parte, la opción --csrf-secret activa la protección frente a ataques de tipo CSRF. Si activas esta opción, todos los formularios incluyen un campo oculto llamado _csrf_token.

Nota El tipo de mecanismo de escape que se aplica y el secreto de CSRF que se utiliza se pueden modificar en cualquier momento en el archivo de configuración apps/frontend/config/settings.yml. Al igual que sucede con el archivo databases.yml, las opciones se pueden configurar para cada entorno de ejecución:

all:
  .settings:
    # Form security secret (CSRF protection)
    csrf_secret: Unique$ecret

     # Output escaping settings
    escaping_strategy: on
    escaping_method:   ESC_SPECIALCHARS