cambio / proc / PID / entorno después del inicio del proceso

11
$ k=v p &
[1] 3028

¿Hay alguna forma de pcambiar el contenido de /proc/3028/environno mencionar k=v mientras p todavía se está ejecutando?

Cetin Sert
fuente
¿Intentaste editar el archivo?
123
¿De qué archivo te preguntas?
ctrl-alt-delor

Respuestas:

12

En Linux, puede sobrescribir el valor de las cadenas de entorno en la pila.

Para que pueda ocultar la entrada sobrescribiéndola con ceros o cualquier otra 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;
}

Correr como:

$ 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

el k=vha sido sobrescrito con \0\0\0.

Tenga en cuenta que setenv("k", "", 1)para sobrescribir el valor no funcionará, ya que en ese caso, "k="se asigna una nueva cadena.

Si no ha modificado la kvariable de entorno con setenv()/ putenv(), entonces también debería poder hacer algo como esto para obtener la dirección de la k=vcadena en la pila (bueno, de una de ellas):

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

Sin embargo, tenga en cuenta que solo elimina una de las k=ventradas recibidas en el entorno. Por lo general, solo hay uno, pero nada impide que nadie pase los dos k=v1y k=v2(o k=vdos veces) en la lista env enviada a execve(). Esa ha sido la causa de vulnerabilidades de seguridad en el pasado, como CVE-2016-2381 . Realmente podría suceder bashantes del shellshock al exportar tanto una variable como una función con el mismo nombre.

En cualquier caso, siempre habrá una pequeña ventana durante la cual la cadena var var aún no se ha anulado, por lo que es posible que desee encontrar otra forma de pasar la información secreta al comando (como una tubería, por ejemplo) si la expone a través de /proc/pid/environEs una preocupación.

También tenga en cuenta que /proc/pid/cmdline, al contrario , /proc/pid/environmentsolo es accesible por procesos con el mismo euid o raíz (o root solo si el euid y el ruid del proceso no son lo mismo que parece).

Puede ocultarles ese valor /proc/pid/environ, pero aún así pueden obtener cualquier otra copia que haya hecho de la cadena en la memoria, por ejemplo, adjuntando un depurador.

Consulte https://www.kernel.org/doc/Documentation/security/Yama.txt para ver formas de evitar que al menos los usuarios no root lo hagan.

Stéphane Chazelas
fuente
8

No ha sido necesario sobrescribir las cadenas anteriores (no realmente en ) la pila del hilo principal en Linux desde 2010.

Ambos /proc/self/cmdliney /proc/self/environson modificables por el propio proceso en tiempo de ejecución, a fuerza de llamar a la prctl()función con PR_SET_MM_ARG_START+ PR_SET_MM_ARG_ENDo PR_SET_MM_ENV_START+ respectivamente PR_SET_MM_ENV_END. Estos establecen directamente los punteros de memoria en el espacio de memoria de la aplicación del proceso, retenido por el núcleo para cada proceso, que se utilizan para recuperar el contenido de /proc/${PID}/cmdliney /proc/${PID}/environ, y por lo tanto, la línea de comandos y el entorno informados por el pscomando.

Entonces, uno simplemente necesita construir un nuevo argumento o cadena de entorno (no vector, aviso: la memoria apuntada debe ser los datos de la cadena real, concatenados y delimitados) y decirle al núcleo dónde está.

Esto está documentado en la página del manual de Linux para la prctl(2)función, así como en la environ(7)página del manual. Lo que no está documentado es que el núcleo rechaza cualquier intento de establecer la dirección de inicio por encima de la dirección de finalización, o la dirección de finalización por debajo de la dirección de inicio; o para (re) establecer cualquiera de las direcciones a cero. Además, este no es el mecanismo original propuesto por Bryan Donlan en 2009, que permitió establecer el inicio y el final en una sola operación, atómicamente. Además, el núcleo no proporciona ninguna forma de obtener los valores actuales de estos punteros.

Esto dificulta modificar el entorno y las áreas de línea de comando con prctl(). Uno tiene que llamar a la prctl()función hasta cuatro veces porque los primeros intentos pueden dar lugar a intentos de establecer el puntero de inicio más alto que el puntero de finalización, dependiendo de dónde estén los datos antiguos y nuevos en la memoria. Uno tiene que llamar a más de cuatro veces si uno quiere asegurarse de que esto no se traduce en una ventana de oportunidad para otros procesos en el sistema para inspeccionar cualquier rango del proceso de espacio de memoria en el período en que el comienzo nuevo / fin se ha establecido pero el nuevo final / inicio no se ha establecido.

Una sola llamada al sistema atómico que establece todo el rango de una sola vez hubiera sido mucho más fácil para los programas de aplicaciones de usar de forma segura.

Una arruga adicional es que, sin una razón realmente buena (dadas las comprobaciones en el núcleo, la sobreescritura de las áreas de datos originales de todos modos , y el hecho de que los equivalentes no son operaciones privilegiadas en ninguno de los BSD), en Linux esto requiere superusuario privilegios

Escribí bastante simple setprocargv()y setprocenvv()funciones para mis conjuntos de herramientas, que emplean esto. Los programas de carga en cadena de los conjuntos de herramientas que están integrados, como setenvy foreground, por lo tanto, reflejan los argumentos de comando encadenados y el entorno, donde Linux lo permite.

# / package / admin / nosh / command / clearenv setenv WIBBLE wobble foreground pause \; cierto &
[1] 1057
# hexdump -C / proc / 1057 / cmdline
00000000 66 6f 72 65 67 72 6f 75 6e 64 00 70 61 75 73 65 | primer plano.pausa |
00000010 00 3b 00 74 72 75 65 00 |.;. Verdadero. |
00000018
# hexdump -C / proc / 1057 / environmental
00000000 57 49 42 42 4c 45 3d 77 6f 62 62 6c 65 00 | WIBBLE = bamboleo |
0000000e
# hexdump -C / proc / 1058 / cmdline
00000000 70 61 75 73 65 00 | pausa. |
00000006
# hexdump -C / proc / 1058 / environmental
00000000 57 49 42 42 4c 45 3d 77 6f 62 62 6c 65 00 | WIBBLE = bamboleo |
0000000e
# # 

Tenga en cuenta que esto no interfiere con las cosas que rastrean el proceso y acceden a su memoria directamente por otros medios (en lugar de a través de estos dos pseudo archivos), y por supuesto deja una ventana antes de que se modifiquen las cadenas donde se puede ver esta información, solo como sobrescribe los datos sobre la pila del hilo principal. Y tal como es el caso con la sobrescritura de los datos, esto no tiene en cuenta las bibliotecas de tiempo de ejecución de lenguaje que hacen copias del entorno (en el montón) en varias circunstancias. En general, no considere que este es un mecanismo tan bueno para pasar "secretos" a un programa como (por ejemplo) que herede un descriptor de archivo abierto al extremo de lectura de una tubería sin nombre, lea en un búfer de entrada totalmente bajo su control que luego limpie.

Otras lecturas

JdeBP
fuente
2
Desde el kernel 3.18, es posible usar PR_SET_MM_MAP que toma una estructura prctl_mm_map y no requiere root.
filbranden
2
JdeBP, @filbranden Desde kernel 3.5 se pueden leer los valores actuales de la ENV / argv punteros de /proc/$pid/stat(además de otros valores que pueda necesitar en struct prctl_mm_map). Vea también mi ejemplo filter_env.c para una pequeña demostración. JdeBP, ¿puedes agregar enlaces a tus setprocargv()/ setprocenvv()funciones?
maxschlepzig