top of page

組み込みソフトウェアのレイヤー別脆弱性スキャンのすすめ

  • 5月15日
  • 読了時間: 12分

組み込みソフトウェアの脆弱性管理で難しいのは、単に「CVE を検出すること」ではありません。難しいのは、その CVE がどのレイヤーに存在し、製品として本当に影響するのかを判断することです。


コンテナを使っている製品であればコンテナイメージ。Linux ベースであれば OS パッケージ。カーネルモジュールやデバイスドライバ。C/C++ で書かれたアプリケーション。さらに、Yocto や Buildroot で取り込まれる OSS、静的リンクされたライブラリ、設定ファイル、起動スクリプト、証明書、秘密情報。

これらを一つのスキャナでまとめて見ようとすると、どうしても粗くなります。逆に、レイヤーごとに「狙うべきもの」と「適した手段」を分けると、検出結果の解釈がかなり楽になります。


SBOM はこの考え方の土台になります。CISA は SBOM を、ソフトウェアコンポーネントとその関係を把握するための情報として位置づけており、脆弱性把握やサプライチェーンリスク管理に利用できます。NIST SP 800-161 Rev.1 も、製品・サービスのサプライチェーンリスクを識別、評価、低減する考え方を示しています。(CISA)


なぜ「レイヤー別」に見るべきなのか


組み込み製品では、同じ CVE でも意味が大きく変わります。

たとえば OpenSSL の脆弱性が検出されたとしても、それが OS パッケージとして入っているのか、アプリケーションに静的リンクされているのか、コンテナ内だけに存在するのか、ビルド時ツールにだけ含まれているのかで対応は変わります。


また、Linux カーネルの脆弱性が検出されても、該当するドライバや機能が無効化されている場合があります。一方で、表向きには見えない古い Wi-Fi ドライバ、USB スタック、独自カーネルパッチの方が重大なリスクになることもあります。


そのため、組み込みソフトウェアの脆弱性スキャンは、次のように分けて考えると実務に落とし込みやすくなります。


コンテナレイヤー:まずは「中に何が入っているか」を見る


組み込み製品でも、近年はコンテナを使う構成が増えています。医療機器、産業機器、ゲートウェイ、エッジ AI デバイスなどでは、アプリケーションをコンテナで分離し、ホスト OS は最小構成にする設計も珍しくありません。


この場合、コンテナスキャンで見るべきものは次の通りです。

  • ベースイメージの OS パッケージ

  • Python、Node.js、Java、Go などの言語依存関係

  • 古い OpenSSL、curl、glibc、busybox など

  • 不要なシェル、パッケージマネージャ、デバッグツール

  • ハードコードされた秘密情報

  • Dockerfile やコンテナ設定の不備


代表的なツールとしては Trivy や Grype が使いやすいです。Trivy はコンテナイメージ、ファイルシステム、Git リポジトリ、VM イメージ、Kubernetes などを対象にでき、脆弱性だけでなく設定ミスやシークレット検出にも対応しています。Grype もコンテナイメージ、ファイルシステム、SBOM を対象に既知脆弱性を検出できます。

例:

trivy image my-embedded-app:1.0

SBOM を生成してからスキャンする場合は、Syft と Grype の組み合わせが分かりやすいです。Syft はコンテナイメージやファイルシステムから SBOM を生成する CLI ツールで、Grype と組み合わせて脆弱性検出に利用できます。

syft my-embedded-app:1.0 -o cyclonedx-json > sbom.json
grype sbom:sbom.json

コンテナレイヤーで重要なのは、ホスト OS とコンテナ OS を混同しないことです。Ubuntu ベースのコンテナを使っていても、ホスト側は Yocto Linux かもしれません。その場合、Ubuntu の CVE 対応と Yocto 側の CVE 対応は別々に管理する必要があります。





OS レイヤー:rootfs をスキャンし、ビルド時情報と突き合わせる


組み込み Linux の OS レイヤーでは、通常のサーバーとは少し見方が変わります。

サーバーであれば apt や dnf のパッケージ管理情報を見れば済むことが多いですが、組み込みでは Yocto、Buildroot、独自ビルド、ベンダー BSP などが入り混じります。完成品の rootfs だけを見ても、どのレシピから入ったものか、どのパッチが当たっているかが分からないことがあります。


OS レイヤーで見るべきものは次の通りです。

  • rootfs 内の OS パッケージ

  • busybox、systemd、openssl、dropbear/openssh、curl、zlib などの基本部品

  • パッケージ管理情報の有無

  • 不要サービス

  • デフォルトアカウント

  • 権限設定

  • SSH、TLS、ログ、監査設定


完成した rootfs に対しては、Trivy の filesystem scan が便利です。

trivy fs ./rootfs

Yocto を使っている場合は、ビルドシステム側の CVE チェックも重要です。Yocto Project には cve-check クラスがあり、BitBake ビルド時に既知 CVE を確認できます。Yocto のドキュメントでは、INHERIT += "cve-check" を設定して利用する方法が示されています。

INHERIT += "cve-check"

さらに、Yocto ではバックポートパッチが当たっている場合があります。その場合、単純にバージョン番号だけを見ると「脆弱」と判定されることがあります。Yocto のドキュメントでは、パッチ内の CVE: CVE-ID タグなどを使って、該当 CVE がパッチ済みであることを扱う仕組みが説明されています。


ここで大事なのは、OS スキャンの結果をそのまま「全部修正」としないことです。組み込み Linux では、ディストリビューションのバージョン表記と実際のパッチ適用状態が一致しない場合があります。


したがって、OS レイヤーでは次の 3 点をセットで見るのが現実的です。

  1. rootfs の実スキャン結果

  2. Yocto / Buildroot などのビルド時 CVE レポート

  3. ベンダー BSP や独自パッチの適用状況




カーネル・ドライバレイヤー:CVE スキャンだけでは足りない


組み込み機器で見落とされやすいのが、カーネルとドライバです。

アプリケーションの CVE や OS パッケージの CVE は比較的検出しやすい一方で、カーネルコンフィグ、外部モジュール、チップベンダー提供ドライバ、独自パッチは機械的なスキャンだけでは判断が難しくなります。


このレイヤーで見るべきものは次の通りです。

  • Linux カーネルのバージョン

  • ベンダー BSP のパッチ状況

  • Wi-Fi、Bluetooth、USB、カメラ、GPU、ストレージなどのドライバ

  • 外部カーネルモジュール

  • 有効化されているカーネルコンフィグ

  • 不要なプロトコルやファイルシステム

  • /dev 配下のデバイス権限

  • ioctl の入力検証

  • ユーザー空間との境界


カーネル CVE は、単純にバージョンだけで判断すると誤判定が出やすい領域です。LTS カーネルやベンダーカーネルでは、上流のバージョン番号は古くても、特定の修正だけバックポートされていることがあります。

そのため、カーネル・ドライバレイヤーでは、次のような確認が必要です。

uname -a
zcat /proc/config.gz | grep CONFIG_USB
lsmod
modinfo <module_name>
find /lib/modules -type f

脆弱性スキャンとしては Yocto の cve-check や SBOM ベースの確認が入口になりますが、最終的には以下の観点で手動トリアージが必要になります。

  • 該当機能がカーネル設定で有効か

  • 該当ドライバが製品でロードされるか

  • 攻撃経路が実際に存在するか

  • 物理アクセスが必要か

  • ネットワーク経由で到達可能か

  • ベンダーがバックポート済みか

  • 独自改変で再導入していないか


特に組み込み製品では、USB、Wi-Fi、Bluetooth、Web カメラ、CAN、シリアル、ストレージ周りは攻撃面になりやすい領域です。ここは「CVE があるか」だけでなく、「そのインターフェースが製品のユースケースで露出しているか」を見る必要があります。


アプリケーションレイヤー:CVE よりも実装不備を見に行く


アプリケーションレイヤーでは、OSS ライブラリの CVE だけでなく、製品固有の実装不備を見ます。


組み込みソフトウェアでは、C/C++、Rust、Go、Python、JavaScript、Lua などが混在することがあります。特に C/C++ では、バッファ境界、整数変換、メモリ解放、ファイルパス処理、権限チェック、暗号 API の誤用などが問題になります。

アプリケーションレイヤーで見るべきものは次の通りです。

  • 入力検証

  • メモリ安全性

  • 認証・認可

  • 暗号 API の使い方

  • 証明書検証

  • ログ出力

  • コマンドインジェクション

  • パストラバーサル

  • 権限昇格

  • アップデート処理

  • デバッグ機能の残存

  • API キーや秘密情報の埋め込み


このレイヤーでは SAST が有効です。たとえば CodeQL は、コード中の脆弱性やエラーを検出するために利用でき、C/C++ 向けの組み込みクエリも提供されています。


ただし、SAST は万能ではありません。特に組み込みでは、独自 RTOS、独自 IPC、ハードウェア制御、割り込み、DMA、共有メモリ、ioctl などが絡むため、一般的な Web アプリ向けルールだけでは足りません。

したがって、アプリケーションレイヤーでは、次のような使い分けが現実的です。

対象

手段

C/C++ の危険な実装

CodeQL、clang-tidy、cppcheck、Coverity など

OSS ライブラリ

SBOM、依存関係スキャン

Web UI / REST API

DAST、API テスト、認証・認可テスト

暗号・証明書処理

コードレビュー、単体テスト、設定レビュー

コマンド実行・ファイル操作

SAST + 手動レビュー

アップデート処理

脅威モデリング + 改ざん・ロールバック試験


アプリケーションレイヤーで最も避けたいのは、「CVE が出ていないから安全」と考えることです。自社コードの脆弱性には CVE が付いていないことが多く、スキャンで見つからない設計不備もあります。


ビルドシステム・OSS レイヤー:SBOM を“成果物”ではなく“管理台帳”にする


組み込み製品では、ビルドシステムそのものが脆弱性管理の中心になります。

Yocto の recipe、Buildroot の package、外部から取得する tarball、Git submodule、静的リンクするライブラリ、ベンダー提供 SDK。これらを把握しないまま完成イメージだけをスキャンしても、再現性のある管理にはなりません。


ここで重要になるのが SBOM です。CycloneDX は、ソフトウェア部品表を表現するための標準の一つで、OWASP は CycloneDX をフルスタックの BOM 標準として説明しています。CycloneDX は ECMA-424 として標準化されており、脆弱性管理やサプライチェーンリスク低減に使える構造を持ちます。(OWASP)


SBOM で管理したいものは次の通りです。

  • コンポーネント名

  • バージョン

  • 取得元

  • ライセンス

  • ハッシュ

  • 依存関係

  • ビルド時に使ったパッチ

  • 静的リンクされたライブラリ

  • 生成されたバイナリとの対応

  • VEX による影響有無の判断


VEX も重要です。脆弱性が検出されても、その製品では該当機能を使っていない、ビルドされていない、到達不能である、といった場合があります。Trivy も CycloneDX 形式の SBOM と VEX を使った脆弱性判断に対応しています。

組み込み製品では、VEX は単なる言い訳ではなく、検出結果を製品リスクに変換するための記録です。




設定・運用レイヤー:脆弱なパッケージがなくても設定

で崩れる


脆弱性スキャンというと CVE に目が向きがちですが、実際の製品では設定ミスも大きなリスクになります。

OS イメージに既知脆弱性が少なくても、次のような設定が残っていれば製品としては危険です。

  • デフォルトパスワード

  • root SSH ログイン許可

  • 不要なポート開放

  • 古い TLS 設定

  • 証明書検証の無効化

  • world-writable なディレクトリ

  • ログ不足

  • 監査設定なし

  • デバッグ API の残存

  • ファームウェア更新時の署名検証なし


この領域では OpenSCAP のような設定・コンプライアンス評価ツールが使えます。OpenSCAP は、システムのセキュリティ設定を確認し、標準や仕様に基づくルールで評価できるツールとして説明されています。Red Hat のドキュメントでも、oscap コマンドラインユーティリティは設定や脆弱性スキャン、セキュリティコンプライアンス検証に利用できるとされています。(static.open-scap.org)


ただし、組み込み製品では一般的な CIS Benchmark や SCAP コンテンツをそのまま適用できないこともあります。たとえば、読み取り専用 rootfs、独自 init、最小 busybox 環境、ネットワーク分離前提の機器では、サーバー向けのベースラインと合わない場合があります。


そのため、設定スキャンは次のように使うと現実的です。

  • 一般的な Linux ベースラインとの差分を見る

  • 製品仕様上、適用できない項目を明確にする

  • 製品固有のルールを追加する

  • リリース前のチェック項目に落とす

  • 例外理由を記録する


ファームウェア全体:最後に完成品として見る


最後に、完成したファームウェアイメージ全体を見ます。

これは開発中の SBOM やビルド時レポートとは別の意味があります。なぜなら、最終イメージには意図せず混入したファイル、古いバイナリ、デバッグ用ツール、テスト用証明書、不要な秘密情報が残ることがあるからです。


ファームウェア全体で見るべきものは次の通りです。

  • rootfs の展開

  • 既知バイナリのバージョン

  • 静的リンクされたライブラリ

  • 秘密鍵・証明書・トークン

  • デバッグスクリプト

  • テストアカウント

  • 不要なシェルやツール

  • Web UI の古い JavaScript ライブラリ

  • init script / systemd service

  • update package の署名検証


実務では、以下のような流れが使いやすいです。

binwalk -e firmware.bin
trivy fs ./_firmware.bin.extracted/
syft ./_firmware.bin.extracted/ -o cyclonedx-json > firmware-sbom.json
grype sbom:firmware-sbom.json

この段階の目的は、開発プロセス上の管理情報と、実際に出荷される成果物が一致しているかを確認することです。


SBOM では入っていないはずのライブラリがファームウェアに存在する。削除したはずの debug binary が残っている。テスト用の秘密鍵が混入している。こうした問題は、完成イメージを見ないと気づけないことがあります。


レイヤー別スキャンの実践パイプライン例


組み込み Linux 製品であれば、次のようなパイプラインが現実的です。


ポイントは、すべてを一つのツールで済ませようとしないことです。

Trivy や Grype は非常に便利ですが、カーネルバックポートや製品固有の攻撃可能性までは自動で判断できません。SAST は自社コードの問題を拾えますが、rootfs の不要サービスまでは見ません。OpenSCAP は設定評価に強いですが、独自ドライバの脆弱性までは見ません。


レイヤーごとに役割を分けることで、検出結果を「大量のアラート」ではなく「製品リスクの判断材料」にできます。


よくあるアンチパターン




まとめ:脆弱性スキャンは「製品を分解して理解する作業」


組み込みソフトウェアの脆弱性スキャンは、単にツールを実行して CVE 一覧を出す作業ではありません。


コンテナにはコンテナの見方があります。OS には OS の見方があります。カーネルとドライバには、バックポートや有効機能を踏まえた判断が必要です。アプリケーションには、自社コード特有の設計・実装リスクがあります。完成したファームウェアには、ビルドプロセスからは見えない混入物が残ることがあります。だからこそ、レイヤー別に見る必要があります。


「この CVE は本当に製品に影響するのか」

「どの構成要素に存在するのか」

「攻撃者はそこへ到達できるのか」

「修正すべきなのか、設定で緩和できるのか、VEX として非影響を記録すべきなのか」


そこまで判断して初めて、脆弱性スキャンは製品セキュリティの活動になります。

組み込み製品では、すべてを自動化することはできません。しかし、レイヤーごとに狙うべきものを分ければ、スキャン結果はかなり扱いやすくなります。大量のアラートに振り回されるのではなく、製品の構造を理解しながら、直すべき場所を正しく見つける。そのための入口として、レイヤー別脆弱性スキャンは非常に有効です。

bottom of page