はじめに
こんにちは、CISO部の兵藤です。日々ZOZOの安全のためにSOC対応を行なっています。
本記事ではサイバー脅威インテリジェンスプラットフォーム「OpenCTI」からMicrosoft Sentinelへの脅威インテリジェンスの取り込みについて紹介します。また、この内容については以下の「Azureで織りなすOpenCTI構築」に続く内容となっています。
目次
背景と概要
以前投稿したように、SOC対応を実施するにあたって各個人で調査収集していた情報を1つのプラットフォームで自動収集、管理し、ゆくゆくはSIEMと連携したいと考えていました。そのプラットフォームとしてOpenCTI(ver 5.11
)を導入しています。
また、ZOZOのSOCでは普段の検知対応においてSIEMを活用しています。具体的にはMicrosoftのSentinelです。この2つを連携し、自動で脅威インテリジェンスを用いた検知の仕組みを構築しました。
Sentinelにも「Microsoft Defender 脅威インテリジェンス」といったDefenderの脅威インテリジェンスを取り込むSentinelのデータコネクタは存在します。ですが、ZOZO独自で活用しているインテリジェンスやX(旧Twitter)にてリアルタイム公開される新鮮なフィッシング情報等インテリジェンスを用いる場合などでOpenCTIはとても便利です。
上記のようにアレンジした脅威インテリジェンスを用いた検知を自動で行うようにOpenCTIのインテリジェンスをSentinelに食わせました。
構築
連携に使用するプレイブック
連携のために、Microsoftが用意してくれているソリューションを利用します。執筆時点(2024年2月22日)ではSentinelのコンテンツハブから取得でき、プレイブックが4つあります。
また、OpenCTI公式のドキュメント1からもこのコンテンツがリンクされています。
4つのプレイブックは以下の通りです。
OpenCTIのインテリジェンスをSentinelに食わせるにはRead Stream- OpenCTI Indicators
とSend to Security Graph API - Batch Import (OpenCTI)
の2つを使用します。
プレイブックを使用するための準備
Sentinel側のデータコネクタ
このプレイブックはMicrosoft Graph APIを利用してインテリジェンスをテナントに作成します。このインテリジェンスをSentinel(Log Analytics)上の脅威インテリジェンスとして確認し検知ルールに組み込むためには、Sentinel上でデータコネクタを作成する必要があります。
このデータコネクタはコンテンツハブの「Threat Intelligence」から取得できます。
必要なデータコネクタはSentinel上で「脅威インテリジェンス プラットフォーム - 非推奨になっています (プレビュー)」といった表示になっており、Microsoftからは非推奨とされています。
ですが、公式ドキュメント2には以前の名称ではありますが、以下のようにこのデータコネクタを使用するように記載されています。
このデータコネクタを起動しておかないと、Microsoft Graph APIでのリクエストはエラーなく登録されますが、Sentinelでその情報を活用できない状態になります。
この状態に自分はとても悩まされました。このデータコネクタの後継である「Threat Intelligence Upload Indicators API (Preview)」を起動しただけではダメな状況でした。2つのデータコネクタを起動して取り込みに成功しています。
SSLサーバー証明書
プレイブックからOpenCTIへのリクエストはHTTPSなので、SSLサーバー証明書が必要です。以前のOpenCTIの構成ではAzure Load Balancer(以降ALB)を使用してAzure Key Vaultから配布される証明書を使用していました。
以前の構成ではSSLサーバー証明書の管理が面倒であり、かつSOC関係者やシステムしかアクセスしないOpenCTIに対してそこまで大規模なロードバランサーは必要ないと考えました。そのため、ALBを使用せずにMicrosoft側で管理されているSSLサーバー証明書を使用できるサービスにリプレースしました。
SSLサーバー証明書管理を省力化でき、小規模運用でき、アクセス制御が効くものとしてAzure Web App for Containers(以降Web App)でnginxを立てることを選択しました。Web AppはデフォルトでHTTPS通信のエンドポイントを提供してくれます。また、マシンスペックもごく小規模で運用でき、コンテナでのデプロイも容易です。仮想サブネットワーク統合でのリプレースもスムーズに進みました。
アクセス制御
アクセス元の制御については「受信トラフィックの構成」の項目から「アクセス制限付きで有効」を構成することによって実現できます。
Netwotk Security Group(以降NSG)と同様にサービスタグやIPアドレスを指定できます。これによってMicrosoft Entra ID(旧Azure Active Directory)からのSAML認証も許可できます。nginxの設定でもIP制限はできますが、Microsoft Entra IDからのアクセスをサービスタグで一括許可したい場合はこちらを構成しておくと便利です。
インジケータの登録
OpenCTIからのインテリジェンスを取り込むためには、OpenCTI側で取り込む対象となるSTIX形式のインジケータを登録する必要があります。以下のようにインジケータがあることを確認しておきます。
Send to Security Graph API
実際にプレイブックを利用してOpenCTIからインテリジェンスを取り込むLogic Appを組みます。Send to Security Graph API - Batch Import (OpenCTI)
プレイブックを最初に実行します。このプレイブックを利用して作成されるOpenCTI-ImportToSentinel
のLogic Appが以降の使用するRead Stream- OpenCTI Indicators
で必要になるので、先にこちらを作成しておかなければなりません。
以下のようなLogic Appができるはずです。
また、このLogic Appにインジケータを作成する3ためのロール(ThreatIndicators.ReadWrite.OwnedBy
)を付与する必要があります。
詳しい付与方法は公式のGitHubを参照してもらえればわかりますが、以下のようにテナント接続後にPowershellを用いてロールを付与します。
$MIGuid = "<Logic App の Managed ID>" $MI = Get-AzureADServicePrincipal -ObjectId $MIGuid $GraphApIAppId = "00000003-0000-0000-c000-000000000000" $PermissionName = "ThreatIndicators.ReadWrite.OwnedBy" $GrphAPIServicePrincipal = Get-AzureADServicePrincipal -Filter "appId eq '$GraphApIAppId'" $AppRole = $GrphAPIServicePrincipal.AppRoles | Where-Object {$_.Value -eq $PermissionName -and $_.AllowedMemberTypes -contains "Application"} New-AzureAdServiceAppRoleAssignment -ObjectId $MI.ObjectId -PrincipalId $MI.ObjectId -ResourceId $GrphAPIServicePrincipal.ObjectId -Id $AppRole.Id
Microsoft Entra IDのOpenCTI-ImportToSentinel
表記のエンタープライズアプリケーションを確認します。アクセス許可の項目で以下のようにロールが付与されていることを確認できれば成功です。
Read Stream- OpenCTI Indicators
次にRead Stream- OpenCTI Indicators
プレイブックを利用してOpenCTIからインテリジェンスを取得するための2つ目のLogic Appを組みます。
カスタムコネクタ
このプレイブックを利用するためにはカスタムコネクタを作成する必要があります。このカスタムコネクタはOpenCTIへGraphQLを利用してインテリジェンスを取得するためのものです。
以下のようにプレイブックの説明欄にリンクがあるので、そこからデプロイが可能です。
デプロイ後はカスタムコネクタのリソースとは別でAPI接続のリソースができます。このAPI接続にOpenCTIで利用するAPIキーを設定します。
ここで注意すべきは、APIキーの記載方法です。AzureのAPI接続にAPIキーを登録する場合は以下の注意書きがあり、Bearer
から記載する必要があります。
OpenCTI-IndicatorsStream
プレイブックを実行すると、以下のようなLogic App(OpenCTI-IndicatorsStream)ができるはずです。
これでOpenCTIからインテリジェンスを取得するための大枠ができました。ですが、これだけでは動きません。Until hasNextPage is false
内部のParse JSON Indicators
のアクションなど、最新のOpenCTIのGraphQLのレスポンスに対応していないため、スキーマを修正する必要があります。
具体的にはスキーマ内部のcreators
がcreator
になっていたり、このcreators
の形式をarray
に変更したり、indicator_types
のtype
項目を編集したりと、レスポンスによって様々です。
レスポンスの例についてはOpenCTIの/graphql
階層でGraphQL Playgroundがあるので、Logic Appのクエリ結果を以下のように確認できます。OpenCTIのver 5.12
以上ではクエリのfilters
等が異なりますので公式ドキュメントを参照ください。
このGraphQL Playgroundを活用して、インジケータがOpenCTIから返されるレスポンスをサンプルとして取得すれば、Logic Appのスキーマの修正が容易です。以下の「サンプルのペイロードを使用してスキーマを作成する」から自動的にスキーマを生成してくれます。
上記のようにエラーが出るアクションごとにスキーマを修正し切れば、OpenCTIからのインテリジェンスを取得するためのLogic Appが完成します。このAppが最後にSend to Security Graph API
へ送信することで、OpenCTIからのインテリジェンスをSentinelへ食わせることができます。
運用
分析ルールの作成
OpenCTIからのインテリジェンスを取り込むことができたら、次はそれを活用するための分析ルール(検知するためのルール)を作成します。このルールについては先述したコンテンツハブの「Threat Intelligence」から取得できます。
この分析ルールには大体の欲しいKQLの書き方が揃っているので独自で作成することは少ないですが、Microsoft製品以外のログ分析は当然自力で書くことになると思います。
例えばSWG製品を導入している場合は、そのログと取り込んだ脅威インテリジェンスを組み合わせて検知ルールを作成することになります。以下にIPv4アドレスのIOC情報と突合させるためのKQLの例を示します。
let dt_lookBack = 2h; let ioc_lookBack = 30d; let IP_Indicators = ThreatIntelligenceIndicator | where TimeGenerated >= ago(ioc_lookBack) | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId | where Active == true and ExpirationDateTime > now() | where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP) | extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP) | extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity) | extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity) | where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."; IP_Indicators | join kind=innerunique ( SWG_log() | where TimeGenerated >= ago(dt_lookBack) | extend SWG_TimeGenerated = TimeGenerated | where isnotempty(DstIpAddr) | where LogSummary !startswith "Block" ) on $left.TI_ipEntity == $right.DstIpAddr | where SWG_TimeGenerated < ExpirationDateTime | summarize SWG_TimeGenerated = arg_max(SWG_TimeGenerated, *) by IndicatorId, DstIpAddr | project SWG_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, DstIpAddr, User, Hostname, ExpirationDateTime, ConfidenceScore, Type | extend timestamp = SWG_TimeGenerated
上記KQLは例のためにSWGでのログをSentinelに以下のようなテーブル形式で取り込んでいると仮定しています。1例なので、実際の環境に合わせて修正する必要があります。
TimeGenerated | LogSummary | DstIpAddr | DstPort | User | Hostname | SrcIpAddr | SrcPort |
---|---|---|---|---|---|---|---|
2024-02-22T09:10:30.8885957Z | Block Risk Connection | 192.0.2.1 | 443 | user01 | HOST01 | 192.0.2.2 | 4444 |
上記はIPでの分析例なのでURLやファイルハッシュなどのIOC情報を突合させる場合はそれぞれ別々の分析ルールを作成することになります。
これらの分析ルールによって自動で取得した脅威インテリジェンスを活用して検知できます。
取り込むインテリジェンスの選定
自分たちがOpenCTIで収集している脅威インテリジェンスは膨大にあります。作成されるインジケータをなんでも取り込んでしまうと過検知の量も膨大になります。人材も豊富で大規模なSOCであればそれでも対応できるかもしれませんが、自分たちのSOCではそれは難しいです。また、IPアドレスでのIOC情報はCDNのIPアドレスなども多く含まれているため、鮮度が落ちやすい情報も多いです。
ある程度の検知精度を出すためにインテリジェンスの信頼度、鮮度、作成元などの情報でSentinelに取り込むインテリジェンスを選定します。
選定する場合はOpenCTIに取り込む元々のインテリジェンスを制限するのではなくLogic Appでフィルタリングを行います。あくまでOpenCTIは情報の泉として活用したいからです。
Logic Appでのフィルタリングは以下のようにUntil hasNextPage is false
内部のSwitch 2
アクションの各ケースに追加します。追加内容は制御アクションです。
制御アクションとしてx_opencti_score
の値によってインテリジェンスを取り込むかどうかを判断する「条件」(if文に相当)を設定します。以下の例だと信頼度が50未満の場合はIPv6のインテリジェンスを取り込まないように設定しています。
検知精度
セキュリティの観点から詳しいことをこの場で記載できませんが、検知精度としてはいい具合にZOZOの環境にチューニングできていると感じています。OpenCTIからのインテリジェンスを活用して検知することで、これまで検知できていなかった脅威を見つけることができています。
まとめ
OpenCTIからのインテリジェンスをSentinelに食わせ、検知する仕組みや運用法を紹介しました。OpenCTIを用いた自動でのインテリジェンス検知運用の記事は事例が少ないので、参考になれば幸いです。
ZOZOではこれからも脅威インテリジェンスを逐次収集し、意思決定プロセスに必要なインテリジェンスの活用に努めていき、ZOZOの安全性の向上を図っていきたいと考えています。
おわりに
ZOZOでは、一緒に安全なサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクから是非ご応募ください!