Bonjour à tous
Je vois l’urgence — je te livre immédiatement un script PHP prêt à déployer (avec commentaires en français) qui combine plusieurs protections contre faux profils / messages IA et attaques DDoS légères à moyennes : rate-limiting, honeypot, validation CAPTCHA (reCAPTCHA ou hCaptcha), heuristiques de détection de texte IA, blocage temporaire IP, journalisation prête pour fail2ban, et suggestions pour WAF / CDN pour attaques volumétriques.
J’ai vérifié les meilleures pratiques et fournisseurs conseillés (Cloudflare pour DDoS/CDN, ModSecurity/OWASP CRS pour WAF, Fail2Ban pour bannissement côté serveur, reCAPTCHA/hCaptcha pour CAPTCHA, et options d’API de détection d’IA).
Voici le code final (tout en tabulations, comme tu préfères). Remplace les valeurs YOUR_... par les clés / chemins réels. Ce fichier est pensé pour être inclus dans les endpoints d’inscription/message (ex : register.php, post_message.php).
Code : Tout sélectionner
<?php
// protection_global.php
// Usage : include 'protection_global.php'; puis appeler check_request_protection('register');
// Remplacer les constantes YOUR_* par les valeurs réelles.
// -------------------- CONFIG --------------------
define('RATE_LIMIT_WINDOW', 60); // fenêtre en secondes
define('RATE_LIMIT_MAX', 10); // max requêtes par IP par window (ajuster selon besoin)
define('TEMP_BAN_SECONDS', 300); // durée du bannissement temporaire
define('LOG_FILE', __DIR__ . "/security.log"); // fichier de log (assurez-vous permissions)
define('IP_STORE_DIR', sys_get_temp_dir() . "/ip_store"); // dossier pour compteurs et bans
// CAPTCHA (choisir l'un ou l'autre, ou les deux)
define('RECAPTCHA_SECRET', 'YOUR_RECAPTCHA_SECRET'); // si utilisé
define('HCAPTCHA_SECRET', 'YOUR_HCAPTCHA_SECRET'); // si utilisé
// API detection IA optionnelle (si tu veux appeler un service payant)
define('AI_DETECT_API_URL', 'https://api.example-ai-detector.com/v1/check'); // placeholder
define('AI_DETECT_API_KEY', 'YOUR_AI_DETECT_KEY'); // placeholder
// Liste noire de domaines / patterns (liens courts, domaines suspects)
$BLACKLISTED_DOMAINS = [
'bit\.ly', 'tinyurl\.com', 't\.co', 'freeporn', 'cheap-loans', 'xn--' // regex parts
];
// User-Agent suspects simples
$SUSPICIOUS_UA = [
'curl', 'wget', 'python-requests', 'libwww-perl', 'nmap', 'scrapy', 'bot'
];
// -------------------- INIT --------------------
if (!is_dir(IP_STORE_DIR)) {
mkdir(IP_STORE_DIR, 0755, true);
}
// -------------------- FONCTIONS --------------------
/**
* Journalise un évènement de sécurité (pratique pour fail2ban)
*/
function sec_log($msg) {
$time = date('Y-m-d H:i:s');
error_log("[$time] $msg\n", 3, LOG_FILE);
}
/**
* Retourne l'IP réelle (support Cloudflare / proxies si présents)
*/
function get_client_ip() {
// Respecter Cloudflare / proxies usuels
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}
$keys = ['HTTP_X_FORWARDED_FOR','HTTP_CLIENT_IP','REMOTE_ADDR'];
foreach ($keys as $k) {
if (!empty($_SERVER[$k])) {
// X-Forwarded-For peut contenir une liste
$parts = explode(',', $_SERVER[$k]);
return trim($parts[0]);
}
}
return '0.0.0.0';
}
/**
* Simple stockage file-based pour compteurs / bans
*/
function ip_store_path($ip, $name) {
$safe = preg_replace('/[^a-z0-9_\-\.]/i', '_', $ip);
return IP_STORE_DIR . "/{$safe}_{$name}.dat";
}
/**
* Incrémente et vérifie rate limit. Renvoie true si ok, false si dépassement.
*/
function rate_limit_check($ip) {
$path = ip_store_path($ip, 'rl');
$data = ['ts' => time(), 'count' => 0];
if (file_exists($path)) {
$json = @file_get_contents($path);
$data = json_decode($json, true) ?: $data;
// si fenêtre expirée => reset
if ($data['ts'] + RATE_LIMIT_WINDOW <= time()) {
$data = ['ts' => time(), 'count' => 0];
}
}
$data['count']++;
file_put_contents($path, json_encode($data), LOCK_EX);
if ($data['count'] > RATE_LIMIT_MAX) {
// créer ban
$banpath = ip_store_path($ip, 'ban');
$ban = ['until' => time() + TEMP_BAN_SECONDS];
file_put_contents($banpath, json_encode($ban), LOCK_EX);
sec_log("RATE_LIMIT_BAN ip=$ip count={$data['count']}");
return false;
}
return true;
}
/**
* Vérifie si IP est bannie
*/
function is_ip_banned($ip) {
$banpath = ip_store_path($ip, 'ban');
if (file_exists($banpath)) {
$ban = json_decode(file_get_contents($banpath), true);
if ($ban && isset($ban['until'])) {
if ($ban['until'] > time()) return true;
// expiration -> supprimer
@unlink($banpath);
return false;
}
}
return false;
}
/**
* Honeypot : champ caché. Si rempli => bot
*/
function check_honeypot() {
// Attendu dans POST : 'hp_email' caché dans le front-end (display:none)
if (!empty($_POST['hp_email'])) {
sec_log("HONEYPOT_FILLED ip=" . get_client_ip());
return false;
}
return true;
}
/**
* Vérifie captcha (reCAPTCHA v2/v3 ou hCaptcha)
*/
function verify_captcha() {
// reCAPTCHA
if (!empty($_POST['g-recaptcha-response']) && RECAPTCHA_SECRET !== 'YOUR_RECAPTCHA_SECRET') {
$resp = post_verify('https://www.google.com/recaptcha/api/siteverify', [
'secret' => RECAPTCHA_SECRET,
'response' => $_POST['g-recaptcha-response'],
'remoteip' => get_client_ip()
]);
if (!empty($resp['success'])) return true;
sec_log("RECAPTCHA_FAIL ip=" . get_client_ip() . " resp=" . json_encode($resp));
return false;
}
// hCaptcha
if (!empty($_POST['h-captcha-response']) && HCAPTCHA_SECRET !== 'YOUR_HCAPTCHA_SECRET') {
$resp = post_verify('https://hcaptcha.com/siteverify', [
'secret' => HCAPTCHA_SECRET,
'response' => $_POST['h-captcha-response'],
'remoteip' => get_client_ip()
]);
if (!empty($resp['success'])) return true;
sec_log("HCAPTCHA_FAIL ip=" . get_client_ip() . " resp=" . json_encode($resp));
return false;
}
// Si aucun captcha configuré, on permet (mais tu dois configurer captcha pour registrations/messages)
return true;
}
/**
* utilitaire POST pour verification externe
*/
function post_verify($url, $params) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
$res = curl_exec($ch);
if ($res === false) {
curl_close($ch);
return [];
}
curl_close($ch);
return json_decode($res, true) ?: [];
}
/**
* Heuristiques simples pour détecter message IA / spam
* Retourne array [ok => bool, reasons => []]
*/
function analyze_message_text($text) {
global $BLACKLISTED_DOMAINS;
$reasons = [];
$ok = true;
$len = mb_strlen($text);
$words = str_word_count(strip_tags($text));
// 1) trop court ou trop long
if ($len < 5) { $ok = false; $reasons[] = 'message_trop_court'; }
if ($len > 5000) { $ok = false; $reasons[] = 'message_trop_long'; }
// 2) trop de liens
preg_match_all('#https?://#i', $text, $m);
if (count($m[0]) > 2) { $ok = false; $reasons[] = 'trop_de_liens'; }
// 3) domaines blacklistés
foreach ($BLACKLISTED_DOMAINS as $d) {
if (preg_match("#{$d}#i", $text)) { $ok = false; $reasons[] = 'domaine_blacklist'; break; }
}
// 4) ponctuation/ratio bizarre (heuristique)
$punct = preg_match_all('/[!?.,:;]{1,}/u', $text, $pm);
if ($words > 0 && ($punct / max(1,$words)) > 5) { $ok = false; $reasons[] = 'ponctuation_bizarre'; }
// 5) répétitions simples (bot)
if (preg_match('/(.)\\1{10,}/u', $text)) { $ok = false; $reasons[] = 'repetition_char'; }
// 6) similarité à template : si message contient phrases typiques AI (heuristique)
$ai_markers = ['as an ai','i am an ai','i am an artificial','generated by','openai','chatgpt'];
foreach ($ai_markers as $m) {
if (stripos($text, $m) !== false) { $ok = false; $reasons[] = 'mention_ai_marker'; break; }
}
// 7) optionnel : appel API detection IA si configurée
if (AI_DETECT_API_KEY !== 'YOUR_AI_DETECT_KEY') {
$det = call_ai_detect_api($text);
if ($det && !empty($det['is_ai']) && $det['score'] > 0.7) {
$ok = false; $reasons[] = 'api_detect_ai';
}
}
return ['ok'=>$ok, 'reasons'=>$reasons];
}
/**
* Exemple d'appel vers API de détection IA (optionnel). Doit renvoyer ['is_ai'=>bool,'score'=>float]
*/
function call_ai_detect_api($text) {
$ch = curl_init(AI_DETECT_API_URL);
$payload = json_encode(['text' => mb_substr($text,0,5000)]);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . AI_DETECT_API_KEY
]);
curl_setopt($ch, CURLOPT_TIMEOUT, 6);
$res = curl_exec($ch);
if ($res === false) { curl_close($ch); return null; }
curl_close($ch);
return json_decode($res, true);
}
/**
* Vérifie user-agent basique et headers suspects
*/
function check_user_agent_and_headers() {
global $SUSPICIOUS_UA;
$ua = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
foreach ($SUSPICIOUS_UA as $s) {
if (stripos($ua, $s) !== false) {
sec_log("SUSPICIOUS_UA ip=" . get_client_ip() . " ua=" . substr($ua,0,200));
return false;
}
}
// Si pas d'UA -> suspect
if (empty($ua)) {
sec_log("NO_UA ip=" . get_client_ip());
return false;
}
return true;
}
/**
* Blocage immédiat et réponse standardisée
*/
function block_and_exit($reason='blocked') {
http_response_code(429);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['status'=>'error','reason'=>$reason]);
exit;
}
/**
* Point d'entrée principal à appeler depuis endpoint
* $context : 'register'|'message' etc. (pour politesses différentes)
*/
function check_request_protection($context='generic') {
$ip = get_client_ip();
// 1) ban check
if (is_ip_banned($ip)) {
sec_log("REQUEST_FROM_BANNED ip=$ip context=$context");
block_and_exit('ip_banned');
}
// 2) rate limit
if (!rate_limit_check($ip)) {
block_and_exit('rate_limit_exceeded');
}
// 3) headers / ua
if (!check_user_agent_and_headers()) {
block_and_exit('suspicious_ua');
}
// 4) honeypot
if (!check_honeypot()) {
block_and_exit('honeypot_triggered');
}
// 5) captcha
if (!verify_captcha()) {
block_and_exit('captcha_failed');
}
// 6) si message, analyser texte
if ($context === 'message' && !empty($_POST['message'])) {
$analysis = analyze_message_text($_POST['message']);
if (!$analysis['ok']) {
sec_log("MESSAGE_REJECT ip=$ip reasons=" . implode(',', $analysis['reasons']) . " snippet=" . substr(strip_tags($_POST['message']),0,200));
block_and_exit('message_rejected');
}
}
// Si tout ok -> continuer
return true;
}
?>
Je ne sais pas si je peux poster ça, mais au cas où à supprimer s’il le faut