1. Web Design
  2. HTML/CSS
  3. HTML

Asegure sus Formularios con Formularios Claves

La seguridad es un tema muy polémico. Garantizar que sus sitios están seguros es extremadamente importante para cualquier aplicación web. De hecho, he pasado el 70% de mi tiempo asegurando aplicaciones. Una de las cosas más importantes que tenemos que asegurar son los formularios. Hoy vamos revisar un método para prevenir esto, se llama XSS (Cross-site scripting) y Cross-site instancias de falsificación en formularios.
Scroll to top

Spanish (Español) translation by Rodney Martinez (you can also view the original English article)

La seguridad es un tema muy polémico. Garantizar que sus sitios están seguros es extremadamente importante para cualquier aplicación web. De hecho, he pasado el 70% de mi tiempo asegurando aplicaciones. Una de las cosas más importantes que tenemos que asegurar son los formularios. Hoy vamos revisar un método para prevenir esto, se llama XSS (Cross-site scripting) y Cross-site instancias de falsificación en formularios.

¿Por qué?

Los datos POST pueden ser enviados desde un sitio web a otro. ¿Qué tiene ésto de malo? Veamos un escenario sencillo….

Un usuario se registro en su sitio web, éste visita otro sitio web durante su sesión. Este sitio web podrá enviar datos POST a su sitio web - por ejemplo, con AJAX. Debido a que el usuario está registrado en su sitio web, el otro sitio web podrá enviar datos post hacia formularios asegurados que solamente están accesibles después de iniciar sesión.

Además, debemos proteger nuestras páginas contra ataques usando cURL.

¿Cómo hacemos esto?

¡Con formularios claves! Añadiremos un símbolo # especial (un formulario clave) a cada formulario para garantizar que los datos solamente serán procesados cuando hayan sido enviados desde su sitio web. Después un formulario de envío, nuestro código PHP validará el formulario clave que fue enviado contra el formulario clave que habíamos configurado en la sesión.

Lo Qué Debemos Hacer:

  1. Añadir un formulario clave a cada formulario.
  2. Almacenar el formulario clave en una sesión.
  3. Validar un formulario clave después de enviar un formulario.

Paso 1: Un formulario sencillo.

Login Form

Primero, necesitamos un formulario sencillo para propósitos solamente de demostración. Uno de los formularios más importantes que tenemos que asegurar es el formulario para iniciar sesión. El formulario de acceso es vulnerable a fuertes ataques brutales. Así que vamos a crear un archivo nuevo, y guardarlo como index.php en su web-root. Agregue el siguiente código dentro del cuerpo:

1
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
	<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
3
	<head>
4
		<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
5
		<title>Securing forms with form keys</title>
6
	</head>
7
	<body>
8
		<form action="" method="post">
9
		<dl>
10
			<dt><label for="username">Username:</label></dt>
11
			<dd><input type="text" name="username" id="username" /></dd>
12
			<dt><label for="username">Password:</label></dt>
13
			<dd><input type="password" name="password" id="password" /></dd>
14
			<dt></dt>
15
			<dd><input type="submit" value="Login" /></dd>
16
		</dl>
17
		</form>
18
	</body>
19
	</html>

Ahora tenemos una página XHTML sencilla con un formulario de registro. Si quiere usar formularios clave en su sitio web, usted puede reemplazar el código de arriba con el de su página de acceso. Ahora, vamos a continuar con la acción real.

Paso 2: Crear una Clase

Vamos a crear una clase PHP para nuestro formulario clave. Porque cada página web puede contener solamente un formulario clave, podríamos hacer un semifallo de nuestra clase para asegurar que nuestra clase está usada de forma correcta. Debido al semifalllo que creamos es un tema de Programación Orientada a Objetos más avanzado, omitiremos esa parte. Ahora va a crear un archivo llamado formkey.class.php y lo colocará en su web-root. Ahora tenemos que pensar en las funciones que necesitamos. Primero, necesitamos una función para generar un formulario clave para que podamos colocarlo en nuestro formulario. En su archivo PHP coloque el siguiente código.

1
<?php
2
3
//You can of course choose any name for your class or integrate it in something like a functions or base class

4
class formKey
5
{
6
	//Here we store the generated form key

7
	private $formKey;
8
	
9
	//Here we store the old form key (more info at step 4)

10
	private $old_formKey;
11
	
12
	//Function to generate the form key

13
	private function generateKey()
14
	{
15
		
16
	}
17
}
18
?>

Arriba, usted vio una clase con tres partes: dos variables y una función. Hacemos la función privada porque esta función solamente será usada por nuestras funciones output, las cuales crearemos después. En las dos variables, almacenaremos los formularios claves. Estos, también son privados porque sólo pueden ser utilizados por funciones dentro de nuestra clase.

Ahora, tenemos que pensar en una manera para generar nuestro formulario clave. Puesto que nuestro formulario clave debe ser único (de lo contrario no tenemos algo de seguridad), usamos una combinación de la dirección IP de los usuarios para unir la clave al usuario, mt_rand() para hacerlo único, y la función uniquid() para hacerlo aún más único. Además, encriptamos esta información con el método md5() para crear un hash único que luego, podemos insertarlo en nuestras páginas. Ya que usamos md5(), un usuario no puede ver lo que usamos para generar una clave. Toda la función sería:

1
//Function to generate the form key

2
private function generateKey()
3
{
4
	//Get the IP-address of the user

5
	$ip = $_SERVER['REMOTE_ADDR'];
6
	
7
	//We use mt_rand() instead of rand() because it is better for generating random numbers.

8
	//We use 'true' to get a longer string.

9
	//See http://www.php.net/mt_rand for a precise description of the function and more examples.

10
	$uniqid = uniqid(mt_rand(), true);
11
	
12
	//Return the hash

13
	return md5($ip . $uniqid);
14
}

Inserte el código arriba en su archivo formkey.class.php. Reemplace la función con la función nueva.

Paso 3: Inserte un Formulario Clave en Nuestro Formulario

Para este paso, creamos una función nueva que produce un campo HTML oculto con nuestro formulario clave. La función consiste de tres pasos:

Generate, Save, OutputGenerate, Save, OutputGenerate, Save, Output

  1. Generar un formulario clave con nuestra función generateKey().
  2. Almacenar el formulario clave en nuestra variable $formKey y en una sesión.
  3. Producir el campo HTML.

Llamamos a nuestra función outputKey() y la hicimos publica, porque tenemos que usarla fuera de nuestra clase. Nuestra función invocará a la función generateKey() para generar un formulario clave nuevo y lo guardará en una sesión local. Por último, creamos el código XHTML. Ahora añadimos el siguiente código dentro de nuestra clase PHP:

1
//Function to output the form key

2
public function outputKey()
3
{
4
	//Generate the key and store it inside the class

5
	$this->formKey = $this->generateKey();
6
	//Store the form key in the session

7
	$_SESSION['form_key'] = $this->formKey;
8
	
9
	//Output the form key

10
	echo "<input type='hidden' name='form_key' id='form_key' value='".$this->formKey."' />";
11
}

Ahora, vamos a agregar el formulario clave para nuestro formulario de acceso para asegurarlo. Tenemos que incluir la clase en nuestro archivo index.php. Además, necesitamos iniciar sesión debido a que nuestra clase usa sesiones para almacenar la clave generada. Por esto, agregamos el siguiente código arriba del doctype y la etiqueta head:

1
<?php
2
//Start the session

3
session_start();
4
//Require the class

5
require('formkey.class.php');
6
//Start the class

7
$formKey = new formKey();
8
?>

El código anterior es muy sencillo. Iniciamos sesión (porque almacenamos el formulario clave) y cargamos el archivo de la clase PHP. Después de eso, iniciamos la clase con new formKey(), esto creará nuestra clase y lo almacenará en $formKey. Ahora solamente tenemos que editar nuestro formulario para que contenga el formulario clave:

1
<form action="" method="post">
2
<dl>
3
	<?php $formKey->outputKey(); ?>
4
	<dt><label for="username">Username:</label></dt>
5
	<dd><input type="text" name="username" id="username" /></dd>
6
	<dt><label for="username">Password:</label></dt>
7
	<dd>input type="password" name="password" id="password" /></dd>
8
<dl>
9
</form>

¡Y eso es todo! Debido a que creamos la función outputKey(), solamente tenemos que incluirlo en nuestro formulario. Podemos usar formularios clave en cada formulario con sólo añadir <?php $formKey> outputKey(); ?> Ahora revise la fuente de su página web y puede ver que hay un formulario clave adjuntado al formulario. El único paso que queda por hacer es validar las solicitudes.

Paso 4: Validación

No validaremos el formulario completo, solamente el formulario clave. Validar el formulario es algo muy sencillo en PHP y, además, puede encontrar tutoriales por toda la web. Vamos a validar el formulario clave. Ya que nuestra función "generateKey" sobrescribe el valor de la sesión, entonces vamos a agregar un constructor a nuestra clase PHP. Un constructor será invocado cuando nuestra clase sea creada (o construida). El constructor almacenará la clave anterior dentro de la clase antes de que podamos crear una nueva, por lo que siempre tendremos el formulario clave anterior para validar nuestro formulario. Si no hacemos esto, no podremos validar el formulario clave. Agregue la siguiente función PHP a su clase:

1
//The constructor stores the form key (if one exists) in our class variable.

2
function __construct()
3
{
4
	//We need the previous key so we store it

5
	if(isset($_SESSION['form_key']))
6
	{
7
		$this->old_formKey = $_SESSION['form_key'];
8
	}
9
}

Un constructor siempre deberá llamarse __construct(). Cuando el constructor es invocado nosotros revisamos si una sesión está ajustad, y si es así, lo almacenamos de forma local en nuestra variable old_formKey.

Ahora podemos validar nuestro formulario clave. Creamos una función sencilla dentro de nuestra clase que valida el formulario clave. Esta función deberá ser publica porque vamos a usarla afuera de la clase. La función validará el valor POST del formulario clave contra el valor almacenado del formulario clave. Agregue esta función a la clase PHP:

1
//Function that validated the form key POST data

2
public function validate()
3
{
4
	//We use the old formKey and not the new generated version

5
	if($_POST['form_key'] == $this->old_formKey)
6
	{
7
		//The key is valid, return true.

8
		return true;
9
	}
10
	else
11
	{
12
		//The key is invalid, return false.

13
		return false;
14
	}
15
}

Dentro del index.php, validamos el formulario clave usando la función que acabamos de crear en nuestra clase. Por supuesto, solamente validamos después de la solicitud POST. Agregue el siguiente código después: $formKey = new formKey();

1
$error = 'No error';
2
3
//Is request?

4
if($_SERVER['REQUEST_METHOD'] == 'post')
5
{
6
	//Validate the form key

7
	if(!isset($_POST['form_key']) || !$formKey->validate())
8
	{
9
		//Form key is invalid, show an error

10
		$error = 'Form key error!';
11
	}
12
	else
13
	{
14
		//Do the rest of your validation here

15
		$error = 'No form key error!';
16
	}
17
}

Creamos una variable $error que almacena nuestro mensaje de error. Si un solicitud POST ha sido enviada, entonces nosotros validamos el formKey con $formKey->validate(). Si esto devuelve el valor false, entonces el formulario clave no es valido y mostrará un mensaje de error. Note que solamente validamos el formulario clave -- se espera que usted valide el resto del formulario.

En su HTML, usted puede colocar el siguiente código para mostrar un mensaje de error:

1
	<div><?php if($error) { echo($error); } ?></div>

Esto hará que se imprima o echo la variable $error, claro, si fue configurada.

FormFormForm

Si usted enciende su servidor y se dirige hacia el archivo index.php, verá que nuestro formulario y el mensaje "No error". Cuando usted envía un formulario, usted verá el mensaje "No form key error" ya que es una solicitud POST validad. Ahora, vuelva a cargar la página y acepte cuando su navegador solicite que el dato POST sea enviado otra vez. Verá que nuestro código activo un mensaje de error: "Form key error! Ahora su formulario clave está protegido contra entradas de otros sitios web ¡y de errores con actualización de páginas! El error también se muestra después de una actualización porque un formulario nuevo fue generado después de que enviamos el formulario. Esto es bueno porque, ahora, el usuario ya no puede publicar accidentalmente un formulario dos veces.

Código completo

Aquí está el código completo tanto de PHP como de HTML:

index.php

1
<?php
2
//Start the session

3
session_start();
4
//Require the class

5
require('formkey.class.php');
6
//Start the class

7
$formKey = new formKey();
8
9
$error = 'No error';
10
11
//Is request?

12
if($_SERVER['REQUEST_METHOD'] == 'post')
13
{
14
	//Validate the form key

15
	if(!isset($_POST['form_key']) || !$formKey->validate())
16
	{
17
		//Form key is invalid, show an error

18
		$error = 'Form key error!';
19
	}
20
	else
21
	{
22
		//Do the rest of your validation here

23
		$error = 'No form key error!';
24
	}
25
}
26
?>
27
	
28
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
29
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
30
<head>
31
	<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
32
	<title>Securing forms with form keys</title>
33
</head>
34
<body>
35
	<div><?php if($error) { echo($error); } ?>
36
	<form action="" method="post">
37
	<dl>
38
		<?php $formKey->outputKey(); ?>
39
40
		<dt><label for="username">Username:</label></dt>
41
		<dd><input type="text" name="username" id="username" /></dd>
42
		<dt><label for="username">Password:</label></dt>
43
		<dd><input type="password" name="password" id="password" /></dd>
44
		<dt></dt>
45
		<dd><input type="submit" value="Submit" /></dd>
46
	<dl>
47
	</form>
48
</body>
49
</html>

fomrkey.class.php

1
<?php
2
3
//You can of course choose any name for your class or integrate it in something like a functions or base class

4
class formKey
5
{
6
	//Here we store the generated form key

7
	private $formKey;
8
	
9
	//Here we store the old form key (more info at step 4)

10
	private $old_formKey;
11
	
12
	//The constructor stores the form key (if one excists) in our class variable

13
	function __construct()
14
	{
15
		//We need the previous key so we store it

16
		if(isset($_SESSION['form_key']))
17
		{
18
			$this->old_formKey = $_SESSION['form_key'];
19
		}
20
	}
21
22
	//Function to generate the form key

23
	private function generateKey()
24
	{
25
		//Get the IP-address of the user

26
		$ip = $_SERVER['REMOTE_ADDR'];
27
		
28
		//We use mt_rand() instead of rand() because it is better for generating random numbers.

29
		//We use 'true' to get a longer string.

30
		//See http://www.php.net/mt_rand for a precise description of the function and more examples.

31
		$uniqid = uniqid(mt_rand(), true);
32
		
33
		//Return the hash

34
		return md5($ip . $uniqid);
35
	}
36
37
	
38
	//Function to output the form key

39
	public function outputKey()
40
	{
41
		//Generate the key and store it inside the class

42
		$this->formKey = $this->generateKey();
43
		//Store the form key in the session

44
		$_SESSION['form_key'] = $this->formKey;
45
		
46
		//Output the form key

47
		echo "<input type='hidden' name='form_key' id='form_key' value='".$this->formKey."' />";
48
	}
49
50
	
51
	//Function that validated the form key POST data

52
	public function validate()
53
	{
54
		//We use the old formKey and not the new generated version

55
		if($_POST['form_key'] == $this->old_formKey)
56
		{
57
			//The key is valid, return true.

58
			return true;
59
		}
60
		else
61
		{
62
			//The key is invalid, return false.

63
			return false;
64
		}
65
	}
66
}
67
?>

Conclusión

Agregue este código para cada formulario que sea importante en su sitio web e incrementará la seguridad de su formulario de forma dramática. Incluso detiene los problemas de actualización, como vimos en el paso 4. Ya que el formulario clave solamente es válido para una solicitud, ya no es posible una segunda publicación.

Este fue mi primer tutorial, espero que le guste ¡y qué lo use para mejorar su seguridad! Por favor, déjeme conocer sus opiniones a través de los comentarios. ¿Tienes usted un método mejor? ¡Déjenos saberlo!

Lecturas Adicionales

  • Nos puede seguir a través de Twitter o, también, puede suscribir a NETTUTS RSS Feed para artículos y tutoriales diarios sobre desarrollo web.