Files
kaubuntu.re/includes/security.php
T

278 lines
7.6 KiB
PHP

<?php
/**
* Fonctions de sécurité pour la validation et l'assainissement des entrées
*/
/**
* Valide et assainit un ID de vidéo UUID
*
* @param string $id ID à valider
* @return string|false ID validé ou false si invalide
*/
function validateVideoId($id) {
if (empty($id)) {
return false;
}
// Nettoyer l'entrée
$id = trim($id);
// Vérifier le format UUID (format PeerTube)
if (!preg_match('/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i', $id)) {
return false;
}
return $id;
}
/**
* Valide et assainit une requête de recherche
*
* @param string $query Requête à valider
* @return string|false Requête validée ou false si invalide
*/
function validateSearchQuery($query) {
if (empty($query)) {
return false;
}
// Nettoyer l'entrée
$query = trim($query);
// Limiter la longueur
if (strlen($query) > 200) {
return false;
}
// Supprimer les caractères dangereux mais garder les caractères utiles pour la recherche
$query = preg_replace('/[<>"\']/', '', $query);
return $query;
}
/**
* Valide un numéro de page
*
* @param mixed $page Page à valider
* @return int Page validée (minimum 1)
*/
function validatePageNumber($page) {
$page = intval($page);
return max(1, $page);
}
/**
* Valide un ID de catégorie
*
* @param mixed $categoryId ID de catégorie à valider
* @return int|false ID validé ou false si invalide
*/
function validateCategoryId($categoryId) {
$categoryId = intval($categoryId);
// Les IDs de catégorie PeerTube sont entre 1 et 20
if ($categoryId < 1 || $categoryId > 20) {
return false;
}
return $categoryId;
}
/**
* Valide et assainit un User-Agent
*
* @param string $userAgent User-Agent à valider
* @return bool True si valide
*/
function validateUserAgent($userAgent) {
if (empty($userAgent)) {
return false;
}
// Bloquer les User-Agents suspects
$blockedPatterns = [
'/curl/i',
'/wget/i',
'/python/i',
'/bot/i',
'/scanner/i',
'/sqlmap/i'
];
foreach ($blockedPatterns as $pattern) {
if (preg_match($pattern, $userAgent)) {
return false;
}
}
return true;
}
/**
* Valide les en-têtes HTTP pour détecter les tentatives d'attaque
*
* @return bool True si les en-têtes sont sûrs
*/
function validateHttpHeaders() {
// Vérifier le User-Agent
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (!validateUserAgent($userAgent)) {
error_log('SECURITY: Suspicious User-Agent detected: ' . $userAgent);
return false;
}
// Vérifier les en-têtes suspects
$suspiciousHeaders = [
'HTTP_X_FORWARDED_FOR',
'HTTP_X_REAL_IP',
'HTTP_CLIENT_IP'
];
foreach ($suspiciousHeaders as $header) {
if (isset($_SERVER[$header])) {
$value = $_SERVER[$header];
// Bloquer les IPs privées dans les en-têtes de forwarding
if (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
error_log('SECURITY: Suspicious IP in header ' . $header . ': ' . $value);
}
}
}
return true;
}
/**
* Génère un token CSRF sécurisé
*
* @return string Token CSRF
*/
function generateCSRFToken() {
// Démarrer la session seulement si les en-têtes n'ont pas été envoyés
if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
session_start();
}
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
/**
* Valide un token CSRF
*
* @param string $token Token à valider
* @return bool True si le token est valide
*/
function validateCSRFToken($token) {
if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
session_start();
}
if (!isset($_SESSION['csrf_token'])) {
return false;
}
return hash_equals($_SESSION['csrf_token'], $token);
}
/**
* Applique des en-têtes de sécurité HTTP
*/
function setSecurityHeaders() {
// Protection contre le clickjacking (permettre les iframes du même site)
header('X-Frame-Options: SAMEORIGIN');
// Protection contre le MIME sniffing
header('X-Content-Type-Options: nosniff');
// Protection XSS basique
header('X-XSS-Protection: 1; mode=block');
// Politique de référent
header('Referrer-Policy: strict-origin-when-cross-origin');
// Content Security Policy avec support Mastodon et PeerTube
$mastodonDomain = '';
$peertubeDomain = '';
// Extraire le domaine Mastodon si configuré
if (defined('MASTODON_INSTANCE_URL')) {
$mastodonParsed = parse_url(MASTODON_INSTANCE_URL);
if ($mastodonParsed && isset($mastodonParsed['host'])) {
$mastodonDomain = $mastodonParsed['scheme'] . '://' . $mastodonParsed['host'];
}
}
// Extraire le domaine PeerTube si configuré
if (defined('PEERTUBE_URL')) {
$peertubeParsed = parse_url(PEERTUBE_URL);
if ($peertubeParsed && isset($peertubeParsed['host'])) {
$peertubeDomain = $peertubeParsed['scheme'] . '://' . $peertubeParsed['host'];
}
}
// Détecter si on est en développement local
$isLocalDev = in_array($_SERVER['HTTP_HOST'] ?? '', ['127.0.0.1:8080', '127.0.0.1:8001', 'localhost:8080', 'localhost:8001', '127.0.0.1', 'localhost']);
$csp = "default-src 'self'; ";
$csp .= "style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; ";
$csp .= "script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; ";
// Images : autoriser les domaines externes plus HTTPS général en dev
$imgSrc = "'self' data: " . ($mastodonDomain ? $mastodonDomain : '') . " " . ($peertubeDomain ? $peertubeDomain : '');
if ($isLocalDev) {
$imgSrc .= " https: http:";
} else {
$imgSrc .= " https:";
}
$csp .= "img-src " . $imgSrc . "; ";
$csp .= "font-src 'self' https://cdnjs.cloudflare.com; ";
// Frames : autoriser PeerTube et HTTPS général
$frameSrc = "'self' " . ($peertubeDomain ? $peertubeDomain : '');
if ($isLocalDev) {
$frameSrc .= " https: http:";
} else {
$frameSrc .= " https:";
}
$csp .= "frame-src " . $frameSrc . "; ";
// Connexions : autoriser Mastodon et PeerTube
$connectSrc = "'self' " . ($mastodonDomain ? $mastodonDomain : '') . " " . ($peertubeDomain ? $peertubeDomain : '');
if ($isLocalDev) {
$connectSrc .= " ws: wss:"; // WebSockets pour le dev
}
$csp .= "connect-src " . $connectSrc . "; ";
$csp .= "object-src 'none'; ";
$csp .= "base-uri 'self';";
header('Content-Security-Policy: ' . $csp);
// HTTPS strict transport security (seulement si HTTPS)
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
}
}
/**
* Valide l'origine de la requête pour les requêtes AJAX
*
* @return bool True si l'origine est valide
*/
function validateAjaxOrigin() {
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$host = $_SERVER['HTTP_HOST'] ?? '';
if (empty($origin) || empty($host)) {
return false;
}
$expectedOrigin = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') . '://' . $host;
return $origin === $expectedOrigin;
}
?>