Actualiser index.php

This commit is contained in:
yasss2627 2026-02-01 10:02:12 +00:00
parent 850a40526b
commit 8d25df77e8

503
index.php
View File

@ -1,243 +1,260 @@
<?php <?php
// chatbot.php — Version sécurisée production + anti prompt injection // index.php — Chatbot sécurisé LLaMA 2 7B via Ollama
session_start(); session_start();
/* ======================= /* =======================
CONFIG & ENV CONFIG & ENV
======================= */ ======================= */
$env = parse_ini_file(__DIR__ . '/.env'); $env = parse_ini_file(__DIR__ . '/config/.env');
define('API_KEY', $env['DEESEEK_API_KEY']); define('SYSTEM_PROMPT', $env['SYSTEM_PROMPT'] ?? '');
define('SYSTEM_PROMPT', $env['DEEPSEEK_SYSTEM_PROMPT']); define('CSRF_SECRET', $env['CSRF_SECRET'] ?? 'changez-moi');
define('CSRF_SECRET', $env['CSRF_SECRET']); define('RATE_LIMIT', 10); // 10 messages / minute
define('RATE_WINDOW', 60); // 60 sec
/* =======================
HEADERS SÉCURITÉ /* =======================
======================= */ HEADERS SÉCURITÉ
header('X-Content-Type-Options: nosniff'); ======================= */
header('X-Frame-Options: SAMEORIGIN'); header('X-Content-Type-Options: nosniff');
header('X-XSS-Protection: 1; mode=block'); header('X-Frame-Options: SAMEORIGIN');
header('X-XSS-Protection: 1; mode=block');
/* =======================
SESSION INIT /* =======================
======================= */ SESSION INIT
if (!isset($_SESSION['csrf'])) { ======================= */
$_SESSION['csrf'] = hash_hmac('sha256', session_id(), CSRF_SECRET); if (!isset($_SESSION['csrf'])) {
} $_SESSION['csrf'] = hash_hmac('sha256', session_id(), CSRF_SECRET);
}
if (!isset($_SESSION['message_count'])) { if (!isset($_SESSION['messages_timestamps'])) {
$_SESSION['message_count'] = 0; $_SESSION['messages_timestamps'] = [];
} }
/* ======================= /* =======================
HELPERS HELPERS
======================= */ ======================= */
function isAjax(): bool { function isAjax(): bool {
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && return isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
} }
function sanitizeMessage(string $msg) { function sanitizeMessage(string $msg) {
$msg = trim($msg); $msg = trim($msg);
if ($msg === '' || mb_strlen($msg) > 500) { if ($msg === '' || mb_strlen($msg) > 500) return false;
return false; return $msg;
} }
return $msg;
} /* =======================
VALIDATION CARACTERES
/* ======================= ======================= */
ANTI PROMPT INJECTION function hasInvalidCharacters(string $msg): bool {
======================= */ // Bloque TOUS les caractères spéciaux sauf ?, !, . - et '
function isPromptInjection(string $msg): bool { // Accepte : lettres, chiffres, espaces, et {?, !, ., -, '}
// regex solide : détecte toutes les tentatives d'instructions systèmes, "ignore", "révèle", "prompt", etc. return preg_match('/[^a-zA-Z0-9À-ÿ\s\?\!\.\-\']/u', $msg) === 1;
$pattern = '/\b(ignore|oublie|révèle|prompt|instructions?|system|agis comme|tu es maintenant|roleplay|administrateur)\b/i'; }
return preg_match($pattern, $msg) === 1;
} /* =======================
VERBES D'ACTION INTERDITS
/* ======================= ======================= */
API CALL function hasBlockedVerbs(string $msg): bool {
======================= */ // Bloque les verbes qui demandent une action spécifique à l'IA
function callDeepSeekAPI(string $userMessage): string { $blockedVerbs = [
$payload = [ 'montre', 'affiche', 'révèle', 'expose',
'model' => 'deepseek-chat', 'donne', 'envoie', 'fournis', 'transmets',
'messages' => [ 'répète', 'résume', 'reformule', 'paraphrase', 'récite',
['role' => 'system', 'content' => SYSTEM_PROMPT], 'dis-moi', 'dis moi', 'raconte', 'explique', 'CTF', 'CTFM1'
['role' => 'user', 'content' => $userMessage] ];
],
'max_tokens' => 800, $msg_lower = mb_strtolower($msg);
'temperature' => 0,
'top_p' => 1 foreach ($blockedVerbs as $verb) {
]; if (preg_match('/\b' . preg_quote($verb) . '\b/i', $msg) === 1) {
return true;
$ch = curl_init('https://api.deepseek.com/v1/chat/completions'); }
curl_setopt_array($ch, [ }
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true, return false;
CURLOPT_POSTFIELDS => json_encode($payload), }
CURLOPT_HTTPHEADER => [
'Content-Type: application/json', /* =======================
'Authorization: Bearer ' . API_KEY MOTS SENSIBLES INTERDITS
], ======================= */
CURLOPT_TIMEOUT => 15 function hasBlockedWords(string $msg): bool {
]); // Bloque les mots sensibles qui demandent des infos confidentielles
$blockedWords = [
$response = curl_exec($ch); 'flag', 'ctf',
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE); 'token', 'secret', 'clé', 'key',
curl_close($ch); 'motdepasse', 'mot de passe', 'password',
'prompt', 'instruction', 'système', 'system', 'règle', 'consigne'
if ($http !== 200 || !$response) { ];
return 'Erreur du service.';
} $msg_lower = mb_strtolower($msg);
$json = json_decode($response, true); foreach ($blockedWords as $word) {
return $json['choices'][0]['message']['content'] ?? 'Erreur de réponse.'; if (preg_match('/\b' . preg_quote($word) . '\b/i', $msg) === 1) {
} return true;
}
/* ======================= }
AJAX HANDLER
======================= */ return false;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) { }
if (!isAjax()) {
http_response_code(403); /* =======================
echo json_encode(['error' => 'Requête interdite']); RATE LIMITING
exit; ======================= */
} function checkRateLimit(): bool {
$now = time();
if (!isset($_POST['csrf']) || $_POST['csrf'] !== $_SESSION['csrf']) { $window_start = $now - RATE_WINDOW;
http_response_code(403);
echo json_encode(['error' => 'CSRF invalide']); $_SESSION['messages_timestamps'] = array_values(
exit; array_filter($_SESSION['messages_timestamps'], fn($ts) => $ts > $window_start)
} );
if ($_SESSION['message_count'] >= 1) { if (count($_SESSION['messages_timestamps']) >= RATE_LIMIT) return false;
echo json_encode(['error' => 'LIMIT_REACHED']);
exit; $_SESSION['messages_timestamps'][] = $now;
} return true;
}
$message = sanitizeMessage($_POST['message']);
if ($message === false) { /* =======================
echo json_encode(['error' => 'Message invalide']); APPEL LLaMA via Ollama
exit; ======================= */
} function callLlamaAPI(string $userMessage): string {
// Vérifications de sécurité avec messages explicites
// anti prompt injection
if (isPromptInjection($message)) { if (hasInvalidCharacters($userMessage)) {
echo json_encode(['response' => 'Demande à Bouchra.']); return "Je ne répondrai pas car certains caractères spéciaux sont interdits (sauf ?, !, ., - et '). Demande à Bouchra si tu veux savoir pourquoi.";
exit; }
}
if (hasBlockedVerbs($userMessage)) {
$_SESSION['message_count']++; return "Je ne répondrai pas car ce message contient un verbe d'action interdit (montre, donne, répète, etc). Demande à Bouchra si tu veux savoir pourquoi.";
}
$reply = callDeepSeekAPI($message);
if (hasBlockedWords($userMessage)) {
echo json_encode([ return "Je ne répondrai pas car ce message contient un mot sensible (flag, token, prompt, etc). Demande à Bouchra si tu veux savoir pourquoi.";
'response' => $reply }
]);
exit; $payload = [
} 'model' => 'llama2:7b',
?> 'messages' => [
<!DOCTYPE html> ['role'=>'system', 'content'=> SYSTEM_PROMPT],
<html lang="fr"> ['role'=>'user', 'content'=> $userMessage],
<head> ],
<meta charset="UTF-8"> 'max_tokens' => 800,
<meta name="viewport" content="width=device-width, initial-scale=1.0"> 'temperature' => 0.7
<title>Assistant IA</title> ];
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style> $ch = curl_init('http://localhost:11434/v1/chat/completions');
body { margin:0; font-family:Inter,sans-serif; background:linear-gradient(135deg,#667eea,#764ba2); height:100vh; display:flex; justify-content:center; align-items:center; } curl_setopt_array($ch, [
.chat-wrapper { width:100%; max-width:900px; height:700px; background:#fff; border-radius:24px; display:flex; flex-direction:column; overflow:hidden; } CURLOPT_RETURNTRANSFER => true,
.chat-header { padding:20px; background:linear-gradient(135deg,#667eea,#764ba2); color:#fff; font-weight:600; } CURLOPT_POST => true,
#chat { flex:1; padding:20px; background:#f8fafc; overflow-y:auto; } CURLOPT_POSTFIELDS => json_encode($payload),
.message { display:flex; margin-bottom:16px; } CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
.message.user { justify-content:flex-end; } CURLOPT_TIMEOUT => 300
.message-bubble { max-width:70%; padding:12px 16px; border-radius:14px; font-size:14px; } ]);
.user .message-bubble { background:#667eea; color:#fff; }
.bot .message-bubble { background:#fff; color:#1e293b; } $response = curl_exec($ch);
.input-area { padding:20px; border-top:1px solid #e5e7eb; } $http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
#limitPopup { display:none; margin-bottom:10px; padding:10px; background:#fff3cd; color:#92400e; border-radius:10px; font-size:13px; text-align:center; } $error = curl_error($ch);
form { display:flex; gap:10px; } curl_close($ch);
input { flex:1; padding:12px; border-radius:10px; border:1px solid #ccc; }
button { padding:12px 20px; border:none; border-radius:10px; background:#667eea; color:#fff; font-weight:600; cursor:pointer; } // DEBUG: Log l'erreur
button:disabled { opacity:.6; cursor:not-allowed; } error_log("Ollama API - HTTP: $http, Error: $error, Response: " . substr($response, 0, 200));
</style>
</head> if ($error) {
<body> return "Erreur de connexion à Ollama: $error";
}
<div class="chat-wrapper">
<div class="chat-header">Assistant IA</div> if ($http !== 200) {
return "Erreur Ollama (HTTP $http). Vérifiez que le service tourne sur localhost:11434";
<div id="chat"> }
<div class="message bot">
<div class="message-bubble"> if (!$response) {
Bonjour, comment puisje vous aider? return "Pas de réponse du service LLaMA.";
</div> }
</div>
</div> $json = json_decode($response, true);
<div class="input-area"> if (!$json || !isset($json['choices'][0]['message']['content'])) {
<div id="limitPopup"> error_log("JSON decode error: " . json_last_error_msg());
Pour plus, prends l'abonnement en demandant à Bouchra return "Erreur: réponse LLaMA malformée.";
</div> }
<form onsubmit="sendMsg();return false;">
<input type="hidden" id="csrf" value="<?= htmlspecialchars($_SESSION['csrf']) ?>"> return $json['choices'][0]['message']['content'];
<input id="userInput" type="text" placeholder="Votre message…" required> }
<button id="sendBtn">Envoyer</button>
</form> /* =======================
</div> AJAX HANDLER
</div> ======================= */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
<script>
const chat = document.getElementById('chat'); if (!isAjax()) { http_response_code(403); echo json_encode(['error'=>'Requête interdite']); exit; }
const input = document.getElementById('userInput'); if (!isset($_POST['csrf']) || $_POST['csrf'] !== $_SESSION['csrf']) { http_response_code(403); echo json_encode(['error'=>'CSRF invalide']); exit; }
const btn = document.getElementById('sendBtn'); if (!checkRateLimit()) { http_response_code(429); echo json_encode(['error'=>'LIMIT_REACHED']); exit; }
const csrf = document.getElementById('csrf').value;
const popup = document.getElementById('limitPopup'); $message = sanitizeMessage($_POST['message']);
if ($message === false) { echo json_encode(['error'=>'Message invalide']); exit; }
function addMsg(text, isUser) {
const msg = document.createElement('div'); $reply = callLlamaAPI($message);
msg.className = 'message ' + (isUser ? 'user' : 'bot'); echo json_encode(['response'=>$reply]);
exit;
const bubble = document.createElement('div'); }
bubble.className = 'message-bubble';
bubble.textContent = text; // 🔒 XSS SAFE ?>
msg.appendChild(bubble); <!DOCTYPE html>
chat.appendChild(msg); <html lang="fr">
chat.scrollTop = chat.scrollHeight; <head>
} <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
async function sendMsg() { <title>bobentaz</title>
const txt = input.value.trim(); <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
if (!txt) return; <style>
body { margin:0; font-family:Inter,sans-serif; background:linear-gradient(135deg,#667eea,#764ba2); height:100vh; display:flex; justify-content:center; align-items:center; }
addMsg(txt, true); .chat-wrapper { width:100%; max-width:900px; height:700px; background:#fff; border-radius:24px; display:flex; flex-direction:column; overflow:hidden; }
input.value = ''; .chat-header { padding:20px; background:linear-gradient(135deg,#667eea,#764ba2); color:#fff; font-weight:600; }
btn.disabled = true; #chat { flex:1; padding:20px; background:#f8fafc; overflow-y:auto; }
.message { display:flex; margin-bottom:16px; }
const res = await fetch('', { .message.user { justify-content:flex-end; }
method:'POST', .message-bubble { max-width:70%; padding:12px 16px; border-radius:14px; font-size:14px; }
headers:{ .user .message-bubble { background:#667eea; color:#fff; }
'Content-Type':'application/x-www-form-urlencoded', .bot .message-bubble { background:#fff; color:#1e293b; }
'X-Requested-With':'XMLHttpRequest' .input-area { padding:20px; border-top:1px solid #e5e7eb; }
}, #limitPopup { display:none; margin-bottom:10px; padding:10px; background:#fff3cd; color:#92400e; border-radius:10px; font-size:13px; text-align:center; }
body:'message='+encodeURIComponent(txt)+'&csrf='+encodeURIComponent(csrf) form { display:flex; gap:10px; }
}); input { flex:1; padding:12px; border-radius:10px; border:1px solid #ccc; }
button { padding:12px 20px; border:none; border-radius:10px; background:#667eea; color:#fff; font-weight:600; cursor:pointer; }
const data = await res.json(); button:disabled { opacity:.6; cursor:not-allowed; }
.rate-counter { font-size:12px; color:#666; margin-top:8px; text-align:center; }
if (data.error === 'LIMIT_REACHED') { </style>
popup.style.display = 'block'; </head>
input.disabled = true; <body>
btn.disabled = true;
addMsg('Accès limité.', false); <div class="chat-wrapper">
return; <div class="chat-header">Assistant IA (bobentaz)</div>
}
<div id="chat">
addMsg(data.response || 'Erreur.', false); <div class="message bot">
btn.disabled = false; <div class="message-bubble">Bonjour, comment puisje vous aider ?</div>
} </div>
</script> </div>
</body> <div class="input-area">
</html> <div id="limitPopup">Limite de 10 messages par minute atteinte. Veuillez réessayer plus tard.</div>
<form onsubmit="sendMsg();return false;">
<input type="hidden" id="csrf" value="<?= htmlspecialchars($_SESSION['csrf']) ?>">
<input id="userInput" type="text" placeholder="Votre message…" required>
<button id="sendBtn">Envoyer</button>
</form>
<div class="rate-counter">10 messages/minute</div>
</div>
</div>
<script>
const chat=document.getElementById('chat'),input=document.getElementById('userInput'),btn=document.getElementById('sendBtn'),csrf=document.getElementById('csrf').value,popup=document.getElementById('limitPopup');
function addMsg(text,isUser){const msg=document.createElement('div');msg.className='message '+(isUser?'user':'bot');const bubble=document.createElement('div');bubble.className='message-bubble';bubble.textContent=text;msg.appendChild(bubble);chat.appendChild(msg);chat.scrollTop=chat.scrollHeight;}
async function sendMsg(){const txt=input.value.trim();if(!txt)return;addMsg(txt,true);input.value='';btn.disabled=true;const res=await fetch('',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded','X-Requested-With':'XMLHttpRequest'},body:'message='+encodeURIComponent(txt)+'&csrf='+encodeURIComponent(csrf)});const data=await res.json();if(data.error==='LIMIT_REACHED'){popup.style.display='block';addMsg('Limite atteinte. Attendez quelques secondes.',false);btn.disabled=false;return;}popup.style.display='none';addMsg(data.response||'Erreur.',false);btn.disabled=false;}
</script>
</body>
</html