
El formulario de solicitud de cambio de sitio web ha sido un tema corriente por aquí durante un tiempo y voy a seguir con eso por un tiempo. No vamos a repetir todo el HTML y JavaScript que hace que el formulario funcione, así que si necesita ponerse al día, consulte el primer artículo.
Lo que tenemos en este punto es un formulario bastante atractivo que tiene una experiencia de usuario bastante agradable. Sin embargo, siento que le faltan dos cosas importantes. A) los correos electrónicos de notificación en sí mismos son correos electrónicos de texto bastante insípidos y básicos y B) casi no hay seguridad en el formulario en sí.
Gracias a Daniel Friedrich, sé que he implementado una seguridad más seria en el formulario y ese será el enfoque de este artículo. Los dos grandes objetivos son:
- El formulario está siendo enviado por un ser humano.
- Ese ser humano no está haciendo nada nefasto
Coincidencia de tokens
Lo primero que vamos a hacer es generar un “token”, esencialmente un código secreto. Este token será parte de nuestra “sesión”, lo que significa que se almacena en el lado del servidor. Esta ficha además se aplicará como entrada oculta en el formulario cuando se genere por primera vez en el navegador. Eso significa que esta ficha existe. ambos en el lado del cliente y en el lado del servidor y podemos emparejarlos cuando se envía el formulario y asegurarnos de que sean iguales. Lo que esto hace es garantizar que cualquier envío del formulario es nuestra forma y no un script de terceros que se lamenta de nosotros desde un servidor diferente.
Primero necesitaremos iniciar una sesión, luego crear una función para construir el token, aplicarlo a la sesión y devolverlo para nuestro uso.
session_start();
function generateFormToken($form) {
// generate a token from an unique value
$token = md5(uniqid(microtime(), true));
// Write the generated token to the session variable to check it against the hidden field when the form is sent
$_SESSION[$form.'_token'] = $token;
return $token;
}
El valor único aquí proviene de un hash md5 de la función microtime, pero Daniel dice que hay una gran cantidad de otros métodos de cifrado (como valores de sal …). Ahora que tenemos esta función, justo antes de construir el formulario en el marcado, podemos llamarlo para crear el valor.
<?php
// generate a new token for the $_SESSION superglobal and put them in a hidden field
$newToken = generateFormToken('form1');
?>
Luego, coloque el token como entrada oculta en el formulario en sí:
<input name="token" type="hidden" value="<?php echo $newToken; ?>">
Ahora estamos preparados para comparar los valores de los tokens entre sí cuando se envía el formulario. Crearemos una función para hacer eso.
function verifyFormToken($form) {
// check if a session is started and a token is transmitted, if not return an error
if (!isset($_SESSION[$form.'_token'])) {
return false;
}
// check if the form is sent with token in it
if (!isset($_POST['token'])) {
return false;
}
// compare the tokens against each other if they are still the same
if ($_SESSION[$form.'_token'] !== $_POST['token']) {
return false;
}
return true;
}
Esta función comprueba si el token existe en los dos lugares requeridos y si coinciden. Si todas esas tres cosas son verdaderas, la función devuelve verdadero, si no, devuelve falso. Ahora verificamos ese valor antes de continuar. La estructura básica es:
if (verifyFormToken('form1')) {
// ... more security testing
// if pass, send email
} else {
echo "Hack-Attempt detected. Got ya!.";
writeLog('Formtoken');
}
Hackear Logging
Observe que en la estructura anterior usamos una función llamada writeLog () que toma una cadena. Hay una variedad de circunstancias en las que hemos detectado un juego sucio y debemos detenernos. En esos casos, llamaremos a la función writeLog () para registrar el error para nuestra propia referencia y luego morir ()
Esta función intenta escribir en un archivo de texto en el servidor (ese archivo necesitará los permisos de archivo adecuados, el usuario puede escribirlo) o, si eso falla, se lo enviará por correo electrónico.
function writeLog($where) {
$ip = $_SERVER["REMOTE_ADDR"]; // Get the IP from superglobal
$host = gethostbyaddr($ip); // Try to locate the host of the attack
$date = date("d M Y");
// create a logging message with php heredoc syntax
$logging = <<<LOG
n
<< Start of Message >>
There was a hacking attempt on your form. n
Date of Attack: {$date}
IP-Adress: {$ip} n
Host of Attacker: {$host}
Point of Attack: {$where}
<< End of Message >>
LOG;
// open log file
if($handle = fopen('hacklog.log', 'a')) {
fputs($handle, $logging); // write the Data to file
fclose($handle); // close the file
} else { // if first method is not working, for example because of wrong file permissions, email the data
$to = '[email protected]';
$subject="HACK ATTEMPT";
$header="From: [email protected]";
if (mail($to, $subject, $logging, $header)) {
echo "Sent notice to admin.";
}
}
}
Nada PUBLICADO que no pedimos
Si se nos envía algún valor que no tenga nombres de entradas en nuestro propio formulario, algo raro es definitivamente pasando. Crearemos una “lista blanca” de nombres de publicaciones aceptables y luego revisaremos cada uno.
// Building a whitelist array with keys which will send through the form, no others would be accepted later on
$whitelist = array('token','req-name','req-email','typeOfChange','urgency','URL-main','addURLS', 'curText', 'newText', 'save-stuff');
// Building an array with the $_POST-superglobal
foreach ($_POST as $key=>$item) {
// Check if the value $key (fieldname from $_POST) can be found in the whitelisting array, if not, die with a short message to the hacker
if (!in_array($key, $whitelist)) {
writeLog('Unknown form fields');
ie("Hack-Attempt detected. Please use only the fields in the form");
}
}
URL válida
La validación del lado del cliente en este formulario observa esto, por lo que debería ser detectado desde el principio, pero por supuesto también deberíamos estar comprobando la parte posterior.
// Lets check the URL whether it's a real URL or not. if not, stop the script
if (!filter_var($_POST['URL-main'],FILTER_VALIDATE_URL)) {
writeLog('URL Validation');
die('Please insert a valid URL');
}
Valores de limpieza
En este punto, hemos realizado todas nuestras comprobaciones de seguridad y estamos procediendo a crear y enviar el correo electrónico. Tomar todas las entradas exactamente como se ingresaron y enviarlas es un riesgo potencial para la seguridad. Por ejemplo, JavaScript podría ingresarse en un campo de texto y luego enviarse por correo electrónico y potencialmente ejecutarse cuando se abre el correo electrónico.
Para los campos como “Nombre”, donde no hay razón para usar etiquetas especiales, los eliminaremos por completo con strip_tags (). Para las áreas de texto, donde puede haber ocasiones para usar algunas etiquetas, solo usaremos la función htmlentities () para convertirlas de manera segura.
Ejemplo:
$message .= "Name: " . strip_tags($_POST['req-name']) . "n";
$message .= "NEW Content: " . htmlentities($_POST['newText']) . "n";
Más mejor limpieza
Krinkle escribe para decir:
Originalmente usaste strip_tags () para campos normales y htmlentities()
por el contenido. Esto está bien, excepto que es una buena práctica declarar ENT_NOQUOTES y “UTF-8 ″ también, ya que de lo contrario caracteres como el acento en la e (” é “) podrían convertirse en basura como Å @. Y dado que la mayoría de los servidores agregan barras a la entrada traída a través de $ _POST[] No es malo ejecutar stripslashes por si acaso, de lo contrario, tendría una barra diagonal delante de cada comilla simple o doble que se ingresó en el formulario una vez que esté en el buzón.
function stripcleantohtml($s){
// Restores the added slashes (ie.: " I'm John " for security in output, and escapes them in htmlentities(ie.: " etc.)
// Also strips any <html> tags it may encounter
// Use: Anything that shouldn't contain html (pretty much everything that is not a textarea)
return htmlentities(trim(strip_tags(stripslashes($s)), ENT_NOQUOTES, "UTF-8"));
}
function cleantohtml($s){
// Restores the added slashes (ie.: " I'm John " for security in output, and escapes them in htmlentities(ie.: " etc.)
// It preserves any <html> tags in that they are encoded aswell (like <html>)
// As an extra security, if people would try to inject tags that would become tags after stripping away bad characters,
// we do still strip tags but only after htmlentities, so any genuine code examples will stay
// Use: For input fields that may contain html, like a textarea
return strip_tags(htmlentities(trim(stripslashes($s)), ENT_NOQUOTES, "UTF-8"));
}
Uso en este ejemplo:
$message .= "Name: " . stripcleantohtml($_POST['req-name']) . "n";
$message .= "NEW Content: " . cleantohtml($_POST['newText']) . "n";
¿Por qué no utilizar un CAPTCHA?
Los CAPTCHA hacen un buen trabajo al mantener el spam fuera de los formularios, pero nos preocupaba más la piratería que el spam. Además, dado que este formulario es para personas que probablemente NOS GUSTA y estamos tratando de AYUDAR, no las vamos a hacer pasar por el molesto salto de aro de un CAPTCHA. Sin embargo, si está interesado, un captcha casero muy simple es preguntar algo como “¿Cuánto es diez menos cinco?” en una entrada de texto y luego verifique los valores “5” y cualquier combinación de letras mayúsculas de “cinco” y si coincide, continúe, si no es así, no lo haga. Esta versión del formulario ya tiene la función writeLog () que está lista para usar solo para esto, y la lógica encajaría muy bien en las líneas 75 y 76.
Si desea un CAPTCHA un poco más sólido, consulte reCAPTCHA, que es bastante fácil de usar, ayuda a las personas y es muy accesible.
¿Gurús de la seguridad?
Cuando hablamos de seguridad por aquí, suele haber muchas opiniones sobre cómo se están haciendo las cosas. Si tiene ideas, compártalas a continuación de la manera más constructiva que pueda, y las asimilaré y veré cómo podemos mejorar a medida que avanzamos.
Ver demostración Descargar archivos