J'ai besoin de cacher des arguments sensibles à un programme que j'exécute, mais je n'ai pas accès au code source. J'exécute également cela sur un serveur partagé, donc je ne peux pas utiliser quelque chose comme hidepid
parce que je n'ai pas les privilèges sudo.
Voici quelques choses que j'ai essayées:
export SECRET=[my arguments]
, suivi d'un appel à./program $SECRET
, mais cela ne semble pas aider../program `cat secret.txt`
oùsecret.txt
contient mes arguments, mais le toutps
- puissant est capable de flairer mes secrets.
Existe-t-il un autre moyen de masquer mes arguments qui n'implique aucune intervention de l'administrateur?
ps
ne fait rien de magique pour "flairer vos secrets". Quoi qu'il en soit, les programmes raisonnablement écrits devraient plutôt offrir une option de ligne de commande pour lire un secret à partir d'un fichier spécifié ou de stdin au lieu de le prendre directement comme argument.Réponses:
Comme expliqué ici , Linux place les arguments d'un programme dans l'espace de données du programme et garde un pointeur sur le début de cette zone. C'est ce qui est utilisé par
ps
et ainsi de suite pour rechercher et afficher les arguments du programme.Étant donné que les données se trouvent dans l'espace du programme, il peut les manipuler. Faire cela sans changer le programme lui-même implique de charger un shim avec une
main()
fonction qui sera appelée avant la vraie main du programme. Cette cale peut copier les vrais arguments dans un nouvel espace, puis écraser les arguments d'origine de sorte queps
ne voir que nuls.Le code C suivant le fait.
Il n'est pas possible d'intervenir
main()
, mais vous pouvez intervenir sur la fonction de bibliothèque C standard__libc_start_main
, qui continue d'appeler main. Compilez ce fichiershim_main.c
comme indiqué dans le commentaire au début et exécutez-le comme indiqué. J'ai laissé unprintf
dans le code afin de vérifier qu'il est bien appelé. Par exemple, exécutezpuis faites un
ps
et vous verrez une commande vide et des arguments affichés.Il y a encore un peu de temps pendant lequel la commande args peut être visible. Pour éviter cela, vous pouvez, par exemple, changer le shim pour lire votre secret à partir d'un fichier et l'ajouter aux arguments passés au programme.
la source
/proc/pid/cmdline
affichera le secret (comme quand oncurl
essaie de cacher le mot de passe il est donné sur la ligne de commande). Pendant que vous utilisez LD_PRELOAD, vous pouvez envelopper main pour que le secret soit copié de l'environnement vers l'argv que principal reçoit. Comme appelerLD_PRELOAD=x SECRET=y cmd
où vous appelezmain()
avec l'argv[]
être[argv[0], getenv("SECRET")]
/proc/pid/environ
. Cela peut être écrasable de la même manière que les arguments, mais il laisse la même fenêtre./proc/pid/cmdline
est public,/proc/pid/environ
n'est pas. Il y avait des systèmes oùps
(un exécutable setuid là-bas) exposait l'environnement de tout processus, mais je ne pense pas que vous en rencontriez de nos jours. L'environnement est généralement considéré comme suffisamment sûr . Il n'est pas sûr de détacher des processus avec le même euid, mais ceux-ci peuvent souvent lire la mémoire des processus par le même euid de toute façon, donc il n'y a pas grand-chose que vous puissiez faire à ce sujet.main
méthode du programme encapsulé supprime également la variable d'environnement pour éviter les fuites accidentelles aux processus enfants. Le wrapper peut également lire tous les arguments de ligne de commande à partir d'un fichier.Lisez la documentation de l'interface de ligne de commande de l'application en question. Il peut bien y avoir une option pour fournir le secret à partir d'un fichier au lieu d'un argument directement.
Si cela échoue, déposez un rapport de bogue sur l'application au motif qu'il n'existe aucun moyen sécurisé de lui fournir un secret.
Vous pouvez toujours soigneusement (!) Adapter la solution dans la réponse de meuh à vos besoins spécifiques. Portez une attention particulière au commentaire de Stéphane et à ses suites.
la source
Si vous devez transmettre des arguments au programme pour le faire fonctionner, vous n'aurez pas de chance quoi que vous fassiez si vous ne pouvez pas utiliser
hidepid
sur procfs.Puisque vous avez mentionné qu'il s'agit d'un script bash, vous devriez déjà avoir le code source disponible, car bash n'est pas un langage compilé.
À défaut, vous pourrez peut- être réécrire la ligne de commande du processus en utilisant
gdb
ou similaire et en jouant avecargc
/argv
une fois qu'il a déjà commencé, mais:Je recommanderais vraiment d'obtenir le code source ou de parler au fournisseur pour faire modifier le code. La fourniture de secrets sur la ligne de commande dans un système d'exploitation POSIX est incompatible avec un fonctionnement sécurisé.
la source
Lorsqu'un processus exécute une commande (via l'
execve()
appel système), sa mémoire est effacée. Pour transmettre des informations à travers l'exécution, lesexecve()
appels système prennent deux arguments pour cela: leargv[]
etenvp[]
tableaux .Ce sont deux tableaux de chaînes:
argv[]
contient les argumentsenvp[]
contient les définitions de variables d'environnement sous forme de chaînes auvar=value
format (par convention).Quand vous faites:
(ici ajouté les guillemets manquants autour de l'expansion des paramètres).
Vous exécutez
cmd
avec le secret (value
) passé à la fois dansargv[]
etenvp[]
.argv[]
sera["cmd", "value"]
etenvp[]
quelque chose comme[..., "PATH=/bin:...", "HOME=...", ..., "SECRET=value", "TERM=xterm", ...]
. Commecmd
ne fait riengetenv("SECRET")
ou équivalent pour récupérer la valeur du secret de cetteSECRET
variable d'environnement, le mettre dans l'environnement n'est pas utile.argv[]
est de notoriété publique. Il apparaît dans la sortie deps
.envp[]
n'est pas de nos jours. Sous Linux, cela apparaît/proc/pid/environ
. Il apparaît dans la sortie deps ewww
sur BSD (et avec procps-ngps
sur Linux), mais uniquement pour les processus fonctionnant avec le même uid effectif (et avec plus de restrictions pour les exécutables setuid / setgid). Il peut apparaître dans certains journaux d'audit, mais ces journaux d'audit ne doivent être accessibles qu'aux administrateurs.En bref, l'environnement qui est transmis à un exécutable est censé être privé ou au moins aussi privé que la mémoire interne d'un processus (auquel, dans certaines circonstances, un autre processus doté des privilèges appropriés peut également accéder avec un débogueur par exemple et peut également être vidée sur le disque).
Étant donné que cela
argv[]
est de notoriété publique, une commande qui attend des données censées être secrètes sur sa ligne de commande est brisée par conception.Habituellement, les commandes qui doivent recevoir un secret vous fournissent une autre interface pour le faire, comme via une variable d'environnement. Par exemple:
Ou via un descripteur de fichier dédié comme stdin:
(
echo
étant intégré, il n'apparaît pas dans la sortie deps
)Ou un fichier, comme le
.netrc
forftp
et quelques autres commandes ouCertaines applications comme
curl
(et c'est aussi l'approche adoptée par @meuh ici ) essaient de cacher le mot de passe qu'ils ont reçuargv[]
des regards indiscrets (sur certains systèmes en écrasant la partie de la mémoire où lesargv[]
chaînes ont été stockées). Mais cela n'aide pas vraiment et donne une fausse promesse de sécurité. Cela laisse une fenêtre entre leexecve()
et l'écrasement oùps
montrera toujours le secret.Par exemple, si un attaquant sait que vous exécutez un script faisant un
curl -u user:somesecret https://...
(par exemple dans un travail cron), tout ce qu'il a à faire est d'expulser du cache les (nombreuses) bibliothèques quicurl
utilisent (par exemple en exécutant ash -c 'a=a;while :; do a=$a$a;done'
) afin pour ralentir son démarrage, et même faire un très inefficaceuntil grep 'curl.*[-]u' /proc/*/cmdline; do :; done
est suffisant pour attraper ce mot de passe dans mes tests.Si les arguments sont le seul moyen de transmettre le secret aux commandes, vous pouvez toujours essayer certaines choses.
Sur certains systèmes, y compris les anciennes versions de Linux, seuls les premiers octets (4096 sur Linux 4.1 et avant) des chaînes
argv[]
peuvent être interrogés.Là, vous pourriez faire:
Et le secret serait caché car il a dépassé les 4096 premiers octets. Maintenant, les gens qui ont utilisé cette méthode doivent le regretter maintenant puisque Linux depuis 4.2 ne tronque plus la liste des arguments dans
/proc/pid/cmdline
. Notez également que ce n'est pas parceps
que ne montrera pas plus de tant d'octets d'une ligne de commande (comme sur FreeBSD où il semble être limité à 2048) que l'on ne peut pas utiliser les mêmes utilisations d'APIps
pour en obtenir plus. Cette approche est cependant valable sur les systèmes oùps
est le seul moyen pour un utilisateur régulier de récupérer ces informations (comme lorsque l'API est privilégiée etps
est setgid ou setuid pour l'utiliser), mais n'est toujours pas potentiellement pérenne là-bas.Une autre approche consisterait à ne pas transmettre le secret
argv[]
mais à injecter du code dans le programme (en utilisantgdb
ou un$LD_PRELOAD
hack) avant sonmain()
démarrage qui insère le secret dans leargv[]
reçu deexecve()
.Avec
LD_PRELOAD
, pour les exécutables liés non-setuid / setgid dynamiquement sur un système GNU:Alors:
À aucun moment n'aurait
ps
montré leps -opid,args
là (-opid,args
étant le secret dans cet exemple). Notez que nous remplaçons les éléments duargv[]
tableau de pointeurs , sans remplacer les chaînes pointées par ces pointeurs, c'est pourquoi nos modifications n'apparaissent pas dans la sortie deps
.Avec
gdb
, toujours pour les exécutables liés dynamiquement non-setuid / setgid et sur les systèmes GNU:Toujours avec
gdb
, une approche non spécifique à GNU qui ne repose pas sur des exécutables liés dynamiquement ou ayant des symboles de débogage et devrait fonctionner pour tout exécutable ELF sous Linux pourrait être au moins:Test avec un exécutable lié statiquement:
Lorsque l'exécutable peut être statique, nous n'avons pas de moyen fiable d'allouer de la mémoire pour stocker le secret, nous devons donc obtenir le secret ailleurs qui est déjà dans la mémoire de processus. C'est pourquoi l'environnement est le choix évident ici. Nous masquons également cette
SECRET
variable env au processus (en le changeant enSECRE=
) pour éviter qu'il ne coule si le processus décide de vider son environnement pour une raison quelconque ou d'exécuter des applications non fiables.Cela fonctionne également sous Solaris 11 (fourni gdb et GNU binutils sont installés (vous devrez peut-être renommer
objdump
àgobjdump
).Sur FreeBSD (au moins x86_64, je ne sais pas ce que sont les 24 premiers octets (qui deviennent 16 lorsque gdb (8.0.1) est interactif suggérant qu'il peut y avoir un bogue dans gdb là-bas) sur la pile), remplacez les définitions
argc
etargv
avec:(vous devrez peut-être également installer le
gdb
package / port car la version fournie avec le système est ancienne).la source
Ce que vous pourriez faire, c'est
puis, en supposant que vous écrivez votre
./program
en C (ou que quelqu'un d'autre le fasse et que vous pouvez le changer ou l'améliorer pour vous), utilisez getenv (3) dans ce programme, peut-être commeet après
export
vous venez de courir./program
dans le même shell. Ou le nom de la variable d'environnement peut lui être transmis (en exécutant./program --secret-var=SECRET
etc ...)ps
ne dira pas votre secret, mais proc (5) peut toujours donner beaucoup d'informations (au moins à d'autres processus du même utilisateur).Voir aussi ceci pour aider à concevoir une meilleure façon de passer des arguments de programme.
Voir cette réponse pour une meilleure explication sur la globalisation et le rôle d'un shell.
Peut-être avez-vous d'
program
autres moyens d'obtenir des données (ou d'utiliser la communication interprocessus plus judicieusement) que des arguments de programme simples (cela devrait certainement le cas, s'il est destiné à traiter des informations sensibles). Lisez sa documentation. Ou peut-être que vous abusez de ce programme (qui n'est pas destiné à traiter des données secrètes).Cacher des données secrètes est vraiment difficile. Ne pas le transmettre via les arguments du programme ne suffit pas.
la source
./program
, donc la première moitié de cette réponse ne semble pas être pertinent.