sanitizing

Sanitizando: cómo validar y escapar datos en WordPress

En éste artículo vamos a aprender a hacer un tratamiento de datos correcto en WordPress. Éste punto es imprescindible para cualquier desarrollo a medida que hagas, ya sea un tema, un plugin, una página de ajustes y/o configuraciones, o un formulario en el que se envían y guardan datos.

Éstas prácticas son imprescindibles para fortalecer la seguridad de nuestro desarrollo. Si tenemos un formulario con un input y hacemos submit, recibiremos el valor de ese input por $_GET o $_POST y probablemente lo guardemos en base de datos. Veremos cómo proteger posibles inyecciones de código no deseado a través de un ejemplo en el que detallaré diferentes tipos de sanitización para diferentes tipos de datos.

El ejemplo: Un formulario con los campos nombre *, e-mail *, dirección y código postal. Vamos a dividirlo en 3 fases: validación del dato enviado, guardado en base de datos, y obtención del dato para mostrarlo.

Validando los datos

Lo primero es verificar que el usuario ha introducido los datos correctamente, es decir, comprobaremos que estamos recibiendo lo que esperamos recibir. En éste ejemplo el campo Nombre es obligatorio, por lo tanto comprobaremos que no está vacío. En el caso del campo e-mail que también es obligatorio, verificaremos que no esté vacío y que realmente el usuario ha introducido una cadena de texto con formato de e-mail y respecto al código postal verificaremos que hemos recibido una cadena númerica de 5 cifras.

La validación de los datos es el primer paso a realizar en el procesado de cualquier formulario, de este modo podremos avisar al usuario de que hemos recibido algún dato que no tiene el formato correcto o no cumple con lo que esperábamos recibir, mejorando la experiencia de usuario y evitando descuageringar nuestra base de datos.

¿Cómo hacemos esto? Pues hay varias formas. Resumiendo, podemos hacer esta validación desde el lado del cliente antes de enviar el formulario, o desde el lado del servidor una vez enviado, aunque lo ideal es hacerlo de las dos formas.

Desde el lado del cliente al input e-mail le asignaremos el type=”email” y al código postal le estableceremos una longitud máxima de 5 caracteres. Además, los campos Nombre e E-mail son obligatorios, por lo tanto añadiremos el atributo required a estos inputs. Nuestro formulario quedaría así:

<form name="my-form" action="" method="post">
  <div class="form-group">
    <label for="name">Nombre *</label>
    <input type="text" name="name" placeholder="Introduce tu nombre" required aria-required="true">
  </div>

  <div class="form-group">
    <label for="email">E-mail *</label>
    <input type="email" name="email" placeholder="Introduce tu e-mail" required aria-required="true">
  </div>

  <div class="form-group">
    <label for="address">Dirección</label>
    <input type="text" name="address" placeholder="Introduce tu dirección">
  </div>

  <div class="form-group">
    <label for="postalcode">Código Postal</label>
    <input type="text" name="postalcode" maxlength="5" placeholder="Introduce tu Código Postal">
  </div>

  <button type="submit" name="btn-submit">Enviar</button>
</form>

NOTA

Si queremos dar soporte a navegadores obsoletos o que no soportan HTML5 podremos utilizar librerías de validación tipo jQueryValidate

Lo siguiente es hacer la comprobación desde el lado del servidor, es decir, con PHP y usando funciones de WordPress así como la clase WP_Error, para avisar al usuario y no continuar con el procesado de los datos en caso de encontrar algo que no coincida con lo que esperábamos recibir:

<?php

if (isset( $_POST['btn-submit'] )) { //El formulario ha sido enviado
  global $reg_errors;
  $reg_errors = new WP_Error;

  //Comprobamos que los campos obligatorios no están vacios
  if ( empty( $_POST['name'] ) ) {
    $reg_errors->add("empty-name", "El campo nombre es obligatorio");
  }
  if ( empty( $_POST['email'] ) ) {
    $reg_errors->add("empty-email", "El campo e-mail es obligatorio");
  }

  //Comprobamos que el email tenga un formato de email válido
  if ( !is_email( $_POST['email'] ) ) {
    $reg_errors->add( "invalid-email", "El e-mail no tiene un formato válido" );
  }

  //Comprobamos que código postal tenga una longitud de 5 caracteres
  if ( strlen( $_POST['postalcode'] ) != 5 ) {
    $reg_errors->add( "invalid-postalcode", "El Código Postal debe tener 5 caracteres" );
  }

  if ( is_wp_error( $reg_errors ) ) {
    if (count($reg_errors->get_error_messages()) > 0) {
      foreach ( $reg_errors->get_error_messages() as $error ) {?>
        <p><?php echo $error;?></p>
      <?php }
    }
  }
}

Para validar e-mails podemos usar la función de WordPress is_mail(), que devolverá true en caso de que la cadena tenga formato de e-mail, false en caso contrario. Podríamos validar cualquier tipo de campo mediante el uso de expresiones regulares, aquí algunos ejemplos:

<?php

//Comprobar si tiene formato de usuario de twitter
if (!preg_match('/^[A-Za-z0-9_]{1,15}$/', $_POST['twitter_user'])) {
  $reg_errors->add( "invalid-twitter-user", "El usuario de twitter no tiene un formato válido" );
}

//Comprobar si tiene formato de usuario de facebook
if (!preg_match('/^[a-z\d\.]{5,}$/', $_POST['facebook_user'])) {
  $reg_errors->add( "invalid-facebook-user", "El usuario de facebook no tiene un formato válido" );
}

//Comprobar que una contraseña tenga al menos una mayúscula, una minúscula, un número o carácter especial y mínimo 8 caracteres
if (!preg_match('/(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/', $_POST['password'])) {
  $reg_errors->add( "invalid-password", "La contraseña requiere al menos una mayúscula, una minúscula, un número o carácter especial y mínimo 8 caracteres" );
}

//Comprobar ISBN
if (!preg_match('/(?:(?=.{17}$)97[89][ -](?:[0-9]+[ -]){2}[0-9]+[ -][0-9]|97[89][0-9]{10}|(?=.{13}$)(?:[0-9]+[ -]){2}[0-9]+[ -][0-9Xx]|[0-9]{9}[0-9Xx])/', $_POST['isbn'])) {
  $reg_errors->add( "invalid-isbn", "El ISBN introducido no tiene un formato válido" );
}

Guardado en base de datos

Una vez procesado el formulario tanto desde el lado del cliente como del lado de servidor, guardamos los datos en base de datos.

En el caso del campo nombre, hemos dispuesto solamente que sea obligatorio, pero la persona que rellena el formulario puede escribir una “a” y sería perfectamente válido ya que no está vacío. En este campo una persona podría introducir caracteres extraños o intentar hacer una inyección de código malicioso y pasaría nuestra validación.

Antes de guardar los datos, debemos sanitizarlos para limpiar posibles ataques, usando las funciones de limpieza de datos que nos provee WordPress. Estas funciones chequean que los caracteres estén codificados sobre UTF-8, convierten el carácter “<” en entidad HTML y eliminan etiquetas, líneas rotas, tabulaciones y espacios en blanco sobrantes:

  • sanitize_email()
  • sanitize_file_name()
  • sanitize_html_class()
  • sanitize_key()
  • sanitize_meta()
  • sanitize_mime_type()
  • sanitize_option()
  • sanitize_sql_orderby()
  • sanitize_text_field()
  • sanitize_title()
  • sanitize_title_for_query()
  • sanitize_title_with_dashes()
  • sanitize_user()

Para nuestro ejemplo usaremos sanitize_email() y sanitize_text_field(). Suponiendo que nuestro ejemplo actualice los datos de un CPT:

update_post_meta( $post_id, 'user_name', sanitize_text_field($_POST["name"]));
update_post_meta( $post_id, 'user_email', sanitize_email($_POST["email"]));
update_post_meta( $post_id, 'user_address', sanitize_text_field($_POST["address"]));
update_post_meta( $post_id, 'user_postalcode', sanitize_text_field($_POST["postalcode"]));

Mostrando el dato

Para mostrar el dato, WordPress también nos ofrece una serie de funciones de seguridad, escapando lo que tenemos guardado en base de datos:

  • esc_html()
  • esc_url()
  • esc_js()
  • esc_attr()
  • esc_textarea()

Cuando queramos mostrar un dato en cualquiera de nuestras plantillas:

echo esc_attr(get_post_meta( $post_id, 'user_name', true ));
echo esc_attr(get_post_meta( $post_id, 'user_email', true ));
echo esc_attr(get_post_meta( $post_id, 'user_address', true ));
echo esc_attr(get_post_meta( $post_id, 'user_postalcode', true ));

Puedes tener un formulario para la recogida de datos y obviar estas recomendaciones…y funcionará, pero es inseguro y deja una puerta abierta a posibles ataques. Poner en práctica estas recomendaciones lleva más tiempo y aumentan el presupuesto final, pero mejorarás la experiencia de usuario y la seguridad de tu sitio.

¿Te ha resultado útil esta información? 🍺

Si este post te ha resuelto un problema, invítame a un café o a una cerveza. Con este pequeño gesto me animas a seguir escribiendo.

Comentarios

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *:

  • El fin del tratamiento es únicamente la moderación de comentarios para evitar spam
  • La legitimación es tu consentimiento al comentar
  • No se comunicará ningún dato a terceros salvo por obligación legal
  • Tienes derecho al acceso, rectificación y eliminación de los comentarios