#!/usr/bin/perl -T use strict; use warnings; use CGI qw(:standard -utf8); use Digest::SHA qw(sha256_hex); # криптостійкий хеш для токена use CGI::Cookie; # робота з куками use Fcntl qw(:flock); # блокування файлу для сесії use utf8; # Шлях до файлу сесії (поза веб-коренем!) my $SESSION_DIR = '/var/tmp/myapp_sessions'; my $SESSION_FILE = "$SESSION_DIR/sessions.dat"; # Створити директорію, якщо немає unless (-d $SESSION_DIR) { mkdir $SESSION_DIR, 0700 or die "Cannot create $SESSION_DIR: $!" } # ──────────────────────────────────────────────── # Функції для роботи з сесією та CSRF-токеном # ──────────────────────────────────────────────── sub get_or_create_session_id { my $sid = cookie('MYAPP_SID'); unless ($sid) { $sid = sha256_hex(time() . rand() . $$ . $^T . $ENV{REMOTE_ADDR}); print header(-cookie => cookie( -name => 'MYAPP_SID', -value => $sid, -path => '/', -secure => 1, # тільки HTTPS у продакшені -httponly => 1, -samesite => 'Strict' # найсильніший захист )); } return $sid; } sub generate_csrf_token { my $sid = shift; return sha256_hex($sid . time() . rand() . 'salt_for_csrf_2026'); } sub store_csrf_token { my ($sid, $token) = @_; open my $fh, '>>', $SESSION_FILE or die "Cannot open session file: $!"; flock($fh, LOCK_EX) or die "Cannot lock: $!"; print $fh "$sid:$token\n"; close $fh; } sub validate_csrf_token { my ($sid, $user_token) = @_; return 0 unless $user_token && $sid; open my $fh, '<', $SESSION_FILE or return 0; while (my $line = <$fh>) { chomp $line; my ($stored_sid, $stored_token) = split /:/, $line, 2; if ($stored_sid eq $sid && $stored_token eq $user_token) { # Опціонально: видалити токен після використання (one-time) # close $fh; unlink $SESSION_FILE; return 1; return 1; } } close $fh; return 0; } # ──────────────────────────────────────────────── # Основна логіка # ──────────────────────────────────────────────── my $sid = get_or_create_session_id(); # Показ форми (GET) if (request_method() eq 'GET') { my $csrf_token = generate_csrf_token($sid); store_csrf_token($sid, $csrf_token); print header(-charset => 'UTF-8'); print <<'HTML';
Недійсний CSRF-токен. Можлива атака. Спробуйте ще раз.
"; exit; } # Якщо токен валідний — обробляємо дані my $newpass = param('newpass') // ''; # Тут ваша логіка (збереження пароля тощо) # Важливо: екрануйте вивід, якщо потрібно виводити $newpass print header(-charset => 'UTF-8'); print <<'HTML';(Це приклад — реальний пароль не виводимо)
Повернутися HTML