Или еще раз возвращаясь к напечатанному. Сегодня поговорим о защите от спама.
В первоначально описанном способе копия был баг (промотайте в конец), из-за которого можно было легко и непринужденно загадить ящик получателя спамом, да еще и в автоматическом режиме. Отправить картинки, вирусы, или загадить чужой ящик не получится, но вот закидать «Войной и миром» ящик владельца сайта вполне таки да.
Чтобы уменьшить такую вероятность, добавим в скрипт отправки почты каптчу. Поскольку скрипт, отправляющий почту (mail.php
) к компонентам WordPress не относится, то каптчу придется изобретать свою. Лично мне это оказалось даже хорошо, т.к. совсем недавно я писал о создании ASCII-каптчи, копия, и мне прямо-таки жгло показать ее работу в реальном проекте, а не только в учебных примерах. Посему ее и используем для защиты от спама.
Переписать скрипт mail.php
так, чтобы он смог использовать ASCII-каптчу.
1. Пользователь в форме обратной связи, вводит сообщение.
2. По нажатию кнопки «Отправить» сообщение передается скрипту mail.php
3. Если сообщение было отправлено из формы, то скрипт генерирует каптчу, HTML-страницу, содержащую параметры сообщения (имя, текст, электронный адрес пользователя), ASCII-изображение каптчи, поле для ввода кода, элементы управления (кнопки) с помощью которых пользователь может ввести код, обновить код подтверждения, отправить код и сообщение.
4. Скрипт также должен обработать возможные ошибки. Если они есть, пользователю выводится соответствующее сообщение и страница с формой ввода сообщения открывается вновь.
5. Если каптча введена неверно, пользователю демонстрируется сообщение об ошибке ввода каптчи, и дается возможность повторить ввод каптчи. Информация в сообщении сохраняется.
6. Если код введен верно и другие ошибки отсутствуют, сообщение передается на заранее указанный в скрипте e-mail.
1. Условимся, что код каптчи будем отправлять с помощью cookie, примерно так, как это описано здесь, чтобы ненароком не поломать сессию движка WordPress.
2. Подключаем каптчу:
include ('captcha.php');
3. Пишем небольшую функцию, заменяющую перевод строки CR+LF
, который по стандарту должен поступать WWW-серверу из формы на HTML-тег <br>
(ниже скажу, где оно надо):
function br_repl($str) { return str_replace("\r\n","<br>",$str); }
Формирование кода каптчи, ее отображение в виде псевдографики, отображение пользователю введенных в форме отправки сообщения данных могут несколько раз повториться при работе скрипта. Соответственно, целесообразно объединить эти действия в одну функцию, которую позже вызывать, где надо.
function createform ($name, $email, $sub, $message, $allnum, $errcaptcha)
{
...
}
Функции передаются следующие параметры:
$name
(строка) — имя, которое ввел пользователь в форме отправки сообщений для связи с владельцем сайта.
$email
(строка) — e-mail пользователя
$sub
(строка) — тема сообщения
$message
(строка) — текст сообщения
$allnum
(массив строк) — массив, содержащий все ASCII-изображения цифр каптчи, копия.
$errcaptcha
(логическое значение) — флаг, установленный в истину (true
) означающий, что предыдущий код каптчи был введен неверно. Если флаг установлен в false
, форма отображается первый раз, попыток ввода каптчи не было. См. основную часть скрипта ниже.
Внутри функции:
1. Получаем код каптчи нужной длины (функция из файла captcha.php
, см. описание здесь копия):
$captchacode=getcaptchacode(6);
2. Отправляем пользователю cookie с MD5-хэшем сгенерированного кода и временем действия 5 минут.
setcookie('mycaptchamd5',md5($captchacode),time()+300);
3. Получаем псевдографическое изображение каптчи (функция из файла captcha.php
):
$captcha=getpgnum($captchacode,$allnum," ",1,false,false);
4. Проверяем флаг ошибки ($errcaptcha
), если он установлен, присваиваем переменной $emess
строку, содержащую соответствующее сообщение:
$emess="";
if ($errcaptcha)
{
$emess="<b><font color='red'>Проверочный код введен неверно</font></b>";
}
5. Возвращаем HTML-страницу с формой, содержащей все данные, ASCII-изображение каптчи и элементы управления:
return "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8'><style type='text/css'> TABLE { width: 300px; /* Ширина таблицы */ border-collapse: collapse; /* Убираем двойные линии между ячейками */ } TD, TH { padding: 3px; /* Поля вокруг содержимого таблицы */ border: 1px solid gold; /* Параметры рамки */ font: 12pt/10pt monospace; } #footer { position: fixed; /* Фиксированное положение */ left: 0; bottom: 0; /* Левый нижний угол */ padding: 10px; /* Поля вокруг текста */ width: 100%; /* Ширина слоя */ } </style><title>Отправка сообщения</title>< /head><body bgcolor='black' text='silver'><center><h2>Отправка сообщения</h2><br> <b>Проверьте ваши данные и подтвердите, что вы не робот</b></center> <table align='center'> <tr><td>Name:</td><td>$name</td></tr> <tr><td>E-mail:</td><td>$email</td></tr> <tr><td>Subject:</td><td>$sub</td></tr> <tr><td colspan='2'><center>Message</center></td></tr> <tr><td colspan='2'>".br_repl($message)."</td></tr> <tr><td colspan='2'><center>Security code </center></td></tr> <tr><td colspan='2'><center><code><font color='lime'><pre>".$captcha." </pre></font></code></center></td></tr> <tr><td colspan='2'><center>$emess</center></td></tr>". "<tr><td colspan='2'><center><form action='".$_SERVER['PHP_SELF']."' method='POST'> <p><b>Введите проверочный код</b></br> <p><input type='text' name='captchacode'></p> <p> <input type='submit' name='checkcode' value='Проверить код'> <input type='submit' name='updcode' value='Обновить код'> </p> <input type='hidden' name='name' value='$name'> <input type='hidden' name='email' value='$email'> <input type='hidden' name='sub' value='$sub'> <input type='hidden' name='message' value='$message'> <input type='hidden' name='myself' value='true'> </form></center></td></tr> <tr><td colspan='2'><center><font color='#0099FF'>Для изменения данных вернитесь в форму отправки с помощью кнопки 'Назад' браузера</font></center></td></tr> </table></body></html> ";
После заполнения всех полей и нажатия кнопки «Отправить», пользователю будет отображена следующая форма:
Если пользователь введет каптчу с ошибкой, форма будет выглядеть таким образом:
Из кода формы должно быть понятно, что каптча обрабатывается тем же самым скриптом (<form action='".$_SERVER['PHP_SELF']."' method='POST'>
).
Особое внимание следует обратить на скрытые (hidden
) поля формы. Они хранят данные, переданные пользователем в форме ввода сообщения во время обработки.
Текст сообщения перед отображением обрабатывается ранее созданной функцией br_repl($message)
, чтобы правильно отобразить переносы строки.
Внимание! Скрытое поле myself
здесь тоже не просто так, хотя его значение жестко задано в true
, оно позволит в дальнейшем определить скрипту, откуда пришли обрабатываемые данные — из HTML-формы ввода сообщения или из самого скрипта после отправки кода каптчи или запроса на ее обновление.
Примечание: Средствами PHP обрабатываются две кнопки submit
с разными именами. Теория тут, копия. Практика будет ниже.
Итак, с функциями закончили, приступим к написанию основного тела скрипта.
Определим основные переменные:
$err=false; //статус ошибки
Переменная-флаг. Если в процессе выполнения скрипта будет найдена необрабатываемая ошибка, то флаг будет установлен в true, и далее скрипт получит уведомление об ошибке, соответствующе его обработав.
$usermessage=""; //сообщение, выводимое пользователю
С этой переменной интересно — весь вывод в HTML будет храниться в ней. Объясняю подробнее — если в своем скрипте мы хотим использовать редирект, то всю выводимую информацию нужно показать пользователю после отправки заголовка редиректа. Поэтому мы сначала формируем вывод в отдельной переменной, и только когда нужно выводим его пользователю.
Переменные, для формирования сообщения:
$address="admin@example.org"; //адрес куда отправляем
Примечание: значение переменной заменяем на свое.
$name=""; //имя пользователя
$email=""; //обратный адрес
$sub=""; //тема
$message=""; //сообщение
Переменные для управления редиректом (автоматическим открытием следующей страницы в процессе работы скрипта):
Надо или нет производить редирект:
$redir=false; //статус редиректа
На какую страницу следует отправлять пользователя:
$rediraddr="http://tolik-punkoff.com/obratnaya-svyaz/"; //адрес редиректа пользователя после отправки сообщения
Замените адрес >http://tolik-punkoff.com/obratnaya-svyaz/
на нужный вам.
Время до редиректа в секундах:
$redirtime=3; //время до редиректа (сек)
Собственно, критических ошибок, т.е. таких, случись которые, скрипт не сможет нормально работать, в нашем случае не так много:
1. Обращение к скрипту GET-запросом. Понятно, что скрипт обрабатывает данные из HTML-формы, передающиеся ему запросом POST. Если кому-то вдруг вздумалось вести адрес скрипта напрямую, например, в адресной строке браузера, то скрипту просто нечего будет обрабатывать.
2. Отсутствие необходимых данных (имени отправителя, сообщения, обратного адреса) в запросе POST. Это может быть связано, например, с ошибкой или опечаткой в коде формы ввода сообщения
3. Ошибка функции PHP mail()
.
Если эти ошибки произошли, то следует вывести пользователю соответствующее сообщение и переопределить его на заданную в переменной $rediraddr
страницу.
Возможную ошибку функции mail()
будем обрабатывать позже, в ходе основной работы скрипта.
Примечание: Неверный ввод каптчи критической ошибкой не является, в данном случае скрипт должен просто оповестить пользователя о вводе некорректного кода и сгенерировать новый код, не производя редирект, естественно, и сохранив данные введенные в форме отправки сообщения.
Итак, проверяем запрос:
if ((empty($_POST))||($_SERVER['REQUEST_METHOD']!='POST')) //запрос некорректный { $usermessage="Ошибка запроса POST! "; $err=true; } else //устанавливаем переменные { $email = (isset($_POST['email'])) ? $_POST['email'] : false; $name = (isset($_POST['name'])) ? $_POST['name'] : false; $sub = (isset($_POST['sub'])) ? $_POST['sub'] : false; $message = (isset($_POST['message'])) ? $_POST['message'] : false; $myself = (isset($_POST['myself'])) ? true : false; //проверяем заполнение полей if (!$name || strlen($name) < 1) //поле имени { $usermessage.="Укажите свое имя.<br> "; $err=true; } if (!$email || strlen($email) < 3) //поле e-mail { $usermessage.="Укажите корректный адрес электронной почты.<br> "; $err=true; } if (!$sub || strlen($sub) < 1) //поле темы { $usermessage.="Укажите тему обращения.<br> "; $err=true; } if(!$message || strlen($message) < 1) //поле сообщения { $usermessage.="Введите сообщение.<br> "; $err=true; } }
Если массив $_POST
пуст или метод вызова скрипта не POST
(элемент REQUEST_METHOD
массива $_SERVER
имеет значение, отличное от 'POST'
), то в $usermessage
записываем сообщение об ошибке и устанавливаем флаг ошибки $err
в true
. Иначе, устанавливаем значения необходимых переменных из соответствующих элементов массива $_POST
, проверяя, есть эти элементы в массиве.
Для установки значений переменных используем тернарный оператор. Т.е. следующий код:
$email = (isset($_POST['email'])) ? $_POST['email'] : false;
В том случае, если существует (isset()
) элемент 'email'
массива $_POST
, переменной будет присвоено значение $_POST['email']
, в противном случае - логическое значение false
.
Аналогично поступим и с другими данными, которые должны прийти из формы ввода сообщения, либо из формы ввода кода каптчи. Помните выше было про скрытые поля в форме ввода каптчи?
Исключение составляет переменная $myself
, которая служит для проверки, откуда пришел запрос, от самого ли скрипта отправки сообщения, где соответствующее скрытое поле установлено в true
, или в скрипт из внешней формы, где поля myself
вообще быть не должно. Если вдруг оно будет во внешней форме - автор сам виноват, проверка каптчи будет безбожно глючить и срабатывать не всегда.
Далее производим проверку на корректность заполнения полей - проверяем, задана ли соответствующая переменная и какова длина строки, если <1, значит поле пустое.
if (!$name || strlen($name) < 1) //поле имени { $usermessage.="Укажите свое имя.<br> "; $err=true; }
Если переменная не задана, то добавляем к переменной $usermessage
соответствующее сообщение пользователю и устанавливаем флаг ошибки $err
.
Для переменной $email
проверяется, чтобы длина строки была не менее 3 символов. Полноценная валидация e-mail адреса для такого скрипта излишество. Во-первых, потому что это само по себе тот еще геморрой, а во-вторых, пользователю перед отправкой сообщения дается возможность перепроверить введенные данные.
Вот что получается, если к скрипту обратились с помощью GET-запроса (кто-то ввел адрес скрипта в браузере напрямую, минуя форму отправки сообщения)
Или, например, если в форме ввода сообщения допущена опечатка, скажем, в названии полей для ввода имени и e-mail, или же они отсутствуют:
Вывод сообщений и редирект
Итак, наконец, мы подобрались к основной работе, к тому, для чего скрипт и предназначен - выводу и проверке кода каптчи и отправке сообщения на заданный e-mail:
//основная работа if (!$err) //делаем, если нет ошибок { ... тут будет код ... }
Но сначала уделим еще немного вопросу редиректа и вывода сообщений (форм ввода или сообщений об ошибке/успешной отправке сообщения) пользователю.
После всех предварительных проверок (на запрос POST и заполнение всех полей) проверяется флаг ошибки. Если он не установлен ($err==false
), то выполняем основную работу, если он установлен, то сразу переходим далее.
В зависимости от состояния флага ошибки устанавливается флаг редиректа, причем делается это с помощью условного оператора, что позволяет установить флаг редиректа в ходе основной работы независимо от флага ошибки. Если использовать тернарный оператор, то флаг редиректа будет жестко привязан к флагу ошибки. Чтобы этого не допустить, используем обычный if
:
//если ранее произошла ошибка //уcтанавливаем флаг редиректа if ($err) { $redir = true; }
Теперь о самом редиректе. Во-первых, должна быть возможность установить его вручную или по наличию ошибки. Возможность установки вручную мы оставили, и воспользуемся ей. Во-вторых, и это самое главное, редирект средствами PHP должен быть выполнен раньше выдачи в браузер любых текстовых сообщений, в т.ч. и HTML-тегов, пустых строк.
Правильный и неправильный код редиректа подробно описан здесь копия.
Используя предыдущую информацию, пишем такой код:
//редирект if ($redir) { header( 'Refresh: '.$redirtime.'; url='.$rediraddr ); }
Осталось только вывести пользователю сообщение. В нашем случае это может быть:
1. Форма ввода кода каптчи
2. Форма ввода кода каптчи с ошибкой (если предыдущий код каптчи неверный).
3. Сообщение о критической ошибке (не POST-запрос, ошибка заполнения полей, ошибка функции mail()
)
4. Сообщение об успешно отправленном e-mail
Чтобы соответствующим образом оформить сообщение об ошибке, используем переменную-флаг $err
:
//сообщение пользователю if ($err) //об ошибке { echo "<b><font color='red'>".$usermessage."</font></b></br> <font color='blue'>Вы будете перенаправлены обратно через ".$redirtime." секунд(ы)</font>"; } else //какое-то другое { echo $usermessage; }
Приведу его код целиком, ниже дам пояснения:
if (!$err) //делаем, если нет ошибок { if ( (!isset($_COOKIE['mycaptchamd5'])) || !$myself ) //cookie не установлен, //каптча не введена { //генерируем форму с каптчей и данными $usermessage=createform ($name,$email, $sub,$message,$allnum,false); } else //каптча введена или нажата кнопка 'Обновить код' { if (isset($_POST['updcode'])) //обновить код { //генерируем форму с каптчей и данными $usermessage=createform ($name,$email, $sub,$message,$allnum,false); } if (isset($_POST['checkcode'])) //проверить код { //извлекаем код, введенный пользователем в соотв. поле формы и получаем //MD5-хэш $usercodemd5=md5(trim($_POST['captchacode'])); $gencodemd5=$_COOKIE['mycaptchamd5']; //вытаскиваем ранее сохраненный хэш //проверка каптчи if ($usercodemd5==$gencodemd5) //код введен верно { setcookie("mycaptchamd5","",time()-300); //удаляем cookie //отправка сообщения //формируем сообщение $mes = "Имя: ".$name."\n\nТема: " .$sub."\n\nСообщение: ".$message. "\n\n"."E-mail to answer: $email\n\n"; //отправляем $send = mail ($address,$sub,$mes, "Content-type:text/plain; charset = UTF-8\r\nFrom:$address"); if ($send) //сообщение успешно отправлено { //сообщение пользователю об успехе $usermessage=".<center><b><font color='blue'> Сообщение успешно отправлено!<br> Форма обратной связи будет открыта через ".$redirtime." секунд(ы) </center></b></font>"; redir=true; //устанавливаем статус редиректа } else //ошибка функции mail() { $usermessage="Внутренняя ошибка при отправке сообщения! :("; $err=true; } } else //код неправильный { //генерируем форму с каптчей и данными $usermessage=createform ($name,$email, $sub,$message,$allnum,true); } } } }
Итак, если после проверки того, что запрос POST и проверки полей POST-запроса, ошибок не обнаружено, делаем основную работу по проверке каптчи, формированию и отправке сообщения. (if (!$err)
).
Далее, проверяем, установлен ли cookie и из какой формы пришел запрос - из формы ввода сообщения или из формы проверки каптчи и подтверждения данных:
(if ( (!isset($_COOKIE['mycaptchamd5'])) || !$myself )
). Для проверки cookie проверяем наличие нашего cookie с именем mycaptchamd5
в суперглобальном массиве $_COOKIE
, а для проверки, откуда именно пришел POST-запрос, используется скрытое поле myself
, которое есть в форме проверки каптчи (<input type='hidden' name='myself' value='true'>
). В зависимости от этого поля устанавливается переменная $myself
.
Если хоть одно из этих условий не соблюдено, будем считать, что пользователь пришел в скрипт проверки каптчи из формы отправки сообщения. Генерируем форму с каптчей и данными, код каптчи и засылаем пользователю cookie:
$usermessage=createform ($name,$email, $sub,$message,$allnum,false);
тут все понятно, первые 4 переменные - данные из POST-запроса (см. выше), переменная $allnum
- из скрипта captcha.php
, а false
- генерируем форму без сообщения об ошибочно введенной каптче, тому ще пользователь ее не вводил еще. Функция createform()
была подробно описана выше.
Иначе, получается, что cookie уже заслана пользователю, и он нажал одну из кнопок submit
в форме проверки каптчи. Либо кнопку 'Проверить код', либо кнопку 'Обновить код', которым, соответственно, присвоены имена upcode
и checkcode
в форме.
Как работать с двумя и более кнопками типа submit в форме, я объяснял здесь или здесь, так что все должно быть понятно - надо проверить, какое имя кнопки есть в запросе POST, в случае с кнопкой submit оно там будет одно, какую кнопку нажали, такое и будет.
Если нажата кнопка 'Обновить код', заново генерируем форму с каптчей и данными (они сохранятся, т.к. придут скрипту в скрытых полях - см. выше) и засылаем новую cookie:
if (isset($_POST['updcode'])) //обновить код { $usermessage=createform ($name,$email, $sub,$message,$allnum,false); //генерируем //форму с каптчей и данными }
Если это не так, значит, нажата кнопка 'Проверить код'.
Проверяем это 🙂
if (isset($_POST['checkcode']))
{
...
}
Тут бы корректнее сделать вложенные условия, но фактически, ничего особо страшного не произойдет - если кто-то изменил запрос так, что не будет ни одного значения (checkcode
или upcode
), сообщение вам не придет, а кулхацкер сам дурак.
Если нажата эта кнопка, проверяем корректность введенного кода каптчи:
1. Вытаскиваем в отдельную переменную $usercodemd5
введенный пользователем в соответствующее поле формы (<input type='text' name='captchacode'>
) код каптчи, и получаем его MD5-хэш:
$usercodemd5=md5(trim($_POST['captchacode']));
2. Если значения переменных совпадают (if ($usercodemd5==$gencodemd5)
) - код введен верно, отправляем сообщение.
3. Иначе, генерируем новую каптчу и новую cookie:
. . . else //код неправильный { $usermessage=createform ($name,$email, $sub,$message,$allnum,true); }
Обратите внимание. В функции createform()
последний параметр установлен в true
. Это уведомит пользователя о неправильном вводе каптчи.
Но к редиректу не приведет, флаг редиректа по умолчанию false
, а пока в основном рабочем процессе мы его не трогали.
Если же каптча введена правильно, то отправляем сообщение на указанный в скрипте e-mail:
1. Удаляем более не нужный cookie: setcookie("mycaptchamd5","",time()-300);
2. Формируем сообщение: $mes = "Имя: ".$name."\n\nТема: " .$sub."\n\nСообщение: ".$message."\n\n"."E-mail to answer: $email\n\n";
3. Отправляем, используя функцию mail ()
$send = mail ($address,$sub,$mes,"Content-type:text/plain; charset = UTF-8\r\nFrom:$address");
4. Проверяем на ошибки, соответственно, устанавливая флаг ошибки $err
, или формируя сообщение пользователю об успехе:
if ($send) //сообщение успешно отправлено { //сообщение пользователю об успехе $usermessage="<center><b><font color='blue'>Сообщение успешно отправлено!<br> Форма обратной связи будет открыта через ".$redirtime." секунд(ы) "; $redir=true; //устанавливаем статус редиректа } else //ошибка функции mail() { $usermessage="Внутренняя ошибка при отправке сообщения! :("; $err=true; }
Итак, мы защитили скрипт отправки сообщений от всякой школоты и кулхацкеров.
Обход этой каптчи:
Реализация не совсем промышленная. Мне хочется, чтобы вы подумали, над тем, как данный способ защиты обойти. Можете воспользоваться своим методом и попытаться заспамить мне почтовый ящик. Кто заспамит - получит минус два вопроса на зачете.
Можно спамить просто так, но желательно, с описанием способа. Я знаю 5.
Комментарии на lj.rossia.org будут открыты для всех, можно анонимно предлагать, как способы обхода, так и способы защиты. Стирать не буду ничего, кроме флуда, спама и личных оскорблений.
Форма обратной связи для WordPress, без плагина здесь или здесь
Все делается так же, с той лишь разницей, что обновленный архив скачиваете по ссылке ниже, и не забываете закачать captcha.php
в директорию темы, вместе с mail.php
.
Смотреть код скрипта на PasteBin
Скачать все одним архивом
Тесты и куски кода