GNU/Linux >> Linux の 問題 >  >> Linux

Linux で LD_PRELOAD と LD_LIBRARY_PATH をブロックする方法はありますか?

基本的に、アプリの実行環境を制御する必要があります。それについて魔法はありません。頭に浮かぶいくつかの解決策:

<オール>
  • どうにかして、心配しているすべてのバイナリを setuid/setgid として設定できます (私の知る限り、root が所有しなければならないという意味ではありません)。 Linux は通常、setuid/setgid プロセスへのアタッチを防ぎます。 root 以外が所有する setuid の場合は確認してください!

  • LD_PRELOAD の確認を拒否する ld の代わりに、安全なローダーを使用してアプリを実行できます。これにより、一部の既存のアプリが破損する可能性があります。詳細については、Mathias Payer の作品を参照してください。ただし、すぐに適用できる既成のツールはないと思います。

  • LD_PRELOAD と dlsym を無効にする libc を使用してバイナリを再構築できます。適切なオプションを渡せば musl がそれを実行できると聞いたことがありますが、現在どのように行われているかについての情報を再確認することはできません.

  • 最後に、アプリをサンドボックス化して、アプリがカスタム環境で他のプロセスを直接起動したり、ユーザーのホーム ディレクトリを変更したりできないようにすることができます。このための既製のツールもありません (非常に多くの作業が進行中であり、まだ展開可能なものはありません)。

  • 実行する必要があるアプリ、ユーザー、脅威モデルによっては、上記のソリューションやその他の候補ソリューションにはおそらく制限があります。質問をより正確にすることができれば、それに応じてその回答を改善しようとします。

    編集: 悪意のあるユーザーは自分の実行環境のみを変更できることに注意してください (ただし、何らかの悪用によって特権を root にエスカレートできるが、他の問題を処理する必要がある場合は除きます)。そのため、ユーザーは通常、同じ権限でコードを実行できるため、LD_PRELOAD インジェクションを使用しません。攻撃はいくつかのシナリオで意味があります:

    • クライアント サーバー ソフトウェアのクライアント側でセキュリティ関連のチェックを破る (通常、ビデオ ゲームでチートを行う、またはクライアント アプリにディストリビューターのサーバーでの検証手順をバイパスさせる)
    • ユーザーのセッションまたはプロセスを引き継ぐときに永続的なマルウェアをインストールする (ユーザーがログアウトするのを忘れていてデバイスに物理的にアクセスできるため、または細工されたコンテンツでユーザーのアプリの 1 つを悪用したため)

    Steve DL の指摘のほとんどは適切です。「最良の」アプローチは、より制御しやすいランタイム リンカー (RTLD) を使用することです。 「LD_ " 変数は glibc にハードコードされています (elf/rtld.c で始まります) )。 glibc RTLD には多くの「機能」があり、ELF 自体でさえ、その DT_RPATH および DT_RUNPATH エントリと $ORIGIN にはいくつかの驚きがあります。 (https://unix.stackexchange.com/questions/22926/where-do-executables-look-for-shared-objects-at-runtime を参照してください)。

    通常、通常のパーミッションまたは制限されたシェルを使用できないときに特定の操作を防止 (または変更) したい場合は、代わりにライブラリを強制的にロードして libc 呼び出しをラップすることができます。これはまさにマルウェアが使用しているトリックであり、これはつまり、それに対して同じテクニックを使うのは難しい.

    RTLD を実際にフックできるオプションの 1 つは、audit です。 この機能を使用するには、LD_AUDIT を設定します 共有オブジェクト (定義された監査 API という名前の関数を含む) をロードします。利点は、読み込まれる個々のライブラリをフックできることです。欠点は、環境変数で制御されることです...

    あまり使用されないトリックは、ld.so のもう 1 つです。 「機能」:/etc/ld.so.preload .これでできることは、すべての動的プロセスに独自のコードをロードすることです。利点は、制限されたファイルによって制御され、ルート以外のユーザーが変更したり上書きしたりできないことです (理由の範囲内で、ユーザーが独自のツールチェーンをインストールしたり、同様のトリック)

    以下は実験的なものです コードでこれを行う場合、本番環境で使用する前にこれについてよく考える必要がありますが、それが可能であることを示しています。

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

    gcc -nostartfiles -shared -Wl,-soname,ldwrap2.so -ldl -o ldwrap2 ldwrap2.c でコンパイル .これは LD_PRELOAD でテストできます /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
    

    (はい、そのパスが「信頼されていない」ため、それ自体を検出したため、プロセスを停止しました。)

    これが機能する方法は次のとおりです:

    • _init() という名前の関数を使用する プロセスが開始する前に制御を取得します (微妙な点は、ld.so.preload のためにこれが機能することです) スタートアップは LD_PRELOAD の前に呼び出されます ライブラリ、これは文書化されていませんが )
    • dl_iterate_phdr() を使用 このプロセスですべての動的オブジェクトを反復処理します (/proc/self/maps の rummaging とほぼ同等です) )
    • すべてのパスを解決し、信頼できるプレフィックスのハードコードされたリストと比較します
    • LD_LIBRARY_PATH 経由で見つかったものも含め、プロセスの開始時にロードされたすべてのライブラリを検索します 、しかしそうではない その後 dlopen() でロードされたもの .

    これには単純な geteuid()>100 があります 問題を最小限に抑えるための条件。 RPATHS を信頼したり、それらを個別に処理したりすることは決してないため、このアプローチにはそのようなバイナリ用の調整が必要です。代わりに syslog 経由でログを記録するように中止コードを簡単に変更できます。

    /etc/ld.so.preload を変更した場合 これを間違えると、システムがひどく壊れる可能性があります . (静的にリンクされたレスキュー シェルがありますよね?)

    unshare を使用して、制御された方法で便利にテストできます と mount --bind その効果を制限する (つまり、プライベート /etc/ld.so.preload を持つ )。 root (または CAP_SYS_ADMIN が必要です) ) unshare の場合 ただし:

    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"
    

    ユーザーが ssh 経由でアクセスする場合、OpenSSH の ForceCommand そして Match group おそらく、専用の「信頼できないユーザー」sshd デーモン用に調整された起動スクリプトを使用できます。

    要約すると、要求したことを正確に実行できる (LD_PRELOAD を防ぐ) 唯一の方法は、ハッキングされた、またはより構成可能なランタイム リンカーを使用することです。上記は、信頼できるパスによってライブラリを制限できる回避策です。これにより、このようなステルス マルウェアの脅威を取り除くことができます。

    最後の手段として、ユーザーに sudo の使用を強制することができます すべてのプログラムを実行するには、これにより環境がきれいにクリーンアップされます。setuid であるため、それ自体は影響を受けません。単なるアイデア;-) sudo について 、同じライブラリ トリックを使用して、プログラムがユーザーに NOEXEC でバックドア シェルを提供するのを防ぎます。


    はい、方法があります。そのユーザーに任意のコードを実行させないでください。制限付きのシェルを提供するか、事前に定義された一連のコマンドのみを提供してください。

    これらの変数を消去しない非標準の権限昇格メカニズムを使用しない限り、マルウェアの実行を防ぐことはできません。通常の権限昇格メカニズム (setuid、setgid、または setcap 実行可能ファイル、プロセス間呼び出し) は、これらの変数を無視します。したがって、これはマルウェアを防止することではなく、マルウェアを検出することのみを目的としています。

    LD_PRELOADLD_LIBRARY_PATH ユーザーは、インストールされた実行可能ファイルを実行して、異なる動作をさせることができます。大したこと:ユーザーは独自の実行可能ファイル (静的にリンクされたものを含む) を実行できます。すべての execve をログに記録している場合、得られるのは少しの説明責任だけです 呼び出します。しかし、これに頼ってマルウェアを検出しているのであれば、監視を逃れる可能性のあるものはたくさんあるので、気にする必要はありません。多くのプログラミング言語は LD_LIBRARY_PATH に似た機能を提供します :CLASSPATHPERLLIBPYTHONPATH など。すべてをブラックリストに登録するつもりはありません。ホワイトリストのアプローチだけが役立ちます。

    少なくとも、ptrace をブロックする必要があります。 同様に:ptrace で 、任意の実行可能ファイルを作成して、任意のコードを実行できます。 ptrace をブロックしています 良いアイデアかもしれませんが、主な理由は、その周りに非常に多くの脆弱性が発見されているため、発見されていない脆弱性がいくつかある可能性が高いためです.

    制限されたシェルでは、LD_* ユーザーは事前に承認された一連のプログラムと LD_* しか実行できないため、変数は実際には懸念事項です。 この制限を回避できるようにします。一部の制限付きシェルでは、変数を読み取り専用にすることができます。


    Linux
    1. Linux で現在の rpath を検査する方法はありますか?

    2. mmap、msync、および Linux プロセスの終了

    3. 不良ブロックを再起動する方法はありますか?

    1. Linux で正しい CLOCK_TAI を取得する方法はありますか?

    2. Linux - ソフトウェアでメモリの速度を特定する方法はありますか?

    3. Linux でブロック デバイスのキャッシュ ヒット/ミス率を取得する方法はありますか?

    1. Linuxでファイルとディレクトリを隠す簡単な方法

    2. Dhcpd:Dhcpプールのステータスを確認する方法はありますか?

    3. Linux に標準の終了ステータス コードはありますか?