diff --git a/cybersecurity/Network Reputation Service/script-nrs.py b/cybersecurity/Network Reputation Service/script-nrs.py new file mode 100644 index 0000000..c7ae41e --- /dev/null +++ b/cybersecurity/Network Reputation Service/script-nrs.py @@ -0,0 +1,712 @@ +#!/usr/bin/env python3 +""" +Script d'Audit Firewall - Network Reputation Service +Auteur: Converti depuis PowerShell +Date: 04/09/2025 +Version: 1.3 (Python) +""" + +import json +import requests +import argparse +import sys +import os +from datetime import datetime, timezone +from pathlib import Path +from urllib.parse import urlparse +import time +import getpass +from typing import List, Dict, Any, Optional, Tuple +import math + +# Import des bibliothèques avec gestion des erreurs +try: + from colorama import init, Fore, Back, Style + init(autoreset=True) + COLORAMA_AVAILABLE = True +except ImportError: + COLORAMA_AVAILABLE = False + print("Attention: colorama n'est pas installé. Les couleurs ne seront pas disponibles.") + print("Installez avec: pip install colorama") + +try: + from tqdm import tqdm + TQDM_AVAILABLE = True +except ImportError: + TQDM_AVAILABLE = False + print("Attention: tqdm n'est pas installé. Les barres de progression ne seront pas disponibles.") + print("Installez avec: pip install tqdm") + +# Configuration globale +TIMEOUT_DEFAULT = 10 +GRADE_COLORS = { + 'A+': '#28a745', 'A': '#52b83a', 'B+': '#7ac92e', 'B': '#a3da23', + 'C+': '#cceb17', 'C': '#f5f90c', 'D+': '#f7d808', 'D': '#f9b604', + 'E+': '#fb9500', 'E': '#fd7300', 'F+': '#ff5100', 'F': '#dc3545' +} + +BLOCK_KEYWORDS = [ + "site bloqué", "access denied", "filtrage web", + "Access Denied", "Site Blocked", "blocked", "forbidden" +] + +BROWSER_HEADERS = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", + "Accept-Language": "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7" +} + +def write_color_output(text: str, color: str = "WHITE") -> None: + """Affiche du texte en couleur si colorama est disponible""" + if not COLORAMA_AVAILABLE: + print(text) + return + + color_map = { + "RED": Fore.RED, + "GREEN": Fore.GREEN, + "YELLOW": Fore.YELLOW, + "BLUE": Fore.BLUE, + "CYAN": Fore.CYAN, + "MAGENTA": Fore.MAGENTA, + "WHITE": Fore.WHITE, + "GRAY": Fore.LIGHTBLACK_EX + } + + color_code = color_map.get(color.upper(), Fore.WHITE) + print(f"{color_code}{text}") + +def prerequisites() -> bool: + """Vérifie les prérequis du script""" + write_color_output("\n === Vérification des prérequis ===", "CYAN") + + # Vérification du fichier JSON + script_dir = Path(__file__).parent + json_file = script_dir / "file-nrs.json" + + if not json_file.exists(): + write_color_output("ERREUR: Le fichier 'file-nrs.json' n'existe pas!", "RED") + write_color_output("Veuillez télécharger ou créer le fichier depuis:", "YELLOW") + write_color_output("https://gitea.tips-of-mine.com/Tips-Of-Mine/Powershell/src/branch/main/cybersecurity/Network%20Reputation%20Service/file-nrs.json", "BLUE") + write_color_output("Le fichier doit être placé dans le même dossier que ce script.", "YELLOW") + return False + + write_color_output("✓ Fichier 'file-nrs.json' trouvé", "GREEN") + + # Vérification des modules Python requis + required_modules = { + 'requests': 'requests', + 'colorama': 'colorama', + 'tqdm': 'tqdm' + } + + missing_modules = [] + for module_name, pip_name in required_modules.items(): + try: + __import__(module_name) + write_color_output(f"✓ Module {module_name} disponible", "GREEN") + except ImportError: + write_color_output(f"⚠ Module {module_name} manquant", "YELLOW") + missing_modules.append(pip_name) + + if missing_modules: + write_color_output(f"Modules manquants (optionnels): {', '.join(missing_modules)}", "YELLOW") + write_color_output(f"Installez avec: pip install {' '.join(missing_modules)}", "YELLOW") + + return True + +def calculate_category_score(results: List[Dict]) -> float: + """Calcule le score d'une catégorie""" + if not results: + return 0.0 + + correct_results = sum(1 for result in results if result['is_correct']) + return round((correct_results / len(results)) * 100, 2) + +def convert_score_to_grade(score: float) -> str: + """Convertit un score en note""" + if score >= 95: return 'A+' + elif score >= 90: return 'A' + elif score >= 85: return 'B+' + elif score >= 80: return 'B' + elif score >= 75: return 'C+' + elif score >= 70: return 'C' + elif score >= 65: return 'D+' + elif score >= 60: return 'D' + elif score >= 55: return 'E+' + elif score >= 50: return 'E' + elif score >= 45: return 'F+' + else: return 'F' + +def get_url_status(url: str, expected_action: str, proxy_url: Optional[str] = None, + proxy_auth: Optional[Tuple[str, str]] = None, timeout: int = TIMEOUT_DEFAULT) -> Dict: + """Teste une URL et détermine si elle est bloquée ou autorisée""" + + result = { + 'url': url, + 'expected': expected_action, + 'actual_result': 'Indéterminé', + 'test_status': 'Échec', + 'score': None, + 'status_code': '', + 'details': '', + 'response_time': '' + } + + # Configuration des proxies + proxies = {} + if proxy_url: + proxies = { + 'http': proxy_url, + 'https': proxy_url + } + + # Configuration de l'authentification proxy + auth = None + if proxy_auth: + auth = proxy_auth + + start_time = time.time() + + try: + response = requests.get( + url, + headers=BROWSER_HEADERS, + timeout=timeout, + proxies=proxies, + auth=auth, + verify=True, + allow_redirects=True + ) + + response_time = round(time.time() - start_time, 2) + result['response_time'] = str(response_time) + result['status_code'] = str(response.status_code) + + # Vérification des mots-clés de blocage dans le contenu + keyword_found = any(keyword.lower() in response.text.lower() for keyword in BLOCK_KEYWORDS) + + if keyword_found: + result['actual_result'] = "Bloqué" + result['details'] = "Page de blocage détectée." + else: + result['actual_result'] = "Autorisé" + result['details'] = "Le site a été atteint sans blocage." + + except requests.exceptions.Timeout: + result['actual_result'] = "Bloqué (Timeout)" + result['details'] = f"La requête a expiré après {timeout}s, indiquant un blocage probable." + result['response_time'] = str(timeout) + + except requests.exceptions.ConnectionError: + result['actual_result'] = "Erreur de Connexion" + result['details'] = "Impossible de joindre le serveur" + + except requests.exceptions.RequestException as e: + result['actual_result'] = "Erreur Script" + result['details'] = f"Erreur inattendue: {str(e)}" + return result + + except Exception as e: + result['actual_result'] = "Erreur Script" + result['details'] = f"Erreur inattendue: {str(e)}" + return result + + # Détermination si le test est conforme + is_blocked = result['actual_result'] in ['Bloqué', 'Bloqué (Timeout)'] + should_be_blocked = expected_action == 'block' + + if (is_blocked and should_be_blocked) or (not is_blocked and not should_be_blocked): + result['test_status'] = "Conforme" + result['score'] = 1 + result['details'] += " (Résultat conforme à l'attendu)" + else: + result['test_status'] = "Non Conforme" + result['score'] = 0 + result['details'] += f" (NON CONFORME; Attendu: {expected_action}; Obtenu: {result['actual_result']})" + + return result + +def check_categories(categories: Dict, proxy_url: Optional[str] = None, + proxy_auth: Optional[Tuple[str, str]] = None, timeout: int = TIMEOUT_DEFAULT) -> List[Dict]: + """Teste toutes les catégories et URLs""" + + all_results = [] + categories_data = categories.get('categorie', []) + + write_color_output(f"\n=== Début du test de {len(categories_data)} catégories ===", "CYAN") + + # Barre de progression principale si tqdm est disponible + category_progress = None + if TQDM_AVAILABLE: + category_progress = tqdm(categories_data, desc="Catégories", unit="cat") + + for category_idx, category in enumerate(categories_data): + if not TQDM_AVAILABLE: + write_color_output(f"\n=== Test de la catégorie: {category['nom']} ({category_idx + 1}/{len(categories_data)}) ===", "CYAN") + + category_results = [] + urls = category.get('urls', []) + + # Barre de progression pour les URLs si tqdm est disponible + url_progress = None + if TQDM_AVAILABLE: + url_progress = tqdm(urls, desc=f"URLs de '{category['nom']}'", unit="url", leave=False) + + for url_idx, url_obj in enumerate(urls): + if not TQDM_AVAILABLE: + write_color_output(f"Test de: {url_obj['url']} ({url_idx + 1}/{len(urls)})", "YELLOW") + + result = get_url_status( + url_obj['url'], + url_obj['expected_action'], + proxy_url, + proxy_auth, + timeout + ) + + test_result = { + 'category': category['nom'], + 'category_id': category.get('id', ''), + 'url': url_obj['url'], + 'reputation': url_obj.get('reputation', ''), + 'expected_action': url_obj['expected_action'], + 'status': result['actual_result'], + 'status_code': result['status_code'], + 'response_time': result['response_time'], + 'error': result['details'], + 'score': result['score'], + 'is_correct': result['test_status'] == "Conforme" + } + + if not TQDM_AVAILABLE: + status_color = "GREEN" if test_result['is_correct'] else "RED" + write_color_output( + f" → Résultat: {test_result['status']} | Attendu: {test_result['expected_action']} | Correct: {test_result['is_correct']}", + status_color + ) + + category_results.append(test_result) + all_results.append(test_result) + + if url_progress: + url_progress.update(1) + + if url_progress: + url_progress.close() + + category_score = calculate_category_score(category_results) + category_grade = convert_score_to_grade(category_score) + + if not TQDM_AVAILABLE: + write_color_output(f"Score de la catégorie '{category['nom']}': {category_score}% (Note: {category_grade})", "MAGENTA") + + if category_progress: + category_progress.set_postfix({ + 'Score': f"{category_score}%", + 'Grade': category_grade + }) + category_progress.update(1) + + if category_progress: + category_progress.close() + + return all_results + +def generate_html_report(results: List[Dict], output_path: Path) -> None: + """Génère le rapport HTML""" + write_color_output("Génération du rapport HTML...", "YELLOW") + + # Calcul des scores par catégorie + categories_dict = {} + for result in results: + cat_name = result['category'] + if cat_name not in categories_dict: + categories_dict[cat_name] = [] + categories_dict[cat_name].append(result) + + category_scores = [] + for cat_name, cat_results in categories_dict.items(): + score = calculate_category_score(cat_results) + grade = convert_score_to_grade(score) + + category_scores.append({ + 'category': cat_name, + 'score': score, + 'grade': grade, + 'color': GRADE_COLORS[grade], + 'total_urls': len(cat_results), + 'correct_results': sum(1 for r in cat_results if r['is_correct']), + 'results': cat_results + }) + + # Score global + global_score = calculate_category_score(results) + global_grade = convert_score_to_grade(global_score) + global_color = GRADE_COLORS[global_grade] + + # Génération du HTML + current_time = datetime.now().strftime('%d/%m/%Y à %H:%M') + + html_content = f""" + +
+ + +Rapport généré le {current_time}
+ + +Catégorie | +Score (%) | +Note | +URLs Totales | +Résultats Corrects | +
---|---|---|---|---|
{cat_score['category']} | +{cat_score['score']}% | +{cat_score['grade']} | +{cat_score['total_urls']} | +{cat_score['correct_results']} | +
URL | +Réputation | +Action Attendue | +Statut | +Correct | +
---|---|---|---|---|
{url_display} | +{result['reputation']} | +{result['expected_action']} | +{result['status']} | +{correct_text} | +