Falcoホワイトリスト型カスタムルールの検討

はじめに

FalcoはLinux OS向けのランタイムセキュリティツールであり、システム上での異常なアクティビティや潜在的なセキュリティリスクをリアルタイムで検知し、警告するように設計されています。 Kubernetesとシームレスに統合できるため、クラウド上のWebアプリケーションのセキュリティモニタリングに非常に適しています。 前回の記事で、Falcoの概要、デプロイ方法、実際の検出例について詳しく紹介しました。 Falcoプロジェクトでは、異常検知のためのデフォルトルールセットが提供されており、開発者によってメンテナンスされています。ユーザーは必要に応じてこれらのルールを選択し、利用することが可能です。

Falcoのデフォルトルールセットは強力ですが、前回の記事で指摘した通り、未知のセキュリティリスクを検知できないケースが存在します。 攻撃者は既知の攻撃パターンに加え、常に新たな方法で脆弱性を探り、侵入を試みます。 この記事では、そうした脅威に対抗するために、ホワイトリスト型カスタムルールの活用を検討しましたので、それについて紹介したいと思います。

前回記事のおさらい

Falcoの仕組みと特徴

FalcoはLinux OS向けのランタイムセキュリティツールで、 システム上で発生する異常なアクティビティや潜在的なセキュリティリスクをリアルタイムで検知し、警告するよう設計されています。 このツールの核となるのは、カーネルレベルでのシステムコールを監視するモジュール、異常なイベントを検知するための監視ルールに基づくエージェント、そして外部に通知を行うFalcosidekickエージェントです。

Falcoシステム構成
Falcoシステム構成

Falcoの監視ルールは、開発元が提供するデフォルトセットを利用することも、組織のセキュリティポリシーに合わせてカスタマイズすることも可能です。

Falcoインシデント検知の例

前回の記事では、シンプルなnginx podを起動し、以下の3つの操作を実施した際のFalcoによる検知能力を検証しました。検知結果はWeb UIで確認し、さらにjsonログファイルをダウンロードして詳細に分析しました。

試したケース

  1. bashシェル実行: kubectl execコマンドでコンテナに接続し、bashシェルを起動します。
  2. viインストール実行: kubectl execコマンドでコンテナに接続し、viエディタをインストールして実行します。
  3. 外部ssh接続: kubectl execコマンドでコンテナに接続し、443ポートを介して外部サーバにssh接続します。

それぞれのログ検出結果

  1. bashシェル実行: Terminal shell in containerルールにマッチし、対応するログが生成されました。
  2. viインストール実行: Drop and execute new binary in containerルールにマッチし、対応するログが生成されました。
  3. 外部ssh接続: Disallowed SSH Connection Non Standard Portルールにマッチし、対応するログが生成されました。

これらの結果は、Falcoが予定されたイベントを正確に検知できる能力を有していることを示しています。しかし、侵入者が工夫を凝らすことで検知を回避する可能性があることも明らかになりました。

なぜホワイトリスト型カスタムルールなのか

問題点

前述の例で、Falcoが特定の異常行動を検知できることを示しましたが、侵入者がFalcoの監視を回避するために工夫を凝らす可能性があります。 ここでは、検証した3つのケースでの潜在的な回避方法と、それに対抗するためのホワイトリスト型カスタムルールの必要性について考察します。

  • 1. bashシェル実行:

bashコマンドを/dev/hogeにコピーし、それを実行することで、Terminal shell in containerルールの検知を回避することが可能です。このルールは特定のバイナリ名(ash, bash, csh, ksh, sh, tcsh, zsh, dash)を検出対象としていますが、ファイル名が変更されると検知できなくなります。

    
root@v130:~# kubectl exec -it nginx -- cp /bin/bash /dev/hoge
root@v130:~# kubectl exec -it nginx -- /dev/hoge
root@nginx:/# 
exit
    
  
  • 2. viインストール実行:

viを/dev/viにコピーし、実行することで、Drop and execute new binary in containerルールの検知を回避できます。このルールはoverlayfsの最上位層に新規作成されたファイルの実行を検知しますが、/devディレクトリはtmpfsがマウントされており、検知条件から外れてしまいます。

    
root@nginx:/# apt install vim
root@nginx:/# cp /usr/bin/vi /dev/vi
root@nginx:/# /dev/vi test
    
  
  • 3. 外部ssh接続:
  • 実行ファイル名を偽装したり、標準以外のポートを使用してSSH接続することで、Disallowed SSH Connection Non Standard Portルールを回避できます。このルールは特定のポート(80, 8080, 88, 443, 8443, 53, 4444)への接続を検知するため、条件に合致しなければ検知されません。

    これらのケースは、Falcoのデフォルトルールセットでは検知しきれないシナリオを示しています。攻撃者はFalcoのルールを分析し、これらを回避する新たな手法を常に模索しています。

    Falcoのデフォルトルールセットは、セキュリティ脆弱性に関する広範な情報を提供するMITRE ATT&CK データベースを基に作られており、既知のリスクを広くカバーしています。 しかし、実行バイナリファイル名、ファイルの置き場所、ポート番号などを検知条件にしたルールも多く、常に新たな攻撃パターンにさらされ、どうしても監視ルールの作成がいたちごっこになってしまいます。

    そこで、すべてのアクティビティを網羅できるようなルールファイルを作成し、出力されたログを元に無害なものを逐次、検知の除外条件に加えていく、ホワイトリスト型のルールセットが作成可能か検討してみました。

    ホワイトリスト型カスタムルールの考え方

    ホワイトリスト型カスタムルールでは、システムで許可されるアクティビティのみを明確に定義し、それ以外の全てを潜在的な脅威として扱います。 このアプローチにより、未知の攻撃手法に対しても、もれなく対応することが可能になります。 以下では、この考え方を具体的なルール作成の例を通じて説明します。

    Falcoで検出可能なイベントの種類は大まかに次の三種類に分類できます。

    1. TCP、UDPなどのネットワークに関するイベント
    2. コマンド実行などプロセス生成に関するイベント
    3. ファイルの改変、新規作成などの、ファイル操作に関するイベント

    それぞれについて、ホワイトリスト型カスタムルールを考えてみました。

    ネットワーク

    例えば、80、443ポートを除くすべてのTCPのインバウンドコネクションを検出するカスタムルールは次のようなものになります。

        
        - macro: inbound
          condition: >
            (((evt.type in (accept,listen) and evt.dir=<)) or
            (fd.typechar = 4 or fd.typechar = 6) and
            (fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8") and (evt.rawres >= 0 or evt.res = EINPROGRESS))
    
    
    - list: expected_tcp_ports
      items: [80, 443]
    
    - macro: expected_tcp_traffic
      condition: fd.port in (expected_tcp_ports)
    
    - rule: Inbound TCP Traffic
      condition: inbound and fd.l4proto=tcp and not expected_tcp_traffic
      output: ...
      priority: WARNING
      tags: [catch_all, inbound, container]
        
      

    macro: inboundでは、インバウンドのtcpコネクションに関する条件を定義しています。

    macro: expected_tcp_trafficで除外条件を定義しています。除外条件を追加する場合には、ここに追加します。

    rule: Inbound TCP Trafficがルール本体で、condition:が検出条件、output:には検出条件がマッチした場合の出力ログをテンプレート形式で定義します。 not expected_tcp_trafficとあるように、マクロで定義した条件の頭にnotをつけて、除外条件としていることがポイントです。

    プロセス生成

    全てのコマンド実行、プロセス生成を検出するルールは次の様になります。

        
        - macro: spawned_process
          condition: (evt.type in (execve, execveat) and evt.dir=<)
    
    
    - macro: proc_trusted
      condition: (...)
    
    - rule: Process Creation
      condition: spawned_process and not proc_trusted
      output: ...
      priority: WARNING
      tags: [catch_all, process]
        
      

    macro: spawned_processはプロセス生成イベントをキャッチするためのマクロです。

    macro: proc_trustedに信頼するプロセス、すなわち除外条件の定義を追加していきます。

    rule: Process Creationがルール本体です。信頼するプロセス以外のすべてのプロセス生成イベントに対し、output:に定義したログを出力します。 not proc_trustedとあるように、マクロで定義した条件の頭にnotをつけて、除外条件としていることがポイントです。

    ファイル操作

    ファイル操作の内、書き込みモードでのファイルオープンを全て検知するルールは次の様になります。

        
        - macro: open_write
          condition: (evt.type in (open,openat,openat2) and evt.is_open_write=true and fd.typechar='f' and fd.num>=0)
    
    
    - macro: file_write_exempt
      condition: (...)
    
    - rule: Unexpected File Write
      condition: open_write and not file_write_exempt
      output: ...
      priority: WARNING
      tags: [catch_all, file_write]
        
      

    macro: open_write は、書き込みモードでのファイルオープンをキャッチするためのマクロです。

    macro: file_write_exemptには、ログファイルへの書き込みなど、通常のアクティビティで想定される条件を追加していきます。

    rule: Unexpected File Writeがルール本体です。condition:にマッチしたイベントに対してoutput:に定義された形式でログ出力することを設定しています。 not file_write_exemptとあるように、マクロで定義した条件の頭にnotをつけて、除外条件としていることがポイントです。

    以上で、TCPのインバウンドコネクション、プロセス生成、書き込みモードのファイルオープンを検知するルールを例に、キャッチオール形式でルールを作成し、除外条件をホワイトリストとして追加していくやり方を説明しました。

    実際には、TCPのアウトバウンドコネクション、UDPインバウンド・アウトバンド通信、ファイル操作に関しては、renameやdelete、symlink、hardlinkに関する条件などにも対応する必要があり、上に掲げた例の他にいくつかルールを作成する必要があります。 しかし、おおまかな考え方については上記で説明した通りになります。

    カスタムルールの書き方、適用方法

    Falcoでカスタムルールを作成し適用する方法を簡潔に説明します。

    カスタムルールの書き方

    Falcoをhelmでインストールする場合には、helm chartのカスタマイズファイルにカスタムルールを記述することができます。

    custom-rules.yaml

        
    customRules:
      custom-rules.yaml: |-
        - rule: Example rule 1
          desc: ...
          ...
        - rule: Example rule 2
          ...
        
      
    ここで注意点として、最初の"customRules:"と2行目の"custom-rules.yaml: |-"が必要で、ルール自体のインデントも二段分深くする必要があるようです。(参考)

    helm chartのカスタマイズファイルは複数読み込むことができますので、カスタムルールが長くなる場合には、それぞれ専用のカスタマイズファイルに書くと良いでしょう。 Falcoのカスタムルールをさらに別のcustom-rules2.yamlファイルから読み込むには、同様のフォーマットで記述します。

    custom-rules2.yaml

        
    customRules:
      custom-rules2.yaml: |-
        - rule: Example rule 3
          desc: ...
          ...
        - rule: Example rule 4
          ...
        
      

    ここでの注意点としては、"custom-rules2.yaml: |-""- rule: Example rule 3"等の行が同じファイルの別部分や他のファイルと重複しないようにする必要があります。

    カスタムルールのデプロイ方法

    デフォルトルールセットに加えて、カスタムルールを利用する場合には、次のコマンドラインでhelmを実行します。

        
    helm install falco falcosecurity/falco --namespace falco --create-namespace \
    -f myvalues.yaml -f custom-rules1.yaml -f custom-rules2.yaml
        
      

    デフォルトルールセットを利用せずに、カスタムルールのみを利用したい場合には、myvalues.yamlファイルの中身を次の様にし、上記のコマンドでhelmを実行します。(参考)

        
    falcoctl:
      config:
        artifact:
          install:
            enabled: false
          follow:
            enabled: false
    
    

    falco: rules_file: - /etc/falco/falco_rules.local.yaml - /etc/falco/rules.d

    カスタムルールは改ざんされないのか

    Helmにより適用されたFalcoのルールはKubernetesのConfigMapに格納されます。この設定ファイルへの不正な改ざんを試みる行為は、Falcoによって検出可能です。攻撃者がFalcoのルールファイルを書き換えるためには、コンテナ内からKubernetesのAPIを介してConfigMapにアクセスする必要があります。このようなアクセスは、Falcoの監視ルールによって検知され警告されます。

    ホワイトリスト形式カスタムルールの実際

    それでは実際のホワイトリスト形式カスタムルールはどのように作成すれば良いでしょうか。ここではインバウンドのTCPコネクションのケースを例に手順を説明します。

    Kubernetesのコントロールプレーンが使用するポートはこのページにまとまっています。これを元に初期バージョンのカスタムルールファイルを作成します。

    1. いったんある程度のものができたら、Falcoに適用します。
    2. 検出されるログを収集、解析します。
    3. さらに、除外ルールを追加していきます。

    検知不要なイベントに対するログが出なくなるまで、ステップ2と3を繰り返します。

    初期バージョンの作成

    カスタムルールの初期バージョンとして、Kubernetesのコントロールプレーンのトラフィックを除外するルールファイルを作成しました。

    custom-rules-inboud-tcp0.yaml

        
    customRules:
      custom-rules-inboud-tcp0.yaml: |-
    
    
    - macro: container
      condition: (container.id != host)
    
    - macro: inbound_tcp
      condition: >
        ((evt.type in (accept,accept4,listen) and evt.dir=<) and
        (fd.typechar = 4 or fd.typechar = 6) and
        (fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8") and
        (evt.rawres >= 0 or evt.res = EINPROGRESS))
    
    - macro: k8s_kubelet
      condition: >
        proc.name = "kubelet" and (
        (evt.type in (accept4) and fd.sport = 10250)
        )
    
    - macro: k8s_apiserver
      condition: >
        k8s.ns.name = "kube-system" and
        k8s.pod.name startswith kube-apiserver- and (
        (evt.type = accept4 and fd.sport = 6443)
        )
    
    - macro: k8s_etcd
      condition: >
        k8s.ns.name = "kube-system" and
        k8s.pod.name startswith etcd- and (
        (evt.type = accept4 and fd.sport = 2380)
        )
    
    - macro: expected_tcp_traffic
      condition: (
        k8s_kubelet or
        k8s_apiserver or
        k8s_etcd
        )
    
    - rule: TCP Inbound
      desc: Detect unexpected Inbound TCP connection
      enabled: true
      condition: inbound_tcp and fd.l4proto=tcp and not expected_tcp_traffic
      output: >
        Unexpected Inbound TCP Traffic
        (evt_type=%evt.type host=%evt.hostname
        connection=%fd.name fd_proto=%fd.l4proto
        cIP=%fd.cip sIP=%fd.sip cPort=%fd.cport sPort=%fd.sport
        process=%proc.name command=%proc.cmdline proc_exepath=%proc.exepath parent=%proc.pname
        user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid terminal=%proc.tty
        exe_flags=%evt.arg.flags %container.info)
      priority: WARNING
      tags: [catch_all, tcp_inbound, network, ktaka]
        
      

    Falcoのデプロイ

    以下のコマンドでFalcoをデプロイします。

        
    helm install falco falcosecurity/falco --namespace falco --create-namespace \
    -f myvalues.yaml -f custom-rules-inboud-tcp0.yaml
        
      

    ログ収集と解析

    Falcoのデプロイ後しばらくすると、WebUIに大量のログが記録されます。全体を見通すために、json形式のログファイルをExportし、jqを駆使してどのようなログが記録されているのか解析します。

    次の例は、一件の検出イベントに対するjsonログです。

        
    ktaka@bulls:~/Downloads$ jq .[2] falco_events.json 
    {
      "uuid": "0effbce1-6012-4e16-84ee-1f7ec2012a1a",
      "output": "04:21:50.762917673: Warning Unexpected Inbound TCP Traffic (evt_type=accept4 host=v136 connection=10.244.5.1:45754->10.244.5.226:2801 fd_proto=tcp cIP=10.244.5.1 sIP=10.244.5.226 cPort=45754 sPort=2801 process=falcosidekick command=falcosidekick proc_exepath=/app/falcosidekick parent=containerd-shim user=falcosidekick user_uid=1234 user_loginuid=-1 terminal=0 exe_flags= container_id=d38b28bb9ab9 container_image=docker.io/falcosecurity/falcosidekick container_image_tag=2.28.0 container_name=falcosidekick k8s_ns=falco k8s_pod_name=falco-falcosidekick-749d77d5c7-22jph)",
      "priority": "Warning",
      "rule": "TCP Inbound",
      "time": "2023-12-22T04:21:50.762917673Z",
      "source": "syscall",
      "output_fields": {
        "container.id": "d38b28bb9ab9",
        "container.image.repository": "docker.io/falcosecurity/falcosidekick",
        "container.image.tag": "2.28.0",
        "container.name": "falcosidekick",
        "evt.arg.flags": null,
        "evt.hostname": "v136",
        "evt.time": 1703218910762917600,
        "evt.type": "accept4",
        "fd.cip": "10.244.5.1",
        "fd.cport": 45754,
        "fd.l4proto": "tcp",
        "fd.name": "10.244.5.1:45754->10.244.5.226:2801",
        "fd.sip": "10.244.5.226",
        "fd.sport": 2801,
        "k8s.ns.name": "falco",
        "k8s.pod.name": "falco-falcosidekick-749d77d5c7-22jph",
        "proc.cmdline": "falcosidekick",
        "proc.exepath": "/app/falcosidekick",
        "proc.name": "falcosidekick",
        "proc.pname": "containerd-shim",
        "proc.tty": 0,
        "user.loginuid": -1,
        "user.name": "falcosidekick",
        "user.uid": 1234
      },
      "hostname": "v136",
      "tags": [
        "catch_all",
        "ktaka",
        "network",
    
    
    "tcp_inbound"
    

    ]

    }

    今回の例ではこのようなログが、1分25秒間の間に8308件と大量に記録されていました。

        
    $ jq .[].time falco_events.json |wc -l 
    8308
    $ jq .[0].time falco_events.json
    "2023-12-22T04:21:51.067533099Z"
    $ jq .[8307].time falco_events.json
    "2023-12-22T04:20:26.389131383Z"
        
      

    このログを、jqを使って見やすい形式に整形します。 今回の例では、元のログファイルから必要そうなフィールドのみを拾い出し、重複行を一行にまとめることで見やすい形になりました。

        
    $ jq '.[].output_fields' falco_events.json|jq  -r '[."fd.sport", ."evt.type", ."k8s.pod.name"]|@csv' | sort  -u -t, -k 1 
    2801,"accept4","falco-falcosidekick-749d77d5c7-22jph"
    2801,"accept4","falco-falcosidekick-749d77d5c7-9g2lq"
    2802,"accept4","falco-falcosidekick-ui-6885c6cffd-dslgc"
    2802,"accept4","falco-falcosidekick-ui-6885c6cffd-hsb4j"
    6379,"accept","falco-falcosidekick-ui-redis-0"
    8080,"accept4","coredns-5dd5756b68-48lkl"
    8080,"accept4","coredns-5dd5756b68-759kd"
    8181,"accept4","coredns-5dd5756b68-48lkl"
    8181,"accept4","coredns-5dd5756b68-759kd"
    8765,"accept","falco-6tcd5"
    8765,"accept","falco-ch66w"
    8765,"accept","falco-dbcf4"
    8765,"accept","falco-dqphh"
    8765,"accept","falco-wpt8n"
    8765,"accept","falco-wt662"
        
      

    この内容をみると、どうやらログ出力の原因となった通信は、falco関連のプロセスと、corednsに起因するもので、いずれも無害なものと判断して良さそうです。

    そこで、この結果を元に以下のようなマクロを作成しました。 これらをホワイトリストとして除外条件に加ることで、1分25秒間の間に8308件出力されていたfalcoの関連プロセス間通信によるの大量のログは全く出力されなくなります。

        
        - macro: k8s_coredns
          condition: >
            k8s.ns.name = "kube-system" and
            k8s.pod.name startswith coredns- and ( 
            evt.type = accept4 and fd.sport in (8080,8181)
            )
    
    
    - macro: falco
      condition: >
        k8s.ns.name = "falco" and
        k8s.pod.name startswith falco- and (
        (evt.type = accept and fd.sport = 8765)
        )
    
    - macro: falco_falcosidekick
      condition: >
        k8s.ns.name = "falco" and
        k8s.pod.name startswith falco-falcosidekick- and (
        (evt.type = accept4 and fd.sport = 2801)
        )
    
    - macro: falco_falcosidekick_ui
      condition: >
        k8s.ns.name = "falco" and
        k8s.pod.name startswith falco-falcosidekick-ui- and (
        (evt.type = accept4 and fd.sport = 2802)
        )
    
    - macro: falco_redis
      condition: >
        k8s.ns.name = "falco" and
        k8s.pod.name startswith falco-falcosidekick-ui-redis- and (
        (evt.type = accept and fd.sport = 6379)
        )
        
      

    また、しばらく運用していると違う条件で通信イベントのログが出力されますが、同じ要領で順次除外条件をホワイトリストに加えていきます。

    最終的なカスタムルールファイル

    最終的なカスタムルールファイルは次の様になりました。 このルールファイルでFalcoを起動し直すと、無害な通信に起因するログが出力されなくなりました。

    custom-rules-inboud-tcp1.yaml

        
    customRules:
      custom-rules-inboud-tcp0.yaml: |-
    
    
    - macro: container
      condition: (container.id != host)
    
    - macro: inbound_tcp
      condition: >
        ((evt.type in (accept,accept4,listen) and evt.dir=<) and
        (fd.typechar = 4 or fd.typechar = 6) and
        (fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8") and
        (evt.rawres >= 0 or evt.res = EINPROGRESS))
    
    - macro: k8s_kubelet
      condition: >
        proc.name = "kubelet" and (
        (evt.type in (accept4) and fd.sport = 10250)
        )
    
    - macro: k8s_apiserver
      condition: >
        k8s.ns.name = "kube-system" and
        k8s.pod.name startswith kube-apiserver- and (
        (evt.type = accept4 and fd.sport = 6443)
        )
    
    - macro: k8s_etcd
      condition: >
        k8s.ns.name = "kube-system" and
        k8s.pod.name startswith etcd- and (
        (evt.type = accept4 and fd.sport = 2380)
        )
    
    - macro: k8s_coredns
      condition: >
        k8s.ns.name = "kube-system" and
        k8s.pod.name startswith coredns- and ( 
        evt.type = accept4 and fd.sport in (8080,8181)
        )
    
    - macro: falco
      condition: >
        k8s.ns.name = "falco" and
        k8s.pod.name startswith falco- and (
        (evt.type = accept and fd.sport = 8765)
        )
    
    - macro: falco_falcosidekick
      condition: >
        k8s.ns.name = "falco" and
        k8s.pod.name startswith falco-falcosidekick- and (
        (evt.type = accept4 and fd.sport = 2801)
        )
    
    - macro: falco_falcosidekick_ui
      condition: >
        k8s.ns.name = "falco" and
        k8s.pod.name startswith falco-falcosidekick-ui- and (
        (evt.type = accept4 and fd.sport = 2802)
        )
    
    - macro: falco_redis
      condition: >
        k8s.ns.name = "falco" and
        k8s.pod.name startswith falco-falcosidekick-ui-redis- and (
        (evt.type = accept and fd.sport = 6379)
        )
    
    - macro: expected_tcp_traffic
      condition: (
        k8s_kubelet or
        k8s_apiserver or
        k8s_etcd or
        k8s_coredns or
        falco or
        falco_falcosidekick or
        falco_falcosidekick_ui or
        falco_redis
        )
    
    - rule: TCP Inbound
      desc: Detect unexpected Inbound TCP connection
      enabled: true
      condition: inbound_tcp and fd.l4proto=tcp and not expected_tcp_traffic
      output: >
        Unexpected Inbound TCP Traffic
        (evt_type=%evt.type host=%evt.hostname
        connection=%fd.name fd_proto=%fd.l4proto
        cIP=%fd.cip sIP=%fd.sip cPort=%fd.cport sPort=%fd.sport
        process=%proc.name command=%proc.cmdline proc_exepath=%proc.exepath parent=%proc.pname
        user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid terminal=%proc.tty
        exe_flags=%evt.arg.flags %container.info)
      priority: WARNING
      tags: [catch_all, tcp_inbound, network, ktaka]
        
      

    その他のカスタムルール

    前節でTCPのインバウンド通信を例に、ホワイトリスト形式のカスタムルールの作成方法について説明しました。

    その他に、作成すべきカスタムルールとしては、以下のようなものが考えられます。

    1. ネットワーク通信の残り、すなわち、TCPアウトバウンド、UDPインバウンド、UDPアウトバウンド通信に関するもの
    2. プロセス生成に関するもの
    3. ファイル操作に関するもの

    1.のネットワーク通信の残りについては、前節と同じようにホワイトリストを作成していけば問題ありません。 2.、3.に関しては、基本的に同じ手順でホワイトリストの作成ができますが、ルールの記述方法に関して、難しさがあることがわかりました。

    プロセス生成

    プロセス生成のホワイトリストに関しては、ルールの記述が比較的容易なものと、どう書くべきか難しい部分が存在しています。

    簡単な部分の例

    簡単な部分は、kubernetesのコントロールプレーンなど、目的がはっきりしているプロセス達です。以下は、大量に出力されるログから作成した、除外条件マクロの一部です。

        
        - macro: proc_kube_proxy
          condition: k8s.ns.name = kube-system and k8s.pod.name startswith kube-proxy- and (
            proc.exepath = /usr/sbin/xtables-nft-multi
            )
        
      

    ログファイルの中に/usr/sbin/xtables-nft-multiの起動に関するログが大量にありました。その中でkube-proxy-xxxxという名前のポッド内で起動されるものであれば、問題ないであろうと判断し、このマクロを作成しました。 kube-proxy-xxxxはリナックスカーネルのiptablesを操作してproxyを実現しており、/usr/sbin/xtables-nft-multiはiptablesコマンドのバイナリ実体であるからです。

        
        - macro: proc_falco
          condition: k8s.ns.name = falco and k8s.pod.name startswith falco-falcosidekick-ui- and (
            proc.exepath = /app/falcosidekick-ui
            )
        
      

    ログファイルの中に/app/falcosidekick-uiの実行に関するログが大量にありました。これもfalco-falcosidekick-ui-xxxxという名前のポッド内で起動されていたので、問題ないと判断し、このマクロを作成しました。

        
        - macro: proc_kubelet
          condition: container.id = host and proc.pname = kubelet and (
            proc.exepath = /sbin/xtables-nft-multi
            or proc.exepath = /usr/sbin/xtables-nft-multi
            or proc.exepath = /usr/bin/mount
            or proc.exepath = /usr/bin/umount
            )
        
      

    ログファイルの中には、他にもxtables-nft-multimountumountに関するものがありました。その中でも、proc.pname = kubeletすなわち親プロセスがkubeletであるものは問題ないであろうと判断し、このマクロを作成しました。

    難しい部分

    一方で、プロセス起動に関するログとして、cpmvrmcmpdirnametardateapt-compatdpkgなど、それだけでは一見、問題があるかどうか判断がつかないものも大量に存在していました。

    その多くは、ホストのOS上で、cronやsystemdにより定期的に実行されるスクリプトの中から呼び出されるものでした。

    一例としてcronジョブに関係していそうなログに対して、それを除外するためのマクロを以下に示します。

        
        - macro: proc_cron
          condition: container.id = host and (
            (proc.name in (apt-compat, aptitude, dpkg, logrotate, sysstat, debian-sa1) and (proc.aname[2] = cron or proc.aname[3] = cron or proc.aname[4] = cron))     
            or (proc.name = sh and proc.pname = cron)
            or proc.exepath = /usr/bin/run-parts
            or proc.exepath = /usr/bin/cmp
            or proc.exepath = /usr/bin/basename
            or proc.exepath = /usr/bin/dirname
            or (proc.exepath = /usr/bin/cp and proc.cmdline = "cp -p /var/lib/aptitude/pkgstates aptitude.pkgstates")
            or (proc.exepath = /usr/bin/dash and proc.cmdline = "savelog /bin/savelog -c 7 aptitude.pkgstates")
            or (proc.exepath = /usr/bin/rm and proc.cmdline = "rm -f -- .//aptitude.pkgstates.6 .//aptitude.pkgstates.6.gz")
            or (proc.exepath = /usr/bin/gzip and proc.cmdline = "gzip -f -9 .//aptitude.pkgstates.0")
            )
        
      

    このマクロは、以下の条件にマッチするものです。

    • (apt-compat, aptitude, dpkg, logrotate, sysstat, debian-sa1)については、祖先のいずれかがcronであるもの
    • shは、親プロセスがcronであるもの
    • cmp、basename、dirname、run-partsはそれ自体(書き込みが無いため無害とみなす)
    • cp, dash, rm, gzipについては、コマンドラインが完全一致するもの

    それぞれのログに対してアドホックに条件を書いているので指定の仕方が統一されていないことなどから、今後も検討、改善を続けることが必要だと考えています。

    ファイル操作

    ファイル操作のホワイトリストに関しては、実際にやってみると、稼働中のシステムではたいへん多くのファイル読み書きのイベントが発生していることがわかり、それを整理してホワイトリストを作成するのは大変でした。

    ファイル書き込み操作に関するホワイトリストマクロの一部を以下に紹介します。

        
        - macro: file_trusted_process
          condition: (
            proc.exepath = /usr/sbin/runc
            or proc.exepath = /usr/bin/kubelet
            or proc.exepath = /usr/bin/apt-config
            or proc.exepath = /usr/bin/apt-get
            or proc.exepath = /usr/lib/apt/apt-helper
            or proc.exepath = /usr/bin/dpkg
            or proc.exepath = /usr/local/bin/containerd
            or proc.exepath = /usr/local/bin/containerd-shim-runc-v2
            or proc.exepath = /usr/bin/systemctl
            or proc.exepath = /usr/bin/systemd-tmpfiles
            or proc.exepath = /usr/lib/systemd/systemd
            or proc.exepath = /usr/lib/systemd/systemd-journald
            or proc.exepath = /usr/sbin/logrotate
            or proc.exepath = /usr/lib/sysstat/sadc
            or proc.exepath = /usr/bin/kmod
            or proc.exepath = /usr/sbin/xtables-nft-multi
            or proc.exepath = /usr/bin/dbus-daemon
            or proc.exepath = /usr/bin/udevadm
            or proc.exepath = /usr/bin/umount
            or proc.exepath = /opt/cni/bin/bridge
            or proc.exepath = /opt/cni/bin/flannel
            or proc.exepath = /opt/cni/bin/host-local
            or proc.exepath = /opt/cni/bin/loopback
            or proc.exepath = /usr/bin/cmp
            or proc.exepath = /usr/bin/sar.sysstat
            or (k8s.ns.name = kube-system and (
               (k8s.pod.name startswith etcd- and proc.exepath = /usr/local/bin/etcd)
               or (k8s.pod.name startswith kube-apiserver- and proc.exepath = /usr/local/bin/kube-apiserver)
               or (k8s.pod.name startswith kube-proxy- and proc.exepath = /usr/local/bin/kube-proxy)
               or (k8s.pod.name startswith kube-proxy- and proc.exepath = /usr/sbin/xtables-nft-multi)
               or (k8s.pod.name startswith coredns- and proc.exepath = /coredns)
            ))
            or (k8s.ns.name = falco and (
               (k8s.pod.name startswith falco- and proc.exepath = /usr/bin/falco)
               or (k8s.pod.name startswith falco- and proc.exepath = /usr/bin/falcoctl)
               or (k8s.pod.name startswith falco-falcosidekick- and proc.exepath = /app/falcosidekick)
               or (k8s.pod.name startswith falco-falcosidekick-ui- and proc.exepath = /app/falcosidekick-ui)
               or (k8s.pod.name startswith falco-falcosidekick-ui-redis- and proc.exepath = /opt/redis-stack/bin/redis-server)
            ))
            or (k8s.ns.name = kube-flannel and (
               (k8s.pod.name startswith kube-flannel and proc.exepath = /opt/bin/flanneld)
               or (k8s.pod.name startswith kube-flannel and proc.exepath = /sbin/xtables-nft-multi)
            ))
            )
    
    
    - macro: file_write
      condition: (
        (container.id = host and (
          fd.name in (/dev/null)
          or (proc.exepath = /usr/bin/dash and fd.name in (/run/motd.dynamic.new, /var/lib/apt/daily_lock))
          or (proc.exepath = /usr/bin/bash and fd.name in (/dev/tty, /root/.bash_history))
          or (proc.exepath = /usr/bin/bash and proc.name = sa2 and fd.name pmatch (/var/log/sysstat/))
          or (proc.exepath = /usr/bin/bash and proc.name = sa2 and proc.pname = systemd and fd.name = /usr/lib/sysstat/sa2 and proc.cmdline = "sa2 /usr/lib/sysstat/sa2 -A")
          or (proc.exepath = /usr/sbin/sshd and fd.name in (/dev/null, /dev/ptmx, /dev/pts/0, /dev/tty, /proc/self/oom_score_adj, /var/log/lastlog, /var/log/wtmp, /var/run/utmp))
          or (proc.exepath = /usr/bin/cp and proc.cmdline = "cp -p /var/lib/aptitude/pkgstates aptitude.pkgstates" and fd.name = "/var/backups/aptitude.pkgstates")
          or (proc.exepath = /usr/bin/gzip and proc.cmdline = "gzip -f -9 .//aptitude.pkgstates.0" and fd.name = "/var/backups/aptitude.pkgstates.0.gz")
          or (proc.cmdline = "6 init" and proc.name = 6 and proc.pname = runc and fd.name startswith /proc/self/oom_score_adj)
        ))
        or (k8s.ns.name = falco and (
           (k8s.pod.name startswith falco-falcosidekick-ui-redis- and proc.exepath = /opt/redis-stack/nodejs/bin/node and fd.name startswith /redisinsight/content/)
        ))
        )
    
    - macro: file_write_exempt
      condition: (
        file_write
        or file_trusted_process
        or user_apps_file_write
        )
        
      

    このマクロのポイントは次の通りです。

    • macro: file_trusted_processでは、信頼できるプロセスを定義しています。これらのプロセスによるファイル書き込みは問題ないと判断しました。
    • macro: file_writeでは、dash、bash、cp、gzipなど、有害無害の判断が難しい汎用的なユーティリティプログラムによるファイル書き込みを、コマンドラインの完全一致や、対象ファイル名のディレクトリ名の一致など、より厳しい条件で絞り込んでいます。
    • macro: file_write_exemptは、上記二つを組み合わせたものです。

    汎用的なユーティリティプログラムによるファイル書き込みに関して、安全なものを識別するためにどのような条件が適切なのか、ホワイトリスト全体としての管理のし易さなどに関して、今後の改善が必要であると考えております。

    まとめ

    FalcoはLinux OSに特化したランタイムセキュリティツールで、 システム上で発生する異常なアクティビティや潜在的なセキュリティリスクをリアルタイムで検知し、警告します。 前回の記事では、Falcoプロジェクトで用意されているデフォルトルールセットだけでは、未知の脅威に対して必ずしも万全ではないということを指摘しました。 この課題に対処するため、本記事ではホワイトリスト型のカスタムルールを用いるアプローチを探求しました。

    ホワイトリスト型カスタムルールの導入により、ネットワーク通信、プロセス生成、ファイル操作など、各種イベントに対して全インシデントを検知可能なルールセットを構築することができます。 Falcoの運用中に記録される大量のログデータを分析することで、これらのルールを継続的に調整し、検知の除外条件を追加していくことが可能です。

    実際にやってみると、ネットワーク通信に関しては、比較的容易にルールファイルの作成が可能であることがわかりました。 一方で、プロセス生成やファイル操作に関するルール作成に関しては、ルールの精度を高め、誤検知を減らすためにルールの書き方の細部について改善検討を続けていく必要があることがわかりました。

    Falcoのホワイトリスト型カスタムルールは、未知の脅威に対しても高いレベルの保護を提供する強力なツールです。継続的な監視、分析、およびルールの微調整を通じて、Falcoはクラウド上のWebアプリケーションのセキュリティ強化に重要な役割を果たし続けるでしょう。

このブログについて

KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。

おすすめ

合わせて読みたい

このブログについて

KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。