Esencialmente, necesita controlar el entorno de ejecución de las aplicaciones. No hay magia al respecto. Un par de soluciones que se me ocurren:
-
De alguna manera, podría configurar todos los archivos binarios que le preocupan como setuid/setgid (eso no significa que deban ser propiedad de root, que yo sepa). Linux normalmente evita la conexión a un proceso setuid/setgid. ¡Sin embargo, verifique si lo hace para setuid que no sea de propiedad raíz!
-
Podría usar un cargador seguro para ejecutar sus aplicaciones en lugar de ld, que se niega a reconocer LD_PRELOAD. Esto puede romper algunas aplicaciones existentes. Consulte el trabajo de Mathias Payer para obtener más información, aunque dudo que haya una herramienta estándar que pueda aplicar.
-
Podría reconstruir sus archivos binarios con una libc que deshabilite LD_PRELOAD y dlsym. Escuché que musl puede hacer eso si pasa las opciones correctas, pero no puedo volver a encontrar información sobre cómo en este momento.
-
Y, por último, puede aislar sus aplicaciones y evitar que las aplicaciones inicien directamente otros procesos con un entorno personalizado o que modifiquen el directorio de inicio del usuario. Tampoco existe una herramienta preparada para esto (es mucho trabajo en progreso y aún no se puede implementar nada).
Probablemente haya límites para las soluciones anteriores y otras soluciones candidatas según las aplicaciones que necesite ejecutar, quiénes son los usuarios y cuál es el modelo de amenaza. Si puede hacer que su pregunta sea más precisa, intentaré mejorar esa respuesta en consecuencia.
Editar: tenga en cuenta que un usuario malintencionado solo puede modificar su propio entorno de ejecución (a menos que pueda escalar los privilegios a la raíz con algún exploit, pero luego tiene otros problemas que manejar). Por lo tanto, un usuario normalmente no usaría inyecciones de LD_PRELOAD porque ya puede ejecutar código con los mismos privilegios. Los ataques tienen sentido en algunos escenarios:
- romper las comprobaciones relacionadas con la seguridad en el lado del cliente del software cliente-servidor (por lo general, hace trampas en los videojuegos o hace que una aplicación cliente pase por alto algún paso de validación con el servidor de su distribuidor)
- instalar malware permanente cuando se hace cargo de la sesión o el proceso de un usuario (ya sea porque se olvidó de cerrar la sesión y usted tiene acceso físico al dispositivo o porque explotó una de sus aplicaciones con contenido manipulado)
La mayoría de los puntos de Steve DL son buenos, el "mejor" enfoque es usar un enlazador en tiempo de ejecución (RTLD) sobre el que tiene más control. El "LD_
" las variables están codificadas en glibc (comienza con elf/rtld.c
). El RTLD de glibc tiene muchas "características", e incluso ELF mismo tiene algunas sorpresas con sus entradas DT_RPATH y DT_RUNPATH, y $ORIGIN
(consulte https://unix.stackexchange.com/questions/22926/where-do-executables-look-for-shared-objects-at-runtime).
Normalmente, si desea evitar (o alterar) ciertas operaciones cuando no puede usar permisos normales o un shell restringido, puede forzar la carga de una biblioteca para envolver llamadas libc; este es exactamente el truco que está usando el malware, y esto significa es difícil usar la misma técnica contra él.
Una opción que le permite conectar el RTLD en acción es la auditoría función, para usar esto configura LD_AUDIT
para cargar un objeto compartido (que contiene las funciones nombradas de la API de auditoría definida). El beneficio es que puedes conectar las bibliotecas individuales que se cargan, el inconveniente es que se controla con una variable de entorno...
Un truco menos usado es uno más de los ld.so
"características":/etc/ld.so.preload
. Lo que puede hacer con esto es cargar su propio código en cada proceso dinámico, la ventaja es que está controlado por un archivo restringido, los usuarios no root no pueden modificarlo ni anularlo (dentro de lo razonable, por ejemplo, si los usuarios pueden instalar su propia cadena de herramientas o trucos similares).
A continuación hay algunos experimentales código para hacer esto, probablemente debería pensar mucho en esto antes de usarlo en producción, pero muestra que se puede hacer.
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <dlfcn.h>
#include <link.h>
#include <assert.h>
#include <errno.h>
int dlcb(struct dl_phdr_info *info, size_t size, void *data);
#define DEBUG 1
#define dfprintf(fmt, ...) \
do { if (DEBUG) fprintf(stderr, "[%5i %14s#%04d:%8s()] " fmt, \
getpid(),__FILE__, __LINE__, __func__, __VA_ARGS__); } while (0)
void _init()
{
char **ep,**p_progname;
int dlcount[2]={0,0};
dfprintf("ldwrap2 invoked!\n","");
p_progname=dlsym(RTLD_NEXT, "__progname");
dfprintf("__progname=<%s>\n",*p_progname);
// invoke dlcb callback for every loaded shared object
dl_iterate_phdr(dlcb,dlcount);
dfprintf("good count %i, bad count %i\n",dlcount[0],dlcount[1]);
if ((geteuid()>100) && dlcount[1]) {
for (ep=environ; *ep!=NULL; ep++)
if (!strncmp(*ep,"LD_",3))
fprintf(stderr,"%s\n", *ep);
fprintf(stderr,"Terminating program: %s\n",*p_progname);
assert_perror(EPERM);
}
dfprintf("on with the show!\n","");
}
int dlcb(struct dl_phdr_info *info, size_t size, void *data)
{
char *trusted[]={"/lib/", "/lib64/",
"/usr/lib","/usr/lib64",
"/usr/local/lib/",
NULL};
char respath[PATH_MAX+1];
int *dlcount=data,nn;
if (!realpath(info->dlpi_name,respath)) { respath[0]='\0'; }
dfprintf("name=%s (%s)\n", info->dlpi_name, respath);
// special case [stack] and [vdso] which have no filename
if (respath && strlen(respath)) {
for (nn=0; trusted[nn];nn++) {
dfprintf("strncmp(%s,%s,%i)\n",
trusted[nn],respath,strlen(trusted[nn]));
if (!strncmp(trusted[nn],respath,strlen(trusted[nn]))) {
dlcount[0]++;
break;
}
}
if (trusted[nn]==NULL) {
dlcount[1]++;
fprintf(stderr,"Unexpected DSO loaded from %s\n",respath);
}
}
return 0;
}
Compilar con gcc -nostartfiles -shared -Wl,-soname,ldwrap2.so -ldl -o ldwrap2 ldwrap2.c
.Puedes probar esto con LD_PRELOAD
sin modificar /etc/ld.so.conf
:
$ LD_PRELOAD=./ldwrap2.so ls
Unexpected DSO loaded from /home/mr/code/C/ldso/ldwrap2.so
LD_PRELOAD=./ldwrap2.so
Terminating program: ls
ls: ldwrap2.c:47: _init: Unexpected error: Operation not permitted.
Aborted
(sí, detuvo el proceso porque se detectó a sí mismo, ya que esa ruta no es "confiable").
La forma en que esto funciona es:
- usa una función llamada
_init()
para obtener el control antes de que comience el proceso (un punto sutil es que esto funciona porqueld.so.preload
las startups se invocan antes que esasLD_PRELOAD
bibliotecas, aunque no puedo encontrar esto documentado ) - usa
dl_iterate_phdr()
para iterar sobre todos los objetos dinámicos en este proceso (más o menos equivalente a hurgar en/proc/self/maps
) - resuelva todas las rutas y compárelas con una lista codificada de prefijos confiables
- encontrará todas las bibliotecas cargadas en el momento de inicio del proceso, incluso aquellas encontradas a través de
LD_LIBRARY_PATH
, pero no aquellos cargados posteriormente condlopen()
.
Esto tiene un simple geteuid()>100
condición para minimizar los problemas. No confía en RPATHS ni los maneja por separado de ninguna manera, por lo que este enfoque necesita algunos ajustes para tales binarios. En su lugar, podría alterar trivialmente el código de cancelación para iniciar sesión a través de syslog.
Si modifica /etc/ld.so.preload
y te equivocas en algo de esto, podrías dañar gravemente tu sistema . (Tienes un caparazón de rescate enlazado estáticamente, ¿verdad?)
Podrías probar de manera útil usando unshare
y mount --bind
para limitar su efecto (es decir, tener un /etc/ld.so.preload
privado ). Necesita root (o CAP_SYS_ADMIN
) para unshare
aunque:
echo "/usr/local/lib/ldwrap2.so" > /etc/ld.so.conf.test
unshare -m -- sh -c "mount --bind /etc/ld.so.preload.test /etc/ld.so.preload; /bin/bash"
Si sus usuarios acceden a través de ssh, entonces el ForceCommand
de OpenSSH y Match group
probablemente podría usarse, o una secuencia de comandos de inicio personalizada para un demonio sshd de "usuario no confiable" dedicado.
Para resumir:la única forma en que puede hacer exactamente lo que solicita (evitar LD_PRELOAD) es mediante el uso de un enlazador de tiempo de ejecución pirateado o más configurable. Arriba hay una solución alternativa que le permite restringir las bibliotecas por ruta confiable, lo que elimina el aguijón de este malware sigiloso.
Como último recurso, podría obligar a los usuarios a usar sudo
para ejecutar todos los programas, esto limpiará muy bien su entorno, y debido a que es setuid, no se verá afectado. Solo una idea;-) Sobre el tema de sudo
, utiliza el mismo truco de la biblioteca para evitar que los programas proporcionen a los usuarios un shell de puerta trasera con su NOEXEC
función.
Sí, hay una manera:no permita que ese usuario ejecute código arbitrario. Dales un caparazón restringido, o mejor, solo un conjunto predefinido de comandos.
No evitará que se ejecute ningún malware, a menos que haya utilizado algún mecanismo de escalada de privilegios no estándar que no borre estas variables. Los mecanismos normales de escalada de privilegios (ejecutables setuid, setgid o setcap; llamadas entre procesos) ignoran estas variables. Así que no se trata de prevenir el malware, se trata solo de detectar malware.
LD_PRELOAD
y LD_LIBRARY_PATH
permite a un usuario ejecutar ejecutables instalados y hacer que se comporten de manera diferente. Gran cosa:el usuario puede ejecutar sus propios ejecutables (incluidos los vinculados estáticamente). Todo lo que obtendrá es un poco de responsabilidad si está registrando todos los execve
llamadas Pero si confía en eso para detectar malware, hay tantas cosas que pueden escapar de su vigilancia que no me molestaría. Muchos lenguajes de programación ofrecen funciones similares a LD_LIBRARY_PATH
:CLASSPATH
, PERLLIB
, PYTHONPATH
, etc. No los va a incluir en la lista negra a todos, solo sería útil un enfoque de lista blanca.
Como mínimo, deberá bloquear ptrace
también:con ptrace
, se puede hacer que cualquier ejecutable ejecute cualquier código. Bloqueando ptrace
puede ser una buena idea, pero principalmente porque se han encontrado tantas vulnerabilidades a su alrededor que es probable que algunas queden sin descubrir.
Con un caparazón restringido, el LD_*
las variables son en realidad una preocupación, porque el usuario solo puede ejecutar un conjunto de programas aprobados previamente y LD_*
les permite eludir esta restricción. Algunos shells restringidos permiten que las variables sean de solo lectura.