本文の内容は、2021年4月13日にKaizhe Huangが投稿したブログ(https://sysdig.com/blog/unveil-processes-falco-cloud/)を元に日本語に翻訳・再構成した内容となっております。
クラウドネイティブ環境では、適切なツールがなければブラックボックスになってしまうため、悪意のあるプロセスの検出は、常に複雑であり、困難な作業です。そして、悪意のあるプロセスが隠されている場合は、さらに複雑になります。
オープンソースのツールを使って検知を逃れるマルウェアが報告されています。このマルウェアが使用したオープンソースプロジェクトは、Sysdig社の元チーフアーキテクトであるGianluca氏が作成したツールであるlibprocesshiderです。
libprocesshiderツールは、Linuxのライブラリプリロード技術を悪用してプロセスを隠すことができます。libcで提供されている重要な関数readdir()を悪意のあるシェアードライブラリで上書きすることで、psやtopなどのプロセスを監視するために一般的に使用されているツールが盲目になります。
この記事では、このプロセス隠しのシナリオを、コンテナやKubernetesクラスターにどのように適用できるかを検討します。また、Falcoがこれらの攻撃をどのように検知し、軽減するかについても説明します。
コンテナ内のプロセスを隠す
この例では、libprocesshider を使用して、悪意のないプロセスである sleep を隠します。
そのためには、まずprocesshidder.cを編集して、process_to_filter定数を設定します。
/* * Every process with this name will be excluded */ static const char* process_to_filter = "sleep";
次に、悪意のあるCコードを共有ライブラリとしてコンパイルします。
次に、それを/etc/ld.so.preloadに追加します。
この設定ファイルは、他のすべてのライブラリよりも先にロードされる、ユーザーが指定した追加のELF共有ライブラリのリストを定義します。これは、他の共有ライブラリの関数を選択的にオーバーライドするために使用できる強力なツールです。
psでリストアップされたsleepプロセスは通常このように表示されます:
PID 1182でsleepプロセスが実行されているのがわかります。
悪意のあるライブラリが/etc/ld.so.preloadに追加された後は、このようになります:
sleepプロセスはなくなっています。
topを使った場合も同じことが起こります:
しかし、このプロセスはまだ生きています。psやtopのような監視ツールが依存するreaddir()関数では見えていないだけです。
sleepプロセスは、コンテナのシステムランタイム情報を含む/procファイルシステムで見つけることができます:
もう1つの注意点は、コンテナ内で /etc/ld.so.preload を変更しても、ホストからのプロセス表示には影響しないということです:
ホストからdocker topを呼び出しても、コンテナ内で実行されているプロセスが表示されます。
表示されているPIDは、ホストレベルのプロセス名前空間で割り当てられているものです。(つまり、tail はコンテナ内で PID 1 を持っていました)。
ここから導き出される結論は、コンテナのファイルシステムを改ざんしても、ホストには影響がないということです(ホストのパスがマウントされていないと仮定する場合)。
Kubernetesの場合はどうでしょうか?
KubernetesのPodにプロセスを隠す
サイドカーを使ってアプリケーションコンテナを監視する場合、KubernetesポッドでshareProcessNamespace機能を有効にするのが一般的です。プロセスの名前空間を共有することで、サイドカーのコンテナからアプリケーションプロセスが見えるようになります。
以下は、このようなPodを設定するための簡略化された例です:
apiVersion: v1 kind: Pod metadata: name: shared labels: app: nginx spec: shareProcessNamespace: true containers: - name: ubuntu image: ubuntu command: ["sh"] args: ["-c", "--", "while true; do top -n 1 -b; sleep 1; done"] env: - name: CONAINER_NAME value: ubuntu - name: nginx image: kaizheh/nginx env: - name: CONAINER_NAME value: nginx
ubuntu Podは、top経由でnginx Podを監視するために使用されます:
通常のシナリオでは、nginxポッドでsleepを実行した後、ubuntuコンテナのログでそれを確認することができます:
ここで、攻撃者がnginx Podにアクセスし、悪意のあるプロセスを隠したとします。
# echo /usr/local/lib/libprocesshider.so >> /etc/ld.so.preload
sleepプロセスは、nginxコンテナ内でpsを実行すると見えなくなります。しかし、ubuntuコンテナの視点ではまだ見えています。
これは、ubuntuコンテナには独自のライブラリがあり、それがnginxコンテナのものとは異なるためです。
ubuntuコンテナからsleepをどのように隠すのでしょうか?
コンテナがプロセスを共有しているため、攻撃者はnginxコンテナ内でpsを実行することで、ubuntuコンテナの多くの情報を得ることができます。具体的には:
USER PID … COMMAND root 6 … sh -c -- while true; do top -n 1 -b; sleep 1; done
これは、ubuntuコンテナのエントリープロセスのようで、PIDは6です。
これで、攻撃者は、ubuntuのルートファイルシステムが/proc/6/rootにあることを推測できます。そして、悪意のある共有ライブラリオブジェクトを適切な場所にコピーするだけでよいのです。
# echo /usr/local/lib/libprocesshider.so > /proc/6/root/etc/ld.so.preload
すると、ubuntuコンテナからもsleepプロセスが見えなくなります:
アプリケーションコンテナを監視するためのサイドカー・コンテナは、役に立たなくなります。
同様に、ホストレベルのプロセス名前空間を使用して他のコンテナを監視する監視ツールも、攻撃者が監視ポッドにアクセスすると騙されてしまいます。
Falcoによる悪意のあるプロセスの解明
この攻撃の仕組みはわかりました。しかし、どのようにしてそれを検出することができるでしょうか?
Falcoはシステムイベントを利用して、/etc/ld.so.preloadファイルが変更されたことを検出し、疑わしいイベントとしてフラグを立てます。
Falcoには「write below etc」というルールが標準で用意されていますが、このシナリオに対応するために新しいルールを作成することができます。
- rule: Modify ld.so.preload desc: Detect an attempt to write to file /etc/ld.so.preload condition: open_write and fd.name=/etc/ld.so.preload output: "File below /etc/ld.so/preload opened for writing (user=%user.name command=%proc.cmdline parent=%proc.pname pcmdline=%proc.pcmdline file=%fd.name program=%proc.name gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4] container_id=%container.id image=%container.image.repository)" priority: WARNING tags: [filesystem, mitre_persistence, mitre_defense_evasion]
そして、/etc/ld.so.preloadを修正しようとする試みがあると、次のようなFalcoアラートが発生します。
00:44:41.298026834: Warning File below /etc/ld.so/preload opened for writing (user=root command=bash parent=containerd-shim pcmdline=containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/43fecfe06f03b0bd833a2ea0e28d59008d7d10ab0643503480685d04817471be -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc file=/etc/ld.so.preload program=bash gparent=containerd ggparent=systemd gggparent=<NA> container_id=43fecfe06f03 image=kaizheh/ubuntu)
Gianlucaの記事でも指摘されていますが、悪意のあるプロセスが隠されていても、悪意のあるプロセスからの活動は隠せません。Syscallsは決して嘘をつきません。
その他のFalcoルールは、典型的なマルウェア(クリプトマイナーの動作を含む)を検出するために使用できます。
- Container Drift Detected (chmod):マルウェアが着地して実行の準備をしているかどうかを検出します。
- Outbound Connection to C2 Servers:コマンド&コントロールサーバーへのネットワーク接続を検出します。
- Detect outbound connections to common miner pool ports:暗号化マイニングプールへのアウトバウンド接続を検出します。
- Write below etc:ほとんどのマルウェアは、cronjobによって持続性を確立します。
その他の緩和策
検出メカニズムとしてFalcoを使用する以外にも、プリロードファイルの改ざんを緩和する方法がいくつかあります。そのほとんどは、コンテナイメージのセキュリティに関するベストプラクティスと考えられています。
読み取り専用のルートファイルシステムを有効にする:読み取り専用のルートファイルシステムを有効にすると、/etc などのルートファイルシステムへの書き込みが一切禁止されます。
非rootユーザーとして実行する:アプリケーションの脆弱性を利用して攻撃者がモニタリングポッドにアクセスした場合、攻撃者は非 root 権限しか取得できません。この場合、/etc/ld.so.preload ファイルを変更することはできません。
最小限のベースイメージの使用:コンテナのファイルシステムでは、ダイナミックローディングをサポートするために、共有ライブラリを読み込むためのlibdl.soなどのライブラリが必要です。とはいえ、マイクロサービスの場合、必要なライブラリは開発段階でわかることもあるので、ダイナミックローディングの共有ライブラリをサポートしないようにイメージを制限するのは良い方法です。
AppArmorプロファイルを適用する:AppArmorプロファイルは、その適切な細やかな制御により、ファイルの改ざんを防ぐのに最適です。以下のサンプルプロファイルを適用すると、/etcフォルダが改ざんされないように保護できます。
profile k8s-apparmor-deny-write-below-etc flags=(attach_disconnected, mediate_deleted) { # allow to be terminated by runc signal (receive) peer=unconfined, file, deny /etc/** w, }
etcフォルダ以下のファイルの改ざんはすべてブロックされます:
まとめ
libprocesshiderのような防御回避技術を発見するためには、優れたファイル整合性監視(FIM)ソリューションが必要です。
しかし、コンテナ化された環境では、FIMがより困難になります。
クラスター内の各コンテナは、それぞれ独自のファイルシステムを持っています。読み取り専用のルートファイルシステムが有効になっていないと、重要なファイルが改ざんされてしまいます。また、コンテナは行ったり来たりして、たいていは短命です。このような刹那的な環境では、ファイルの変更を検出することはさらに難しくなります。
Sysdigは、カーネルレベルのシステムコールにクラウドネイティブのコンテキストを付加することで、クラウドネイティブ環境を深く理解することができます。詳しくはFalcoとSysdig Secureをご覧ください。
Falcoの詳細を知りたい方は:
Falco.orgで始める
GitHubでFalcoプロジェクトをチェックする
Falco コミュニティに参加する
Falco Slackでメンテナに会う
ツイッターで @falco_org をフォローする