V zásadě musíte ovládat spouštěcí prostředí aplikací. Není v tom žádná magie. Pár řešení, která mě napadají:
-
Mohli byste nějak nastavit všechny binární soubory, které vás znepokojují, jako setuid/setgid (což neznamená, že je musí vlastnit root, pokud vím). Linux normálně brání připojení k procesu setuid/setgid. Prosím ověřte, zda tak činí pro setuid nevlastněný rootem!
-
Ke spouštění aplikací můžete použít bezpečný zavaděč namísto ld, který odmítá uznat LD_PRELOADs. To může poškodit některé stávající aplikace. Více najdete v práci Mathiase Payera, i když pochybuji, že existuje nějaký běžně dostupný nástroj, který můžete použít.
-
Můžete znovu vytvořit své binární soubory pomocí knihovny libc, která zakáže LD_PRELOAD a dlsym. Slyšel jsem, že to musl dokáže, pokud projde správnými možnostmi, ale momentálně nemohu najít informace o tom, jak.
-
A nakonec můžete své aplikace umístit do izolovaného prostoru a zabránit aplikacím v přímém spouštění jiných procesů s vlastním prostředím nebo v úpravě domovského adresáře uživatele. Ani na to neexistuje žádný hotový nástroj (je na tom hodně práce a zatím není nic použitelné).
Pravděpodobně existují omezení pro výše uvedená řešení a další kandidátská řešení v závislosti na tom, jaké aplikace potřebujete spustit, kdo jsou uživatelé a jaký je model hrozby. Pokud můžete svou otázku zpřesnit, pokusím se ji odpovídajícím způsobem vylepšit.
Upravit: mějte na paměti, že uživatel se zlými úmysly může upravit pouze své vlastní spouštěcí prostředí (pokud nemůže eskalovat oprávnění na root pomocí nějakého exploitu, ale pak budete muset řešit další problémy). Uživatel by tedy obvykle nepoužíval injekce LD_PRELOAD, protože již může spouštět kód se stejnými oprávněními. Útoky mají smysl pro několik scénářů:
- prolomení bezpečnostních kontrol na klientské straně softwaru klient-server (obvykle cheaty ve videohrách nebo nucení klientské aplikace obejít některé kroky ověření se serverem svého distributora)
- instalace trvalého malwaru, když převezmete relaci nebo proces uživatele (buď proto, že se uživatel zapomněl odhlásit a vy máte fyzický přístup k zařízení, nebo proto, že jste zneužili některou z jeho aplikací s vytvořeným obsahem)
Většina bodů Steva DL je dobrá, „nejlepší“ přístup je použít run-time linker (RTLD), nad kterým máte větší kontrolu. "LD_
" proměnné jsou pevně zakódovány do glibc (začínají elf/rtld.c
). Glibc RTLD má mnoho „funkcí“ a dokonce i samotný ELF má několik překvapení se svými položkami DT_RPATH a DT_RUNPATH a $ORIGIN
(viz https://unix.stackexchange.com/questions/22926/where-do-executables-look-for-shared-objects-at-runtime).
Normálně, pokud chcete zabránit (nebo změnit) určité operace, když nemůžete použít normální oprávnění nebo omezený shell, můžete místo toho vynutit načtení knihovny, aby zabalila volání libc – to je přesně ten trik, který malware používá, a to znamená je těžké proti tomu použít stejnou techniku.
Jednou z možností, která vám umožní zapojit RTLD do akce, je audit Chcete-li tuto funkci použít, nastavte LD_AUDIT
k načtení sdíleného objektu (obsahujícího definované funkce auditovacího API). Výhodou je, že můžete připojit jednotlivé načítané knihovny, nevýhodou je, že je to řízeno proměnnou prostředí...
Méně používaný trik je další z ld.so
"features" :/etc/ld.so.preload
. Co s tím můžete udělat, je načíst svůj vlastní kód do každého dynamického procesu, výhodou je, že je řízen omezeným souborem, uživatelé bez oprávnění root jej nemohou upravovat ani přepisovat (v rozumných mezích, např. pokud si uživatelé mohou nainstalovat svůj vlastní toolchain nebo podobné triky).
Níže je několik experimentálních Chcete-li to provést, měli byste si to před použitím ve výrobě důkladně promyslet, ale ukazuje, že to lze udělat.
#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;
}
Kompilace s gcc -nostartfiles -shared -Wl,-soname,ldwrap2.so -ldl -o ldwrap2 ldwrap2.c
.Můžete to otestovat pomocí LD_PRELOAD
bez úpravy /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
(ano, zastavilo a zastavilo proces, protože se detekovalo, protože tato cesta není „důvěryhodná“.)
Funguje to takto:
- použijte funkci s názvem
_init()
získat kontrolu před zahájením procesu (důmyslným bodem je, že to funguje, protoželd.so.preload
starty jsou vyvolány před těmi jakýmikoliLD_PRELOAD
knihovny, i když to nemohu najít zdokumentované ) - použijte
dl_iterate_phdr()
iterovat přes všechny dynamické objekty v tomto procesu (přibližně ekvivalentní prohrabování v/proc/self/maps
) - vyřešte všechny cesty a porovnejte je s pevně zakódovaným seznamem důvěryhodných předpon
- najde všechny knihovny načtené při spuštění procesu, dokonce i ty nalezené pomocí
LD_LIBRARY_PATH
, ale ne ty následně načtenédlopen()
.
Toto má jednoduchý geteuid()>100
stav pro minimalizaci problémů. Nedůvěřuje RPATHS ani je nijak zvlášť nezpracovává, takže tento přístup potřebuje pro takové binární soubory nějaké vyladění. Můžete triviálně změnit kód přerušení tak, aby se místo toho přihlašoval přes syslog.
Pokud upravíte /etc/ld.so.preload
a pokud se něco z toho zmýlíte, mohli byste vážně narušit svůj systém . (Máte staticky propojený záchranný shell, že?)
Mohli byste užitečně testovat kontrolovaným způsobem pomocí unshare
a mount --bind
omezit jeho účinek (tj. mít soukromý /etc/ld.so.preload
). Potřebujete root (nebo CAP_SYS_ADMIN
) pro unshare
ačkoli:
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"
Pokud vaši uživatelé přistupují přes ssh, pak ForceCommand
OpenSSH a Match group
pravděpodobně by se dal použít, nebo přizpůsobený spouštěcí skript pro specializovaného démona sshd "nedůvěryhodný uživatel".
Abych to shrnul:jediný způsob, jak můžete udělat přesně to, co požadujete (zabránit LD_PRELOAD), je použití hacknutého nebo lépe konfigurovatelného linkeru za běhu. Výše uvedené je řešení, které vám umožní omezit knihovny důvěryhodnou cestou, která takový kradmý malware odstraní.
Jako poslední možnost můžete přinutit uživatele, aby používali sudo
pro spouštění všech programů to pěkně vyčistí jejich prostředí a protože je to setuid, samo to neovlivní. Jen nápad;-) K tématu sudo
, používá stejný trik s knihovnami, aby zabránil programům poskytovat uživatelům backdoor shell se svým NOEXEC
funkce.
Ano, existuje způsob:nedovolte tomuto uživateli spouštět libovolný kód. Dejte jim omezený shell, nebo lépe, pouze předdefinovanou sadu příkazů.
Spuštění žádného malwaru byste nezabránili, pokud byste nepoužili nějaký nestandardní mechanismus eskalace oprávnění, který tyto proměnné nevymaže. Normální mechanismy eskalace oprávnění (spustitelné soubory setuid, setgid nebo setcap; meziprocesová volání) tyto proměnné ignorují. Nejde tedy o prevenci malwaru, jde pouze o detekci malwaru.
LD_PRELOAD
a LD_LIBRARY_PATH
umožňuje uživateli spouštět nainstalované spustitelné soubory a přimět je, aby se chovaly odlišně. Velký problém:uživatel může spouštět své vlastní spustitelné soubory (včetně staticky propojených). Jediné, co byste získali, je trochu odpovědnosti, pokud zaznamenáváte všechny execve
hovory. Ale pokud se na to spoléháte při detekci malwaru, je toho tolik, co může uniknout vašemu dohledu, že bych se tím neobtěžoval. Mnoho programovacích jazyků nabízí vybavení podobné LD_LIBRARY_PATH
:CLASSPATH
, PERLLIB
, PYTHONPATH
, atd. Nechystáte je všechny na černou listinu, užitečný by byl pouze přístup na bílou listinu.
Přinejmenším byste museli zablokovat ptrace
také:s ptrace
, může být vytvořen jakýkoli spustitelný soubor ke spuštění libovolného kódu. Blokování ptrace
může to být dobrý nápad – ale především proto, že kolem toho bylo nalezeno tolik zranitelností, že je pravděpodobné, že pár jich zůstane neobjevených.
S omezeným shellem, LD_*
proměnné jsou ve skutečnosti problémem, protože uživatel může spouštět pouze předem schválenou sadu programů a LD_*
umožňuje jim toto omezení obejít. Některé omezené shelly umožňují, aby proměnné byly pouze pro čtení.