#!/usr/bin/perl -T use strict; use warnings; use CGI qw(:standard -utf8); use HTML::Entities qw(encode_entities); use URI::Escape qw(uri_escape_utf8); use Digest::SHA qw(sha256_hex); # для CSRF (якщо потрібно) use utf8; # ──────────────────────────────────────────────── # Функції екранування (контекстно-залежні) # ──────────────────────────────────────────────── # 1. Для тексту в HTML (body, div, p тощо) sub html_escape { return encode_entities(shift // '', '<>&"\''); } # 2. Для значень атрибутів (value="", href="", src="" тощо) sub attr_escape { my $str = shift // ''; $str =~ s/"/"/g; # додатково екрануємо " return encode_entities($str, '<>&"\''); } # 3. Для URL в href/src (наприклад посилання) sub url_escape { return uri_escape_utf8(shift // ''); } # 4. Для JavaScript-контексту (якщо виводите в return $str; } # ──────────────────────────────────────────────── # Основна логіка # ──────────────────────────────────────────────── # Отримуємо дані з форми (приклад: пошук або коментар) my $query = param('q') // ''; my $comment = param('comment') // ''; my $user_url = param('link') // ''; # Тріммінг (завжди!) s/^\s+|\s+$//g for ($query, $comment, $user_url); # Мінімальна валідація (whitelist — не заміна escaping!) if (length($query) > 500) { print header(-status => '400 Bad Request', -type => 'text/plain'); print "Запит надто довгий"; exit; } # ──────────────────────────────────────────────── # Вивід з повним захистом + безпекові заголовки # ──────────────────────────────────────────────── print header( -charset => 'UTF-8', -type => 'text/html', 'Content-Security-Policy' => "default-src 'self'; script-src 'none'; object-src 'none'; base-uri 'self';", 'X-Content-Type-Options'=> 'nosniff', 'X-Frame-Options' => 'DENY', 'X-XSS-Protection' => '1; mode=block', 'Referrer-Policy' => 'strict-origin-when-cross-origin' ); print <<'HTML';
Ваш запит: @{[ html_escape($query) ]}
Коментар користувача:
Посилання (якщо надіслано):
@{[ attr_escape($user_url) ]}