Или еще раз возвращаясь к напечатанному. Сегодня поговорим о защите от спама.
В первоначально описанном способе копия был баг (промотайте в конец), из-за которого можно было легко и непринужденно загадить ящик получателя спамом, да еще и в автоматическом режиме. Отправить картинки, вирусы, или загадить чужой ящик не получится, но вот закидать "Войной и миром" ящик владельца сайта вполне таки да.
Чтобы уменьшить такую вероятность, добавим в скрипт отправки почты каптчу. Поскольку скрипт, отправляющий почту (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($captchacod e),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/ob ratnaya-svyaz/"; //адрес редиректа пользователя после отправки сообщения
Замените адрес
>http://tolik-punkoff.com/obratnaya-svy az/
на нужный вам.Время до редиректа в секундах:
$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['captchacod e']));
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/p lain; charset = UTF-8\r\nFrom:$address");
4. Проверяем на ошибки, соответственно, устанавливая флаг ошибки
$err
, или формируя сообщение пользователю об успехе:if ($send) //сообщение успешно отправлено
{
//сообщение пользователю об успехе
$usermessage="<center><b><font color='blue'>Сообщение успешно отправлено!<br>
Форма обратной связи будет открыта через ".$redirtime." секунд(ы) </center> </b></font>";
$redir=true; //устанавливаем статус редиректа
}
else //ошибка функции mail()
{
$usermessage="Внутренняя ошибка при отправке сообщения! :(";
$err=true;
}
Итак, мы защитили скрипт отправки сообщений от всякой школоты и кулхацкеров.
Обход этой каптчи:
Реализация не совсем промышленная. Мне хочется, чтобы вы подумали, над тем, как данный способ защиты обойти. Можете воспользоваться своим методом и попытаться заспамить мне почтовый ящик. Кто заспамит - получит минус два вопроса на зачете.
Можно спамить просто так, но желательно, с описанием способа. Я знаю 5.
Комментарии на lj.rossia.org будут открыты для всех, можно анонимно предлагать, как способы обхода, так и способы защиты. Стирать не буду ничего, кроме флуда, спама и личных оскорблений.
Форма обратной связи для WordPress, без плагина здесь или здесь
Все делается так же, с той лишь разницей, что обновленный архив скачиваете по ссылке ниже, и не забываете закачать
captcha.php
в директорию темы, вместе с mail.php
.Смотреть код скрипта на PasteBin
Скачать все одним архивом
Тесты и куски кода
Заметка в PDF
Это репост заметки из моего блога на сайте http://tolik-punkoff.com
Оригинал заметки находится здесь: http://tolik-punkoff.com/2017/06/01/form