Téléverser les fichiers vers "/"

This commit is contained in:
2026-04-20 13:26:13 +02:00
commit e456c6d94e
3 changed files with 999 additions and 0 deletions

102
README.md Normal file
View File

@@ -0,0 +1,102 @@
# Audit SUID GUID Linux
## Shell
### Installation
Copier audit-suid.sh
Exemple
```
cp audit-suid.sh /usr/local/bin/audit-suid.sh
chmod +x /usr/local/bin/audit-suid.sh
```
### Exécution
```
# Exécuter avec les droits root
sudo /usr/local/bin/audit-suid.sh
```
### Planification avec cron
```
# Audit quotidien à 2h du matin
sudo crontab -e
# Ajouter la ligne :
0 2 * * * /usr/local/bin/audit-suid.sh
```
### Personalisation
Modifiez le fichier /etc/audit/suid_whitelist.conf pour ajouter vos propres binaires légitimes.
Ce script vous aidera à maintenir la sécurité de vos serveurs Linux en identifiant rapidement les risques potentiels liés aux permissions SUID/SGID.
## Pyhton
### Installation
Copier audit-suid.py
Exemple
```
# Sauvegarde du script
sudo mkdir -p /usr/local/bin/
sudo cp suid_audit.py /usr/local/bin/audit-suid.py
sudo chmod +x /usr/local/bin/audit-suid.py
# Création des répertoires
sudo mkdir -p /etc/audit /var/log/audit
```
### Utilisation
```
# Exécution simple
sudo python3 audit-suid.py
# Avec options
sudo python3 audit-suid.py --max-depth 15 --no-temp
# Aide
python3 audit-suid.py --help
# Exécution avec droits root (obligatoire)
sudo /usr/local/bin/audit-suid.py
```
### Options disponibles
Option Description
--no-temp Ne pas vérifier /tmp, /var/tmp, /dev/shm
--no-orphan Ne pas vérifier les fichiers orphelins
--no-hash Ne pas calculer les hashs MD5 (plus rapide)
--max-depth N Profondeur maximale de recherche (défaut: 20)
--log-dir PATH Répertoire des logs (défaut: /var/log/audit)
--whitelist PATH Fichier JSON de whitelist
### Cron
```
# Éditer crontab
sudo crontab -e
# Ajouter pour une exécution quotidienne à 2h
0 2 * * * /usr/local/bin/audit-suid.py --no-hash
# Exécution hebdomadaire avec tous les contrôles
0 3 * * 0 /usr/local/bin/audit-suid.py
```
### Format du fichier de whitelist (JSON)
```json
{
"SUID": [
"/bin/su",
"/usr/bin/sudo",
"/usr/bin/passwd"
],
"SGID": [
"/usr/bin/wall",
"/usr/bin/write"
]
}
```

582
audit-suid.py Normal file
View File

@@ -0,0 +1,582 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script d'audit des fichiers SUID et GUID pour Linux
Sans dépendances externes
Auteur: Zen6
Version: 1.0
"""
import os
import sys
import stat
import hashlib
import logging
import argparse
import subprocess
import json
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Set, Tuple
from dataclasses import dataclass, asdict
# Configuration
@dataclass
class AuditConfig:
log_dir: str = "/var/log/audit"
whitelist_file: str = "/etc/audit/suid_whitelist.json"
excluded_dirs: List[str] = None
max_depth: int = 20
check_temp_dirs: bool = True
check_orphans: bool = True
compute_hashes: bool = True
def __post_init__(self):
if self.excluded_dirs is None:
self.excluded_dirs = [
"/proc", "/sys", "/dev", "/run", "/snap",
"/var/lib/docker", "/var/lib/lxcfs", "/proc/*",
"/sys/*", "/dev/*"
]
@dataclass
class FileAudit:
path: str
type: str
permissions: str
owner: str
group: str
size: int
size_human: str
mtime: str
hash_md5: str = ""
is_whitelisted: bool = False
alert_level: str = "INFO"
class SUIDGUIDAuditor:
def __init__(self, config: AuditConfig = None):
self.config = config or AuditConfig()
self.whitelist = {"SUID": set(), "SGID": set()}
self.alerts = []
self.results = []
# Configuration du logging
self.setup_logging()
def setup_logging(self):
"""Configure le système de logging"""
# Création du répertoire de logs
Path(self.config.log_dir).mkdir(parents=True, exist_ok=True)
# Nom des fichiers de log
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
self.log_file = Path(self.config.log_dir) / f"audit_{timestamp}.log"
self.report_file = Path(self.config.log_dir) / f"report_{timestamp}.txt"
self.alert_file = Path(self.config.log_dir) / f"alerts_{timestamp}.txt"
self.json_file = Path(self.config.log_dir) / f"audit_{timestamp}.json"
self.csv_file = Path(self.config.log_dir) / f"audit_{timestamp}.csv"
# Configuration du logging (sans couleurs)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(self.log_file),
logging.StreamHandler()
]
)
self.logger = logging.getLogger(__name__)
def load_whitelist(self):
"""Charge la liste blanche depuis un fichier JSON"""
try:
if Path(self.config.whitelist_file).exists():
with open(self.config.whitelist_file, 'r') as f:
data = json.load(f)
self.whitelist["SUID"] = set(data.get("SUID", []))
self.whitelist["SGID"] = set(data.get("SGID", []))
self.logger.info(f"Liste blanche chargée: {len(self.whitelist['SUID'])} SUID, {len(self.whitelist['SGID'])} SGID")
else:
self.create_default_whitelist()
except Exception as e:
self.logger.error(f"Erreur lors du chargement de la whitelist: {e}")
def create_default_whitelist(self):
"""Crée une liste blanche par défaut"""
default_whitelist = {
"SUID": [
"/bin/su", "/bin/ping", "/bin/mount", "/bin/umount",
"/usr/bin/passwd", "/usr/bin/sudo", "/usr/bin/chsh",
"/usr/bin/chfn", "/usr/bin/gpasswd", "/usr/bin/crontab",
"/usr/bin/at", "/usr/bin/newgrp", "/usr/sbin/unix_chkpwd",
"/usr/lib/openssh/ssh-keysign", "/usr/lib/dbus-1.0/dbus-daemon-launch-helper",
"/usr/bin/sg", "/usr/bin/pkexec", "/usr/bin/ksu"
],
"SGID": [
"/usr/bin/wall", "/usr/bin/write", "/usr/bin/ssh-agent",
"/usr/bin/locate", "/usr/bin/mlocate", "/usr/bin/bsd-write",
"/usr/bin/chage", "/usr/bin/expiry"
]
}
try:
Path(self.config.whitelist_file).parent.mkdir(parents=True, exist_ok=True)
with open(self.config.whitelist_file, 'w') as f:
json.dump(default_whitelist, f, indent=4)
self.whitelist["SUID"] = set(default_whitelist["SUID"])
self.whitelist["SGID"] = set(default_whitelist["SGID"])
self.logger.info(f"Liste blanche par défaut créée: {self.config.whitelist_file}")
except Exception as e:
self.logger.error(f"Erreur lors de la création de la whitelist: {e}")
def get_file_info(self, filepath: str) -> Tuple[str, str, str, int, str]:
"""Récupère les informations d'un fichier"""
try:
stat_info = os.stat(filepath, follow_symlinks=False)
permissions = stat.filemode(stat_info.st_mode)
owner = str(stat_info.st_uid)
group = str(stat_info.st_gid)
size = stat_info.st_size
mtime = datetime.fromtimestamp(stat_info.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
# Récupérer les noms d'utilisateur/groupe si possible
try:
import pwd
owner = pwd.getpwuid(stat_info.st_uid).pw_name
except:
pass
try:
import grp
group = grp.getgrgid(stat_info.st_gid).gr_name
except:
pass
return permissions, owner, group, size, mtime
except Exception as e:
self.logger.debug(f"Erreur sur {filepath}: {e}")
return "unknown", "unknown", "unknown", 0, "unknown"
def compute_md5(self, filepath: str) -> str:
"""Calcule le hash MD5 d'un fichier"""
if not self.config.compute_hashes:
return ""
try:
hash_md5 = hashlib.md5()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()
except Exception as e:
self.logger.debug(f"Erreur MD5 sur {filepath}: {e}")
return ""
def check_suid_sgid(self, filepath: str) -> List[str]:
"""Vérifie les bits SUID et SGID"""
try:
st = os.stat(filepath, follow_symlinks=False)
mode = st.st_mode
types = []
if mode & stat.S_ISUID:
types.append("SUID")
if mode & stat.S_ISGID:
types.append("SGID")
return types
except:
return []
def should_exclude(self, filepath: str) -> bool:
"""Vérifie si le chemin doit être exclu"""
for excluded in self.config.excluded_dirs:
if filepath.startswith(excluded):
return True
return False
def audit_directory(self, start_path: str = "/"):
"""Audite récursivement un répertoire"""
self.logger.info(f"Début de l'audit depuis {start_path}")
total_files = 0
try:
for root, dirs, files in os.walk(start_path, followlinks=False):
# Filtrer les répertoires exclus
dirs[:] = [d for d in dirs if not self.should_exclude(os.path.join(root, d))]
# Vérifier la profondeur maximale
depth = root.count(os.sep)
if depth > self.config.max_depth:
continue
for file in files:
filepath = os.path.join(root, file)
total_files += 1
if total_files % 10000 == 0:
self.logger.info(f"Progression: {total_files} fichiers parcourus...")
suid_sgid_types = self.check_suid_sgid(filepath)
if suid_sgid_types:
self.process_file(filepath, suid_sgid_types)
except KeyboardInterrupt:
self.logger.warning("Audit interrompu par l'utilisateur")
sys.exit(1)
except Exception as e:
self.logger.error(f"Erreur lors de l'audit: {e}")
self.logger.info(f"Audit terminé. {total_files} fichiers parcourus.")
def process_file(self, filepath: str, types: List[str]):
"""Traite un fichier avec bits SUID/SGID"""
permissions, owner, group, size, mtime = self.get_file_info(filepath)
for file_type in types:
is_whitelisted = filepath in self.whitelist.get(file_type, set())
alert_level = "INFO" if is_whitelisted else "WARNING"
hash_md5 = ""
if not is_whitelisted and self.config.compute_hashes:
hash_md5 = self.compute_md5(filepath)
audit = FileAudit(
path=filepath,
type=file_type,
permissions=permissions,
owner=owner,
group=group,
size=size,
size_human=self.human_readable_size(size),
mtime=mtime,
hash_md5=hash_md5,
is_whitelisted=is_whitelisted,
alert_level=alert_level
)
self.results.append(audit)
if is_whitelisted:
self.logger.info(f"[OK {file_type}] {filepath}")
else:
self.logger.warning(f"[ALERTE {file_type}] {filepath} (propriétaire: {owner}, groupe: {group})")
self.alerts.append(audit)
def check_temp_directories(self):
"""Vérifie les répertoires temporaires"""
if not self.config.check_temp_dirs:
return
temp_dirs = ["/tmp", "/var/tmp", "/dev/shm"]
self.logger.info("Vérification des répertoires temporaires...")
for temp_dir in temp_dirs:
if os.path.exists(temp_dir):
try:
for root, dirs, files in os.walk(temp_dir):
for file in files:
filepath = os.path.join(root, file)
types = self.check_suid_sgid(filepath)
if types:
self.logger.error(f"[URGENT] Fichier SUID/SGID dans {temp_dir}: {filepath}")
alert = FileAudit(
path=filepath,
type=",".join(types),
permissions="unknown",
owner="unknown",
group="unknown",
size=0,
size_human="0B",
mtime="unknown",
alert_level="CRITICAL"
)
self.alerts.append(alert)
self.results.append(alert)
except Exception as e:
self.logger.debug(f"Erreur lors de la vérification de {temp_dir}: {e}")
def check_orphan_files(self):
"""Vérifie les fichiers sans propriétaire valide"""
if not self.config.check_orphans:
return
self.logger.info("Recherche des fichiers orphelins...")
try:
cmd = "find / -type f \\( -perm -4000 -o -perm -2000 \\) \\( -nouser -o -nogroup \\) 2>/dev/null"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=300)
for line in result.stdout.splitlines():
if line.strip():
self.logger.error(f"[ORPHELIN] {line}")
alert = FileAudit(
path=line.strip(),
type="ORPHAN",
permissions="unknown",
owner="unknown",
group="unknown",
size=0,
size_human="0B",
mtime="unknown",
alert_level="CRITICAL"
)
self.alerts.append(alert)
self.results.append(alert)
except subprocess.TimeoutExpired:
self.logger.error("Timeout lors de la recherche des fichiers orphelins")
except Exception as e:
self.logger.error(f"Erreur lors de la vérification des orphelins: {e}")
def human_readable_size(self, size: int) -> str:
"""Convertit la taille en format lisible"""
if size == 0:
return "0 B"
units = ['B', 'KB', 'MB', 'GB', 'TB']
i = 0
while size >= 1024 and i < len(units) - 1:
size /= 1024.0
i += 1
return f"{size:.2f} {units[i]}"
def generate_reports(self):
"""Génère tous les rapports (texte, JSON, CSV)"""
# Rapport texte détaillé
self.generate_text_report()
# Rapport JSON
self.generate_json_report()
# Rapport CSV
self.generate_csv_report()
self.logger.info(f"Rapport texte généré: {self.report_file}")
self.logger.info(f"Rapport JSON généré: {self.json_file}")
self.logger.info(f"Rapport CSV généré: {self.csv_file}")
self.logger.info(f"Alertes sauvegardées: {self.alert_file}")
def generate_text_report(self):
"""Génère un rapport texte détaillé"""
with open(self.report_file, 'w') as f:
f.write("=" * 80 + "\n")
f.write("RAPPORT D'AUDIT SUID/SGID\n")
f.write(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"Serveur: {os.uname().nodename}\n")
f.write(f"Système: {os.uname().sysname} {os.uname().release}\n")
f.write("=" * 80 + "\n\n")
# Résumé
f.write("RÉSUMÉ\n")
f.write("-" * 40 + "\n")
f.write(f"Total fichiers audités: {len(self.results)}\n")
f.write(f"Anomalies SUID: {len([r for r in self.alerts if r.type == 'SUID'])}\n")
f.write(f"Anomalies SGID: {len([r for r in self.alerts if r.type == 'SGID'])}\n")
f.write(f"Fichiers orphelins: {len([r for r in self.alerts if r.type == 'ORPHAN'])}\n")
f.write(f"Fichiers whitelistés: {len([r for r in self.results if r.is_whitelisted])}\n\n")
# Anomalies détaillées
if self.alerts:
f.write("ANOMALIES DÉTECTÉES\n")
f.write("-" * 40 + "\n")
# Séparer par type
suid_alerts = [a for a in self.alerts if a.type == 'SUID']
sgid_alerts = [a for a in self.alerts if a.type == 'SGID']
orphan_alerts = [a for a in self.alerts if a.type == 'ORPHAN']
if suid_alerts:
f.write("\n[SUID SUSPECTS]\n")
for i, alert in enumerate(suid_alerts, 1):
f.write(f"\n{i}. {alert.path}\n")
f.write(f" Type: {alert.type}\n")
f.write(f" Propriétaire: {alert.owner}\n")
f.write(f" Groupe: {alert.group}\n")
f.write(f" Permissions: {alert.permissions}\n")
f.write(f" Taille: {alert.size_human}\n")
f.write(f" Dernière modification: {alert.mtime}\n")
if alert.hash_md5:
f.write(f" MD5: {alert.hash_md5}\n")
if sgid_alerts:
f.write("\n[SGID SUSPECTS]\n")
for i, alert in enumerate(sgid_alerts, 1):
f.write(f"\n{i}. {alert.path}\n")
f.write(f" Type: {alert.type}\n")
f.write(f" Propriétaire: {alert.owner}\n")
f.write(f" Groupe: {alert.group}\n")
f.write(f" Permissions: {alert.permissions}\n")
f.write(f" Taille: {alert.size_human}\n")
f.write(f" Dernière modification: {alert.mtime}\n")
if alert.hash_md5:
f.write(f" MD5: {alert.hash_md5}\n")
if orphan_alerts:
f.write("\n[FICHIERS ORPHELINS]\n")
for i, alert in enumerate(orphan_alerts, 1):
f.write(f"\n{i}. {alert.path}\n")
f.write(f" Type: {alert.type}\n")
else:
f.write("✓ AUCUNE ANOMALIE DÉTECTÉE\n\n")
# Tous les fichiers SUID/SGID (y compris whitelist)
f.write("\n\nLISTE COMPLÈTE DES FICHIERS SUID/SGID\n")
f.write("-" * 40 + "\n")
for result in sorted(self.results, key=lambda x: x.path):
status = "[WHITELIST]" if result.is_whitelisted else "[ALERTE]"
f.write(f"{status} {result.type}: {result.path}\n")
f.write(f" Permissions: {result.permissions}, Owner: {result.owner}, Group: {result.group}\n")
# Recommandations
f.write("\n\nRECOMMANDATIONS\n")
f.write("-" * 40 + "\n")
f.write("1. Examinez chaque binaire suspect manuellement\n")
f.write("2. Supprimez les bits SUID/SGID si non nécessaires:\n")
f.write(" sudo chmod u-s <fichier> # Pour SUID\n")
f.write(" sudo chmod g-s <fichier> # Pour SGID\n")
f.write(f"3. Ajoutez les binaires légitimes à la whitelist:\n")
f.write(f" {self.config.whitelist_file}\n")
f.write("4. Vérifiez l'intégrité des binaires système\n")
f.write("5. Mettez en place une surveillance régulière\n")
def generate_json_report(self):
"""Génère un rapport JSON"""
report_data = {
"metadata": {
"timestamp": datetime.now().isoformat(),
"hostname": os.uname().nodename,
"system": f"{os.uname().sysname} {os.uname().release}",
"total_files_audited": len(self.results),
"total_alerts": len(self.alerts)
},
"alerts": [asdict(alert) for alert in self.alerts],
"all_files": [asdict(result) for result in self.results]
}
with open(self.json_file, 'w') as f:
json.dump(report_data, f, indent=2)
def generate_csv_report(self):
"""Génère un rapport CSV pour analyse avec tableur"""
import csv
with open(self.csv_file, 'w', newline='') as f:
if self.results:
fieldnames = ['path', 'type', 'permissions', 'owner', 'group',
'size', 'size_human', 'mtime', 'hash_md5',
'is_whitelisted', 'alert_level']
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for result in self.results:
writer.writerow(asdict(result))
def save_alerts_file(self):
"""Sauvegarde les alertes dans un fichier simple"""
with open(self.alert_file, 'w') as f:
f.write("# Fichier d'alertes SUID/SGID\n")
f.write(f"# Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write("# Format: TYPE|PATH|OWNER|GROUP|LEVEL\n\n")
for alert in self.alerts:
f.write(f"{alert.type}|{alert.path}|{alert.owner}|{alert.group}|{alert.alert_level}\n")
def print_summary(self) -> int:
"""Affiche un résumé dans la console"""
print("\n" + "=" * 80)
print("RÉSUMÉ DE L'AUDIT")
print("=" * 80)
print(f"Log complet: {self.log_file}")
print(f"Rapport détaillé: {self.report_file}")
print(f"Rapport JSON: {self.json_file}")
print(f"Rapport CSV: {self.csv_file}")
print(f"Alertes: {self.alert_file}")
print(f"Whitelist: {self.config.whitelist_file}")
if self.alerts:
print(f"\n⚠️ ATTENTION: {len(self.alerts)} anomalie(s) détectée(s) !")
print(f" - SUID suspects: {len([a for a in self.alerts if a.type == 'SUID'])}")
print(f" - SGID suspects: {len([a for a in self.alerts if a.type == 'SGID'])}")
print(f" - Orphelins: {len([a for a in self.alerts if a.type == 'ORPHAN'])}")
print("Consultez les rapports pour plus de détails")
return 1
else:
print("\n✓ AUCUNE ANOMALIE DÉTECTÉE")
print("Tous les fichiers SUID/SGID sont dans la whitelist")
return 0
def run(self) -> int:
"""Exécute l'audit complet"""
print("=" * 80)
print("SUID/SGID Security Audit Tool - Linux Privilege Scanner")
print(f"Début de l'audit: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 80)
# Vérification des droits root
if os.geteuid() != 0:
self.logger.error("Ce script doit être exécuté en tant que root")
print("ERREUR: Ce script doit être exécuté en tant que root")
print(f"Utilisez: sudo python3 {sys.argv[0]}")
return 1
# Chargement de la configuration
self.load_whitelist()
# Exécution des audits
self.audit_directory()
self.check_temp_directories()
self.check_orphan_files()
# Génération des rapports
self.generate_reports()
self.save_alerts_file()
# Affichage du résumé
return self.print_summary()
def main():
"""Fonction principale avec parsing des arguments"""
parser = argparse.ArgumentParser(
description="Audit des fichiers SUID et GUID sur Linux",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Exemples:
sudo python3 audit-suid.py # Audit complet
sudo python3 audit-suid.py --no-temp # Sans vérification /tmp
sudo python3 audit-suid.py --max-depth 10 # Limite à 10 niveaux
sudo python3 audit-suid.py --no-hash # Sans calcul MD5 (plus rapide)
"""
)
parser.add_argument('--no-temp', action='store_true',
help="Ne pas vérifier les répertoires temporaires")
parser.add_argument('--no-orphan', action='store_true',
help="Ne pas vérifier les fichiers orphelins")
parser.add_argument('--no-hash', action='store_true',
help="Ne pas calculer les hashs MD5 (plus rapide)")
parser.add_argument('--max-depth', type=int, default=20,
help="Profondeur maximale de recherche (défaut: 20)")
parser.add_argument('--log-dir', type=str, default="/var/log/audit",
help="Répertoire des logs (défaut: /var/log/audit)")
parser.add_argument('--whitelist', type=str, default="/etc/audit/suid_whitelist.json",
help="Fichier de whitelist (défaut: /etc/audit/suid_whitelist.json)")
args = parser.parse_args()
# Configuration
config = AuditConfig(
log_dir=args.log_dir,
whitelist_file=args.whitelist,
max_depth=args.max_depth,
check_temp_dirs=not args.no_temp,
check_orphans=not args.no_orphan,
compute_hashes=not args.no_hash
)
# Exécution de l'audit
auditor = SUIDGUIDAuditor(config)
exit_code = auditor.run()
sys.exit(exit_code)
if __name__ == "__main__":
main()

315
audit-suid.sh Normal file
View File

@@ -0,0 +1,315 @@
#!/bin/bash
# Script d'audit des fichiers SUID et GUID
# Auteur: Zen6
# Date: $(date +%Y-%m-%d)
# Version: 1.0
# Configuration
LOG_DIR="/var/log/audit"
LOG_FILE="$LOG_DIR/suid_guid_audit_$(date +%Y%m%d_%H%M%S).log"
REPORT_FILE="$LOG_DIR/suid_guid_report_$(date +%Y%m%d_%H%M%S).txt"
ALERT_FILE="$LOG_DIR/suid_guid_alerts_$(date +%Y%m%d_%H%M%S).txt"
WHITELIST_FILE="/etc/audit/suid_whitelist.conf"
# Couleurs pour l'affichage
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Fonction de logging
log() {
echo -e "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# Fonction d'affichage
print_header() {
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}========================================${NC}"
}
# Vérification des privilèges root
check_root() {
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}Ce script doit être exécuté en tant que root${NC}"
echo "Utilisez: sudo $0"
exit 1
fi
}
# Création des répertoires nécessaires
setup_directories() {
if [[ ! -d "$LOG_DIR" ]]; then
mkdir -p "$LOG_DIR"
chmod 750 "$LOG_DIR"
fi
}
# Création du fichier de whitelist par défaut
create_default_whitelist() {
if [[ ! -f "$WHITELIST_FILE" ]]; then
mkdir -p "$(dirname $WHITELIST_FILE)"
cat > "$WHITELIST_FILE" << EOF
# Liste blanche des binaires SUID/SGID légitimes
# Format: chemin/du/binaire [SUID|SGID|BOTH]
/bin/su SUID
/bin/ping SUID
/bin/mount SUID
/bin/umount SUID
/usr/bin/passwd SUID
/usr/bin/sudo SUID
/usr/bin/chsh SUID
/usr/bin/chfn SUID
/usr/bin/gpasswd SUID
/usr/bin/crontab SUID
/usr/bin/at SUID
/usr/bin/newgrp SUID
/usr/sbin/unix_chkpwd SUID
/usr/bin/wall SGID
/usr/bin/write SGID
/usr/bin/ssh-agent SGID
/usr/bin/locate SGID
/usr/bin/mlocate SGID
EOF
log "Fichier de whitelist créé: $WHITELIST_FILE"
fi
}
# Fonction pour charger la whitelist
load_whitelist() {
declare -gA WHITELIST_SUID
declare -gA WHITELIST_SGID
if [[ -f "$WHITELIST_FILE" ]]; then
while IFS= read -r line || [[ -n "$line" ]]; do
# Ignorer les commentaires et lignes vides
[[ "$line" =~ ^#.*$ ]] && continue
[[ -z "$line" ]] && continue
read -r path type <<< "$line"
if [[ -n "$path" ]]; then
case "$type" in
SUID) WHITELIST_SUID["$path"]=1 ;;
SGID) WHITELIST_SGID["$path"]=1 ;;
BOTH) WHITELIST_SUID["$path"]=1; WHITELIST_SGID["$path"]=1 ;;
esac
fi
done < "$WHITELIST_FILE"
fi
}
# Fonction principale d'audit
audit_files() {
local tmp_file="/tmp/suid_audit_$$.tmp"
print_header "AUDIT DES FICHIERS SUID ET GUID"
log "Début de l'audit des fichiers SUID/SGID"
# Recherche des fichiers SUID
log "Recherche des fichiers SUID..."
find / -type f -perm -4000 2>/dev/null | while read -r file; do
if [[ -f "$file" ]]; then
local perms=$(ls -l "$file" | awk '{print $1}')
local owner=$(ls -l "$file" | awk '{print $3}')
local size=$(ls -lh "$file" | awk '{print $5}')
local mtime=$(stat -c %y "$file" 2>/dev/null | cut -d. -f1)
# Vérification dans la whitelist
if [[ -z "${WHITELIST_SUID[$file]}" ]]; then
echo "$file|SUID|$owner|$perms|$size|$mtime" >> "$tmp_file"
echo -e "${RED}[ALERTE SUID]${NC} $file (propriétaire: $owner, permissions: $perms)"
log "ALERTE SUID: $file (propriétaire: $owner)"
else
echo -e "${GREEN}[OK SUID]${NC} $file (whitelisté)"
log "OK SUID: $file (whitelisté)"
fi
fi
done
# Recherche des fichiers SGID
log "Recherche des fichiers SGID..."
find / -type f -perm -2000 2>/dev/null | while read -r file; do
if [[ -f "$file" ]]; then
local perms=$(ls -l "$file" | awk '{print $1}')
local group=$(ls -l "$file" | awk '{print $4}')
local size=$(ls -lh "$file" | awk '{print $5}')
local mtime=$(stat -c %y "$file" 2>/dev/null | cut -d. -f1)
# Vérification dans la whitelist
if [[ -z "${WHITELIST_SGID[$file]}" ]]; then
echo "$file|SGID|$group|$perms|$size|$mtime" >> "$tmp_file"
echo -e "${RED}[ALERTE SGID]${NC} $file (groupe: $group, permissions: $perms)"
log "ALERTE SGID: $file (groupe: $group)"
else
echo -e "${GREEN}[OK SGID]${NC} $file (whitelisté)"
log "OK SGID: $file (whitelisté)"
fi
fi
done
# Génération du rapport
if [[ -f "$tmp_file" ]]; then
generate_report "$tmp_file"
rm -f "$tmp_file"
else
log "Aucune anomalie SUID/SGID détectée"
echo -e "${GREEN}Aucune anomalie SUID/SGID détectée${NC}"
fi
}
# Génération du rapport détaillé
generate_report() {
local tmp_file="$1"
{
echo "=========================================="
echo "RAPPORT D'AUDIT SUID/SGID"
echo "Date: $(date '+%Y-%m-%d %H:%M:%S')"
echo "Serveur: $(hostname)"
echo "=========================================="
echo ""
echo "ANOMALIES DÉTECTÉES:"
echo "--------------------"
local suid_count=0
local sgid_count=0
while IFS='|' read -r file type owner perms size mtime; do
if [[ "$type" == "SUID" ]]; then
((suid_count++))
echo ""
echo "[$suid_count] FICHIER SUID SUSPECT: $file"
echo " Propriétaire: $owner"
echo " Permissions: $perms"
echo " Taille: $size"
echo " Dernière modification: $mtime"
echo " Recommandation: Vérifier la légitimité de ce binaire"
elif [[ "$type" == "SGID" ]]; then
((sgid_count++))
echo ""
echo "[$sgid_count] FICHIER SGID SUSPECT: $file"
echo " Groupe: $owner"
echo " Permissions: $perms"
echo " Taille: $size"
echo " Dernière modification: $mtime"
echo " Recommandation: Vérifier la légitimité de ce binaire"
fi
done < "$tmp_file"
echo ""
echo "STATISTIQUES:"
echo "-------------"
echo "Total SUID suspects: $suid_count"
echo "Total SGID suspects: $sgid_count"
echo ""
echo "RECOMMANDATIONS:"
echo "----------------"
echo "1. Examinez chaque binaire suspect manuellement"
echo "2. Supprimez les bits SUID/SGID si non nécessaires: chmod u-s <fichier>"
echo "3. Ajoutez les binaires légitimes à la whitelist: $WHITELIST_FILE"
echo "4. Vérifiez l'intégrité des binaires système"
echo "5. Surveillez les changements régulièrement"
} > "$REPORT_FILE"
# Création du fichier d'alertes (uniquement les anomalies)
grep -E "^\[ALERTE" "$LOG_FILE" > "$ALERT_FILE" 2>/dev/null || true
log "Rapport généré: $REPORT_FILE"
log "Alertes sauvegardées: $ALERT_FILE"
}
# Fonction de vérification supplémentaire (fichiers dans /tmp, /var/tmp)
check_temp_directories() {
print_header "VÉRIFICATION DES RÉPERTOIRES TEMPORAIRES"
log "Vérification des répertoires /tmp et /var/tmp..."
for dir in /tmp /var/tmp /dev/shm; do
if [[ -d "$dir" ]]; then
echo -e "${YELLOW}Vérification de $dir:${NC}"
find "$dir" -type f \( -perm -4000 -o -perm -2000 \) 2>/dev/null | while read -r file; do
echo -e "${RED}[URGENT] Fichier suspect dans $dir: $file${NC}"
log "URGENT: Fichier SUID/SGID dans $dir: $file"
done
fi
done
}
# Fonction de vérification des fichiers sans propriétaire
check_orphan_files() {
print_header "FICHIERS SUID/SGID SANS PROPRIÉTAIRE"
log "Recherche des fichiers SUID/SGID sans propriétaire valide..."
find / -type f \( -perm -4000 -o -perm -2000 \) -nouser -o -nogroup 2>/dev/null | while read -r file; do
echo -e "${RED}[ORPHELIN] $file${NC}"
log "ORPHELIN: $file"
echo "$file" >> "$ALERT_FILE"
done
}
# Fonction de vérification des hash MD5 pour les binaires système
check_system_binaries() {
print_header "VÉRIFICATION DES BINAIRES SYSTÈME"
log "Vérification des binaires système critiques..."
local system_bins=("/bin/su" "/bin/ping" "/usr/bin/sudo" "/usr/bin/passwd")
for bin in "${system_bins[@]}"; do
if [[ -f "$bin" ]]; then
local md5=$(md5sum "$bin" 2>/dev/null | awk '{print $1}')
log "Hash MD5 de $bin: $md5"
echo "$bin: $md5" >> "$LOG_DIR/system_hashes_$(date +%Y%m%d).txt"
fi
done
}
# Fonction principale
main() {
echo -e "${BLUE}"
cat << "EOF"
╔═══════════════════════════════════════╗
║ SUID/SGID Security Audit Tool ║
║ Linux Privilege Scanner ║
╚═══════════════════════════════════════╝
EOF
echo -e "${NC}"
check_root
setup_directories
create_default_whitelist
load_whitelist
echo ""
audit_files
echo ""
check_temp_directories
echo ""
check_orphan_files
echo ""
check_system_binaries
print_header "RÉSUMÉ DE L'AUDIT"
echo -e "Log complet: ${GREEN}$LOG_FILE${NC}"
echo -e "Rapport détaillé: ${GREEN}$REPORT_FILE${NC}"
echo -e "Alertes: ${RED}$ALERT_FILE${NC}"
echo -e "Whitelist: ${YELLOW}$WHITELIST_FILE${NC}"
if [[ -s "$ALERT_FILE" ]]; then
echo -e "\n${RED}⚠️ ATTENTION: Des anomalies ont été détectées !${NC}"
echo "Consultez $ALERT_FILE pour plus de détails"
exit 1
else
echo -e "\n${GREEN}✓ Aucune anomalie détectée${NC}"
exit 0
fi
}
# Gestion des signaux
trap 'echo -e "\n${RED}Script interrompu par utilisateur${NC}"; exit 1' INT TERM
# Exécution
main "$@"