ルール一本を六回書き直す
目標
合成したプロセス実行記録24件を対象に、Sigmaルールを6本書きます。毎回実際に実行して、何件当てて何件見逃したかを数字で確認しながら、ルールを絞り込んでいきます。
なぜ重要なのか
検知ルールが現場で死ぬ理由は、たいてい文法ではありません。広すぎて誤検知がたまると、人がアラートを見なくなり、狭すぎてアドレスが1つ変わっただけで、何も捕まえられなくなります。その間を見つける唯一の方法は、データに実行してみることです。ルールを書いて、目で読んで「合っているだろう」とリリースする習慣が、検知ギャップの最大の原因です。
そのため、このラボは、ルールを書くたびにインシデント記録に適用してみます。当たった件数と、誤って捕まえた件数が数字で出れば、「この条件を入れると、何が一緒に入ってくるのか」を、勘ではなく観察で知ることになります。これが、検知エンジニアリングが、セキュリティ業務の中でも特にソフトウェア工学に似ている理由です。ルールはコードで、インシデント記録はテストデータです。
ステップ
- インシデント記録を親プロセスごとに数えて、
/root/sigma/01-parents.txtに保存してください。 - cronが親である実行をすべて捕まえる最小のルールを、
/root/sigma/02-cron-any.ymlに書いてください。 - そのうち、コマンドラインに
curlやwgetがあるものだけを捕まえるように絞り込み、/root/sigma/03-outbound.ymlに書いてください。セレクションは1つだけ使います。 - 社内リポジトリ(
repo.corp.internal)に送る正常なバックアップを、フィルターで取り除いて、/root/sigma/04-filtered.ymlに書いてください。conditionにnotを使います。 - アドレスを書かず、振る舞いで捕まえるルールを、
/root/sigma/05-behaviour.ymlに書いてください。取得してきたものをシェルにパイプで渡すものと、シェルを付けて外部に接続するものの、2つです。|reモディファイアを必ず使います。 - スケジュールされたジョブの外部リクエストとリバースシェルを、
selection_で始まる2つのセレクションに分け、1 of selection_*でまとめて、/root/sigma/06-combined.ymlに書いてください。社内リポジトリのフィルターは、共通でかけます。 - ステップ6のルールに、リリース用のメタデータを埋めて、
/root/sigma/07-release.ymlに書いてください。 - ステップ3とステップ7のルールの判定結果を、
/root/sigma/08-review.mdに数字と判断で書いてください。
参考
- データは
/opt/lab/fixtures/detection/proc_events.jsonlで、1行が1件のインシデントです。jq -r '.ParentImage' <파일>のように読んでください(プレースホルダーはファイル名です)。 - ルールを実行してみるツールが一緒にあります。
python3 /opt/lab/fixtures/detection/sigma_eval.py --lint <규칙>は構造だけを見て、--ids <규칙> <사건파일>は、一致したインシデントのidを出します(プレースホルダーはルールとインシデントファイルです)。--helpで、サポート範囲を確認してください。 - よくある間違い:
|containsで短い単語を探してしまう場合です。ncはrsyncの中にも入っています。 - よくある間違い: セレクションの中で、値をリストで与えたものをANDと勘違いしてしまう場合です。1つのフィールドの中のリストはORです。
- ステップ6–7のグラウンドトゥルースセット(実際の侵害4件)は、ステップ5とステップ3で見たものを合わせると見えます。
まずデータをざっと見る
/opt/lab/fixtures/detection/proc_events.jsonlを、親プロセス(ParentImage)ごとに数えて保存してください(保存先: /root/sigma/01-parents.txt)。1行に、パス1つとその件数が一緒にあればよいです。
jq -r '.ParentImage'で値だけを取り出してから、sort | uniq -cで数えます。ルールを書く前に、何がどれだけあるかを見る習慣をつけるステップです。
最も小さなルール
cronが親である実行をすべて捕まえる最小のSigmaルールを書いてください(保存先: /root/sigma/02-cron-any.yml)。必須項目(title・logsource・detection)だけあればかまいません。
detectionの下に、名前を付けたセレクションを1つ置き、conditionにその名前を書きます。インシデントのParentImageの値が何かは、ステップ1の結果にあります。
外部に出たものだけ
ステップ2のルールを絞り込んで、コマンドラインにcurlかwgetがあるものだけを捕まえるように書いてください(保存先: /root/sigma/03-outbound.yml)。detectionのセレクションは1つだけ置きます。
1つのセレクションの中でフィールドを2つ書くとAND、1つのフィールドに値をリストで与えるとORです。部分文字列を見るには、フィールド名のあとにモディファイアを付けます。
正常なものを1つ取り除く
ステップ3の結果から、社内リポジトリ(repo.corp.internal)に送る正常なバックアップを除いて書いてください(保存先: /root/sigma/04-filtered.yml)。セレクションを絞り込まず、フィルターを別に置いて、conditionにnotでつけます。
detectionの下に、セレクションを2つ置いて、名前を変えて付けます。conditionはselection and not <필터이름>の形になります(プレースホルダーはフィルター名です)。
アドレスではなく振る舞いで
取得してきたものをシェルにパイプで渡すものと、シェルを付けて外部に接続するものの2つを捕まえるルールを書いてください(保存先: /root/sigma/05-behaviour.yml)。|reモディファイアを使い、ルールの中に宛先のアドレスを書かないでください。
正規表現モディファイアは、필드|re: '<정규식>'の形です(プレースホルダーはフィールドと正規表現です)。パイプ文字や-e /bin/shのような断片が何を意味するのか、ステップ1で見たコマンドラインをもう一度読んでみてください。2つの系統は、セレクション2つに分けて、1 ofでまとめてもかまいません。
2つの系統を1つに
スケジュールされたジョブの外部リクエストとリバースシェルを、selection_で始まるセレクション2つに分け、1 of selection_*でまとめて書いてください(保存先: /root/sigma/06-combined.yml)。社内リポジトリのフィルターは、2つの系統の両方にかけます。
1 of selection_*は、名前がそのパターンに合うセレクションをORでまとめます。フィルターは、そのまとまり全体にand notで付けます。目標は実際の侵害4件です。
リリースできるルールに
ステップ6のルールに、id・status・description・author・date・references・falsepositives・level・tagsを埋めて書いてください(保存先: /root/sigma/07-release.yml)。捕まえるインシデントは、ステップ6と同じである必要があります。
idはUUIDの形である必要があります(python3 -c 'import uuid;print(uuid.uuid4())')。statusは、まだ本番のデータで実行してみていないルールの値を選び、levelは、リバースシェルまで捕まえるルールに合わせて決めてください。tagsには、attack.で始まる項目が1つ以上必要です。
数字で残す
ステップ3とステップ7のルールを、グラウンドトゥルースセット(実際の侵害4件)と照らし合わせて書いてください(保存先: /root/sigma/08-review.md)。ルールごとに、1行にファイル名とmatched=・tp=・fp=・fn=の4つの値を書き、## 판단(韓国語の見出しで、「判断」という意味です)の節に、120文字以上で、何をリリースするかとその理由を書いてください。
値は、--idsの結果とグラウンドトゥルースセットを比べて、自分で数えればよいです。グラウンドトゥルースセットは、ステップ5で見つけた2件と、ステップ3で見つけたもののうち、社内リポジトリではないものを合わせたものです。