web-dev-qa-db-ja.com

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

できれば、Vanilla Linuxカーネル、つまりSELinuxやGrSecurityを使用していないのですか?

最近、野生のUNIXサーバーマルウェアの事例があります。

https://www.virusbtn.com/virusbulletin/archive/2014/07/vb201407-Mayhem

これは、LD_PRELOADを利用して非表示のユーザー空間トロイの木馬を実装します。可能であれば、制限されたユーザーが共有ライブラリのパスをいじることを禁止するとよいと思います。

(たとえば、GrSecurity /トラステッドパスの実行など、さらに進んでいきますが、現時点では実用的ではありません。)

これを確実に行う方法はありますか?

編集:上記の意味は、通常、任意のユーザーがLD_PRELOADを使用して任意のライブラリ実行可能なすべての実行可能ファイルに、それはsetuidまたはsetgidではありません。これは、隠された永続的なマルウェア。特定のユーザーがLD_PRELOADをまったく使用できないようにする方法はありますか

7
DanL4096

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

  1. どういうわけか、心配するすべてのバイナリをsetuid/setgidとして設定できます(これは、私の知る限り、rootが所有している必要があるという意味ではありません)。 Linuxは通常 setuid/setgidプロセスへの接続を防止します 。非root所有のsetuidについてもそうかどうか確認してください!

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

  3. LD_PRELOADとdlsymを無効にするlibcでバイナリを再構築できます。 musl は、適切なオプションを渡せばそれを実行できると聞きましたが、現在の方法に関する情報を見つけることはできません。

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

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

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

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

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つは auditfeature です。これを使用するには、_LD_AUDIT_を設定して共有オブジェクトをロードします(関数という名前の定義済み監査APIが含まれています)。利点は、読み込まれている個々のライブラリをフックすることです。欠点は、環境変数で制御されることです...

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

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

_#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_での巡回とほぼ同等)
  • すべてのパスを解決し、ハードコーディングされた信頼できるプレフィックスのリストと比較する
  • _LD_LIBRARY_PATH_で見つかったものも含めて、プロセスの開始時に読み込まれるすべてのライブラリが見つかりますが、その後dlopen()で読み込まれるものはではありません

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

_/etc/ld.so.preload_を変更して、このいずれかが間違っていると、システムが破損する可能性があります。 (静的にリンクされたレスキューシェルがありますよね?)

unshareと_mount --bind_を使用して制御された方法で効果的にテストすると、その影響を制限できます(つまり、プライベート_/etc/ld.so.preload_を使用します)。ただし、unshareにはroot(または_CAP_SYS_ADMIN_)が必要です。

_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機能を備えたバックドアシェルをユーザーに与えるプログラムを防ぎます。

7
mr.spuratic

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

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

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

少なくとも、ptraceもブロックする必要があります。ptraceを使用すると、任意の実行可能ファイルを作成して任意のコードを実行できます。 ptraceをブロックすることは良い考えですが、その主な理由は、その周辺に非常に多くの脆弱性が見つかっており、発見されないままになっている可能性があるためです。

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