GNU/Linux >> Belajar Linux >  >> Linux

Apakah ada cara untuk memblokir LD_PRELOAD dan LD_LIBRARY_PATH di Linux?

Pada dasarnya, Anda perlu mengontrol lingkungan eksekusi aplikasi. Tidak ada keajaiban tentang itu. Beberapa solusi yang terlintas dalam pikiran:

  1. Anda entah bagaimana dapat mengatur semua binari yang membuat Anda khawatir sebagai setuid/setgid (itu tidak berarti mereka harus dimiliki oleh root, sejauh yang saya tahu). Linux biasanya mencegah pelekatan ke proses setuid/setgid. Harap lakukan verifikasi jika itu dilakukan untuk setuid yang tidak dimiliki root!

  2. Anda dapat menggunakan pemuat aman untuk menjalankan aplikasi Anda alih-alih ld, yang menolak mengakui LD_PRELOADs. Ini dapat merusak beberapa aplikasi yang ada. Lihat lebih lanjut karya Mathias Payer, meskipun saya ragu ada alat siap pakai yang bisa Anda terapkan.

  3. Anda dapat membangun kembali binari Anda dengan libc yang menonaktifkan LD_PRELOAD dan dlsym. Saya mendengar bahwa musl dapat melakukannya jika memberikan opsi yang tepat, tetapi tidak dapat menemukan kembali informasi tentang caranya saat ini.

  4. Dan terakhir, Anda dapat meng-sandbox aplikasi Anda dan mencegah aplikasi meluncurkan proses lain secara langsung dengan lingkungan khusus atau memodifikasi direktori home pengguna. Tidak ada alat siap pakai untuk ini juga (masih banyak pekerjaan yang sedang berjalan dan belum ada yang dapat diterapkan).

Mungkin ada batasan untuk solusi di atas dan kandidat solusi lainnya tergantung pada aplikasi apa yang perlu Anda jalankan, siapa penggunanya, dan apa model ancamannya. Jika Anda dapat membuat pertanyaan Anda lebih akurat, saya akan mencoba dan memperbaiki jawaban itu.

Edit: perlu diingat bahwa pengguna jahat hanya dapat memodifikasi lingkungan eksekusi mereka sendiri (kecuali mereka dapat meningkatkan hak istimewa untuk melakukan root dengan beberapa eksploitasi tetapi kemudian Anda memiliki masalah lain yang harus ditangani). Jadi, pengguna biasanya tidak menggunakan injeksi LD_PRELOAD karena mereka sudah dapat menjalankan kode dengan hak istimewa yang sama. Serangan masuk akal untuk beberapa skenario:

  • melanggar pemeriksaan terkait keamanan di sisi klien perangkat lunak server-klien (biasanya menipu di video game, atau membuat aplikasi klien melewati beberapa langkah validasi dengan server distributornya)
  • memasang malware permanen saat Anda mengambil alih sesi atau proses pengguna (entah karena mereka lupa keluar dan Anda memiliki akses fisik ke perangkat atau karena Anda mengeksploitasi salah satu aplikasi mereka dengan konten buatan)

Sebagian besar poin Steve DL bagus, pendekatan "terbaik" adalah menggunakan run-time linker (RTLD) yang lebih Anda kendalikan. Kode "LD_ " variabel di-hard-code ke dalam glibc (dimulai dengan elf/rtld.c ). RTLD glibc memiliki banyak "fitur", dan bahkan ELF sendiri memiliki beberapa kejutan dengan entri DT_RPATH dan DT_RUNPATH, dan $ORIGIN (lihat https://unix.stackexchange.com/questions/22926/where-do-executables-look-for-shared-objects-at-runtime).

Biasanya jika Anda ingin mencegah (atau mengubah) operasi tertentu ketika Anda tidak dapat menggunakan izin normal atau shell yang dibatasi, Anda dapat memaksa memuat perpustakaan untuk menyelesaikan panggilan libc — inilah trik yang digunakan malware, dan ini berarti sulit untuk menggunakan teknik yang sama untuk melawannya.

Salah satu opsi yang memungkinkan Anda menghubungkan RTLD adalah audit fitur, untuk menggunakan ini Anda mengatur LD_AUDIT untuk memuat objek bersama (berisi fungsi bernama API audit yang ditentukan). Keuntungannya adalah Anda dapat mengaitkan masing-masing pustaka yang sedang dimuat, kekurangannya adalah pustaka ini dikontrol dengan variabel lingkungan...

Trik yang jarang digunakan adalah salah satu dari ld.so "fitur" :/etc/ld.so.preload . Apa yang dapat Anda lakukan dengan ini adalah memuat kode Anda sendiri ke dalam setiap proses dinamis, keuntungannya adalah dikontrol oleh file yang dibatasi, pengguna non-root tidak dapat memodifikasi atau menimpanya (dengan alasan, misalnya jika pengguna dapat menginstal rantai alat mereka sendiri atau trik serupa).

Di bawah ini adalah beberapa eksperimental kode untuk melakukan ini, Anda mungkin harus berpikir keras tentang ini sebelum menggunakannya dalam produksi, tetapi ini menunjukkan bahwa hal itu dapat dilakukan.

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

Kompilasi dengan gcc -nostartfiles -shared -Wl,-soname,ldwrap2.so -ldl -o ldwrap2 ldwrap2.c .Anda dapat menguji ini dengan LD_PRELOAD tanpa memodifikasi /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

(ya, itu berhenti menghentikan proses karena mendeteksi dirinya sendiri, karena jalur itu tidak "tepercaya".)

Cara kerjanya adalah:

  • gunakan fungsi bernama _init() untuk mendapatkan kontrol sebelum proses dimulai (poin halusnya adalah ini berfungsi karena ld.so.preload startup dipanggil sebelum LD_PRELOAD itu perpustakaan, meskipun saya tidak dapat menemukan ini didokumentasikan )
  • gunakan dl_iterate_phdr() untuk mengulangi semua objek dinamis dalam proses ini (kira-kira setara dengan mengobrak-abrik /proc/self/maps )
  • selesaikan semua jalur, dan bandingkan dengan daftar prefiks tepercaya yang di-hardcode
  • itu akan menemukan semua perpustakaan dimuat pada waktu mulai proses, bahkan yang ditemukan melalui LD_LIBRARY_PATH , tetapi tidak yang kemudian dimuat dengan dlopen() .

Ini memiliki geteuid()>100 sederhana kondisi untuk meminimalkan masalah. Itu tidak mempercayai RPATHS atau menanganinya secara terpisah dengan cara apa pun, jadi pendekatan ini memerlukan beberapa penyesuaian untuk binari semacam itu. Sebagai gantinya, Anda dapat mengubah kode batalkan untuk login melalui syslog.

Jika Anda memodifikasi /etc/ld.so.preload dan melakukan kesalahan ini, Anda dapat merusak sistem Anda . (Anda memiliki rescue shell yang ditautkan secara statis, bukan?)

Anda dapat menguji dengan cara terkontrol menggunakan unshare dan mount --bind untuk membatasi efeknya (yaitu memiliki /etc/ld.so.preload pribadi ). Anda memerlukan root (atau CAP_SYS_ADMIN ) untuk unshare meskipun:

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"

Jika pengguna Anda mengakses melalui ssh, maka ForceCommand OpenSSH dan Match group mungkin dapat digunakan, atau skrip startup yang disesuaikan untuk daemon sshd khusus "pengguna tidak tepercaya".

Untuk meringkas:satu-satunya cara Anda dapat melakukan apa yang Anda minta (mencegah LD_PRELOAD) adalah dengan menggunakan run-time linker yang diretas atau lebih dapat dikonfigurasi. Di atas adalah solusi yang memungkinkan Anda membatasi perpustakaan dengan jalur tepercaya, yang menghilangkan malware tersembunyi tersebut.

Sebagai upaya terakhir, Anda dapat memaksa pengguna untuk menggunakan sudo untuk menjalankan semua program, ini akan membersihkan lingkungan mereka dengan baik, dan karena setuid, itu tidak akan terpengaruh dengan sendirinya. Hanya sebuah ide;-) Tentang subjek sudo , ia menggunakan trik perpustakaan yang sama untuk mencegah program memberi pengguna shell pintu belakang dengan NOEXEC fitur.


Ya, ada caranya:jangan biarkan pengguna itu menjalankan kode arbitrer. Beri mereka shell terbatas, atau lebih baik, hanya satu set perintah yang telah ditentukan sebelumnya.

Anda tidak akan mencegah berjalannya malware apa pun, kecuali Anda telah menggunakan beberapa mekanisme eskalasi hak istimewa non-standar yang tidak menghapus variabel-variabel ini. Mekanisme eskalasi hak istimewa normal (setuid, setgid atau setcap yang dapat dieksekusi; panggilan antar-proses) mengabaikan variabel-variabel ini. Jadi ini bukan tentang mencegah malware, ini hanya tentang mendeteksi malware.

LD_PRELOAD dan LD_LIBRARY_PATH memungkinkan pengguna untuk menjalankan executable yang diinstal dan membuatnya berperilaku berbeda. Masalah besar:pengguna dapat menjalankan executable mereka sendiri, (termasuk yang terhubung secara statis). Yang akan Anda dapatkan hanyalah sedikit akuntabilitas jika Anda mencatat semua execve panggilan. Tetapi jika Anda mengandalkan itu untuk mendeteksi malware, ada begitu banyak yang dapat lolos dari pengawasan Anda sehingga saya tidak akan repot. Banyak bahasa pemrograman menawarkan fasilitas yang mirip dengan LD_LIBRARY_PATH :CLASSPATH , PERLLIB , PYTHONPATH , dll. Anda tidak akan memasukkan semuanya ke dalam daftar hitam, hanya pendekatan daftar putih yang akan berguna.

Paling tidak, Anda harus memblokir ptrace juga:dengan ptrace , semua yang dapat dieksekusi dapat dibuat untuk mengeksekusi kode apa pun. Memblokir ptrace bisa menjadi ide yang bagus — tetapi terutama karena begitu banyak kerentanan telah ditemukan di sekitarnya sehingga kemungkinan hanya sedikit yang belum ditemukan.

Dengan shell terbatas, LD_* variabel sebenarnya menjadi perhatian, karena pengguna hanya dapat menjalankan serangkaian program yang telah disetujui sebelumnya dan LD_* memungkinkan mereka untuk melewati pembatasan ini. Beberapa shell yang dibatasi memungkinkan variabel dibuat hanya-baca.


Linux
  1. Dhcpd:Adakah Cara Untuk Memeriksa Status Kumpulan Dhcp?

  2. Apakah ada kode status keluar standar di Linux?

  3. Apakah ada STDCALL di Linux?

  1. penghentian proses mmap, msync dan linux

  2. Apakah ada cara untuk meningkatkan kinerja pipa linux?

  3. Apakah ada cara untuk me-restart badblock?

  1. Cara Mudah Menyembunyikan File dan Direktori di Linux

  2. Ny Cara Mengetahui Ukuran Cache L1, L2, L3 Dan Ram Di Linux?

  3. Linux - Apakah ada cara untuk mengidentifikasi kecepatan memori Anda melalui perangkat lunak?