Evitar spam en formularios PHP
Los formularios de los sitios web que se procesan y se envian a una dirección de correo electrónico pueden ser aprovechados por los "spammers" para hacer envios masivos de "spam" utilizando los servidores de correo y la IP de la maquina en la que se aloja el dominio. En este post vamos a ver como podemos hacer que nuestros formularios sean un poco más seguros, evitando los ataques del tipo "email injection" a los que muchos formularios son sensibles.
En que consiste el "email injection"
Un ataque a un formulario del tipo "email injection" consiste en que el "spammer" aprovecha un formulario de un sitio web para "inyectar" cabeceras de email con direcciones y contenidos diferentes a los establecida en el formulario. Un "spammer" puede, a través de la inyección de cabeceras cambiar tanto el mensaje enviado, como los destinatarios, utilizando nuestro servidor de correo y nuestra maquina y nuestro dominio como emisor del mensaje, con lo que seremos nosotros los que estaremos haciendo "SPAM", y el rastro hasta el "spammer" se pierde.
Para entender como funciona esta técnica, veamos primero la fisionomia de un correo electrónico en su forma más básica:
- Las cabeceras o headers (To:, Subject:, Cc:, etc.)
Una cabecera sigue la sintaxis de "header:valor de cabecera". Las cabeceras se separan con un salto de linea, y se pueden especificar tantas como se quieran. Habitualmente se especificará un "To:", un "Subject:" y un "From:". Para enviar copias a otras cuentas de correo existen cabeceras como "Cc:" y "Bcc:". Otras cabeceras pueden marcar el tipo de contenido que tendrá el mensaje: texto plano, HTML, "multipart" (para envio de attachments, por ejemplo), etc. Más informacion sobre cabeceras de correo electrónico - Body o Cuerpo de Mensaje:
Va a continuación de las cabeceras. Lo único que marca el comienzo del cuerpo del mensaje son 2 saltos de línea. Todo lo que se incluya después de 2 saltos de línea será interpretado como cuerpo del mensaje.
Aquí tenemos un correo de ejemplo:
To:direccion@destinataria.com
Subject:Motivo del mensaje
From:direccion@emisora.com
Este seria el cuerpo del mensaje.
Dicho esto, la técnica utilizada por el spammer es incluir y/o alterar las cabeceras del mensaje por las suyas propias, antes de que se produzcan 2 saltos de línea, y por tanto, comienze el cuerpo del mensaje. Si el spammer consigue alterar o introducir sus cabeceras, los destinatarios y el cuerpo del mensaje se pueden alterar. Asi por ejemplo, si tenemos el siguiente correo:
To:direccion@destinataria.com
Subject:Motivo del mensaje
From:direccion@emisora.com
To:nuevadireccion@destinataria.com
Cc:otradireccion@spameada.com
Subject:Nuevo subject del mensaje spameado
Texto introducido por el spammer.
Este seria el cuerpo del mensaje.
Observamos que después del "From:" original, el "spammer" ha añadido un nuevo "To:" y otras direcciones de correo con la etiqueta "Cc:". También ha introducido un nuevo texto en el cuerpo del mensaje. El hecho de que haya 2 cabeceras "To:", o 2 "Subject:", no supone ningún problema. En la mayoría de los casos, si una cabecera se define 2 veces, la última es la que se tiene en cuenta.
*Un correo básico se compone normalmente de 2 partes: las cabeceras, y un cuerpo del mensaje. Sin embargo, utilizando cabeceras especificas a tal fin, un correo pude tener varios "bodys" o cuerpos de mensaje alternativos. Por simplificar analizamos aquí la estructura de un correo básico.
Como puede el spammer inyectar cabeceras en la función mail() de PHP
la función que habitualmente se utiliza en PHP para enviar a una dirección de correo electrónico los datos de un formulario será mail(). La sintáxis de esta función es la siguiente:
mail (To, Subject, Mensaje, Headers, Parametros_Adicionales);
el siguiente es un script de ejemplo básico de un formulario que se enviará a una dirección de correo:
<?php
$to="webmaster@website.com";//direccion destinataria "hardcoded"
if (!isset($_POST["send"])){
// si no vienen datos-> mostrar el form
?>
<form method="POST" action="<?=$_SERVER[’PHP_SELF’];?>">
To: webmaster@website.com
From: <input type="text" name="sender">
Subject : <input type="text" name="subject">
Message :
<textarea name="message" rows="10" cols="60" lines="20"></textarea>
<input type="submit" name="send" value="Send">
</form>
<?
}else{
// si vienen datos POST.. procesamos y enviamos los datos del form
$from=$_POST[’sender’];
// send mail :
if (mail($to,$_POST[’subject’],$_POST[’message’],"From: $from\n")){
// si se ha enviado correctamente mostramos un mensaje de OK
echo "Su mensaje se ha enviado correctamente a $to.";
}else{
// Se ha producido un error
echo "Su mensaje no se ha podido enviar";
}
}
?>
En el ejemplo anterior, mostramos un formulario de contacto donde el usuario puede incluir el From, el Subject del Mensaje y el Mensaje. El destinatario "To:" esta "hardcoded" y en principio un usuario no tiene la opción de alterarlo. Sin embargo, esto no nos garantiza nada tal y como hemos visto anteriormente. Un usuario spammer, aprovecharía el campo "From:" para introducir sus propias cabeceras. Solo tiene que añadir un salto de línea al campo del formulario e insertar otras cabeceras. El caracter hexadecimal "%0A" es el equivalente a un salto de línea. Por tanto, si el spammer introduce en el campo "From:" el siguiente contenido:
direccionFrom@ficticia.com%0ATo:nuevadireccion@destinataria.com%0ACc:otradireccion@spameada.com%0ASubject:Nuevo subject del mensaje spameado%0A%0ATexto introducido por el spammer en el cuerpo de texto.
Cuando se procese el formulario, las “headers” maliciosas llegarán a la función mail() y gracias a los saltos de línea introducidos en el campo From. Los últimos 2 saltos de línea marcan el comienzo del “body” y a partir de ahí, todo lo demas formará parte del cuerpo del mensaje. En nuestro ejemplo, el spammer no necesita hacer esto último, ya que tiene un campo en el que puede introducir su propio mensaje, pero esta técnica muestra como se puede alterar hasta el cuerpo del mensaje una vez que podemos inyectar nuestras propias cabeceras. El mensaje de correo que devolverá la función mail() será el siguiente:
To:direccion@destinataria.com
Subject:Motivo del mensaje
From:direccion@emisora.com
To:nuevadireccion@destinataria.com
Cc:otradireccion@spameada.com
Subject:Nuevo subject del mensaje spameado
Texto introducido por el spammer en el cuerpo de texto.
Este seria el cuerpo del mensaje.
Que como vimos anteriormente, produce un envio a direcciones diferentes de las previstas, y con otro Subject y Body. Logicamente, el spammer puede introducir tantas direcciones separadas por comas como quiera, con lo que en un simple envio pueden salir cientos de correos SPAM desde nuestro formulario.
En formularios más restrictivos, en los que el cuerpo del mensaje esta "hardcoded" y no se coge desde un campo del formulario, el spammer puede añadir una cabecera del tipo: "Content-Type: multipart/mixed; boundary="SeparadorDePartes", que permitiría crear un mensaje multi-partes. En un mensaje del tipo "multi-partes", se establece una cadena como "separador" que marcará donde comienza y termina cada parte. Asi, "–SeparadorDePartes", marca el comienzo de una nueva parte del mensaje, que terminará cuando se encuentre nuevamente otro "–SeparadorDePartes" que indique el comienzo de otra parte. Finalmente, "–SeparadorDePartes–" con dos guiones al final, marcaría el final del cuerpo del mensaje.
Utilizando esta cabecera y otras similares, un spammer puede ocultar el cuerpo del mensaje originalmente establecido, dejandolo fuera del separador que marca el fin del body. También podría utilizar esta técnica para añadir ficheros adjuntos, ya que cada una de las partes puede tener su propio "Content-Type", y por tanto podemos hacer que una de las partes sea una imagen o un fichero binario, como se puede ver en esta ejemplo de "email injection" avanzado.
Soluciones
Después de habernos extendido en la explicación del problema, con el fin de que quede claro como se produce, podemos sentir un poco de pánico, aunque en realidad la solución o soluciones (hay varias) son bien sencillas.
Detectar los saltos de línea con expresiones regulares
La más abordable inicialmente es proteger nuestro script para que no le lleguen cabeceras indeseadas. Hemos de tener especial cuidado en todos aquellos campos de un formulario cuyo destino sea el parametro "headers" de la función mail(). En nuestro ejemplo básico el "From:". En lugar de pasarlo directamente, tendremos que hacer una comprobación sobre estos campos y asegurarnos que no vienen saltos de línea, en cuyo caso podemos estar sufriendo una "email injection" y podemos detener el script:
<?php
$from = $_POST["sender"];
$from = urldecode($from);
if (eregi("\r",$from) || eregi("\n",$from)){
die("Posible email injection");
}
?>
Otras soluciones alternativas
Utilizar mod_security en el servidor Apache, para que filtre este tipo de "injections". La ventaja del mod_security es que es bastante más potente, ya que permite filtrar otros posibles ataques como los "SQL injections" y que funciona para todos los datos y formularios del servidor, con lo que no hay que ajustar uno por uno diferentes formularios, sino que centralizamos las reglas de seguridad en un solo sitio. La desventaja es que es algo complejo, dependes de que el servidor lo tenga instalado y si no tienes que instalarlo, y por último, su potencia también lo hace algo restrictivo y puedes estar filtrando peticiones al servidor que no son perniciosas.
Utilizar librerias como la Zend_Mail para enviar correos. Esta libreria realiza varias comprobaciones de seguridad antes de enviar el correo, de forma que el usuario no tiene que preocuparse por estos aspectos. Existen otras librerias para el envio de correos que también toman este tipo de precauciones.
En cualquier caso, no es menos cierto que existen muchos más scripts hechos personalmente con usuarios poco o nada experimentados y que son el objetivo de los spammers. Para ellos va este Post, a ver si entre todos les ponemos las cosas un poco más dificiles a los spammers.
Cuidado con los formularios del tipo "Send to a friend"
Es algo obvio pero como observo que todavía es una practica extendida veo aconsejable recordar lo siguiente. Las medidas de seguridad mencionadas en este post le protegen o tratan de hacerlo del uso pernicioso que algunos spammers pueden hacer de un formulario previsto con otra funcionalidad.
Sin embargo, poco o nada se puede hacer con algunos formularios del tipo "send to a friend" o "enviar a un amigo" que no han observado la más mínima medida de seguridad. Me refiero a esos formularios que permiten a un usuario anónimo rellenar un formulario en el que todo se puede establecer: El "To:", el "From:", el "Subject:" y el "Body:". Estos formularios pueden ser utilizados como proxies de correo por cualquiera, "spammer" o no. Cualquiera puede enviar un correo a la dirección que quiera, con el subject que quiera, el cuerpo del mensaje que quiera, y simular la dirección desde que lo enviará que quiera, y sin necesidad de utilizar ninguna técnica ni hack para inyectar cabeceras.
En fin, se que es una obviedad, pero había que comentarlo porque hay muchos formularios así. No utilize NUNCA este tipo de formularios sin tomar precauciones obvias de seguridad. Aseguese al menos de que el "From:" no puede ser establecido por el usuario, enviando los correos desde una dirección "fija". Limite las posibilidades de introducir un "Subject:" o un "Body:" personalizado y siga las reglas de filtrado explicadas en este post para evitar ataques "email injection".
*** POST basado en los siguientes recursos:



Alguna vez he tenido este problema y me salían unos 6.000 email diarios a diferentes cuentas de aol.com desde el formulario. El proveedor me avisó y cortó el servicio, acabando con el problema desde la raíz, el tío… Jeje!!
Una vez se reestableció el servicio depuramos el tema sin tener algún dolorcillo de cabeza con el menda y sus travesuras… Uff!! No quiero ni acordarme.
Muy buena explicación de Oscar sobre el tema…
Bien, lo de la inyección de encabezados es relativamente fácil de proteger, lo que ocurre es que es más fácil aun enviar emails con PHP, por lo que es frecuente encontrarse con formularios que son verdaderos agujeros.
Un buen método es, como sugiere Oscar, eliminar los encabezados indeseados mediante expresiones regulares o métodos similares. Yo suelo usar formularios que envían emails con plantillas html. Normalmente uso una función que busca y reemplaza encabezados indeseados de forma similar a lo siguiente:
function send_email($to, $fromaddr, $subject, $message_text, $message_html){$subject = preg_replace(”/\nfrom\:.*?\n/i”, “”, $subject);
$subject = preg_replace(”/\nbcc\:.*?\n/i”, “”, $subject);
$subject = preg_replace(”/\ncc\:.*?\n/i”, “”, $subject);
$message_text = preg_replace(”/\nfrom\:.*?\n/i”, “”, $message_text);
$message_text = preg_replace(”/\nbcc\:.*?\n/i”, “”, $message_text);
$message_text = preg_replace(”/\ncc\:.*?\n/i”, “”, $message_text);
$message_html = preg_replace(”/\nfrom\:.*?\n/i”, “”, $message_html);
$message_html = preg_replace(”/\nbcc\:.*?\n/i”, “”, $message_html);
$message_html = preg_replace(”/\ncc\:.*?\n/i”, “”, $message_html);
$additional_parameters = “-f $fromaddr”;
// crea encabezados adicionales
$headers = “From: $fromaddr\r\n”;
$headers.=”To: $to\r\n”;
$headers .= “MIME-Version: 1.0\r\n”;
// mensaje HTML (mixto)
if($message_html != “”)
{
$boundary = uniqid(”miunicoid”);
// encabezado para mensajes mixtos
$headers .= “Content-Type: multipart/alternative; boundary = $boundary\r\n\r\n”;
// mensaje texto
$body = “–$boundary\r\n”;
$body .= “Content-Type: text/plain; charset=ISO-8859-1\r\n”;
$body .= “Content-Transfer-Encoding: 7 bit\r\n\r\n”;
$body .= $message_text.”\r\n\r\n”;
// mensaje HTML
$body .= “–$boundary\r\n”;
$body .= “Content-Type: text/html; charset=ISO-8859-1\r\n”;
$body .= “Content-Transfer-Encoding: 7 bit\r\n\r\n”;
$body .= $message_html.”\r\n\r\n”;
}
// mensaje de texto
if($message_html == “”)
{
$headers .= “Content-type: text/plain; charset=iso-8859-1\n”;
$body = $message_text;
}
// envia mensaje
return mail(”", $subject, $body, $headers, $additional_parameters);
}
Tampoco es mala idea complementar el envío de formularios mediante algún CAPTCHA (generador de imágenes con números o textos para comprobar que el formulario está siendo enviado por un humano). He usado frecuentemente y recomiendo el uso de la clase “securimage CAPTCHA” por lo fácil que resulta de incorporar a los formularios. Puedes ver más detalles o bajarla en el siguiente vínculo
http://www.hotscripts.com/Detailed/49400.html
No soy el mejor amigo de las expresiones regulares, creo que a veces hacen el código ilegible. Por eso siempre que existe, busco una solución más simple. Aquí dejo una pequeña contribución para solucionar este problema usando simplemente la función “strtr” de PHP que intercambia usa serie de caracteres por otros:
$from = strtr($_POST['from'],"\r\n\t",'???');
Este código cambiaría las ocurrencias de los caracteres \n (salto de línea), \r (retorno de carro) o \t (tabulador), por el signo ?. Lo uso en todos mis formularios desde que lo encontré en el Zend Framework y por ahora… 100% de efectividad ;)
Gracias por este post, me ha sido muy útil para informarme sobre el tema, ya que he sido víctima de esto… Soy diseñador gráfico y se muy poco de php, es por eso que tu post ha sido particularmente explicativo, ya que me aclaró un poco más el panorama.
Saludos!
Eduardo
Gracias a ti por tu comentario Eduardo. Con esa intención se hizó el post, y me alegra saber que le sirva de utilidad a alguien. Me anima a preparar otros con la misma intención. ¿Que tipo de diseño haces?…
Oscar,
Si visitaste el blog, habrás visto que está referido a diseño gráfico en general, diseño web y usabilidad, y fotografía.
Soy diseñador gráfico de la UBA (Universidad de Buenos Aires), donde dicto clases de diseño gráfico I.
El link de mi estudio es www.criaturacreativa.com, y el del blog ya lo tienes aqui posteado.
Te mando un abrazo y espero te guste mi trabajo :)
PD: sigo luchando con esto, incorpore el código pero lo testee poniendo ‘/n’ y eso , y sigue mandandome los mails… hay alguna forma rápida de testear si esta actuando bien o no?
La verdad q este problema me preocupaba por q estaba pensando en hacer una pagina y no sabia como solucionarlo, ya q recien estoy empezando y todavia no hice ninguna pagina publicada. Esta muy bien explicado y espero q sigas asi (escribiendo).
Eduardo,
La mejor manera de probarlo es simular tu mismo un ataque en un formulario. Si te sientes algo comodo con PHP puedes no enviar el mail y volcar los resultados del proceso a pantalla. De todas formas, si no estas consiguiendo resultados capturando \n es posible que necesites comprobar otros saltos de línea como \r que es el salto de línea en los sistemas windows por ejemplo.
Como bien apunta Manuel, trata de comprobar la existencia de los caracteres “\n”, “\r” y “\t”.
P.D. He tratado de ver tu pagina pero no me ha funcionado, parece que el servidor pueda estar caido, o que la URL no sea correcta.
Muchas gracias Dario. Me alegro que te haya sido de utilidad. Me animas a seguir haciendo post de este tipo que puedan ayudar a otros, como otros me ayudan a mi.
Espero que sigas visitando nuestra república habitualmente.
Muchos saludos y que bueno que tienes el “don” de compartir con todos. Acado de armar un formulario precisamente con el script en php que pones en la parte superior de este tema y quiero anexarle la parte que pone Manuel … $from = strtr($_POST[’from’],”\r\n\t”,’???’);… pero no sé donde ponerla, me podrían auxilar??? Les agradezco y mando un saludos.
P.D. Muy felices fiestas y año nuevo :-))
Querido Francisco,
Tienes que comprobar que no haya saltos de líneas en las variables de tu formulario susceptibles de sufrir el “email injection”, lógicamente, antes de incluirlas en tu función mail() que enviará el correo electrónico desde PHP. Normalmente, la lógica del proceso es recoger la información que viene desde el POST o el GET del formulario, y pasar esa información a los parametros de la función mail() de PHP. Pues antes de pasarlos, aségurate que los campos-variables susceptibles de sufrir el ataque no tengan saltos de línea.
Para esto puedes utilizar la función que Manuel sugería. Espero haberte aclarado tus dudas.
ahora digo yo … si negas el caracter
por medio de javascript en el formulario ? …
yo tengo un javascript que no recuerdo de donde lo saque con una funcion ismail, trate de probar mis formularios intentando pasar una direccion de mail y atras el caracter
para ver que pasaba y dicha funcion de javascript directamente al detectar ese caracter me tira error y salta que la direccion de mail no es valida !!! hay alguna otra forma de probar mis formularios ? o solo el spammer si o si tiene que poner
detras de la direccion de mail ?
Nota en el mensaje que deje no salio el caracter salto de linea
saludos :-)
Sergio, javascript puede desactivarse del lado del cliente, así que no es un método seguro para proteger un formulario. La utilidad de la función que dices va encaminada a validar datos evitándose consultas al servidor para hacerlo, pero no es un método seguro para protegerse del spam.
ups no pense en eso :-( bueno gracias voy a tirar algunas pruebas sin el javascript …
Buenos días, gracias a todos por vuestras aportaciones. Últimamente esta ocurriendo lo siguiente en nuestro formulario de busqueda en el directorio turistea.com
El formulario no utiliza en ningún momento la función mail() de php, pero nos consultas extrañas como “weke.net viagra.com “.
Lo extraño es que la variable que recibimos con la url de busqueda como ejemplo es Malaga.
Pero visualizando el valor de la variable tiene “weke.net viagra.com ”
Ejemplo: www.turistea…. /rbsta?variable=Malaga
pero si visualizamos $variable es “weke.net viagra.com ”
Agradecería mucho vuestra ayuda.
Gracias.
Lo siento Sergio, pero no he sido capaz de entenderte.
Hola Luis. Yo tampoco llego a entender del todo lo que cuentas, pero podría tratarse de un programa robot tanteando el terreno. Es posible que el programa detecte el formulario que llama al script rbust.php y los campos que le manda al mismo, así que prueba a llamar al script rellenando los campos con los valores que le interesa por si cuela. En el caso del buscador no creo que debas preocuparte.
Acabo de leer el artículo antispam y me ha parecido muy interesante. Ahora bien, una duda que tengo -y que al menos en parte guarda relación con el tema del artículo- es qué scripts (JavaScript, PHP o lo que fuere) deben usarse para hacer que, por un lado, los datos que el usuario ha escrito en un formulario no aparezcan al darle en el botón “Atrás” del navegador y, sobre todo, para lograr que cada persona sólo efectúe un único envío de datos y no toda una andanada, cosa que suelen hacer a veces los spamers.
Un saludo y felicitaciones por el artículo
Hola, hoy mi provedor me ha suspendido por abuso del servidor, han utilizado mis formularios para hacer spam, he investigado aqui y alla, buscando métodos para proteger los formularios, en fin, implemente el método Captcha e instale Swift mailer con el siguiente código:
require_once “lib/Swift.php”;
require_once “lib/Swift/Connection/SMTP.php”;
//Start Swift
$swift =& new Swift(new Swift_Connection_SMTP(”192.168.0.32″));
$message =& new Swift_Message(”Envío de Comentario”);
$message->attach(new Swift_Message_Part(
“Nombre: ” . $HTTP_POST_VARS[”alva_nombre”].”".
“Empresa: ” . $HTTP_POST_VARS[”1″].”".
“Email: ” . $HTTP_POST_VARS[”2″].”".
“Lada: ” . $HTTP_POST_VARS[”3″].”".
“Telefonos: ” . $HTTP_POST_VARS[”4″].”".
“Celular: ” . $HTTP_POST_VARS[”5″].”".
“Pais: ” . $HTTP_POST_VARS[”6″].”".
“Estado: ” . $HTTP_POST_VARS[”7″].”".
“Ciudad: ” . $HTTP_POST_VARS[”8″].”".
“Como nos encontro: ” . $HTTP_POST_VARS[”9″].”".
“Mensaje: ” . $HTTP_POST_VARS[”textarea”] ,”text/html”
));
Tengo estos dos metodos, quisiera saber si esto funcionará o sigo en las mismas, espero puedan ayudarme, Gracias.
Hola ACSE. Aunque nunca he usado la clase Swift para el envío de correo, sé que no hace uso de la función mail de php y se supone que no es vulnerable a la inyección de cabeceras, así que supongo que debería ser razonablemente seguro, y más si lo complementas con un captcha.
Muchas Gracias por tu respuesta, parece que por el momento puedo quedarme tranquilo.
Buen dia.
Alberto. Si tu problema es evitar que un formulario se envíe dos veces puedes probar, en primer lugar, a desactivar el botón en cuanto se pulse ( o en cuanto se validen los datos en caso de que se haga validación por javascript) con algo similar a
input onclick=”this.disabled=true” type=”submit”
También puedes meter el formulario en un div y hacerlo invisible al mismo tiempo que muestras otro mensaje, una barra de progreso o lo que se te ocurra.
Claro que esto es sólo para evitar confusiones a nivel de usuario, los spammers no suelen ponerse a rellenar formularios a mano.
Pero no hay mucho más que puedas hacer sin entrar en un verdadero quebradero de cabeza ya que estás hablando de hacer que los navegadores se comporten de manera distinta a la que deben. Puedes aplicar montones de técnicas pero no estoy seguro de que sea fácil de conseguir lo que indicas de forma coherente, pues en este caso están más implicados los navegadores que el código que puedas escribir, ya sea escribiendo encabezados para evitar la caché de la página, usando javascript o lo que sea. Lo único que se me ocurre, que no sé si te vale, es registrar una cookie de sesión, mostrar sólo el formulario a aquellos clientes que tengan la cookie (por ejemplo vía php), modificar su valor al enviar el formulario por otro que sea comprobado para evitar que vuelva a hacer un post en la misma sesión. De esta forma si alguien no tiene la cookie o la tiene en valor ‘enviado’ (por ejemplo) no podrá ver ni enviar el formulario, hasta que la cookie muera con la sesión.
Sitios como estos son los que me gustan en Internet.
Muchas pero que muchas gracias, de veras, lo marcaré para seguir leyendo, muy bueno, y los comentarios….bien buenos tambien, Gracias
Gracias por tus comentarios, Develooping, supongo que cuando hablas de meterse en un quebradero de cabeza te refieres no tanto al hecho de que cada usuario realice un único envío como a la acción de que al retroceder con el navegador no aparezcan los datos recién enviados. Posiblemente el primer efecto pueda lograrse de la manera que tú apuntas mediante un sencillo código de JavaScript, incluso en un simple formulario HTML, pero lo del navegador ya es harina de otro costal.
Muchas gracias de nuevo y un saludo.
Alberto
Exactamente a eso me refería. Me alegro de que lo hayas cogido aunque no lo haya explicado demasiado claramente. Lograr que no se hagan dos posts es relativamente sencillo y se puede hacer con un simple javascript (aunque para más seguridad mejor usar alguna técnica que implique al servidor). Ambos métodos están descritos superficialmente en mi comentario previo.
Lo que ya no es tan claro es cómo hacer que un formulario no muestre los datos previos al dar a botón “back”. Podrías probar con técnicas como pasar por un script de servidor intermedio que redirija el post, pero tampoco es 100% de fiar, ya que todos los navegadores permiten volver atrás varios pasos y no sólo uno.
A ver Alberto, creo que podría hacerse algo como lo que tu comentas utilizando variables de SESSION y sobreescribiendo los POST como comenta Develooping. De todas formas, no termino de entender muy bien para que, o que finalidad persigues. Si explicas la finalidad o lo que quieres conseguir, a lo mejor facilitas el que te ayudemos.
Supongo que será por alguna razón de seguridad que estas planteando esa cuestión. Pero explicate mejor, porque a lo mejor no necesitas hacer “exactamente” eso que tu planteas, sino que la solución pasa por otro lado.
Oscar, Alberto preguntaba qué hacer para que al enviar un formulario y pulsar después el botón back no se muestren de nuevo los campos con los datos enviados. Lo único que se me ocurre al respecto es que el formulario envíe a un script intermedio que reenvíe el post (podría usar para ello la clase php Snoopy). De esta forma se podría conseguir que un “back” le volviera a llevar a la página final, y no al formulario. Pero no creo que pueda evitarse que quien quiera retroceda a la página del formulario, pues aunque podría probarse enviando cabeceras en la página para evitar que se guarde en caché, esto no funciona más que en algún que otro navegador. Claro que pensándolo bien, la cuestión no es evitar que se vuelva atrás y se recupere el valor que había en los campos el formulario, ya que la solución es más sencilla si, como indicas, pensamos “qué queremos conseguir” y no “qué tenemos que hacer”. Verás que al final de mi comentario hay una solución simple al problema aunque no evita que se vuelva atrás. Ni falta que hace.
También preguntaba si hay maneras de evitar que un formulario se envíe dos veces. Ahí sí podría ser útil una variable de sesión (yo también lo combinaría con algo de javascript para ocultar el formulario una vez enviado y evitar una doble pulsación del botón). Lo mejor es combinarlo todo en uno.
Otra opción, más sencilla aún y que acabo de probar que funciona perfectamente es el uso de una captcha en php llamado securimage CAPTCHA. Lo cito y vinculo en mi primer comentario. Su uso es bastante sencillo, y aunque no evita que volvamos a la página del formulario pulsando el botón “atrás”, si intentamos reenviarlo con los mismos valores (bueno, en realidad con el mismo código que el captcha genera), nos invalida el envío.
En realidad no busco una finalidad específica, simplemente me lo planteo como una medida de seguridad general. Excepto en el caso de formularios que piden datos concretos (apellidos, direcciones, contraseñas, etc), es frecuente (o al menos la frecuencia, sin ser mayoritaria en absoluto, puede resulta irritante) que algunos usuarios se dediquen a enviar sucesivas parrafadas bromistas, maliciosas o publicitarias. Para evitar esto pienso que limitar el uso de un formulario a un único envío por cada usuario es una buen sistema, unido, claro está, a otros (remoción de etiquetas HTML, por ejemplo); así si nos mandan algo ajeno a lo solicitado será eso, un “algo” y no una toda una ráfaga de “algos”.
Gracias y un saludo:-)
Bueno Alberto,
Para eso, no creo que haga falta más que lo que te comenta Develooping. “desactivar” el formulario si se ha enviado y listo. Puedes incluso establecer un tiempo en session para volver a activarlo.
Sí, supongo que con un (java)script adecuado el asunto quedaría arreglado, aunque he probado dos sacados de la Red y ninguno ha funcionado satisfactoriamente; el primero no ha surtido efecto, y el segundo sólo lo ha hecho con Internet Explorer, pero desactivando el botón Submit nada más pulsarlo, sin dar tiempo a enviar los datos. En alguna páginas usan otro sistema basado asimismo en JavaScript que también es interesante: situar el formulario en una ventana que se cierra automáticamente tras haber efectuado el envío y mostrarse un mensaje de que tal cosa ya ha sucedido. El problema radica en que JavaScript es menos seguro que PHP por aquello de que puede deshabilitarse desde el lado del cliente. Posiblemente se deba a mi ignorancia total en dicho lenguaje, pero tengo la impresión de que lograr efectos similares o parecidos con PHP debe de ser más complicado.
Gracias y un saludo.
Alberto, aunque haya cosas que puedan resolverse con ambos lenguajes, javascript y PHP tienen naturaleza y finalidades distintas. PHP no puede decirle al navegador cosas como “cierra una ventana”, “oculta el div que contiene el formulario” o “desactiva el botón” salvo que genere código javascript para conseguirlo. Si lo que quieres es ocultar un formulario al enviarlo podrías probar con lo siguiente:
- Mete en formulario en un DIV y dale el id=”divformulario”
- Crea otro DIV que muestre un mensaje y dale el id=”divmensaje” y asígnale style=”display:hidden” para que no se muestre el mensaje hasta que no se lo digamos.
Ambos divs pueden tener el mismo tamaño y posición y divmensaje podría tener el atributo z-index superior a divformulario para que quede encima.
- En la marca FORM del formulario podríamos llamar a una función javascript para que se ejecute cuando lo enviemos con onSubmit=”muestra_mensaje();”
La función muestra_mensaje() la definimos dentro de una marca SCRIPT en la cabecera de nuestra página y podría ser algo como:
function muestra_mensaje(){
document.getElementById(’divformulario’).style.display=’none’;
document.getElementById(’divmensaje’).style.display=’block’;
}
Eso debería ser suficiente para ocultar el formulario al enviarlo.
Incluso, evolucionando un poco lo que ha dicho Develooping, puedes hacerlo todavía más seguro utilizando CSS puras sin Javascript:
Creas una hoja de estilos como la siguiente:
body.sinEnviar #divformulario {
display:block;
}
body.sinEnviar #divmensaje {
display:none;
}
body.enviado #divmensaje {
display:block;
}
body.enviado #divformulario {
display:none;
}
Asi, cuando el body tenga “class=sinEnviar” aparecera el formulario y el mensaje estara oculto, y cuando el body tenga la clase “enviado”, ocurrirá al contrario.
Ahora puedes cambiar la clase del body en el lado del servidor del PHP, y aunque el usuario tenga deshabilitado javascript funcionará igualmente. De alguna forma es más seguro y más compatible.
Muchas gracias, Develooping y Oscar, probaré esos métodos. El que el retroceso del navegador muestre o no los datos recién enviados me preocupa menos que el hecho de desactivar el formulario tras el envío, pues asusta (y disgusta) pensar en la cantidad de majaderías que cometen los graciosos y aburridos de turno con los formularios que aparecen ante ellos. El único punto oscuro para mí en el método de Oscar es el cambio de la clase del body en el lado del servidor PHP (esto es ya fruto de mi gran ignorancia sobre PHP).
Un saludo
Los formularios de los sitios web que se procesan y se envian a una dirección de correo electrónico pueden ser aprovechados por los “spammers” para hacer envios masivos de “spam” utilizando los servidores de correo y la IP de la maquina en la que se aloja el dominio. En este post vamos a ver como podemos hacer que nuestros formularios sean un poco más seguros, evitando los ataques del tipo “email injection” a los que muchos formularios son sensibles…
Hola a todos, la verdad que este post está muy bien explicado, pero el problema que yo tengo es que en mi portal www.pirinet.es quería poner una página de contactos con un formulario en php (/contactos.php no está publicada, pero se puede ver), este formulario direcciona a una página “send.php3″ la cual envía el correo y muestra el mensaje de “enviado”. Hasta aquí todo va bien, pero para evitar el spam, incluí en la página el “captcha” de dreamweaver pero cometo algún error porque ponga o no el código, el mensaje lo envía igual y no me doy cuenta donde está.
Este es el código de contactos.php donde está el form y el captcha:
hexdec(substr($hexcolor,0,2)),’g'=>hexdec(substr($hexcolor,2,2)),’b'=>hexdec(substr($hexcolor,4,2)));
return $dec_color;
}
function generateCode($characters) {
/* list all possible characters, similar looking characters and vowels have been removed */
$possible = ‘23456789bcdfghjkmnpqrstvwxyz’;
$code = ‘’;
$i = 0;
while ($i hex_to_dec($hex_bg_color);
$rgb_text_color=$this->hex_to_dec($hex_text_color);
$rgb_noise_color=$this->hex_to_dec($hex_noise_color);
$code = $this->generateCode($characters);
/* font size will be 60% of the image height */
$font_size = $height * 0.60;
$image = @imagecreate($width, $height) or die(’Cannot Initialize new GD image stream’);
/* set the colours */
$background_color = imagecolorallocate($image, $rgb_bg_color[’r'], $rgb_bg_color[’g'],$rgb_bg_color[’b']);
$text_color = imagecolorallocate($image, $rgb_text_color[’r'], $rgb_text_color[’g'],$rgb_text_color[’b']);
$noise_color = imagecolorallocate($image, $rgb_noise_color[’r'], $rgb_noise_color[’g'],$rgb_noise_color[’b']);
/* generate random dots in background */
for( $i=0; $ifont, $code);
$x = ($width - $textbox[4])/2;
$y = ($height - $textbox[5])/2;
imagettftext($image, $font_size, 0, $x, $y, $text_color, $this->font , $code);
/* save the image */
imagejpeg($image,$img_file);
imagedestroy($image);
echo “”;
$_SESSION[’captcha_code’] = $code;
}
}
?>
Contáctenos
Nombre y Apellidos:
Email: *
Nombre de Usuario: *
Comentario: *
Los campos señalados con * son obligatorios.
Código de Seguridad
Y la pagina “Send.php3″
Mensaje Enviado
Gracias por visitar Pirinet.es
INICIO
Si alguno me puede dar algún dato de donde está el problema se lo agradecería.
Un saludo para todos.
Hola Edu. A menos que me equivoque, Dreamweaver no trae ningún captcha integrado. Supongo que será una extensión de terceras partes. Parece que wordpress se ha comido y modificado algo de código que has escrito, pero creo que de todas formas se entiende.
Por lo que veo, el código sólo genera el captcha, lo asigna a la variable $code y lo guarda también en la variable de sesión “captcha_code”.
Lo que tienes que hacer es comprobar en send.php que se recibe el post de “código de seguridad” y corresponde con la variable de sesión ‘captcha_code’. En caso afirmativo procesa el formulario, y si no es así muestra un mensaje de error.
Te lo indico así por mantener la estructura de lo que has hecho, pero particularmente creo que algo como esto es sencillo de resolver en una sola página. El captcha que he citado en comentarios anteriores trae ejemplos de cómo hacerlo y además es mucho más seguro y versátil.
Hola develooping, primero y principal… mil gracias por haber respondido tan inmediatamente mi consulta, ya estaba medio acobardado con hacer preguntas, porque hace unos días consulté por primera vez en el foro de phpBB-es por un problema que tengo en la base de datos y lo que recibí fue una amonestación de un moderador porque la pregunta estaba mal hecha, y que a las cinco amonestaciones me echaban del foro. Pero bueno, mi tema lo solucioné buscando y buscando por internet hasta que di con la página de un programador de Galicia que tenía lo que necesitaba, asi que lo adapté a mi página y listo, al Dreamweaver para poder insertar captcha se le instala el “captcha_image”, pero no se bien como va, ahora seguiré renegando un poco para crear un registro y reconocimiento de usuarios. Vuelvo a reiterar, mil gracias por contestar tanto para ti como para todos los que escriben en este post. Saludos.
Estoy vuelto loco con esto de los spams que se van por mi formulario, quisiera preguntar si el método que aparece en algunos formularios, en donde hay ciertos caracteres que el usuario tiene que escribir forzosamente para poder continuar o enviar el formulario, sirve para este fin. y si así es, cómo se puede realizar dicha operación, o existe alguna otra herramienta?…
gracias de antemano
Hola Manuel.
En esta entrada y varios comentarios de la misma se describen métodos para implementar un captcha y para evitar el spam, pero estos sólo son válidos si el formulario que citas se procesa mediante php. Es bastante posible que así sea pues es un lenguaje muy extendido.
El captcha (mostrar un gráfico con letras y números para asegurarse de que el formulario está siendo enviado por un humano) podría ser suficiente, aunque para más seguridad tampoco estaría de más alguno de los métodos descritos para filtrar encabezados no deseados.
Por supuesto, para implementar estos métodos deberás tener unas nociones, aunque sean muy básicas, de php.
Muy buen artículo, gracias.
¿Podrían utilizarse sesiones para evitar el spam?
Es decir, en la pagina principal de nuestro sitio web arrancamos una sesion con un nombre determinado. En la pagina de codigo php que envía el mail (p.ej. enviaMail.php), se verifica si la sesión está activa, y ademas crea una variable de sesión cuando se envía el primer mail del tipo timestamp, de forma que registra el momento en que se envió. Si se intenta enviar de nuevo, se compara la variable de sesión con la hora actual del sistema, y si es menor de, por ejemplo, 10 minutos, impide que se envíe un nuevo mail.
La verdad esque no controlo mucho de sesiones y no se si esto podría tener alguna vulnerabilidad, pero me ha parecido una buena solución (además de la detección de saltos de línea y otras soluciones que habéis dado por ahi).
Os agradecería algún comentario al respecto.
Un saludo, y gracias de nuevo.
Juan
Hola Juan.
Posiblemente el método que cuentas pueda evitar de forma más que razonablemente segura que se reenvíe un formulario, de hecho se parece mucho a lo que indiqué en el comentario 23, pero no necesariamente el uso del mismo para el envío de spam, salvo que lo acompañes de alguna medida para tal fin.
Ten en cuenta que el método de inyección de encabezados consiste en insertar cabeceras al formulario que envía el correo, de forma que es perfectamente posible enviar un correo a cientos de destinatarios sin necesidad de tener que enviarlo más que una vez.
Hola.
Sufro del mismo problema que muchos han declarado aqui. Soy diseñador web y no tengo un conocimiento muy avanzado en programación. Buscando en la web vi diversas maneras de evitar el spam en formularios y encontré una que parecia interesante: ocultar un campo de formulario que las personas no puedan ver para generar un filtro, ya que los spambots rellenan todos los campos (visibles o no visibles). Esto es interesante porque rápidamente determinas si el formulario fue llenado por una persona o por un robot. La pregunta que tengo es: alguien conoce un script en PHP para determinar que si el mencionado campo fue llenado, no se procese la solicitud de post o se descarte el mensaje?
Gracias anticipadas!
Saludos!!
Jorge
Hola Jorge. No estoy muy seguro de que el método que propones sea muy eficaz. Al menos no es a prueba de fuego y es sencillo de engañar.
De todas formas, aunque insisto en que no me parece fiable, es también muy sencillo hacer lo que dices y modificar cualquier script para enviar e-mail con PHP para que no lo haga si se ha mandado un valor cualquiera en un campo oculto que hemos puesto como “trampa” (¿He dicho ya que no me parece un método seguro?)
Si el campo oculto se llama “address” con el valor “el_jefe@de_esto.com” (nota que le pongo un nombre que pretende reforzar el engaño), sería simple colocar una condición para que en caso de que ese campo me llegue con valor distinto, no procese el formulario. En este caso bastaría con añadir a cualquier script PHP que procese y envíe lo siguiente
if(€_POST['address']!="el_jefe@de_esto.com"){//el campo viene relleno
echo "ay, ay, ay, pichoncillo";
}else{
//aqui va el código que procesa el formulario
/////THE END
}
Nota que que usado el signo del euro en lugar del dolar, pero es que no conseguía que Wordpress se lo tragara sin rechistar. Será que no querrá que use el formulario para inyectar scripts ;-). Claro que tal y como está el cambio sales ganando
Introducir un poco de PHP que requiera el envío de un post siempre es una buena solución. Sin necesidad del captcha ahora se estila también el poner preguntas sencillas de lógica o texto que hagan mostrarse los emails… tipo “3 5″ o “Escribe mostras emails”. En este link hay un codigo sencillo de manejar:
http://mikecherim.com/gbcms_xml/news_page.php?id=17#n17
Disculpen, pero yo recién estoy empezando y no quisiera que esto me pasara, así que para evitar confusiones. Podrían poner directamente cómo quedaría el formulario con las modificaciones para evitar el spam? o si la verificación va en otro lado, dónde sería? Muchas gracias.
Es muy interesante todo y trate de leerlo con mucha atención pero sigo sin entender NADA!!
estoy sufriendo en este momento de este problema y no se donde meter los cambios que proponene para evitar el “injection” me levantaron el formulario del servidor por estar enviando SPAM… les dejo el PHP para ver si algun alma caritativa me dice como modificarlo, (es evidente que de PHP, … nada)
mil Gracias a todos !!!!!!!!!!!!!
que tal este: Asumiendo que la direccón legítima a la que se desea enviar el correo sea pollo@dominio.com
//… todo el cod. del form
if ($destinatario != “pollo@dominio.com”){
echo “te pille manoseando mi form”;
}else{
// código que envia el form…
El tema que tratáis aquí es muy importante, sin embargo me gustaría preguntar: cuando el formulario está conectado a una base de datos en lugar de a una dirección de correo, ¿qué precauciones conviene adoptar?
Si utilizas un formulario para insertar datos en una base de datos, las precauciones han de extremarse para evitar ‘SQL injections’, que basicamente consiste en la introducción a través del formulario de datos maliciosos que rompan o vulneren o cambien el comportamiento de tu aplicación y tu integridad de datos. Sin entrar en muchos detalles, porque eso sería tema para un post en si mismo, pero básicamente siempre tienes que filtrar y validar los datos introducidos por el usuario. Filtrar para limpiar caracteres de escape como las comillas, segundo validar, para asegurarte que los datos introducidos se ajustan a los que tu aplicación espera, y muy importante, “escapar” los datos antes de introducirlos a la BBDD. Esto es, convertir los caracteres maliciosos como las comillas, barras, punto y coma, etcc, a un equivalente xhtml o encerrados entre comillas para que no puedan romper tus sentencias SQL. Espero haberte dado alguna “idea” genérica de las precauciones a tomar.