組み込みソフトウェアのレイヤー別脆弱性スキャンのすすめ
- 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.0SBOM を生成してからスキャンする場合は、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 ./rootfsYocto を使っている場合は、ビルドシステム側の CVE チェックも重要です。Yocto Project には cve-check クラスがあり、BitBake ビルド時に既知 CVE を確認できます。Yocto のドキュメントでは、INHERIT += "cve-check" を設定して利用する方法が示されています。
INHERIT += "cve-check"さらに、Yocto ではバックポートパッチが当たっている場合があります。その場合、単純にバージョン番号だけを見ると「脆弱」と判定されることがあります。Yocto のドキュメントでは、パッチ内の CVE: CVE-ID タグなどを使って、該当 CVE がパッチ済みであることを扱う仕組みが説明されています。
ここで大事なのは、OS スキャンの結果をそのまま「全部修正」としないことです。組み込み Linux では、ディストリビューションのバージョン表記と実際のパッチ適用状態が一致しない場合があります。
したがって、OS レイヤーでは次の 3 点をセットで見るのが現実的です。
rootfs の実スキャン結果
Yocto / Buildroot などのビルド時 CVE レポート
ベンダー 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 として非影響を記録すべきなのか」
そこまで判断して初めて、脆弱性スキャンは製品セキュリティの活動になります。
組み込み製品では、すべてを自動化することはできません。しかし、レイヤーごとに狙うべきものを分ければ、スキャン結果はかなり扱いやすくなります。大量のアラートに振り回されるのではなく、製品の構造を理解しながら、直すべき場所を正しく見つける。そのための入口として、レイヤー別脆弱性スキャンは非常に有効です。


