| 1 |
8 |
ahitrov@rambler.ru |
package Utils::Spam::SecretForm; |
| 2 |
|
|
|
| 3 |
|
|
use strict; |
| 4 |
|
|
use Digest::MD5; |
| 5 |
|
|
use Contenido::Globals; |
| 6 |
|
|
use Scalar::Util qw(blessed); |
| 7 |
|
|
|
| 8 |
|
|
# Генерация строки для вставки в hidden-поле формы |
| 9 |
|
|
# для подтвеждения действий пользователя. |
| 10 |
|
|
# extra - дополнительный параметр для генерации строки |
| 11 |
|
|
# (к примеру, если человек залогинен, можно передавать его логин) |
| 12 |
|
|
# Не забыть передавать данный параметр при валидации. |
| 13 |
|
|
sub generate { |
| 14 |
|
|
my %opts = @_; |
| 15 |
|
|
|
| 16 |
|
|
my ($start_time, $secret_code) = &get_secret_code(allow_generate_code => 1, memd => $opts{'memd'}); |
| 17 |
|
|
return unless $start_time && $secret_code; |
| 18 |
|
|
|
| 19 |
|
|
my $random = &get_random_string(5); |
| 20 |
|
|
# start secret code time | generate time | hash |
| 21 |
|
|
return $start_time.'|'.time().'|'.$random.'|'.Digest::MD5::md5_hex($start_time.$secret_code.$random.$opts{'extra'}); |
| 22 |
|
|
} |
| 23 |
|
|
|
| 24 |
|
|
# Процедура проверки hidden-поля формы |
| 25 |
|
|
# secret - код, для проверки |
| 26 |
|
|
# ttl - время жизни выданного кода |
| 27 |
|
|
# check_count - true означает разрешение на подсчет количества использований |
| 28 |
|
|
sub validate { |
| 29 |
|
|
my %opts = @_; |
| 30 |
|
|
|
| 31 |
|
|
my $user_secret = $opts{'secret'}; |
| 32 |
|
|
my $ttl = $opts{'ttl'} || 3600; |
| 33 |
|
|
my $extra = $opts{'extra'}; |
| 34 |
|
|
my $check_count = $opts{'check_count'}; |
| 35 |
|
|
my $result = { is_valid => 1, is_expired => 0, count => 1 }; |
| 36 |
|
|
my $memd = blessed($opts{'memd'}) ? $opts{'memd'} : $keeper->MEMD(); |
| 37 |
|
|
|
| 38 |
|
|
return $result unless blessed($memd); |
| 39 |
|
|
|
| 40 |
|
|
my ($user_start_time, $user_generate_time, $user_random, $user_hash) = split(/\|/, $user_secret); |
| 41 |
|
|
|
| 42 |
|
|
# данные о секретной строке |
| 43 |
|
|
my ($start_time, $secret_code) = &get_secret_code(time => $user_start_time, memd => $memd); |
| 44 |
|
|
|
| 45 |
|
|
# Если кэш не работает принимаем любые параметры |
| 46 |
|
|
return $result if !defined($start_time) && !defined($secret_code); |
| 47 |
|
|
|
| 48 |
|
|
if (Digest::MD5::md5_hex($start_time.$secret_code.$user_random.$extra) ne $user_hash) { |
| 49 |
|
|
$result->{'is_valid'} = 0; |
| 50 |
|
|
} |
| 51 |
|
|
|
| 52 |
|
|
if ($result->{'is_valid'} && (($user_generate_time-$start_time > 3600) |
| 53 |
|
|
|| (time()-$user_generate_time > $ttl)) |
| 54 |
|
|
) { |
| 55 |
|
|
$result->{'is_expired'} = 1; |
| 56 |
|
|
} |
| 57 |
|
|
|
| 58 |
|
|
# Если необходима проверка на повторное использование |
| 59 |
|
|
# Данные о количестве использования необходимо сохранить в кэше |
| 60 |
|
|
if ($result->{'is_valid'} && !$result->{'is_expired'} && $check_count) { |
| 61 |
|
|
|
| 62 |
|
|
$result->{'count'} = $memd->incr('usersecret|'.$user_secret, 1) if $memd; |
| 63 |
|
|
|
| 64 |
|
|
unless ($result->{'count'}) { |
| 65 |
|
|
$memd->add('usersecret|'.$user_secret, 1, $ttl) if $memd; |
| 66 |
|
|
$result->{'count'} = 1; |
| 67 |
|
|
} |
| 68 |
|
|
} |
| 69 |
|
|
|
| 70 |
|
|
return $result; |
| 71 |
|
|
} |
| 72 |
|
|
|
| 73 |
|
|
# Процедура, которая возвращает true или false в зависимости от валидности, просроченности |
| 74 |
|
|
# и количества использований |
| 75 |
|
|
sub is_valid_secret { |
| 76 |
|
|
my %opts = @_; |
| 77 |
|
|
|
| 78 |
|
|
my $validate = &validate(%opts); |
| 79 |
|
|
|
| 80 |
|
|
if ($validate->{'is_valid'} && !$validate->{'is_expired'} |
| 81 |
|
|
&& (!$opts{'check_count'} || ($opts{'check_count'} && $validate->{'count'} == 1))) |
| 82 |
|
|
{ |
| 83 |
|
|
return 1; |
| 84 |
|
|
} |
| 85 |
|
|
|
| 86 |
|
|
return undef; |
| 87 |
|
|
} |
| 88 |
|
|
|
| 89 |
|
|
# Процедура получения и генерации при необходимости |
| 90 |
|
|
# секретного кода. Сохраняет свою атуальность в течение суток. |
| 91 |
|
|
# time - для указанного времени вернет актуальный на тот момент код |
| 92 |
|
|
# allow_generate_code - разрешает генерировать код, если он не найден |
| 93 |
|
|
sub get_secret_code { |
| 94 |
|
|
my %opts = @_; |
| 95 |
|
|
|
| 96 |
|
|
# Наличие объекта memcached является обязательным |
| 97 |
|
|
# условием работоспособности |
| 98 |
|
|
my $memd = blessed($opts{'memd'}) ? $opts{'memd'} : $keeper->MEMD(); |
| 99 |
|
|
return unless blessed($memd); |
| 100 |
|
|
|
| 101 |
|
|
my $time = abs(int($opts{'time'})); |
| 102 |
|
|
my $now_time = time(); |
| 103 |
|
|
|
| 104 |
|
|
# Время с округлением до начала часа |
| 105 |
|
|
unless ($time) { |
| 106 |
|
|
$time = $now_time; |
| 107 |
|
|
$time = $time - ($time % 3600); |
| 108 |
|
|
} |
| 109 |
|
|
|
| 110 |
|
|
my $cache_key = 'secret_code|'.$time; |
| 111 |
|
|
|
| 112 |
|
|
my $secret_code = $memd->get($cache_key); |
| 113 |
|
|
return ($time, $secret_code) if $secret_code; |
| 114 |
|
|
|
| 115 |
|
|
if ($opts{'allow_generate_code'}) { |
| 116 |
|
|
$secret_code = &get_random_string(10); |
| 117 |
|
|
$memd->set($cache_key, $secret_code); |
| 118 |
|
|
} |
| 119 |
|
|
|
| 120 |
|
|
return ($time, $secret_code); |
| 121 |
|
|
} |
| 122 |
|
|
|
| 123 |
|
|
|
| 124 |
|
|
# Генерация случайной строки |
| 125 |
|
|
# length - длина строки |
| 126 |
|
|
sub get_random_string { |
| 127 |
|
|
my $length = shift; |
| 128 |
|
|
$length = 10 unless $length && $length =~ /^\d+$/; |
| 129 |
|
|
|
| 130 |
|
|
my $random_chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; |
| 131 |
|
|
my $random_chars_length = length($random_chars); |
| 132 |
|
|
|
| 133 |
|
|
my $string = ''; |
| 134 |
|
|
for (1..$length) { |
| 135 |
|
|
$string .= substr($random_chars, int(rand($random_chars_length)), 1); |
| 136 |
|
|
} |
| 137 |
|
|
|
| 138 |
|
|
return $string; |
| 139 |
|
|
} |
| 140 |
|
|
|
| 141 |
|
|
|
| 142 |
|
|
1; |
| 143 |
|
|
__END__ |
| 144 |
|
|
|
| 145 |
|
|
=head1 NAME |
| 146 |
|
|
|
| 147 |
|
|
Utils::Spam::SecretForm - утилиты генерации и проверки секретной строки для web-форм |
| 148 |
|
|
|
| 149 |
|
|
=head1 SYNOPSIS |
| 150 |
|
|
|
| 151 |
|
|
Генерация: |
| 152 |
|
|
<input type="hidden" name="secret" value="<% Utils::Spam::SecretForm::generate() %>"> |
| 153 |
|
|
|
| 154 |
|
|
Проверка: |
| 155 |
|
|
my $validate = Utils::Spam::SecretForm::validate( secret => $ARGS{'secret'}, check_count => 0|1 ); |
| 156 |
|
|
if ($validate->{'is_valid'} && !$validate->{'is_expired'}) { |
| 157 |
|
|
allowed method |
| 158 |
|
|
} |
| 159 |
|
|
|
| 160 |
|
|
|
| 161 |
|
|
=head1 DESCRIPTION |
| 162 |
|
|
|
| 163 |
|
|
С помощью javascript существует способ подделки http-запроса get и post методов. |
| 164 |
|
|
Некая страница a.html содержит: |
| 165 |
|
|
<form method=post action=http://www.rambler.ru/post.html name="b"> |
| 166 |
|
|
<input type="text" name="text"> |
| 167 |
|
|
<input name="submit" type="submit" value="submit"> |
| 168 |
|
|
</form> |
| 169 |
|
|
<script> |
| 170 |
|
|
document.b.submit.click(); |
| 171 |
|
|
</script> |
| 172 |
|
|
|
| 173 |
|
|
Таким образом, человек, зашедщий на страницу a.html, отправит данную форму. |
| 174 |
|
|
Для борьбы с данным видом атак следует использовать данный модуль. |
| 175 |
|
|
|
| 176 |
|
|
Принцип работы: раз в час генерируется случайная секретная строка, которая хранится в кэше по ключу secret_code|время_генерации. |
| 177 |
|
|
Если кэш не работает, валидация проходит успешно для любого рода запросов. |
| 178 |
|
|
Пользователь при генерации формы получает некий код в hidden поле, который состоит из трех частей: |
| 179 |
|
|
1) время генерации секретной строки |
| 180 |
|
|
2) время выдачи строки пользователю |
| 181 |
|
|
3) рандомная строка |
| 182 |
|
|
4) md5_hex( время выдачи строки пользователю . рандомная строка . секретная строка) |
| 183 |
|
|
При верификации полученного hidden-параметра происходит получение секретной строки из кэша secret_code|время_генерации, сравнение md5_hex(времени . полученной строки) и проверка разницы времени установки поля и текущего времени. |
| 184 |
|
|
Таким образом получаем защиту от несанкционированных запросов. Для защиты от повторного использования кода необходимо использовать параметр check_count, в зависимости от которого в результате верификации вернется количество использования кода (после первого использование count = 1). |
| 185 |
|
|
Для повышения защиты существует возможность добавления extra параметра для генерации hidden-кода, например для залогиненого пользователя, это может быть логин или сессия. Но необходимо не забывать передавать этот параметр во время валидации. |
| 186 |
|
|
|