Comment curl empêche-t-il un mot de passe d'apparaître dans la sortie ps?

68

J'ai remarqué il y a quelque temps que les noms d'utilisateur et les mots de passe donnés en curltant qu'arguments de ligne de commande n'apparaissent pas dans la pssortie (bien qu'ils puissent bien sûr apparaître dans votre historique bash).

De même, ils n'apparaissent pas dans /proc/PID/cmdline.

(La longueur de l'argument combiné nom d'utilisateur / mot de passe peut cependant être dérivée.)

Démonstration ci-dessous:

[root@localhost ~]# nc -l 80 &
[1] 3342
[root@localhost ~]# curl -u iamsam:samiam localhost &
[2] 3343
[root@localhost ~]# GET / HTTP/1.1
Authorization: Basic aWFtc2FtOnNhbWlhbQ==
User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.15.3 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Host: localhost
Accept: */*



[1]+  Stopped                 nc -l 80
[root@localhost ~]# jobs
[1]+  Stopped                 nc -l 80
[2]-  Running                 curl -u iamsam:samiam localhost &
[root@localhost ~]# ps -ef | grep curl
root      3343  3258  0 22:37 pts/1    00:00:00 curl -u               localhost
root      3347  3258  0 22:38 pts/1    00:00:00 grep curl
[root@localhost ~]# od -xa /proc/3343/cmdline 
0000000    7563    6c72    2d00    0075    2020    2020    2020    2020
          c   u   r   l nul   -   u nul  sp  sp  sp  sp  sp  sp  sp  sp
0000020    2020    2020    0020    6f6c    6163    686c    736f    0074
         sp  sp  sp  sp  sp nul   l   o   c   a   l   h   o   s   t nul
0000040
[root@localhost ~]# 

Comment cet effet est-il atteint? Est-ce quelque part dans le code source de curl? (Je suppose que c'est une curlfonctionnalité, pas une psfonctionnalité? Ou est-ce une fonctionnalité du noyau?)


Aussi: cela peut-il être réalisé en dehors du code source d'un exécutable binaire? Par exemple, en utilisant des commandes shell, probablement combinées avec des permissions root?

En d'autres termes, pourrais-je en quelque sorte masquer un argument d'apparaître dans /procou dans une pssortie (même chose, je pense) que j'ai passé à une commande de shell arbitraire ? (Je suppose que la réponse à cette question est "non", mais il semble utile d'inclure cette demi-question supplémentaire.)

Wildcard
la source
16
Pas une réponse, mais notez que cette approche n'est pas sûre . Il existe une fenêtre de course entre le début du programme et l’effacement des chaînes d’arguments pendant laquelle tout utilisateur peut lire le mot de passe. N'acceptez pas de mots de passe sensibles sur la ligne de commande.
R ..
1
Relativement vague: A qui appartiennent les variables d'environnement? et ce que quelqu'un dans la pratique d' utiliser environdirectement pour accéder aux variables d'environnement? - la ligne du bas: la liste des arguments, comme la liste des variables d'environnement, est en mémoire en lecture / écriture dans le processus utilisateur et peut être modifiée par le processus utilisateur.
Scott
1
@ JPhi1618, il suffit de faire du premier caractère de votre grepmodèle une classe de caractères. Par exempleps -ef | grep '[c]url'
Wildcard
1
@ My, ce n'est pas très compliqué. Certaines expressions rationnelles se correspondent et d'autres non. curlcorrespond curlmais [c]urlne correspond pas [c]url. Si vous avez besoin de plus de détails, posez une nouvelle question et je me ferai un plaisir de vous répondre.
Wildcard

Réponses:

78

Lorsque le noyau exécute un processus, il copie les arguments de la ligne de commande dans la mémoire en lecture-écriture appartenant au processus (sur la pile, au moins sous Linux). Le processus peut écrire dans cette mémoire comme n'importe quelle autre mémoire. Lorsque psaffiche l'argument, il lit tout ce qui est stocké à cette adresse particulière dans la mémoire du processus. La plupart des programmes conservent les arguments originaux, mais il est possible de les changer. La description POSIX desps états qui

Il n'est pas spécifié si la chaîne représentée est une version de la liste d'arguments telle qu'elle a été transmise à la commande lorsqu'elle a démarré ou s'il s'agit d'une version des arguments tels qu'ils peuvent avoir été modifiés par l'application. Les applications ne peuvent pas compter sur leur capacité à modifier leur liste d'arguments et à ce que cette modification soit reflétée dans la sortie de ps.

Cela s'explique par le fait que la plupart des variantes Unix reflètent le changement, mais que les implémentations POSIX sur d'autres types de systèmes d'exploitation ne le peuvent pas.

Cette fonctionnalité est d'une utilité limitée car le processus ne peut pas effectuer de modifications arbitraires. À tout le moins, la longueur totale des arguments ne peut pas être augmentée, car le programme ne peut pas modifier l'emplacement où psaller chercher les arguments et ne peut pas étendre la zone au-delà de sa taille d'origine. La longueur peut effectivement être réduite en plaçant des octets nuls à la fin, car les arguments sont des chaînes terminées par un octet nul (style C) (il est impossible d’avoir un groupe d’arguments vides à la fin).

Si vous voulez vraiment creuser, vous pouvez regarder la source d'une implémentation open-source. Sous Linux, la source de psn'est pas intéressante, tout ce que vous verrez ici est qu’elle lit les arguments de la ligne de commande à partir du système de fichiers proc , dans . Le code qui génère le contenu de ce fichier se trouve dans le noyau, dans . La partie de la mémoire du processus (accessible avec ) va de l'adresse à ; ces adresses sont enregistrées dans le noyau au démarrage du processus et ne peuvent plus être modifiées par la suite./proc/PID/cmdlineproc_pid_cmdline_readfs/proc/base.caccess_remote_vmmm->arg_startmm->arg_end

Certains démons utilisent cette possibilité pour refléter leur statut, par exemple, ils changent leur argv[1]chaîne en une chaîne telle que startingou availableou exiting. Beaucoup de variantes unix ont une setproctitlefonction pour le faire. Certains programmes utilisent cette possibilité pour masquer des données confidentielles. Notez que son utilisation est limitée car les arguments de la ligne de commande sont visibles au démarrage du processus.

La plupart des langages de haut niveau copient les arguments dans des objets chaîne et ne permettent pas de modifier le stockage d'origine. Voici un programme en C qui démontre cette capacité en modifiant argvdirectement des éléments.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
    int i;
    system("ps -p $PPID -o args=");
    for (i = 0; i < argc; i++)
    {
        memset(argv[i], '0' + (i % 10), strlen(argv[i]));
    }
    system("ps -p $PPID -o args=");
    return 0;
}

Exemple de sortie:

./a.out hello world
0000000 11111 22222

Vous pouvez voir la argvmodification dans le code source de curl. Curl définit une fonction cleanargdanssrc/tool_paramhlp.c laquelle est utilisé pour changer un argument à tous les espaces en utilisant memset. Dans src/tool_getparam.ccette fonction est utilisée plusieurs fois, par exemple par expurger le mot de passe utilisateur . Comme la fonction est appelée à partir de l'analyse des paramètres, elle se produit tôt dans une invocation curl, mais le vidage de la ligne de commande avant que cela se produise affichera toujours les mots de passe.

Étant donné que les arguments sont stockés dans la mémoire du processus, ils ne peuvent pas être modifiés de l'extérieur, sauf en utilisant un débogueur.

Gilles, arrête de faire le mal
la source
Génial! Donc, en ce qui concerne cet extrait de spécification, je comprends que cela signifie qu'il serait conforme à POSIX de faire en sorte que votre noyau stocke les arguments de ligne de commande d'origine d'un processus en dehors de la mémoire lecture-écriture du processus (en plus de la copie dans la mémoire lecture-écriture) ? Et puis avoir des psarguments de rapport de cette partie de la mémoire du noyau, en ignorant les modifications apportées à la mémoire en lecture-écriture des processus? Mais (si j'ai bien compris?) La plupart des variantes UNIX ne font même pas la première, vous ne pouvez donc pas faire d' psimplémentation la dernière sans modification du noyau, puisque les données d'origine ne sont conservées nulle part?
Wildcard
1
@Wildcard Correct. Il peut y avoir des implémentations Unix qui conservent l'original mais je ne pense pas que ce soit le cas. Le langage C permet de argvmodifier le contenu des entrées (vous ne pouvez pas définir argv[i], mais vous pouvez écrire jusqu'au argv[i][0]bout argv[i][strlen(argv[i])]), il doit donc y avoir une copie dans la mémoire du processus.
Gilles 'SO- arrête d'être méchant'
2
Fonction pertinente dans le code source de curl: github.com/curl/curl/blob/master/src/tool_paramhlp.c#L139
sebasth
4
@Wildcard, Solaris le fait. La ligne de commande vue par / usr / ucb / ps est la copie appartenant au processus (modifiable). La ligne de commande vue par / usr / bin / ps est la copie (immuable) détenue par le noyau. Le noyau ne conserve cependant que les 80 premiers caractères. Tout le reste est tronqué.
BowlOfRed
1
@Wildcard En effet, les valeurs NULL de fin sont des arguments vides. Dans la pssortie, beaucoup d’arguments vides semblent ne pas y être, mais oui, cela fait une différence si vous vérifiez le nombre d’espaces disponibles et que vous pouvez observer plus directement depuis /proc/PID/cmdline.
Gilles 'SO- arrête d'être méchant'
14

Les autres réponses répondent bien à la question de manière générale. Pour répondre spécifiquement " Comment cet effet est-il obtenu? Est-il quelque part dans le code source de curl? ":

Dans la section d'analyse des arguments du code source curl , l' -uoption est gérée comme suit:

    case 'u':
      /* user:password  */
      GetStr(&config->userpwd, nextarg);
      cleanarg(nextarg);
      break;

Et la cleanarg()fonction est définie comme suit:

void cleanarg(char *str)
{
#ifdef HAVE_WRITABLE_ARGV
  /* now that GetStr has copied the contents of nextarg, wipe the next
   * argument out so that the username:password isn't displayed in the
   * system process list */
  if(str) {
    size_t len = strlen(str);
    memset(str, ' ', len);
  }
#else
  (void)str;
#endif
}

Ainsi, nous pouvons voir explicitement que l'argument nom d'utilisateur: mot de passe dans argvest remplacé par des espaces, comme décrit dans les autres réponses.

Trauma numérique
la source
J'aime que le commentaire dans les cleanargétats indique explicitement qu'il fait ce que la question demande!
Floris
3

Un processus peut non seulement lire ses paramètres, mais aussi les écrire.

Je ne suis pas un développeur, je ne suis donc pas familier avec ce genre de choses, mais cela peut être possible de l'extérieur avec une approche similaire au changement de paramètres d'environnement:

https://stackoverflow.com/questions/205064/is-there-a-way-to-change-another-processs-environment-variables

Hauke ​​Laging
la source
OK, mais en exécutant, par exemple bash -c 'awk 1 /proc/$$/cmdline; set -- something; awk 1 /proc/$$/cmdline', cela montre qu'au moins dans le shell, la définition des paramètres est différente de la modification de ce que le noyau considère comme les paramètres du processus.
Wildcard
4
@Wildcard Les arguments positionnels dans un script shell sont initialement des copies de certains arguments de ligne de commande du processus shell. La plupart des shells ne laissent pas le script changer les arguments originaux.
Gilles, arrête de faire le mal.
@ Gilles, oui, c'était le but de mon commentaire. :) L'affirmation générale qu'un processus peut faire cela (première phrase de cette réponse) ne répond pas si cela peut être réalisé avec les fonctionnalités de shell existantes. La réponse à cette question semble être "non", ce que j'ai deviné au bas de ma question.
Wildcard