On peut voir toutes sortes de choses en parcourant le web. Parmi ces choses, on peut remarquer que certains sites utilisent le Javascript à des fins de sécurité. Par exemple gérer l’accès à certaines pages. Mais il faut se rendre compte que mettre le code source permettant de gérer certains accès sur le client du hacker n’est pas la bonne approche !

Contexte

Prenons en exemple un code Javascript permettant d’atteindre une page quelconque réservée aux personnes ayant la combinaison login/mot de passe correct. Le code utilise le login et le mot de passe et fonctionne un peu comme une fonction de hachage. Elle donne un résultat numérique basée sur plusieurs opérations séquentielles sur les deux chaînes de caractères. Si le résultat obtenu est équivalent à la valeur passée en paramètre, alors le code redirige l’utilisateur sur une page en lien avec le mot de passe et le login.

Code source

L’exemple présenté ci-dessous est fortement basé sur un challenge présenté sur un site de challenge francophone.

var total_erreur=0;
function Check() {
    var tabc=Check.arguments; var ok=0;
    var tab="                   azertyuiopqsdfghjklmwxcvbnAZERTYUIOPQSDFGHJKLMWXCVBN0123456789_$&#@";
    for (var no=0;no<tabc.length;no++) {
        checksum=tabc[no];
        var login=document.forms["flog"].elements["login"].value;
        var password="souris";
        var nblog=login.length;
        var nbpass=password.length;
        var sum=1;
        var n=Math.max(nblog,nbpass)
        for (var i=0;i<n;i++) {
            var index1=tab.indexOf(login.substring(i,i+1))+10;
            var index2=tab.indexOf(password.substring(i,i+1))+10;
            sum=sum+(index1*n*(i+1))*(index2*(i+1)*(i+1));
        }
        if (sum==checksum) {
            window.location="/pages/"+login+".php"; ok=1; no=100;
        }
    }
    if (ok==0) {
        total_erreur++;
        alert("Mauvais login ou mot de passe");
        if (total_erreur>2) {
            alert("Vous avez atteint les 3 essais !\nAu revoir");
            window.location="index.php";
        }
    }
}
function Verifie() {
    Check(3696619)
}

Analyse

Ce code étant présent dans le navigateur, il est possible de connaitre les deux informations nécessaires aux brutes forces : l’algorithme et le résultat que l’on doit obtenir.

En regardant de plus près l’algorithme on se rend compte qu’on ne peut pas l’inverser pour obtenir un seul résultat, car c’est une fonction mathématique surjective. De ce fait, il nous faut tester un grand nombre de possibilités et les valider grâce au résultat ‘3696619’.

En lisant le code on constate que le nombre d’itérations est égal au maximum de la longueur entre le login entré et le mot « souris ». On peut en déduire que seul le login doit être recherché et que celui-ci fait 6 caractères. Dans le cas contraire la portion de code suivant aurait un comportement inattendu :

var index1=tab.indexOf(login.substring(i,i+1))+10;
var index2=tab.indexOf(password.substring(i,i+1))+10;

En partant de se postula on peut affiner notre recherche au seul login.

Exploitation

Le brute force se fait en trois étapes : générer un dictionnaire de possibilités, tester le contenu du dictionnaire, et valider les résultats obtenus.

Génération d’un dictionnaire

Beaucoup d’outils différents existent, crunch est un outil open source et bien connu. Pour générer le dictionnaire selon l’analyse faite auparavant :

$ crunch 6 6 -o dico.txt

Tester le dictionnaire

Pour tester toutes les possibilités du dictionnaire, on peut écrire un programme en Javascript que l’on fait tourner avec node.js. Ou, pour des raisons de performance, on retranscrit l’algorithme dans un langage plus « bas niveau » tel que le python ou encore le C.

Dans cet article, l’algorithme est retranscrit en C. L’exploit donne le résultat suivant :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX(x, y) (((x) > (y)) ? (x) : (y))

// Checksum to find
#define CHECKSUM 3696619


int check(const char *login, int loginLenth);

int main(void)
{
    // declare two file for dico.txt and result.txt
    FILE *fp, *fres;
    char *line = NULL;
    size_t len = 0;
    ssize_t read;
    
    // open dico.txt generate by crunch
    fp = fopen("./dico.txt", "r");
    
    // open result.txt for write results 
    fres = fopen("./result.txt", "w+");
    
    if (fp == NULL || fres == NULL)
        exit(EXIT_FAILURE);
        
    // read dico.txt line by line
    while ((read = getline(&line, &len, fp)) != -1) {
        size_t ln = strlen(line) - 1;
        // remove the break line caracter if exist
        if (*line && line[ln] == '\n') 
            line[ln] = '\0';
        
        if ( check(line, ln) )
        {
            // if the word is a valid solution of checksum,
            // store it on result.txt
            if ( !fputs( line, fres ) || !fputs("\n", fres) )
                exit(EXIT_FAILURE);
        }
    }
    
    // close files
    fclose(fp); fclose(fres);
    
    if (line)
        free(line);
    exit(EXIT_SUCCESS);
}

/**
 * Fonction check equivalent to the Javascript code
 * @autor: Joel Gugger
**/
int check(const char *login, int loginLenth)
{
    const char *password = "souris";
    unsigned int nblog = loginLenth;
    unsigned int nbpass = strlen(password);
    unsigned int sum = 1;
    unsigned int n =  MAX(nblog, nbpass);
    for	(int i = 0; i < n; i++)
    {
        char *str = "                   azertyuiopqsdfghjklmwxcvbnAZERTYUIOPQSDFGHJKLMWXCVBN0123456789_$&#@";
        char *e1, *e2;
        int index1, index2;
        e1 = strchr(str, login[i]);
        index1 = (int)(e1 - str) + 10;
        e2 = strchr(str, password[i]);
        index2 = (int)(e2 - str) + 10;
        sum += (index1*n*(i+1))*(index2*(i+1)*(i+1));
    }
    if (sum == CHECKSUM)
        return 1;
    else
        return 0;
}
all: main

main: main.o
  gcc main.o -o main

main.o: main.c
  gcc -o main.o -c main.c -Wall

clean:
  rm *.o && rm main

Le code exécute les étapes suivantes :

  1. On ouvre le dictionnaire fraichement créé
  2. On ouvre un fichier qui contiendra les résultats
  3. On parcourt ligne par ligne le dictionnaire
  4. On passe le mot du dictionnaire dans la fonction de hachage
  5. Si le résultat obtenu est équivalent au résultat rechercher, alors on enregistre le mot dans le fichier de résultat
  6. On ferme les fichiers et on quitte le programme proprement

Valider les résultats

Comme expliquer dans l’analyse, cet algorithme admet plusieurs mots pour le même résultat numérique. Le fichier de résultat contient donc plusieurs lignes, plusieurs centaines même. Dans cet exemple, si le résultat est égal à ‘3696619’, alors on doit être rediriger vers la page correspondante. Un mot parmi les centaines trouvées correspond donc à une page web dont nous connaissons l’URL.

Pour atteindre cette page secrète, il nous faut tester un par un notre liste de résultat en faisant une requête HTTP. Ce processus répétitif est automatisé grâce à un simple script bash.

#!/bin/bash

echo "Testing all possibilies..."

find=0
while IFS='' read -r line || [[ -n "$line" ]]; do
    URL="https://www.exemple.com/page/$line.php"
    if [ $2 ] && [ $2 == "-v" ]; then
        echo "Testing: $URL"
    fi
    response=$(curl --write-out "%{http_code}\n" --silent --output /dev/null "$URL")
    if [ $response == "200" ]; then
        echo "$URL is valid"
        find=1
    fi
done < "$1"

if [ $find == 0 ]; then
    echo "No valid URL find!"
fi

Ce script ouvre le fichier de résultat et exécute une requête HTTP grâce à curl. Si la requête obtient une page (code 200), alors on affiche l’URL testée dans le terminal. Il ne reste plus qu’à laisser tourner le script pendant un petit moment pour trouver la page !

Conclusion

Si l’on devait tirer une leçon de cet exemple, je dirais une seule chose : ne jamais baser une quelconque sécurité uniquement sur le côté client de votre application. Cela revient à déléguer la sécurité de votre plateforme aux hackers !

Développeur et passionné de nouvelles technologies, j'exerce à mi-temps dans une agence digitale suisse. En parallèle, je poursuis mes études de Master en systèmes d'informations complexes. Depuis quelque temps, mon intérêt grandissant pour les enjeux de sécurité au sein des solutions web m'a amené à créer ce blog.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *