Sous Linux, vous pouvez écraser la valeur des chaînes d'environnement sur la pile.
Vous pouvez donc masquer l'entrée en l'écrasant avec des zéros ou autre :
#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;
}
Exécuter en tant que :
$ 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
le k=v
a été remplacé par \0\0\0
.
Notez que setenv("k", "", 1)
écraser la valeur ne fonctionnera pas car dans ce cas, un nouveau "k="
chaîne est allouée.
Si vous n'avez pas autrement modifié le k
variable d'environnement avec setenv()
/putenv()
, alors vous devriez également pouvoir faire quelque chose comme ça pour obtenir l'adresse du k=v
chaîne sur la pile (enfin, de l'un d'entre eux):
#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;
}
Notez cependant qu'il n'en supprime qu'un un du k=v
entrées reçues dans l'environnement. Habituellement, il n'y en a qu'un, mais rien n'empêche quiconque de passer les deux k=v1
et k=v2
(ou k=v
deux fois) dans la liste env passée à execve()
. Cela a été la cause de vulnérabilités de sécurité dans le passé, telles que CVE-2016-2381. Cela pourrait vraiment arriver avec bash
avant shellshock lors de l'exportation d'une variable et d'une fonction portant le même nom.
Dans tous les cas, il y aura toujours une petite fenêtre pendant laquelle la chaîne env var n'a pas encore été remplacée, vous voudrez peut-être trouver un autre moyen de passer le secret informations à la commande (comme un tube par exemple) si elles sont exposées via /proc/pid/environ
est une préoccupation.
Notez également que contrairement à /proc/pid/cmdline
, /proc/pid/environment
n'est accessible que par les processus ayant le même euid ou root (ou root uniquement si l'euid et le ruid du processus ne sont pas les mêmes semble-t-il).
Vous pouvez leur cacher cette valeur dans /proc/pid/environ
, mais ils peuvent toujours obtenir toute autre copie que vous avez faite de la chaîne en mémoire, par exemple en y attachant un débogueur.
Voir https://www.kernel.org/doc/Documentation/security/Yama.txt pour savoir comment empêcher au moins les utilisateurs non root de le faire.
Il n'a pas été nécessaire d'écraser les chaînes ci-dessus (pas vraiment sur ) la pile du thread principal sous Linux depuis 2010.
Les deux /proc/self/cmdline
et /proc/self/environ
sont modifiables par le processus lui-même à l'exécution, à force d'appeler le prctl()
fonction avec respectivement PR_SET_MM_ARG_START
+PR_SET_MM_ARG_END
ou PR_SET_MM_ENV_START
+PR_SET_MM_ENV_END
. Ceux-ci définissent directement les pointeurs de mémoire dans l'espace mémoire de l'application du processus, détenus par le noyau pour chaque processus, qui sont utilisés pour récupérer le contenu de /proc/${PID}/cmdline
et /proc/${PID}/environ
, et donc la ligne de commande et l'environnement signalés par le ps
commande.
Il suffit donc de construire un nouvel argument ou une chaîne d'environnement (pas de vecteur, notez que la mémoire pointée doit être la chaîne de données réelle, concaténée et ␀
-delimited) et indiquez au noyau où il se trouve.
Ceci est documenté dans la page de manuel Linux pour le prctl(2)
fonction ainsi que le environ(7)
page de manuel. Ce qui n'est pas documenté est que le noyau rejette toute tentative de définir l'adresse de début au-dessus de l'adresse de fin, ou l'adresse de fin en dessous de l'adresse de début ; ou pour (re-)mettre l'une ou l'autre adresse à zéro. De plus, ce n'est pas le mécanisme original proposé par Bryan Donlan en 2009, qui permettait de régler le début et la fin en une seule opération, de manière atomique. De plus, le noyau ne fournit aucun moyen d'obtenir les valeurs courantes de ces pointeurs.
Cela rend difficile la modification les zones d'environnement et de ligne de commande avec prctl()
. Il faut appeler le prctl()
fonctionner jusqu'à quatre fois, car les premières tentatives peuvent entraîner des tentatives pour définir le pointeur de début plus haut que le pointeur de fin, selon l'endroit où se trouvent les anciennes et les nouvelles données en mémoire. Il faut appeler ça un plus loin quatre fois si l'on veut s'assurer que cela n'entraîne pas une fenêtre d'opportunité pour les autres processus du système d'inspecter une plage arbitraire de l'espace mémoire du processus dans la période où le nouveau début/fin a été défini mais la nouvelle fin /start n'a pas été.
Un seul appel système atomique définissant la plage entière en une seule fois aurait été beaucoup plus facile à utiliser en toute sécurité pour les programmes d'application.
Un autre problème est que, sans vraiment de bonne raison (compte tenu des vérifications dans le noyau, l'écrasement des zones de données d'origine de toute façon , et le fait que les équivalents ne sont des opérations privilégiées sur aucun des BSD), sous Linux, cela nécessite des privilèges de superutilisateur.
J'ai écrit assez simplement setprocargv()
et setprocenvv()
fonctions pour mes outils, qui emploient cela. Programmes de chargement en chaîne à partir des ensembles d'outils qui sont intégrés, comme setenv
et foreground
, reflètent ainsi les arguments et l'environnement de la commande chaînée, là où Linux le permet.
# /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 #
Notez que cela ne milite pas contre les choses qui tracent le processus et accèdent directement à sa mémoire par d'autres moyens (plutôt que par ces deux pseudo-fichiers), et bien sûr laisse une fenêtre avant que les chaînes ne soient modifiées où cette information peut être vue, juste comme le fait l'écrasement des données au-dessus de la pile du thread principal. Et tout comme c'est le cas avec l'écrasement des données, cela ne tient pas compte des bibliothèques d'exécution de langage qui font des copies de l'environnement (sur le tas) dans diverses circonstances. En général, ne considérez pas cela comme un aussi bon mécanisme pour transmettre des "secrets" à un programme que (par exemple) le faire hériter d'un descripteur de fichier ouvert à l'extrémité de lecture d'un tube sans nom, lu dans un tampon d'entrée entièrement sous votre contrôle que vous essuyez ensuite.
Autres lectures
- Timo Sirainen (2009-10-02). Ajout de l'option PR_SET_PROCTITLE_AREA pour prctl() . Liste de diffusion du noyau Linux.
- https://unix.stackexchange.com/a/432681/5132
- Daniel J. Bernstein. L'interface de vérification du mot de passe . crypter.
- https://github.com/jdebp/nosh/blob/master/source/setprocargv.cpp
- https://github.com/jdebp/nosh/blob/master/source/setprocenvv.cpp