#!/usr/bin/perl -T # -T для taint mode (додатковий захист від небезпечних даних) use strict; use warnings; use CGI qw(:standard -utf8); use HTML::Entities qw(encode_entities); use Digest::SHA qw(sha256_hex); # Для CSRF-токена use Fcntl qw(:flock SEEK_END); # Для простої сесії у файлі use utf8; # Шляхи (змінюйте на ваші, поза веб-коренем!) my $SESSION_FILE = '/var/tmp/myapp_session.dat'; # Для сесій (CSRF) # Дозволені методи exit_with_error('Недозволений метод') if request_method() ne 'POST' && request_method() ne 'GET'; # Генерація/перевірка CSRF-токена (проста сесія на файлі) sub get_session_token { my $session_id = cookie('session_id') // sha256_hex(time() . rand() . $$); # Новий ID, якщо немає my $token = sha256_hex($session_id . time() . rand()); # Зберегти в файл (простий, але thread-unsafe; в production — Redis) open(my $fh, '>>', $SESSION_FILE) or die "Не можу відкрити сесію: $!"; flock($fh, LOCK_EX) or die "Не можу заблокувати: $!"; print $fh "$session_id:$token\n"; close($fh); # Повернути токен і встановити куку (Secure, HttpOnly, SameSite) my $cookie = cookie(-name => 'session_id', -value => $session_id, -secure => 1, -httponly => 1, -samesite => 'Strict'); header(-cookie => $cookie); return $token; } sub validate_csrf { my $user_token = param('csrf_token') // ''; return 0 if !$user_token; # Прочитати сесію open(my $fh, '<', $SESSION_FILE) or return 0; while (<$fh>) { chomp; my ($sid, $stored_token) = split /:/; return 1 if cookie('session_id') eq $sid && $user_token eq $stored_token; } close($fh); return 0; } # Показ форми (GET) if (request_method() eq 'GET') { my $csrf_token = get_session_token(); print header(-charset => 'UTF-8', -type => 'text/html'); print <
Ім'я: $safe_name
Вік: $safe_age
Повернутися до форми HTML # Функція для помилок (не показувати деталі користувачу) sub exit_with_error { my $msg = shift; warn "Помилка: $msg"; # Лог в STDERR (Apache log) print header(-status => 400, -charset => 'UTF-8', -type => 'text/html'); print "Будь ласка, перевірте дані та спробуйте знову.
"; exit; }