Su Linux, puoi sovrascrivere il valore delle stringhe di ambiente nello stack.
Quindi puoi nascondere la voce sovrascrivendola con zeri o qualsiasi altra cosa:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[], char* envp[]) {
char cmd[100];
while (*envp) {
if (strncmp(*envp, "k=", 2) == 0)
memset(*envp, 0, strlen(*envp));
envp++;
}
sprintf(cmd, "cat /proc/%u/environ", getpid());
system(cmd);
return 0;
}
Esegui come:
$ env -i a=foo k=v b=bar ./wipe-env | hd
00000000 61 3d 66 6f 6f 00 00 00 00 00 62 3d 62 61 72 00 |a=foo.....b=bar.|
00000010
il k=v
è stato sovrascritto con \0\0\0
.
Nota che setenv("k", "", 1)
sovrascrivere il valore non funzionerà come in quel caso, un nuovo "k="
la stringa è allocata.
Se non hai modificato diversamente il k
variabile di ambiente con setenv()
/putenv()
, allora dovresti anche essere in grado di fare qualcosa di simile per ottenere l'indirizzo del k=v
stringa in pila (beh, di uno di essi):
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[]) {
char cmd[100];
char *e = getenv("k");
if (e) {
e -= strlen("k=");
memset(e, 0, strlen(e));
}
sprintf(cmd, "cat /proc/%u/environ", getpid());
system(cmd);
return 0;
}
Tieni presente, tuttavia, che ne rimuove solo uno dell'k=v
immissioni ricevute nell'ambiente. Di solito ce n'è solo uno, ma nulla impedisce a nessuno di superare entrambi k=v1
e k=v2
(o k=v
due volte) nell'elenco env passato a execve()
. Questa è stata la causa di vulnerabilità di sicurezza in passato come CVE-2016-2381. Potrebbe davvero accadere con bash
prima di shellshock durante l'esportazione sia di una variabile che di una funzione con lo stesso nome.
In ogni caso, ci sarà sempre una piccola finestra durante la quale la stringa env var non è stata ancora sovrascritta, quindi potresti voler trovare un altro modo per passare il secret informazioni al comando (come una pipe per esempio) se lo esponi tramite /proc/pid/environ
è una preoccupazione.
Si noti inoltre che contrariamente a /proc/pid/cmdline
, /proc/pid/environment
è accessibile solo da processi con lo stesso euid o root (o root solo se l'euid e il ruid del processo non sono gli stessi sembrerebbe).
Puoi nascondere loro quel valore in /proc/pid/environ
, ma potrebbero comunque essere in grado di ottenere qualsiasi altra copia che hai creato della stringa in memoria, ad esempio allegandovi un debugger.
Vedi https://www.kernel.org/doc/Documentation/security/Yama.txt per i modi per impedire almeno agli utenti non root di farlo.
Non è stato necessario sovrascrivere le stringhe sopra (non proprio on ) lo stack del thread principale su Linux dal 2010.
Entrambi /proc/self/cmdline
e /proc/self/environ
sono modificabili dal processo stesso in fase di esecuzione, a forza di chiamare il prctl()
funzione con rispettivamente PR_SET_MM_ARG_START
+PR_SET_MM_ARG_END
o PR_SET_MM_ENV_START
+PR_SET_MM_ENV_END
. Questi impostano direttamente i puntatori di memoria nello spazio di memoria dell'applicazione del processo, tenuto dal kernel per ogni processo, che vengono utilizzati per recuperare il contenuto di /proc/${PID}/cmdline
e /proc/${PID}/environ
, e quindi la riga di comando e l'ambiente riportati dal ps
comando.
Quindi è sufficiente costruire un nuovo argomento o una stringa di ambiente (non un vettore, nota:la memoria puntata deve essere la stringa di dati effettiva, concatenata e ␀
-delimited) e dire al kernel dove si trova.
Questo è documentato nella pagina man di Linux per prctl(2)
funzione così come il environ(7)
pagina manuale. Cosa non documentato è che il kernel rifiuta qualsiasi tentativo di impostare l'indirizzo iniziale sopra l'indirizzo finale o l'indirizzo finale sotto l'indirizzo iniziale; o per (ri)impostare uno degli indirizzi su zero. Inoltre, questo non è il meccanismo originale proposto da Bryan Donlan nel 2009, che consentiva di fissare l'inizio e la fine in un'unica operazione, atomicamente. Inoltre, il kernel non fornisce alcun modo per ottenere i valori correnti di questi puntatori.
Questo rende difficile la modifica l'ambiente e le aree della riga di comando con prctl()
. Bisogna chiamare il prctl()
funzionare fino a quattro volte perché i primi tentativi possono comportare tentativi di impostare il puntatore di inizio più alto del puntatore di fine, a seconda di dove si trovano in memoria i dati vecchi e nuovi. Bisogna chiamarlo un ulteriore quattro volte se si vuole garantire che ciò non si traduca in una finestra di opportunità per altri processi sul sistema di ispezionare un intervallo arbitrario dello spazio di memoria del processo nel periodo in cui il nuovo inizio/fine è stato impostato ma la nuova fine /start non è stato.
Una singola chiamata di sistema atomica che imposta l'intero intervallo in una volta sola sarebbe stata molto più semplice da usare in sicurezza per i programmi applicativi.
Un'altra ruga è che, senza una buona ragione (dati i controlli nel kernel, la sovrascrivibilità delle aree dati originali comunque e il fatto che gli equivalenti non sono operazioni privilegiate su nessuno dei BSD), su Linux ciò richiede privilegi di superutente.
Ho scritto abbastanza semplice setprocargv()
e setprocenvv()
functions per i miei set di strumenti, che utilizzano this. Programmi di caricamento a catena dai set di strumenti incorporati, come setenv
e foreground
, quindi riflettono gli argomenti e l'ambiente del comando concatenato, dove Linux lo consente.
# /package/admin/nosh/command/clearenv setenv WIBBLE wobble foreground pause \; true & [1] 1057 # hexdump -C /proc/1057/cmdline 00000000 66 6f 72 65 67 72 6f 75 6e 64 00 70 61 75 73 65 |foreground.pause| 00000010 00 3b 00 74 72 75 65 00 |.;.true.| 00000018 # hexdump -C /proc/1057/environ 00000000 57 49 42 42 4c 45 3d 77 6f 62 62 6c 65 00 |WIBBLE=wobble.| 0000000e # hexdump -C /proc/1058/cmdline 00000000 70 61 75 73 65 00 |pause.| 00000006 # hexdump -C /proc/1058/environ 00000000 57 49 42 42 4c 45 3d 77 6f 62 62 6c 65 00 |WIBBLE=wobble.| 0000000e #
Nota che questo non milita contro le cose che tracciano il processo e accedono alla sua memoria direttamente con altri mezzi (piuttosto che attraverso questi due pseudo-file), e ovviamente lascia una finestra prima che le stringhe vengano modificate dove queste informazioni possono essere viste, solo come fa la sovrascrittura dei dati sopra lo stack del thread principale. E proprio come nel caso della sovrascrittura dei dati, ciò non tiene conto delle librerie di runtime del linguaggio che eseguono copie dell'ambiente (sull'heap) in varie circostanze. In generale, non considerare questo un buon meccanismo per passare "segreti" a un programma come (diciamo) fargli ereditare un descrittore di file aperto all'estremità di lettura di una pipe senza nome, leggere in un buffer di input interamente sotto il tuo controllo che poi cancelli.
Ulteriori letture
- Timo Sirainen (2009-10-02). Aggiunta l'opzione PR_SET_PROCTITLE_AREA per prctl() . Lista di distribuzione del kernel Linux.
- https://unix.stackexchange.com/a/432681/5132
- Daniel J. Bernstein. L'interfaccia per il controllo della password . cr.yp.to.
- https://github.com/jdebp/nosh/blob/master/source/setprocargv.cpp
- https://github.com/jdebp/nosh/blob/master/source/setprocenvv.cpp