Essentiellement, vous devez contrôler l'environnement d'exécution des applications. Il n'y a pas de magie là-dedans. Quelques solutions qui me viennent à l'esprit :
-
Vous pouvez en quelque sorte définir tous les fichiers binaires qui vous inquiètent en tant que setuid/setgid (cela ne signifie pas qu'ils doivent appartenir à root, pour autant que je sache). Linux empêche normalement l'attachement à un processus setuid/setgid. Veuillez vérifier si c'est le cas pour les setuid non root !
-
Vous pouvez utiliser un chargeur sécurisé pour exécuter vos applications au lieu de ld, qui refuse de reconnaître les LD_PRELOAD. Cela peut casser certaines applications existantes. Voir le travail de Mathias Payer pour en savoir plus, bien que je doute qu'il existe un outil prêt à l'emploi que vous puissiez simplement appliquer.
-
Vous pouvez reconstruire vos binaires avec une libc qui désactive LD_PRELOAD et dlsym. J'ai entendu dire qu'ils pouvaient le faire s'ils réussissaient les bonnes options, mais je ne trouve pas d'informations sur la manière de procéder pour le moment.
-
Et enfin, vous pouvez mettre vos applications en sandbox et empêcher les applications de lancer directement d'autres processus avec un environnement personnalisé ou de modifier le répertoire personnel de l'utilisateur. Il n'y a pas non plus d'outil prêt à l'emploi pour cela (c'est un travail en cours et rien n'est encore déployable).
Il y a probablement des limites aux solutions ci-dessus et à d'autres solutions candidates en fonction des applications que vous devez exécuter, des utilisateurs et du modèle de menace. Si vous pouvez rendre votre question plus précise, j'essaierai d'améliorer cette réponse en conséquence.
Modifier : gardez à l'esprit qu'un utilisateur malveillant ne peut modifier que son propre environnement d'exécution (à moins qu'il ne puisse élever les privilèges à root avec un exploit, mais vous avez alors d'autres problèmes à gérer). Ainsi, un utilisateur n'utilisera généralement pas les injections LD_PRELOAD car il peut déjà exécuter du code avec les mêmes privilèges. Les attaques ont du sens pour quelques scénarios :
- casser les vérifications liées à la sécurité du côté client du logiciel client-serveur (généralement triche dans les jeux vidéo ou faire en sorte qu'une application cliente contourne une étape de validation avec le serveur de son distributeur)
- installer un logiciel malveillant permanent lorsque vous prenez le contrôle de la session ou du processus d'un utilisateur (soit parce qu'il a oublié de se déconnecter et que vous avez un accès physique à l'appareil, soit parce que vous avez exploité l'une de ses applications avec du contenu spécialement conçu)
La plupart des points de Steve DL sont bons, la "meilleure" approche consiste à utiliser un éditeur de liens d'exécution (RTLD) sur lequel vous avez plus de contrôle. Le "LD_
" les variables sont codées en dur dans la glibc (commencez par elf/rtld.c
). La glibc RTLD a de nombreuses "fonctionnalités", et même ELF lui-même a quelques surprises avec ses entrées DT_RPATH et DT_RUNPATH, et $ORIGIN
(voir https://unix.stackexchange.com/questions/22926/where-do-executables-look-for-shared-objects-at-runtime).
Normalement, si vous souhaitez empêcher (ou modifier) certaines opérations lorsque vous ne pouvez pas utiliser les autorisations normales ou un shell restreint, vous pouvez à la place forcer le chargement d'une bibliothèque pour envelopper les appels libc - c'est exactement l'astuce utilisée par le malware, et cela signifie il est difficile d'utiliser la même technique contre elle.
Une option qui vous permet d'accrocher le RTLD en action est l'audit fonctionnalité, pour l'utiliser, vous définissez LD_AUDIT
pour charger un objet partagé (contenant les fonctions nommées de l'API d'audit définies). L'avantage est que vous pouvez accrocher les bibliothèques individuelles en cours de chargement, l'inconvénient est qu'il est contrôlé avec une variable d'environnement...
Une astuce moins utilisée est une autre des ld.so
"fonctionnalités" :/etc/ld.so.preload
. Ce que vous pouvez faire avec cela est de charger votre propre code dans chaque processus dynamique, l'avantage est qu'il est contrôlé par un fichier restreint, les utilisateurs non root ne peuvent pas le modifier ou le remplacer (dans des limites raisonnables, par exemple si les utilisateurs peuvent installer leur propre chaîne d'outils ou astuces similaires).
Ci-dessous quelques expérimentaux code pour ce faire, vous devriez probablement y réfléchir sérieusement avant de l'utiliser en production, mais cela montre que cela peut être fait.
#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;
}
Compiler avec gcc -nostartfiles -shared -Wl,-soname,ldwrap2.so -ldl -o ldwrap2 ldwrap2.c
.Vous pouvez tester cela avec LD_PRELOAD
sans modifier /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
(oui, il a arrêté le processus parce qu'il s'est détecté, puisque ce chemin n'est pas "de confiance".)
Voici comment cela fonctionne :
- utiliser une fonction nommée
_init()
pour prendre le contrôle avant le début du processus (un point subtil est que cela fonctionne parce queld.so.preload
les startups sont invoquées avant celles-ciLD_PRELOAD
bibliothèques, bien que je ne trouve pas cela documenté ) - utilisez
dl_iterate_phdr()
pour itérer sur tous les objets dynamiques de ce processus (à peu près équivalent à fouiller dans/proc/self/maps
) - résoudre tous les chemins et comparer avec une liste codée en dur de préfixes de confiance
- il trouvera toutes les bibliothèques chargées au démarrage du processus, même celles trouvées via
LD_LIBRARY_PATH
, mais pas ceux chargés ensuite avecdlopen()
.
Cela a un simple geteuid()>100
condition pour minimiser les problèmes. Il ne fait pas confiance à RPATHS ou ne les gère pas séparément de quelque manière que ce soit, donc cette approche nécessite quelques ajustements pour ces binaires. Vous pouvez modifier trivialement le code d'abandon pour vous connecter via syslog à la place.
Si vous modifiez /etc/ld.so.preload
et si vous vous trompez, vous pourriez gravement casser votre système . (Vous avez un shell de secours lié statiquement, n'est-ce pas ?)
Vous pouvez utilement tester de manière contrôlée en utilisant unshare
et mount --bind
pour limiter son effet (c'est-à-dire avoir un /etc/ld.so.preload
privé ). Vous avez besoin de root (ou CAP_SYS_ADMIN
) pour unshare
cependant :
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 vos utilisateurs accèdent via ssh, alors le ForceCommand
d'OpenSSH et Match group
pourrait probablement être utilisé, ou un script de démarrage personnalisé pour un démon sshd dédié "utilisateur non approuvé".
Pour résumer :la seule façon de faire exactement ce que vous demandez (empêcher LD_PRELOAD) est d'utiliser un éditeur de liens d'exécution piraté ou plus configurable. Ci-dessus se trouve une solution de contournement qui vous permet de restreindre les bibliothèques par chemin de confiance, ce qui élimine la piqûre de ces logiciels malveillants furtifs.
En dernier recours, vous pouvez forcer les utilisateurs à utiliser sudo
pour exécuter tous les programmes, cela nettoiera bien leur environnement, et parce que c'est setuid, il ne sera pas affecté lui-même. Juste une idée;-) Au sujet de sudo
, il utilise la même astuce de bibliothèque pour empêcher les programmes de donner aux utilisateurs un shell de porte dérobée avec son NOEXEC
fonctionnalité.
Oui, il y a un moyen :ne laissez pas cet utilisateur exécuter du code arbitraire. Donnez-leur un shell restreint, ou mieux, seulement un ensemble prédéfini de commandes.
Vous n'empêcheriez aucun logiciel malveillant de s'exécuter, à moins que vous n'ayez utilisé un mécanisme d'escalade de privilèges non standard qui n'efface pas ces variables. Les mécanismes normaux d'escalade de privilèges (exécutables setuid, setgid ou setcap; appels inter-processus) ignorent ces variables. Il ne s'agit donc pas d'empêcher les logiciels malveillants, mais uniquement de les détecter.
LD_PRELOAD
et LD_LIBRARY_PATH
permet à un utilisateur d'exécuter des exécutables installés et de les faire se comporter différemment. Gros problème :l'utilisateur peut exécuter ses propres exécutables (y compris ceux liés statiquement). Tout ce que vous obtiendriez, c'est un peu de responsabilité si vous enregistrez tous les execve
appels. Mais si vous comptez sur cela pour détecter les logiciels malveillants, il y a tellement de choses qui peuvent échapper à votre surveillance que je ne m'en soucierais pas. De nombreux langages de programmation offrent des fonctionnalités similaires à LD_LIBRARY_PATH
:CLASSPATH
, PERLLIB
, PYTHONPATH
, etc. Vous n'allez pas tous les mettre sur liste noire, seule une approche de liste blanche serait utile.
À tout le moins, vous devez bloquer ptrace
en plus :avec ptrace
, n'importe quel exécutable peut être fait pour exécuter n'importe quel code. Blocage ptrace
peut être une bonne idée - mais principalement parce que tant de vulnérabilités ont été trouvées autour de lui qu'il est probable que quelques-unes ne soient pas découvertes.
Avec un shell restreint, le LD_*
les variables sont en fait un problème, car l'utilisateur ne peut exécuter qu'un ensemble de programmes pré-approuvés et LD_*
leur permet de contourner cette restriction. Certains shells restreints permettent de rendre les variables en lecture seule.