概要
アプリケーションセキュリティの領域は、この10年で大きく進化してきました。したがって、この進化に伴い、混乱を招くだけでなく、多くのノイズや重複したメッセージを生むツールのジャングルが生まれたのも不思議ではありません。数年前、DevSecOpsやシフトレフトの概念が勢いを増し始めると、多くの人気SCAツールが市場に登場しました――Snyk、Mend(旧WhiteSource)、そしてDependabotを通じてネイティブのSCA機能を追加したGitHubもその一つです。これはセキュリティ業界にとって重要な一歩でしたが、同時に新たなレベルのアラート疲れをもたらし、開発者を圧倒し始めました。
SCAやCSPMツールは、スキャンのたびに何万件もの脆弱性をアラートし、これらの数は新しいライブラリが導入されるたびに、数千件のCVE規模で増えていきます。
従来のアプリケーションセキュリティのアプローチは、膨大なアラートを残しましたが、その優先順位付けはCVSSスコアのみに基づいています。これは、アラートの優先順位付けにおいて重要な要素を考慮しないため、セキュリティ態勢の「見栄えの指標(バニティメトリクス)」に過ぎません。そこで登場するのがOligo、そして特に業界で注目を集めている「ランタイム(実行時)アプリケーションセキュリティ」という概念です。Oligo Securityは、実行時におけるアプリケーションの到達可能性(リーチャビリティ)と可視性を深く提供することで、こうした既知の痛点の一部を解決するために登場しました。
現在、ランタイムアプリケーションセキュリティを実装する一般的な方法は2つありますが、似たようなメッセージングが使われているにもかかわらず、得られる結果の能力は大きく異なります。本記事では、これらの方法と、ランタイムアプリケーションセキュリティにおいて実際にどの程度の到達可能性と可視性を提供するのか、そしてシステムを安全に保ちつつエンジニアのアラート疲れを減らすために知っておくべきことを解説します。
ランタイムアプリケーションセキュリティ:実行されたライブラリ vs. ロードされたライブラリ
今日、多くのツールが、増え続けるエクスプロイトと、侵害が企業にもたらす壊滅的な影響を背景に、ランタイムアプリケーションセキュリティの必要性を理解しています。企業には、単に規制遵守のチェックボックスを埋めるだけではなく、真のカバレッジが必要です。しかし、これは言うほど簡単ではありません。そのため、多くの企業がランタイムアプリケーションセキュリティに目を向け、ソフトウェアが実行・稼働している最中のシステムをリアルタイムに把握し、既知、さらには未知のエクスプロイトから自社を守れていることを確認しようとしています。
ランタイムアプリケーションセキュリティの利点は多岐にわたります――リアルタイムの脅威検知からゼロデイ攻撃の防止、システムやスタック全体のより良い制御まで。しかし、ツールにランタイムアプリケーションセキュリティの提供を任せたときに得られるものは、結果としてかなり多様です。
エンジニアリングの多くの領域と同様に、開発者がコードを書いても、そのすべてが実行時に利用されたり、ロードされたり、実行されたりするわけではありません。この違いは、成果や真陽性(True Positive)の結果において非常に重要です。
実行時にライブラリやファイルが「実行されている」タイミングの違いを知ることは、チームが本当に対処すべき脆弱性の数に大きな差を生み、システムに対する現実的な潜在リスクを最小化します。この違いを把握するために、今日では脆弱なライブラリがシステム内で到達可能かどうかを評価しようとする、いくつかのランタイムアプリケーションセキュリティ手法が用いられています。
TensorFlowの多様性:エコシステムと依存関係を深掘りする
例として、TensorFlowを見てみましょう。TensorFlowは、機械学習のためのエンドツーエンドのオープンソースプラットフォームで、AI実務者に幅広い機能を提供するよう設計されています。
多くの機能を備えた完全なエコシステムを提供します:
- AIモデルの学習 – データ読み込み、オプティマイザ、損失関数、学習関連コード
- TensorBoardによる実験トラッキング
- HWアクセラレーション(CUDA、XNNPack)のためのドライバとライブラリ
- チェックポイントの保存と読み込み(モデルの重みをファイルから推論ハードウェアのメモリへ読み込む)
- モデル推論(入力を与えて出力を予測/生成する)
- モデルのランタイム最適化 – モデル重みの量子化、レイヤーの融合など
現時点で、TensorFlowはモデルを学習するために複数のPythonパッケージに依存しています(それらはさらに多くのパッケージに依存します)。しかし実際には、これらの依存関係のうち本番環境で使われるのは一部に過ぎません。モデルの推論を実行するには、別のサブセットが使われます。
依存関係のグラフは以下で確認できます:
tensorflow==2.15.0
└── tensorflow-macos [required: ==2.15.0, installed: 2.15.0]
├── absl-py [required: >=1.0.0, installed: 2.1.0]
├── astunparse [required: >=1.6.0, installed: 1.6.3]
│ ├── six [required: >=1.6.1,<2.0, installed: 1.16.0]
│ └── wheel [required: >=0.23.0,<1.0, installed: 0.40.0]
├── flatbuffers [required: >=23.5.26, installed: 23.5.26]
├── gast [required: >=0.2.1,!=0.5.2,!=0.5.1,!=0.5.0, installed: 0.5.4]
├── google-pasta [required: >=0.1.1, installed: 0.2.0]
│ └── six [required: Any, installed: 1.16.0]
├── grpcio [required: >=1.24.3,<2.0, installed: 1.60.1]
├── h5py [required: >=2.9.0, installed: 3.10.0]
│ └── numpy [required: >=1.17.3, installed: 1.24.2]
├── keras [required: >=2.15.0,<2.16, installed: 2.15.0]
├── libclang [required: >=13.0.0, installed: 16.0.6]
├── ml-dtypes [required: ~=0.2.0, installed: 0.2.0]
│ ├── numpy [required: >1.20, installed: 1.24.2]
│ ├── numpy [required: >=1.23.3, installed: 1.24.2]
│ └── numpy [required: >=1.21.2, installed: 1.24.2]
├── numpy [required: >=1.23.5,<2.0.0, installed: 1.24.2]
├── opt-einsum [required: >=2.3.2, installed: 3.3.0]
│ └── numpy [required: >=1.7, installed: 1.24.2]
├── packaging [required: Any, installed: 23.0]
├── protobuf [required: >=3.20.3,<5.0.0dev,!=4.21.5,!=4.21.4,!=4.21.3,!=4.21.2,!=4.21.1,!=4.21.0, installed: 4.21.12]
├── setuptools [required: Any, installed: 67.6.1]
├── six [required: >=1.12.0, installed: 1.16.0]
├── tensorboard [required: >=2.15,<2.16, installed: 2.15.2]
│ ├── absl-py [required: >=0.4, installed: 2.1.0]
│ ├── google-auth [required: >=1.6.3,<3, installed: 2.16.1]
│ │ ├── cachetools [required: >=2.0.0,<6.0, installed: 5.3.0]
│ │ ├── pyasn1-modules [required: >=0.2.1, installed: 0.2.8]
│ │ │ └── pyasn1 [required: >=0.4.6,<0.5.0, installed: 0.4.8]
│ │ ├── rsa [required: >=3.1.4,<5, installed: 4.9]
│ │ │ └── pyasn1 [required: >=0.1.3, installed: 0.4.8]
│ │ └── six [required: >=1.9.0, installed: 1.16.0]
│ ├── google-auth-oauthlib [required: >=0.5,<2, installed: 1.2.0]
│ │ ├── google-auth [required: >=2.15.0, installed: 2.16.1]
│ │ │ ├── cachetools [required: >=2.0.0,<6.0, installed: 5.3.0]
│ │ │ ├── pyasn1-modules [required: >=0.2.1, installed: 0.2.8]
│ │ │ │ └── pyasn1 [required: >=0.4.6,<0.5.0, installed: 0.4.8]
│ │ │ ├── rsa [required: >=3.1.4,<5, installed: 4.9]
│ │ │ │ └── pyasn1 [required: >=0.1.3, installed: 0.4.8]
│ │ │ └── six [required: >=1.9.0, installed: 1.16.0]
│ │ └── requests-oauthlib [required: >=0.7.0, installed: 1.3.1]
│ │ ├── oauthlib [required: >=3.0.0, installed: 3.2.2]
│ │ └── requests [required: >=2.0.0, installed: 2.31.0]
│ │ ├── certifi [required: >=2017.4.17, installed: 2022.12.7]
│ │ ├── charset-normalizer [required: >=2,<4, installed: 3.0.1]
│ │ ├── idna [required: >=2.5,<4, installed: 3.4]
│ │ └── urllib3 [required: >=1.21.1,<3, installed: 1.26.14]
│ ├── grpcio [required: >=1.48.2, installed: 1.60.1]
│ ├── Markdown [required: >=2.6.8, installed: 3.4.3]
│ ├── numpy [required: >=1.12.0, installed: 1.24.2]
│ ├── protobuf [required: >=3.19.6,!=4.24.0, installed: 4.21.12]
│ ├── requests [required: >=2.21.0,<3, installed: 2.31.0]
│ │ ├── certifi [required: >=2017.4.17, installed: 2022.12.7]
│ │ ├── charset-normalizer [required: >=2,<4, installed: 3.0.1]
│ │ ├── idna [required: >=2.5,<4, installed: 3.4]
│ │ └── urllib3 [required: >=1.21.1,<3, installed: 1.26.14]
│ ├── setuptools [required: >=41.0.0, installed: 67.6.1]
│ ├── six [required: >1.9, installed: 1.16.0]
│ ├── tensorboard-data-server [required: >=0.7.0,<0.8.0, installed: 0.7.2]
│ └── Werkzeug [required: >=1.0.1, installed: 2.2.3]
│ └── MarkupSafe [required: >=2.1.1, installed: 2.1.2]
├── tensorflow-estimator [required: >=2.15.0,<2.16, installed: 2.15.0]
├── tensorflow-io-gcs-filesystem [required: >=0.23.1, installed: 0.36.0]
├── termcolor [required: >=1.1.0, installed: 2.3.0]
├── typing-extensions [required: >=3.6.6, installed: 4.9.0]
└── wrapt [required: >=1.11.0,<1.15, installed: 1.14.1]
[TensorFlowの依存関係グラフ(pipdeptreeで生成)]
アプリケーションが呼び出す依存関係、メモリにロード(import)されるだけの依存関係、インストールされているだけでアプリケーションでは使われていない依存関係を、自動的に区別することは可能でしょうか?
AI企業の本番環境を見てみましょう。
実行されたライブラリ
以下は、条件付き依存関係であるrequestsが、特定のシナリオでのみimportされる例です。利用方法はユースケースやアプリケーションごとに異なり、下記コードがそれを示しています:
[Tensorflowコードベースのスクリーンショット(GitHub)]
ご覧のとおり、requestsはURLパラメータを使用する場合にのみimportされ、その後HTTPメソッドが呼び出されます。
ランタイムコンテキストのおかげで、アプリケーションがロードされた関数のすべて、または一部を呼び出しているかどうかを判断できます。推測する必要はありません。代わりに、ランタイム情報を活用してノイズを減らし、不要なセキュリティ作業や、本番で悪用不可能な仮説上のリスクを排除できます。
image-processorという画像処理アプリケーションを見てみましょう。
image-processorというアプリが、requestsライブラリ内の脆弱な関数を少なくとも一度呼び出し(実行)たことが分かっています。現在の本番パイプラインでrequests内の脆弱な関数がトリガーされていることは確実で、さらにそのライブラリバージョンはCVE-2023-32681(CVSSスコア6.1)の影響を受けます。
そのため、悪用可能性のリスクを「Medium」から「High」へ自動的に引き上げます:
ロードされたライブラリ
一方で、コードの別の箇所では、TensorFlowは初期化されるとすぐにrequestsをimportし、メモリにロードします:
[Tensorflowコードベースのスクリーンショット(GitHub]
別のアプリケーション、tickets-apiを見てみましょう。tickets APIもtf.kerasモジュールを使用しており、これはrequestsに依存しています。tickets APIの内部では、requestsはライブラリとともに常にimportされますが、まったく実行されません。ランタイムコンテキストのおかげで、本番環境ではこの行でrequestsがimportされていないことが分かります。
そのため、CVE-2023-32681(CVSSスコア6.1)の悪用可能性のリスクを、MediumからLowへ自動的に引き下げます:
インストールされたライブラリ
この例では、ほとんどのライブラリはまったく使用されていません。脆弱なrequests 関数がそもそもメモリにロードされているのか、どこで、そしてランタイムで具体的にどのように、を区別することが重要です。TensorFlowの別の条件付き依存関係であるoauthlibを見てみましょう。これは、実験を追跡するためにアプリケーション内でTensorFlowの一部としてtensorboardを使用する場合にのみロードされ、呼び出されます:
tensorflow==2.15.0
└── tensorflow-macos [required: ==2.15.0, installed: 2.15.0]
├── tensorboard [required: >=2.15,<2.16, installed: 2.15.2]
│ ├── google-auth-oauthlib [required: >=0.5,<2, installed: 1.2.0]
│ │ └── requests-oauthlib [required: >=0.7.0, installed: 1.3.1]
│ │ ├── oauthlib [required: >=3.0.0, installed: 3.2.2]
上の図は、oauthlibがtensorflowの間接依存であることを示しています。
また、アプリケーションでtensorboardを使用しない場合、oauthlibは決してメモリにロードされないため、CVE-2022-36087(CVSSスコア6.5)の緊急性は低くなります。
tensorboard-exporterのランタイムコンテキストは、oauthlib内の脆弱な関数がロードも実行もされていないことを示しています。CVSSスコアだけで判断するとリスクは中程度です。しかし、CVSSが示すように実際のリスクが中程度であるとは限りません。
脆弱な関数が私たちの環境では、どの現実的なシナリオでも一度も実行されないため、リスクをMediumではなくLowとして自動的に分類します:
ロードされたライブラリと、部分的なランタイムアプリケーションセキュリティカバレッジの影響
つまり、コードには多くの依存関係がありますが、それぞれを個別に、異なる形で使用しています。
製品がランタイムアプリケーションセキュリティをサポートすると謳う場合、通常はオペレーティングシステム(OS)のプローブを使って必要な可視性を得ることを意味します。これらのプローブは、OSが特定のファイルを開いたり使用したりするタイミングを可視化できるフックです。プローブにはさまざまな種類がありますが、覚えておくべき点が一つあります。それは、プローブは常に実行時パフォーマンスに影響を与えるということです。要するに、プローブはコードが実行されるたびに評価される追加の`if`文のようなものだと考えられます。
プローブは「ロードされたライブラリ」を特定するための重要な手段です。これは、OSがそのファイルをおそらくメモリにロードしたことを意味します。しかし、上の例で示したとおり、これは必ずしもそのライブラリが実際に使われていることや、実行時のどこかでファイルが実行されることを意味しません。ユーザーに提供される唯一の示唆は、「必要になれば実行できるようにファイルが準備されている」ということです。
深いランタイムアプリケーションセキュリティ分析による「実行された」ライブラリ
ここがOligoが他のソリューションと異なる点です。革新的な特許出願中のeBPFベースのアプローチにより、カーネルレベルのトレーシングと可視性を提供し、ランタイムの真のインストゥルメンテーションを可能にします。これにより、関数が呼び出されたタイミングやライブラリが実行されたタイミングをリアルタイムで理解できるようになりました。これこそが、実行時に単に「ロードされた」ライブラリと、実質的に「実行された」ファイルとの本当の違いです。当社のテレメトリデータに基づく統計では、実行時にロードされるファイルのうち、実行されるのはわずか50%に過ぎません。これは、エンジニアリングにおける認知負荷という観点で極めて重要な指標です。
[Oligoの環境の一つからのサンプルデータ。約50%がロードされますが、実際に実行されるのは約25%にとどまります。]
実際に使用され、コールスタックに至るまで実行されているライブラリを把握することで、CVEノイズをさらにフィルタリングでき、システムに対する実際のセキュリティリスクをより良く、より正確に理解できます。リアルタイムの可視性があれば、実際に悪用可能な脅威が、まさに脆弱な実行関数の中で発生したときに、はるかに迅速に対応できます――そして、まったく実行されていないロード済みライブラリにエネルギーを浪費する必要がありません。
なぜこれが重要なのか
一般的なOSSライブラリを例に取ると、そこには何百行ものコードが含まれています。そしてこのOSSライブラリ自体も、実行するために独自の依存関係を持っています――その結果、このライブラリに紐づくコード量は2倍、あるいは3倍になります。SCAスキャナを実行するとCVEアラートが爆発し、その多くはCriticalやHighです――しかし、どこからリスク低減に着手すべきか分かりません。
実行時にメモリへ単に「ロード」されているだけでなく、実際に呼び出されて関数が実行されているファイルがどれかを理解し、同時に、それらの実行関数が他の機微なデータやシステムへアクセスできるかどうかを理解する――それらこそが優先すべき脆弱性です。こうして、エンジニアのバックログから大量のCVEノイズを取り除けます。
どの依存関係が積極的に呼び出され、リスクをもたらしているかを理解することで、明確さを得てセキュリティ施策の優先順位を付けられます。実際の脅威への対処に集中することで、より効果的なセキュリティ保護を実現し、開発者の信頼を取り戻しつつ、エンジニアリングの取り組みを効率化できます。
エキスパートのヒント
購読して最新のセキュリティ更新情報を入手
モダンアプリもレガシーアプリも防御するために構築
Oligoは、K8s上に構築されたモダンクラウドアプリや、オンプレミスでホストされる旧来のアプリにも、数分で導入できます。
翻訳元: https://www.oligo.security/blog/loaded-vs-executed-libraries