基本的に、アプリの実行環境を制御する必要があります。それについて魔法はありません。頭に浮かぶいくつかの解決策:
<オール>どうにかして、心配しているすべてのバイナリを 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_PRELOAD
と LD_LIBRARY_PATH
ユーザーは、インストールされた実行可能ファイルを実行して、異なる動作をさせることができます。大したこと:ユーザーは独自の実行可能ファイル (静的にリンクされたものを含む) を実行できます。すべての execve
をログに記録している場合、得られるのは少しの説明責任だけです 呼び出します。しかし、これに頼ってマルウェアを検出しているのであれば、監視を逃れる可能性のあるものはたくさんあるので、気にする必要はありません。多くのプログラミング言語は LD_LIBRARY_PATH
に似た機能を提供します :CLASSPATH
、 PERLLIB
、 PYTHONPATH
など。すべてをブラックリストに登録するつもりはありません。ホワイトリストのアプローチだけが役立ちます。
少なくとも、ptrace
をブロックする必要があります。 同様に:ptrace
で 、任意の実行可能ファイルを作成して、任意のコードを実行できます。 ptrace
をブロックしています 良いアイデアかもしれませんが、主な理由は、その周りに非常に多くの脆弱性が発見されているため、発見されていない脆弱性がいくつかある可能性が高いためです.
制限されたシェルでは、LD_*
ユーザーは事前に承認された一連のプログラムと LD_*
しか実行できないため、変数は実際には懸念事項です。 この制限を回避できるようにします。一部の制限付きシェルでは、変数を読み取り専用にすることができます。