¿Cómo protege curl una contraseña para que no aparezca en la salida ps?

68

Hace un tiempo noté que los nombres de usuario y contraseñas dados curlcomo argumentos de línea de comando no aparecen en la pssalida (aunque, por supuesto, pueden aparecer en su historial de bash).

Tampoco aparecen en /proc/PID/cmdline.

(Sin embargo, la longitud del argumento combinado de nombre de usuario / contraseña puede derivarse).

Demostración a continuación:

[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 ~]# 

¿Cómo se logra este efecto? ¿Está en algún lugar del código fuente de curl? (¿Asumo que es una curlcaracterística, no una pscaracterística? ¿O es una característica del núcleo de algún tipo?)


Además: ¿ se puede lograr esto desde fuera del código fuente de un ejecutable binario? Por ejemplo, mediante el uso de comandos de shell, probablemente combinado con permisos de root?

En otras palabras, ¿podría alguna manera enmascarar un argumento de aparecer en /proco en la pssalida (lo mismo, creo) que pasé a algunos arbitraria de comandos shell? (Supongo que la respuesta a esto es "no", pero parece que vale la pena incluir esta media pregunta adicional).

Comodín
fuente
16
No es una respuesta, pero tenga en cuenta que este enfoque no es seguro . Hay una ventana de carrera entre el inicio del programa y la eliminación de las cadenas de argumentos durante las cuales cualquier usuario puede leer la contraseña. No acepte contraseñas confidenciales en la línea de comando.
R ..
1
Relacionado libremente: ¿ a quién pertenecen las variables de entorno? y ¿Alguien en la práctica lo usa environdirectamente para acceder a las variables de entorno? - el resultado final: la lista de argumentos, como la lista de variables de entorno, está en la memoria de proceso de lectura / escritura del usuario, y puede ser modificada por el proceso del usuario.
Scott
1
@ JPhi1618, solo haga que el primer personaje de su greppatrón sea una clase de personaje. Ej .ps -ef | grep '[c]url'
comodín el
1
@mpy, no es muy complicado. Algunas expresiones regulares coinciden y otras no. curlcoincide curlpero [c]urlno coincide [c]url. Si necesita más detalles, haga una nueva pregunta y estaremos encantados de responder.
Comodín el

Respuestas:

78

Cuando el núcleo ejecuta un proceso, copia los argumentos de la línea de comandos en la memoria de lectura y escritura que pertenece al proceso (en la pila, al menos en Linux). El proceso puede escribir en esa memoria como cualquier otra memoria. Cuando psmuestra el argumento, vuelve a leer lo que esté almacenado en esa dirección en particular en la memoria del proceso. La mayoría de los programas mantienen los argumentos originales, pero es posible cambiarlos. La descripción POSIX deps estados que

No se especifica si la cadena representada es una versión de la lista de argumentos tal como se pasó al comando cuando se inició o si es una versión de los argumentos, ya que la aplicación puede haberlos modificado. Las aplicaciones no pueden depender de poder modificar su lista de argumentos y que esa modificación se refleje en la salida de ps.

La razón por la que se menciona esto es que la mayoría de las variantes de Unix reflejan el cambio, pero las implementaciones POSIX en otros tipos de sistemas operativos pueden no hacerlo.

Esta característica es de uso limitado porque el proceso no puede realizar cambios arbitrarios. Como mínimo, la longitud total de los argumentos no se puede aumentar, porque el programa no puede cambiar la ubicación donde psbuscará los argumentos y no puede extender el área más allá de su tamaño original. La longitud se puede disminuir efectivamente colocando bytes nulos al final, porque los argumentos son cadenas terminadas en nulo estilo C (esto no se puede distinguir de tener un montón de argumentos vacíos al final).

Si realmente quiere excavar, puede ver el origen de una implementación de código abierto. En Linux, la fuente de psno es interesante, todo lo que verá allí es que lee los argumentos de la línea de comandos del sistema de archivos proc , en . El código que genera el contenido de este archivo está en el kernel, en in . La parte de la memoria del proceso (a la que se accede con ) va de la dirección a ; Estas direcciones se registran en el núcleo cuando se inicia el proceso y no se pueden cambiar después./proc/PID/cmdlineproc_pid_cmdline_readfs/proc/base.caccess_remote_vmmm->arg_startmm->arg_end

Algunos demonios usan esta capacidad para reflejar su estado, por ejemplo, cambian su argv[1]cadena por una como startingo availableo exiting. Muchas variantes de Unix tienen una setproctitlefunción para hacer esto. Algunos programas usan esta capacidad para ocultar datos confidenciales. Tenga en cuenta que esto es de uso limitado ya que los argumentos de la línea de comandos son visibles mientras se inicia el proceso.

La mayoría de los lenguajes de alto nivel copian los argumentos en los objetos de cadena y no ofrecen una forma de modificar el almacenamiento original. Aquí hay un programa en C que demuestra esta habilidad cambiando argvelementos directamente.

#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;
}

Salida de muestra:

./a.out hello world
0000000 11111 22222

Puedes ver argvmodificaciones en el código fuente curl. Curl define una función cleanargen lasrc/tool_paramhlp.c que se usa para cambiar un argumento a todos los espacios usando memset. En src/tool_getparam.cesta función se usa varias veces, por ejemplo, al redactar la contraseña del usuario . Dado que la función se llama desde el análisis de parámetros, ocurre temprano en una invocación curl, pero al volcar la línea de comando antes de que esto suceda, aún se mostrarán las contraseñas.

Como los argumentos se almacenan en la memoria del proceso, no se pueden cambiar desde el exterior, excepto mediante el uso de un depurador.

Gilles 'SO- deja de ser malvado'
fuente
¡Excelente! Entonces, con respecto a ese fragmento de especificaciones, entiendo que significaría que sería compatible con POSIX hacer que su núcleo almacene los argumentos de la línea de comandos originales de un proceso fuera de la memoria de lectura y escritura del proceso ( además de la copia en la memoria de lectura y escritura) ? ¿Y luego tiene psargumentos de informe de la memoria de ese núcleo, ignorando cualquier cambio realizado en la memoria de lectura y escritura de los procesos? Pero (¿si lo entendí bien?) La mayoría de las variaciones de UNIX ni siquiera hacen lo primero, por lo que no puede hacer que una psimplementación haga lo segundo sin modificaciones del núcleo, ya que los datos originales no se guardan en ningún lado.
Comodín el
1
@Wildcard Correcto. Puede haber implementaciones de Unix que mantienen el original, pero no creo que ninguno de los comunes lo haga. El lenguaje C permite que el contenido de argvlas entradas a cambiar (no se puede establecer argv[i], pero se puede escribir en argv[i][0]a través argv[i][strlen(argv[i])]), por lo que no tiene que ser una copia en la memoria del proceso.
Gilles 'SO- deja de ser malvado'
2
Función relevante en el código fuente curl: github.com/curl/curl/blob/master/src/tool_paramhlp.c#L139
sebasth
44
@Wildcard, Solaris hace esto. La línea de comando vista por / usr / ucb / ps es la copia (mutable) propiedad del proceso. La línea de comando vista por / usr / bin / ps es la copia (inmutable) propiedad del núcleo. Sin embargo, Kernel solo conserva los primeros 80 caracteres. Cualquier otra cosa está truncada.
BowlOfRed
1
@Wildcard De hecho, los nulos finales son argumentos vacíos. En el psresultado, muchos argumentos vacíos parecen que no hay nada allí, pero sí, hace una diferencia si verifica cuántos espacios hay y puede observar más directamente desde /proc/PID/cmdline.
Gilles 'SO- deja de ser malvado'
14

Las otras respuestas responden bien a la pregunta de manera general. Para responder específicamente " ¿Cómo se logra este efecto? ¿Está en algún lugar del código fuente de curl? ":

En la sección de análisis de argumentos del código fuente curl , la -uopción se maneja de la siguiente manera:

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

Y la cleanarg()función se define de la siguiente manera:

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
}

Por lo tanto, podemos ver explícitamente que el argumento nombre de usuario: contraseña argvse sobrescribe con espacios, como se describe en las otras respuestas.

Trauma digital
fuente
¡Me gusta que el comentario en los cleanargestados explícitamente indique que está haciendo lo que hace la pregunta!
Floris
3

Un proceso no solo puede leer sus parámetros sino también escribirlos.

No soy desarrollador, así que no estoy familiarizado con estas cosas, pero puede ser posible desde el exterior con un enfoque similar al cambio de los parámetros del entorno:

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

Hauke ​​Laging
fuente
De acuerdo, pero ejecutar, por ejemplo, bash -c 'awk 1 /proc/$$/cmdline; set -- something; awk 1 /proc/$$/cmdline'muestra que al menos en el shell, establecer los parámetros es distinto de modificar lo que el núcleo ve como los parámetros del proceso.
Comodín el
44
@Wildcard Los argumentos posicionales en un script de shell son inicialmente copias de algunos de los argumentos de la línea de comandos del proceso de shell. La mayoría de los shells no permiten que el script cambie los argumentos originales.
Gilles 'SO- deja de ser malvado'
@Gilles, sí, ese fue el punto de mi comentario. :) Que la afirmación general de que un proceso puede hacer eso (primera oración de esta respuesta) no responde si esto puede lograrse mediante las funciones de shell existentes. La respuesta a esto parece ser "no", que es lo que supuse al final de mi pregunta.
Comodín el