Questo tutorial propone un’alternativa all’utilizzo dei captcha per prevenire lo spam dei software automatici.
La tecnica “honeypot” (barattolo di miele), rivista e corretta in diversi aspetti, è conosciuta da tempo ma spesso viene poco considerata. Non si garantisce l’eliminazione totale dei messaggi indesiderati – traguardo impossibile vista l’esistenza degli spammer umani – ma in determinati contesti e situazioni si possono raggiungere ottimi risultati.
L’esperimento vuole essere un tentativo di andare incontro agli utenti senza obbligarli, come avviene oggi, a sforzi cognitivi superflui. Di contro, come è giusto che sia, si spostano sul fornitore del servizio tutti gli òneri relativi ai controlli.
Un punto deve essere chiaro: la soluzione definitiva contro lo spam ancora non esiste e, di certo, non si sta promettendo di riuscire a trasformare il piombo in oro.
Se non desideri leggere i vari capitoli del tutorial puoi passare direttamente all’esempio finale [1].
Indice
- Introduzione [2]
- Ambiti di utilizzo [3]
- Tipi di bot [4]
- La tecnica “honeypot” [5]
- Esempio n° 1 [6]
- Esempio n° 2 [7]
- Esempio n° 3 [8]
- Esempio n° 4 [9]
- Download degli esempi [10]
Introduzione
L’utilizzo dei captcha è stato, per diversi anni, il metodo più efficace per contrastare i messaggi dei programmi automatici. Con il passare del tempo, però, gli spammer si sono evoluti e con loro anche i software dedicati.
Per prevenire la lettura delle immagini da parte dei bot è iniziata una corsa alla creazione di codici sempre più distorti, complessi e criptici. Il risultato ottenuto è sicuramente un abbattimento dello spam ma anche dell’usabilità relativa ai moduli (contatti, guestbook, registrazione etc etc). Gli utenti, costretti a decifrare parole spesso impossibili, hanno visto allungarsi i tempi per raggiungere il proprio obiettivo e, in alcuni casi, è stato addirittura impossibile raggiungerlo (pensiamo a visitatori con particolari deficit visivi).
Dal 2005, oltre ai captcha, è disponibile anche Akismet [11], un servizio che analizza i messaggi con dei filtri euristici e riesce ad individuare i tentativi di spam; i risultati sono ottimi nella maggior parte dei casi. Akismet viene spesso indentificato esclusivamente come plugin di WordPress ma in realtà è possibile collegarlo a qualsiasi modulo. Il servizio è gratuito per uso personale e a pagamento negli altri casi. È efficace soprattutto quando il modulo contiene una textarea per i messaggi, meno negli altri casi (moduli di registrazione).
Questo tutorial parte dai seguenti presupposti:
- in questo momento storico lo spam non può essere eliminato del tutto. Già solo l’esistenza di persone fisiche che dedicano il loro tempo a riempire moduli sparsi per il web vanifica ogni possibile controllo automatico attualmente conosciuto
- ad oggi non esiste un metodo “assoluto” che, da una parte, tuteli gli utenti permettendo loro un invio di dati semplice e, dall’altra, blocchi i messaggi indesiderati
- Akismet è un ottimo servizio ma è efficace per individuare i messaggi di spam. L’obiettivo della ricerca, invece, è di rendere semplici anche i form di registrazione, dove in realtà non è tanto necessario prevenire le email pubblicitarie, quanto la registrazione automatica di utenti.
- “progettare per gli utenti” è uno dei princìpi cardine su cui baso il mio lavoro. Per questo motivo viene spontaneo notare come la questione dello spam, invece che essere a carico del fornitore del servizio, grava da troppo tempo sugli utenti a cui spetta l’ònere della prova: dimostrare di essere umani. In realtà dovrebbe avvenire il contrario.
- a mio parere l’eliminazione di codici particolarmente complessi (vedi reCAPTCHA), a fronte di un sensibile miglioramento per gli utenti, vale l’aumento di qualche caso di spam. A questo aggiungo che un programmatore, se ha deciso fermamente di “bucare” un vostro modulo, state sicuri che ci riuscirà. Tra i vari fattori è necessario anche valutare quanto i vostri form siano interessanti da questo punto di vista.
- è un esperimento che vale la pena provare. Nel caso in cui fallisse, magari perchè il sito in oggetto è particolarmente appetibile, si può sempre ripristinare un codice di controllo. L’importante è averci provato.
Ambiti in cui tentare l’utilizzo di questa tecnica
Ogni sito ha le proprie caratteristiche. In alcuni casi una pagina poco frequentata (anche dalle persone in carne ed ossa) potrebbe non essere presa di mira dagli spammer; in altre situazioni invece ci si potrebbe trovare perennemente sotto attacco.
È necessario valutare di che tipo di modulo stiamo parlando.
Ipotizziamo un generico “contatti” contenente solo 3 campi: nome, email e messaggio. Su un form così semplice, per esempio, bisogna valutare attentamente il proprio lavoro; infatti, al posto di un sofisticato programma, basterebbe un operatore umano per inviare in poco tempo decine di messaggi. Senza controlli antiflooding anche la persona meno maliziosa potrebbe essere invogliata ad effettuare invii multipli.
Se invece ipotizziamo un form di registrazione con molti campi, con controlli client e server sulla formattazione di alcuni dati (indirizzo, telefono, data di nascita), potrebbe essere una buona idea scaricare l’utente dello sforzo aggiuntivo derivante dalla decodifica di un codice.
In definitiva: provare costa poco e la valutazione dei risultati spetta a voi che gestite il sito. In ogni caso, se lo ritenente opportuno, uno script captcha è sempre pronto per essere implementato.
Tipi di bot
Sostanzialmente possiamo distinguere 3 tipi di strumenti per creare spam: i “playback bots“, i “formfills bots” e… gli umani.
Ogni programma è generalmente costruito ad hoc con caratteristiche peculiari che possono variare anche di molto da software a software. Ne esistono di semplici e di più evoluti; in testa a tutti, anche se non sempre è scontato, ci sono gli operatori in carne ed ossa.
Trovare un metodo che li contrasti tutti è quasi impossibile dato che, come già detto, non esiste una casistica certa e se lo spammer ha deciso di “bucare” il vostro modulo… bhè: sappiate che ci riuscirà. Meglio puntare a bloccare la massa.
Playback bots
Questo tipo di bot viene lanciato dopo che un operatore ha compilato la prima volta il modulo, “registrando” i campi da riempire. Al successivo passaggio il software prenderà come riferimento l’url di invio del form ed inizierà ad inviare dati secondo lo schema preparato inizialmente.
Formfills bots
Sono i bot più comuni: una volta trovato un modulo riempiono i campi che trovano al suo interno e inviano i dati.
Operatori umani
Può sembrare un paradosso associare “bot” e “umani” nella stessa frase ma queste persone si comportano esattamente da robot: trovano un modulo, lo riempiono di spam ed inviano dati. L’aggravante è che sono, o dovrebbero essere, intelligenti e quindi sono in grado di superare captcha, indovinelli e quiz.
Honeypot: come funziona la trappola del barattolo del miele
La soluzione proposta, conosciuta da tempo, presuppone che molti dei bot in circolazione siano “stupidi“, ovvero non siano capaci di interpretare nè javascript e nè i fogli di stile. Sulla base di questo presuntuoso assunto, inseriremo nel modulo un campo non visibile all’occhio umano (“invisibile” è diverso da “nascosto”).
Nella maggior parte dei casi, infatti, un programma automatico non sarà in grado di distinguere tutti gli attributi di un elemento e, proprio per essere sicuro di passare l’eventuale validazione, tenderà a riempire tutti i campi che incontra, compreso quello “civetta”.
È su questo principio che si basa la nostra trappola. All’invio dei dati faremo un semplice controllo: se il campo invisibile è stato riempito allora, quasi sicuramente, è stato utilizzato un robot automatico. Ecco spiegata la semplice tecnica del barattolo di miele (“honeypot trap”).
Torno a ripetermi: cerchiamo di combattere la massa, non i singoli “draghi di fine livello“. Un bot programmato ad hoc sul nostro modulo sarà in grado, con più o meno difficoltà, di passare tutti i possibili controlli.
Ora che sappiamo, seppur superficialmente, chi è il nostro nemico, passiamo alla difesa. La tecnica è adattabile sia a semplici moduli di invio dati che a form di registrazione. Per il tipo di campi richiesti, il primo caso si presta in maniera naturale ad un attacco spam. Nella seconda ipotesi, invece, è probabile che vengano tentati invii massivi per iscrivere nuovi utenti ma, se il vostro sistema prevede anche la conferma dell’indirizzo email, potrete gestire la situazione senza grossi problemi. È ovvio che un controllo periodico sui sistemi sarà indispensabile, così da poter intervenire se necessario.
Il tutorial illustra 4 possibili step, caratterizzati da una crescente complessità applicativa. Più si sale e più è necessario inserire controlli. Tutti gli esempi sono in php ma, ovviamente, è possibile “tradurli” in qualsiasi altro linguaggio.
Esempio n° 1
È la soluzione base. Volutamente, per semplicità d’uso, non utilizza cookie, sessioni o database per salvare o rendere random alcuni dati. Su siti non troppo “in vista” dovrebbe fornire una discreta protezione purchè si inserisca almeno un controllo antiflooding.
Vediamo il codice da inserire in testa alla pagina del modulo per creare un paio di variabili che ci serviranno (vedremo perchè e come):
<?php
/*
* Filename: antispam-form1.php
*/
$time = time();
?>
Utilizziamo qualche linea di javascript (jQuery richiesto) nell’head della pagina:
il form, invece, è così costruito:
Vediamo nel dettaglio la struttura della pagina.
Con “time()” otteniamo la data di caricamento del modulo (in secondi). Il valore di $time è quindi utilizzato per rendere univoco, ad ogni caricamento, l’id del form e le classi dei vari elementi. Abbiamo introdotto il prefisso “form_” e “class_” perchè, secondo le specifiche w3c, un id o una classe non possono iniziare con un numero. Infine, se non realmente necessario, eliminiamo l’attributo name.
Creare un oggetto univoco ad ogni richiesta rende più difficile ad un bot indentificare gli elementi. È solo il primo passo per creare qualche ostacolo.
Nel campo nascosto “formtime” salviamo la data di caricamento della pagina. Ci servirà per il controllo antiflooding.
Strutturate il modulo secondo le vostre esigenze (ho usato campi in inglese solo a scopo dimostrativo). Volutamente sono stati inseriti degli elementi contenitori (div) con classe “class_<?php echo $time; ?>”. L’obiettivo è rendere abbastanza simile l’intera struttura del form con quella del campo “honeypot”.
attenzione! lasciare vuoto questo campo
attenzione! lasciare vuoto questo campo
La trappola vera e propria, il campo invisibile in grado di attirare il bot. Notiamo subito che l’elemento è contenuto in un div nascosto attraverso fogli di stile; la classe “special-container” non è altro che:
.special-container { display: none; }
Questi accorgimenti dovrebbero rendere più difficile al software capire quali campi siano nascosti e quali no. In più, per complicare ulteriormente le cose, la classe css fondamentale dovrebbe essere inserita in un file esterno in modo che non sia deducibile da una semplice analisi del codice sorgente.
Sebbene l’elemento invisibile si chiami “name” ho notato, dai log ottenuti su siti di prova, che il nome del campo è assolutamente indifferente; “name” non è tanto meglio di “pippo” o di “pluto”. Ciò che invece fa differenza è il tipo di input: da preferirne assolutamente uno di tipo “text” o una “textarea“.
Il messaggio di avviso per gli utenti umani è utile nei rari casi in cui l’utente abbia i css disabilitati (nel 2012?) o, più verosimilmente, siano saltati i fogli di stile per un qualsiasi errore tecnico.
In testa alla pagina abbiamo utilizzato jQuery per disabilitare l’autocomplete sul campo “honeypot”.
$('#myinput').attr("autocomplete","off");
Questa caratterista è necessaria perchè bisogna tener conto di quei browser che hanno attiva la funzionalità di autofill. In alcuni casi, infatti, potrebbe essere riempito il campo invisibile in buona fede e senza che l’utente se ne sia reso conto. È stato utilizzato javascript per due motivi:
- “autocomplete=off” è un attributo valido solo per html5 (non esiste in xhtml)
- ancora una volta: utilizzare javascript rende più difficile la vita al bot che dovrebbero avere attivo un parser per capire di che campo si tratti
La pagina di invio
La pagina di invio prevede delle verifiche sui campi in arrivo. Il codice è stato volutamente scritto separando ogni controllo per illustrare meglio i passi eseguiti; anche in caso di spam conclamato verrà inviata una email a scopo di debug. È ovvio che un codice di produzione dovrà essere rivisto in maniera più razionale.
<?php
/*
* Filename: result1.php
*/
$now = time();
$minsec = 2;
// il metodo di invio è post
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// esiste il campo con la data e non è vuoto
if (!empty($_POST['formtime'])) {
// il campo con la data è numerico
if (is_numeric($_POST['formtime'])) {
// la data di invio è futura rispetto a quella del form
if ($now > $_POST['formtime']) {
// il tempo trascorso tra il caricamento e l'invio è maggiore di n secondi
if (($now - $_POST['formtime']) > $minsec) {
// il campo fake è vuoto
if (isset($_POST['name']) && empty($_POST['name'])) {
$result = "messaggio scritto da umani";
} else {
$result = "bot: riempito campo fake o mancante";
}
} else {
$result = "bot: troppo veloce - tempo di invio: " .($now - $_POST['formtime']). " secondi";
}
} else {
$result = "bot: troppo veloce - tempo: futuro";
}
} else {
$result = "bot: time manomesso";
}
} else {
$result = "bot: manca timestamp";
}
} else {
$result = "bot: la request è diversa da POST";
}
/**************** INVIO EMAIL ******************/
$dest = "tuaemail@provider.com";
$headers = "X-Mailer: PHP ".phpversion()."\n";
$headers .= "From: $_POST[email] <$_POST[email]>\n";
$headers .= "Return-Path: $dest <$dest>\n";
$headers .= "Reply-To: $dest <$dest>\n";
$headers .= "Date: ".date("H:i:s")." ".date("d/m/Y")."\n";
$headers .= "Delivered-to: $dest <$dest>\n";
$headers .= "MIME-Version: 1.0\n";
$corpo = "ora invio: " .$now. "\n ora form: " .$_POST['formtime']. "\r\n" .stripslashes($_POST["message"]);
if (isset($_POST["name"]) && !empty($_POST["name"])) {
$corpo .= "\r\n honeypot:" .stripslashes($_POST["name"]);
}
//invio dell'email
mail($dest,$_POST['host']." ".$result,$corpo,$headers);
echo $result;
?>
La variabile “$minsec” indica i secondi che devono passare tra la visualizzazione del form e l’invio. Il valore ottimale si ottiene valutando caso per caso: è ovvio che su un modulo di un solo campo senza validazione obbligatoria, anche 1 secondo potrebbe generare molti falsi positivi. In genere 2 secondi dovrebbe essere un buon compromesso.
I successivi controlli sono abbastanza chiari. Sostanzialmente si verifica che:
- il metodo di invio sia POST
- il campo “formtime” esista e non sia vuoto
- il campo “formtime” sia numerico
- il campo “formtime” sia inferiore alla data di invio
- il tempo trascorso tra la visualizzazione del form e l’invio non deve essere inferiore a “$minsec”
- il campo “name” deve esistere ed essere vuoto
Superati questi controlli allora si può procedere con l’invio del modulo.
Prima di passare allo step successivo, alcune considerazioni su questo esempio.
Il beneficio principale è, probabilmente, la velocità di implementazione. I controlli inseriti sono banali e non richiedono molta logica. Di contro, a tanta semplicità, si contrappone una situazione abbastanza vantaggiosa per uno spammer professionista che, con un po’ di pazienza e con il tasto “indietro”, potrà effettuare invii multipli senza troppe difficoltà. Tra l’altro il non aver reso random il nome del campo “honeypot” di certo favorisce i software malintezionati.
È necessario, quindi, introdurre qualche controllo aggiuntivo.
Esempio n° 2
Codice da inserire in testa alla pagina:
<?php
/*
* Filename: antispam-form2.php
*/
$salt = 'parola-segreta';
$rchar = chr(97 + mt_rand(0, 25));
$time = time();
$hash_real = md5($salt.$time.$rchar);
$hash_fake = md5($salt.($time+rand(1,999)).$rchar);
$code_real = $rchar.$hash_real;
$code_fake = $rchar.$hash_fake;
setcookie('hashcode',$hash_real, 0, '/');
?>
Utilizziamo jQuery nell’head della pagina:
il form, invece, è così costruito:
Vediamo nel dettaglio come è stata strutturata la pagina.
Rispetto all’esempio precedente salta subito all’occhio che, una volta ottenuto un valore random con “time()”, lo codifichiamo con un salt (valore pseudo casuale) a cui aggiungiamo anche una singola lettera che analizzeremo tra poco. Sostanzialmente stiamo creando un controllo di sicurezza che sfrutta il confronto tra codici. Ciò dovrebbe rendere più difficile falsificare i moduli.
Il passo successivo è ottenere due codici simili (ma necessariamente diversi) per distinguere la classe del campo invisibile da tutte le altre.
A tal proposito aggiungiamo un numero random, compreso tra 1 e 999, ad una delle due variabili derivanti da “time()”. “$time_fake” verrà utilizzata per rendere univoco, ad ogni caricamento, l’id del form e le classi dei vari elementi (tranne il campo invisibile). “$time_real”, invece, ci servirà per l’honeypot e per ricostruire e confrontare i codici di controllo.
Nell’esempio n°1 abbiamo inserito i prefissi “form_” e “class_” dato che le specifiche vietano i numeri come primo carattere. Per risolvere anche questo problema (e rendere definitivamente random gli id, le classi e i nomi degli elementi) utilizzeremo una lettera casuale da usare come primo carattere con la funzione:
$rchar = chr(97 + mt_rand(0, 25));
In buona sostanza, con il codice visto, otterremo un form molto simile a questo:
L’obiettivo era rendere il modulo meno “schematico” possibile, in modo che fosse difficile basarsi su modelli fissi. Abbiamo cercato di rendere randomici più elementi possibili. Volendo potrebbe essere fatto ancora molto, magari creando id univoci anche per i campi “email” e “subject”.
Ovviamente tutto ciò che è random adesso va “memorizzato” per poterlo confrontare nella pagina di invio. Per questo motivo abbiamo introdotto l’utilizzo dei cookie. Al caricamento della pagina salviamo un cookie di sessione con $hash_real come contenuto. Questo dato non è comunque sufficiente per effettuare le verifiche. Abbiamo necessità anche di conoscere l’orario in cui è stato stampato il modulo, la lettera random usata e, ovviamente, la parola segreta.
Il primo degli elementi mancanti lo otteniamo dal campo hidden “formtime”. Anche la lettera casuale ci viene fornita da un elemento nascosto ma questa volta viene stampato attraverso jQuery.
Infine usiamo del codice javascript per nascondere il div “honeypot” che ha, rispetto al primo esempio, una classe random.
Questi due espedienti, seppur banali, implicano che il bot abbia javascript abilitato altrimenti l’elemento “rchar” non sarà presente nella pagina di invio e le operazioni si bloccheranno. A questo aggiungiamo che è necessario che il client sia in grado di accettare i cookie altrimenti si verificherà un errore.
Ecco spiegate in sintesi queste poche righe:
$('#<?php echo $code_real; ?>').attr("autocomplete","off");
$('.<?php echo $code_real; ?>').css('display','none');
$('#formtime').append('');
Come prima è fondamentale l’attributo autocomplete su off.
La pagina di invio
Rispetto all’esempio precedente, la pagina di invio prevede delle verifiche aggiuntive. Anche in questo caso il codice è stato scritto separando ogni controllo per illustrare meglio i passi eseguiti; ogni volta verrà inviata una email a scopo di debug.
<?php
/*
* Filename: result1.php
*/
$salt = 'parola-segreta';
$minsec = 1;
$now = time();
// il metodo di invio è post
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// esiste il campo con il carattere random e non è vuoto
if (!empty($_POST['rchar'])) {
// esiste il campo con la data e non è vuoto
if (!empty($_POST['formtime'])) {
// il campo con la data è numerico
if (is_numeric($_POST['formtime'])) {
// la data di invio è futura rispetto a quella del form
if ($now > $_POST['formtime']) {
// il tempo trascorso tra il caricamento e l'invio è maggiore di n secondi
if (($now - $_POST['formtime']) > $minsec) {
// esiste il cookie
if (isset($_COOKIE['hashcode'])) {
// se il contenuto del cookie è uguale alla chiave generata con il salt
if ($_COOKIE['hashcode'] == md5($salt.$_POST['formtime'].$_POST['rchar'])) {
// ho i dati per comporre il nome del campo honeypot
$honeypot = $_POST['rchar'].$_COOKIE['hashcode'];
// il campo fake esiste ed è vuoto
if (isset($_POST[$honeypot]) && empty($_POST[$honeypot])) {
$result = "messaggio scritto da umani";
} else {
$result = "bot: riempito campo fake o mancante";
}
} else {
echo "codice cookie: " .$_COOKIE['hashcode'];
echo "
";
echo "codice generato: " .md5($salt.$_POST['formtime'].$rchar);
echo "
";
$result = "bot: il contenuto del cookie non corrisponde al codice generato";
}
} else {
$result = "bot: non esiste il cookie";
}
} else {
$result = "bot: troppo veloce - tempo di invio: " .($now - $_POST['formtime']). " secondi";
}
} else {
$result = "bot: troppo veloce - tempo di invio: futuro";
}
} else {
$result = "bot: time manomesso";
}
} else {
$result = "bot: manca timestamp";
}
} else {
$result = "bot: manca il campo rchar";
}
} else {
$result = "bot: la request è diversa da POST";
}
/**************** INVIO EMAIL ******************/
$dest = "miaemail@provider.com";
$headers = "X-Mailer: PHP ".phpversion()."\n";
$headers .= "From: $_POST[email] <$_POST[email]>\n";
$headers .= "Return-Path: $dest <$dest>\n";
$headers .= "Reply-To: $dest <$dest>\n";
$headers .= "Date: ".date("H:i:s")." ".date("d/m/Y")."\n";
$headers .= "Delivered-to: $dest <$dest>\n";
$headers .= "MIME-Version: 1.0\n";
$corpo = "ora invio: " .$now. "\n ora form: " .$_POST['formtime']. "\r\n" .stripslashes($_POST["message"]);
if (isset($honeypot) && !empty($_POST[$honeypot])) {
$corpo .= "\r\n honeypot:" .stripslashes($_POST[$honeypot]);
}
//invio dell'email
mail($dest,$result,$corpo,$headers);
setcookie('hashcode','', -1000, '/');
echo $result;
?>
Come valore di “$salt” ricordiamoci di inserire la stessa parola chiave usata nella creazione del form. La variabile “$minsec” indica i secondi che devono passare tra la visualizzazione del form e l’invio.
In questo secondo esempio i controlli sono maggiori rispetto a prima ma sempre molto chiari. Sostanzialmente si verifica che:
- il metodo di invio sia POST
- il campo “rchar” esista e non sia vuoto. ciò presuppone javascript attivo
- il campo “formtime” esista e non sia vuoto
- il campo “formtime” sia numerico
- il campo “formtime” sia inferiore alla data di invio
- il tempo trascorso tra la visualizzazione del form e l’invio non deve essere inferiore a “$minsec”
- il cookie di controllo esista
- il valore del cookie di controllo sia uguale al codice md5 dei seguenti campi concatenati: $salt, $_POST[‘formtime’] e $_POST[‘rchar’]
- il campo honeypot deve esitere ma essere vuoto
Il nome del campo invisibile, essendo random, non è deducibile a priori. Si ottiene semplicemente unendo i dati in nostro possesso:
$honeypot = $_POST['rchar'].$_COOKIE['hashcode'];
Superati questi controlli allora si può procedere con l’invio del modulo e con la cancellazione del cookie che dovrebbe evitare l’utilizzo del tasto indietro per effettuare un nuovo submit.
Questo secondo esempio, con qualche controllo in più, è decisamente più efficiente del primo. La chiave di volta, secondo me, è l’utilizzo del campo “rchar” stampato via jQuery e l’utilizzo del cookie che permette di rendere randomici molti elementi.
Dai test effettuati il risultato è ottimo. Probabilmente sono stati effettuati attacchi con bot “stupidi” ma la maggior parte di loro sono stati fermati dalla mancanza del campo “rchar”. Ciò significa che non avevano javascript attivo.
Esempio n° 3
Il terzo step proposto è sostanzialmente identico al secondo ma il codice hash, invece che nel cookie, viene salvato nel database. Ciò implica un maggior lavoro di implementazione ma forse apporta un maggior grado di sicurezza. Si potrebbero anche usare le sessioni ovviamente ma solo se la vostra architettura non prevede un cluster di server.
Per il resto la logica ed il codice sono identici all’esempio numero 2.
Esempio n° 4
Gli esempi appena proposti non fermano i cosìdetti “Formfills bot”, ovvero quelli che ripetono una procedura registrata.
Anche dando un nome completamente random ai campi di testo (un codice al posto di “email”, “messaggio” etc etc) un tool come Selenium è in grado di individuare gli input da riempire basandosi sulla gerarchia degli elementi. Per risolvere anche questo inconveniente sfruttiamo l’evento “keypress”. Selenium, infatti, non permette l’intercettazione dei tasti sulla tastiera e quindi siamo in grado di distinguere tra un modulo riempito a mano ed uno in automatico.
Codice da inserire in testa alla pagina:
<?php
/*
* Filename: antispam-form4.php
*/
session_start();
$salt = 'parola-segreta';
$rchar = chr(97 + mt_rand(0, 25));
$time = time();
$hash_real = md5($salt.$time.$rchar);
$hash_fake = md5($salt.($time+rand(1,999)).$rchar);
$code_real = $rchar.$hash_real;
$code_fake = $rchar.$hash_fake;
$_SESSION["hashcode"] = $hash_real;
// oppure usiamo i cookie con: setcookie('hashcode',$hash_real, 0, '/');
?>
Utilizziamo jQuery nell’head della pagina:
Aggiungiamo, al codice già visto, 2 nuovi funzioni:
Vediamo cosa fanno nel dettaglio le 2 funzioni:
$('#subject').bind('copy paste', function(e) {
e.preventDefault();
});
$('#subject').keypress(function() {
if ($('#<?php echo $rchar; ?>_control').length == 0) {
$('#subject').append('');
$('#<?php echo $rchar; ?>_control').val('<?php echo $code_real; ?>');
}
});
Sono agganciate al campo “subject” ma possiamo scegliere il campo che preferiamo (ottimo il campo “email” se presente).
Prima di tutto impediamo il “copia/incolla” su quel campo perchè, se venisse usata la funzione “incolla” con il tasto destro del mouse, non verrebbe intercettato l’evento “keypress”.
Il modulo html, invece, non subisce cambiamenti rispetto agli esempi precedenti.
Una volta intercettato l’evento “keypress” (valido anche su tastiere virtuali di smartphone e tablet) viene inserito un campo che ha, come nome, il carattere di controllo più un suffisso e, come valore, il codice hash generato in precedenza.
L’ulteriore controllo, infatti, si basa proprio sul fatto che i tool automatici non scatenano l’evento keypress e questo ci permette di inserire un ulteriore “checkpoint” sulla presenza o meno del campo “control”.
Vediamo la pagina di invio:
<?php
/*
* Filename: result4.php
*/
session_start();
$salt = 'parola-segreta';
$minsec = 1;
$now = time();
// il metodo di invio è post
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// esiste il campo con il carattere random e non è vuoto
if (!empty($_POST['rchar'])) {
// esiste il campo con la data e non è vuoto
if (!empty($_POST['formtime'])) {
// il campo con la data è numerico
if (is_numeric($_POST['formtime'])) {
// la data di invio è futura rispetto a quella del form
if ($now > $_POST['formtime']) {
// il tempo trascorso tra il caricamento e l'invio è maggiore di n secondi
if (($now - $_POST['formtime']) > $minsec) {
// esiste la sessione
if (isset($_SESSION['hashcode'])) {
// se il contenuto della sessione è uguale alla chiave generata con il salt
if ($_SESSION['hashcode'] == md5($salt.$_POST['formtime'].$_POST['rchar'])) {
// ho i dati per comporre il nome del campo honeypot
$honeypot = $_POST['rchar'].$_SESSION['hashcode'];
// ho i dati per comporre il campo control
$idcontrol = $_POST['rchar']."_control";
// controllo l'esattezza del campo control
if (!empty($_POST[$idcontrol]) && $_POST[$idcontrol] == $honeypot) {
// il campo fake esiste ed è vuoto
if (isset($_POST[$honeypot]) && empty($_POST[$honeypot])) {
$result = "messaggio scritto da umani: $_POST[$idcontrol]";
} else {
$result = "bot: riempito campo fake o mancante";
}
} else {
$result = "bot: non esiste il campo control";
}
} else {
echo "codice sessione: " .$_SESSION['hashcode'];
echo "
";
echo "codice generato: " .md5($salt.$_POST['formtime'].$_POST['rchar']);
echo "
";
$result = "bot: il contenuto della sessione non corrisponde al codice generato";
}
} else {
$result = "bot: non esiste la sessione";
}
} else {
$result = "bot: troppo veloce - tempo di invio: " .($now - $_POST['formtime']). " secondi";
}
} else {
$result = "bot: troppo veloce - tempo di invio: futuro";
}
} else {
$result = "bot: time manomesso";
}
} else {
$result = "bot: manca timestamp";
}
} else {
$result = "bot: manca il campo rchar";
}
} else {
$result = "bot: la request è diversa da POST";
}
/**************** INVIO EMAIL ******************/
$dest = "miaemail@provider.com";
$headers = "X-Mailer: PHP ".phpversion()."\n";
$headers .= "From: $_POST[email] <$_POST[email]>\n";
$headers .= "Return-Path: $dest <$dest>\n";
$headers .= "Reply-To: $dest <$dest>\n";
$headers .= "Date: ".date("H:i:s")." ".date("d/m/Y")."\n";
$headers .= "Delivered-to: $dest <$dest>\n";
$headers .= "MIME-Version: 1.0\n";
$corpo = "ora invio: " .$now. "\n ora form: " .$_POST['formtime']. "\r\n" .stripslashes($_POST["message"]);
if (isset($honeypot) && !empty($_POST[$honeypot])) {
$corpo .= "\r\n honeypot:" .stripslashes($_POST[$honeypot]);
}
//invio dell'email
mail($dest,$result,$corpo,$headers);
setcookie('hashcode','', -1000, '/');
echo $result;
?>
Ricostruiamo il nome del campo “control” mettendo insieme il carattere random e il suffisso “_control”. Una volta fatto questo siamo in grado di confrontare il valore del campo con il nostro codice hash. Se tutto coincide il form è valido e possiamo andare avanti con la validazione.
// ho i dati per comporre il campo control
$idcontrol = $_POST['rchar']."_control";
// controllo l'esattezza del campo control
if (!empty($_POST[$idcontrol]) && $_POST[$idcontrol] == $honeypot) {
// vado avandi con la validazione
}
Download esempi
Per scaricare gli esempi potete cliccare su antispam-form.zip [10].
Attendo eventuali commenti da chi ha deciso di provare.
2 Comments To "Rivisitazione della tecnica “honeypot” per moduli antispam senza captcha – UPDATE"
#1 Comment By jhonny On 24 Luglio 2012 @ 15:30
bell’articolo! complimenti!!!
#2 Comment By jhonny On 24 Luglio 2012 @ 15:32
l’ho provato e funziona bene come tecnica