#SIDfm

脆弱性の管理と対策に纏わる話

  • 脆弱性情報 /
  • 脆弱性の管理の方法、プロセスの自動化 /
  • パッチ対応の自動化、SOAR

Ansible を利用したシステム構成情報の自動取得

2020.4.23

はじめに

脆弱性には速やかな対応が求められる

脆弱性には速やかな対応が求められます。 ベンダによって脆弱性情報が公開された後、攻撃者は脆弱性を分析し、攻撃コードを作成。その後、攻撃を開始します。 防御側はこの攻撃開始までに脆弱性への対応を行わなければなりません。 攻撃開始までの期間は年々短くなっており、脆弱性への対応は以前と比較してより短期間での実施が求められています。

速やかな対応を阻む要因

では、脆弱性への速やかな対応を阻む要因にはどのようなものがあるでしょうか? そのひとつに、各サーバやサービスで利用しているシステム構成情報の把握があります。 各サーバへ ssh 経由でログインし、パッケージ情報を取得するコマンドを実行してまわることも可能ですが、サーバの台数の増加に伴い工数が増加します。 また各サーバの管理者にメールや電話で聞き取り調査を行い、その結果を元に Excel で構成管理を行うことは、工数のみならず速度の面からも現実的ではなくなりつつあります。 脆弱性への速やかな対応には自動化が必要です。

Ansible を利用したシステム構成情報取得の自動化

このエントリでは、Linux サーバ (CentOS/Ubuntu) にインストール済みのパッケージを取得する Ansible Playbook をご紹介します。

  1. 想定する環境
  2. システム構成情報の自動取得
  3. 取得したシステム構成情報の活用 (1) - Git によるパッケージ情報の変更管理
  4. 取得したシステム構成情報の活用 (2) - 脆弱性の影響の特定
  5. Ansible Playbook の解説
  6. まとめ
Ansible

Ansible とは、オープンソースの構成管理ツールの一種です。 エージェントレスのため、管理対象ノードに追加でインストールするソフトウェアは一部を除き不要です。(ssh 経由での接続は必要)

このエントリで扱う内容は、下図に示された脆弱性対策のフローの中で、「対象ソフトウェアの把握」フェーズに含まれるものです。

脆弱性対策フローでの位置づけ
出典:独立行政法人情報処理推進機構(IPA) セキュリティセンター, 脆弱性対策の効果的な進め方(ツール活用編), p.7

想定する環境

想定する環境を下図に示します。

Ansible コントロールノードとして ctrl.example.com が構築されており、 このコントロールノードは 「host1.example.com (CentOS 7 minimal)」 と 「host2.example.com (Ubuntu Server 18 LTS)」 を管理しているとします。 これら管理対象ノードへは SSH 経由で接続可能であり、SSH 接続でのユーザは sudo によるコマンド実行が許可されているとします。

想定する環境

Ansible のインストールや管理対象ノードの設定については、以下のリンク先をご参照ください。

システム構成情報の自動取得

以下の手順で、管理対象ノードのインストール済みパッケージを取得します。

インストール済みパッケージの自動取得

  1. Ansible コントロールノード ctrl.example.com にログインします。
  2. 作業用のディレクトリを作成し、そのディレクトリへ移動します。
    $ mkdir get_packages
    $ cd get_packages
  3. パッケージ情報を格納するためのホスト名のディレクトリを作成します。
    $ mkdir host1 host2
  4. インベントリファイルを作成します。
    $ vi hosts
    [test_servers]
    host1.example.com
    host2.example.com
  5. パッケージ情報を取得するための Ansible Playbook を作成します。
    $ vi get_packages.yml
    - name: Get packages from hosts
      hosts:
        - test_servers
     
      tasks:
        - name: Get packages
          package_facts:
            manager: auto
          become: true
     
        - name: Output packages
          template:
            src: ./package_list.j2
            dest: "./{{ inventory_hostname_short }}/packages"
            mode: '0644'
          delegate_to: localhost
  6. パッケージ情報を出力するためのテンプレートを作成します。
    $ vi package_list.j2
    # {{ inventory_hostname_short }}
    {% if ansible_facts.os_family == 'RedHat' %}
    {%   for package_name in ansible_facts.packages.keys()|sort %}
    {%     for package in ansible_facts.packages[package_name] %}
    {{       package['name'] }}-{{package['version']}}-{{package['release']}}.{{package['arch']}}
    {%     endfor %}
    {%   endfor %}
    {% elif ansible_facts.os_family == 'Debian' %}
    {%   for package_name in ansible_facts.packages.keys()|sort %}
    {%     for package in ansible_facts.packages[package_name] %}
    {{       [package['name'], package['version']] | join('_') }}
    {%     endfor %}
    {%   endfor %}
    {% endif %}
  7. Playbook を実行します。
    $ ansible-playbook -i hosts get_packages.yml
    実行結果
  8. 実行結果を確認します。
    まず host1.example.com (CentOS 7 minimal) を見てみましょう。
    $ cat host1/packages
    # host1
    acl-2.2.51-14.el7.x86_64
    aic94xx-firmware-30-6.el7.noarch
    alsa-firmware-1.0.28-2.el7.noarch
    alsa-lib-1.1.6-2.el7.x86_64
    alsa-tools-firmware-1.1.0-1.el7.x86_64
    audit-2.8.4-4.el7.x86_64
    audit-libs-2.8.4-4.el7.x86_64
    authconfig-6.2.8-30.el7.x86_64
    basesystem-10.0-7.el7.centos.noarch
    bash-4.2.46-31.el7.x86_64
    (以下省略)
    同様に host2.example.com (Ubuntu Server 18 LTS) も確認します。
    $ cat host2/packages
    # host2
    accountsservice_0.6.45-1ubuntu1
    acl_2.2.52-3build1
    acpid_1:2.0.28-1ubuntu1
    adduser_3.116ubuntu1
    amd64-microcode_3.20180524.1~ubuntu0.18.042
    apparmor_2.12-4ubuntu5.1
    apport_2.20.9-0ubuntu7.7
    apport-symptoms_0.20
    apt_1.6.11
    apt-utils_1.6.11
    (以下省略)

トラブルシューティング

何らかのエラーが発生する場合は「Ansible をインストールする - トラブルシューティング」を参照してください。

取得したシステム構成情報の活用 (1) - Git によるパッケージ情報の変更管理

Git は分散型のバージョン管理システムですが、単純にローカル環境にあるテキストファイルの履歴を管理するツールとしても利用することができます。 Git を利用して、Ansible Playbook で取得したパッケージ情報の変更履歴を管理してみましょう。

Git のインストールと初期設定

  1. Ansible コントロールノード ctrl.example.com にログインします。
  2. Git をインストールします。
    user@ctrl:~$ sudo apt-get install git -y
  3. 作業用のディレクトリに移動します。
    user@ctrl:~$ cd get_packages
  4. リポジトリを作成します。
    user@ctrl:~$ git init
  5. ユーザの設定を行います。
    user@ctrl:~$ git config --global user.email "user@example.com"
    user@ctrl:~$ git config --global user.name "test user"

Playbook の実行とパッケージ情報の登録

  1. Playbook を実行します。
    $ ansible-playbook -i hosts get_packages.yml
  2. パッケージ情報をリポジトリに登録します。
    $ git add */packages
    $ git commit

パッケージの更新と変更管理

  1. 管理対象ホスト host2.example.com にログインします。
  2. vim パッケージを最新版に更新します。
    user@host2:~$ sudo apt-get install vim -y
  3. Ansible コントロールノード ctrl.example.com にログインします。
  4. Ansible Playbook を実行し、パッケージ情報を取得します。
    user@ctrl:~$ ansible-playbook -i hosts get_packages.yml
    実行結果
  5. パッケージ更新前後の差分を表示します。
    user@ctrl:~$ git diff
    diff --git a/host2/packages b/host2/packages
    index abcdefg..hijkelm 100644
    --- a/host2/packages
    +++ b/host2/packages
    @@ -483,10 +483,10 @@ ureadahead_0.100.0-21
     usbutils_1:007-4build1
     util-linux_2.31.1-0.4ubuntu3.3
     uuid-runtime_2.31.1-0.4ubuntu3.3
    -vim_2:8.0.1453-1ubuntu1.1
    -vim-common_2:8.0.1453-1ubuntu1.1
    -vim-runtime_2:8.0.1453-1ubuntu1.1
    -vim-tiny_2:8.0.1453-1ubuntu1.1
    +vim_2:8.0.1453-1ubuntu1.3
    +vim-common_2:8.0.1453-1ubuntu1.3
    +vim-runtime_2:8.0.1453-1ubuntu1.3
    +vim-tiny_2:8.0.1453-1ubuntu1.3
     wget_1.19.4-1ubuntu2.2
     whiptail_0.52.20-1ubuntu1
     wireless-regdb_2018.05.09-0ubuntu1~18.04.1
  6. 最新のパッケージ情報をリポジトリに登録します。
    登録する際はコメントに「脆弱性対応」などのようにパッケージの変更理由を記載しておくと変更履歴を参照する際に役に立ちます。
    $ git add */packages
    $ git commit
  7. git logコマンドで変更履歴を git showコマンドで各変更の差分を確認できるようになります。
    $ git log -p */packages

取得したシステム構成情報の活用 (2) - 脆弱性の影響の特定

SIDfm Biz/Group や他の脆弱性情報サービスから受け取った脆弱性情報には、通常、影響を受けるアプリケーションが記載されています。 しかしながら、その脆弱性がどのサーバに影響するのかについては自ら調査しなければなりません。(SIDfm VM は影響まで自動的に判定します。)

先程の Ansible Playbook で取得したパッケージ情報を利用すれば、ある脆弱性が管理下のサーバに影響するかどうかを判定することが可能です。

ここでは例として vim パッケージを取り上げ、脆弱性の影響の特定を行います。

CentOS 7 (host1.example.com) での影響特定

  1. 脆弱性情報サービスから脆弱性情報を受けとります。
    ここでは SIDfm に登録された脆弱性コンテンツの中から「CentOS 7 の vim に任意の OS コマンドを実行される問題(CVE-2019-12735)」を受け取ったとします。 脆弱性が修正されたパッケージのバージョンは「対処方法」に記載されています。
    CentOS7-CVE-2019-12735-概要
    CentOS7-CVE-2019-12735-対処方法
  2. host1.example.com の vim のバージョンを確認します。
    Ansible コントロールノード ctrl.example.com 上で次のコマンドを実行してください。
    $ grep "vim" host1/packages
    host1/packages:vim-minimal-7.4.160-5.el7.x86_64
  3. バージョンの比較を行います。
    host1.example.com のバージョンは脆弱性が修正されたバージョンより小さく、脆弱性の影響を受けることがわかります。
    [脆弱性が修正されたバージョン]
    vim-minimal-7.4.160-6.el7_6.x86_64.rpm
     
    [host1 のパッケージバージョン]
    vim-minimal-7.4.160-5.el7.x86_64

Ubuntu Server 18 LTS (host2.example.com) での影響特定

  1. 脆弱性情報サービスから脆弱性情報を受けとります。
    ここでは SIDfm に登録された脆弱性コンテンツの中から「Ubuntu の vim に任意の OS コマンドを実行されるなど複数の問題 (CVE-2017-5953, CVE-2019-12735)」を受け取ったとします。 脆弱性が修正されたパッケージのバージョンは「対処方法」に記載されています。
    実行結果
    実行結果
  2. host2.example.com の vim のバージョンを確認します。
    Ansible コントロールノード ctrl.example.com 上で次のコマンドを実行してください。
    $ grep "vim" host2/packages
    host2/packages:vim_2:8.0.1453-1ubuntu1.1
    host2/packages:vim-common_2:8.0.1453-1ubuntu1.1
    host2/packages:vim-runtime_2:8.0.1453-1ubuntu1.1
    host2/packages:vim-tiny_2:8.0.1453-1ubuntu1.1
  3. バージョンの比較を行います。
    host2.example.com のバージョンは脆弱性が修正されたバージョンと同じであり、脆弱性の影響を受けないことが判明しました。
    [脆弱性が修正されたバージョン]
    vim 2:8.0.1453-1ubuntu1.1
    vim-common 2:8.0.1453-1ubuntu1.1
     
    [host2 のパッケージバージョン]
    vim_2:8.0.1453-1ubuntu1.1
    vim-common_2:8.0.1453-1ubuntu1.1

Ansible Playbook の解説

Ansible Playbook - get_packages.yml

この Ansible Playbook は次のタスクから成り立っています。

  1. package_facts モジュールを使用し、各監視対象ノードからパッケージ情報を収集する。
  2. template モジュールを使用し、パッケージ情報をコントロールノード上にファイルとして出力する。

では詳しく見てみます。

6 〜 9 行目が、各監視対象ノードからパッケージ情報を収集するタスクです。 このタスクで使用する package_facts モジュールは、インストール済みのパッケージに関する情報を集め、その結果をansible_factsに格納します。

get_packages.yml:
 1: - name: Get packages from hosts
 2:   hosts:
 3:     - test_servers
 4:  
 5:   tasks:
 6:     - name: Get packages
 7:       package_facts:
 8:         manager: auto     # パッケージマネージャの自動選択 (デフォルト)
 9:       become: true        # 特権で実行

参考までに、ansible_factsに格納されたパッケージ情報を示します。

host1.example.com (CentOS 7 minimal):

"ansible_facts.packages": {
    "GeoIP": [
        {
            "arch": "x86_64", 
            "epoch": null, 
            "name": "GeoIP", 
            "release": "13.el7", 
            "source": "rpm", 
            "version": "1.5.0"
        }
    ], 
    (中略)
    "zlib": [
        {
            "arch": "x86_64", 
            "epoch": null, 
            "name": "zlib", 
            "release": "18.el7", 
            "source": "rpm", 
            "version": "1.2.7"
        }
    ]
}

host2.example.com (Ubuntu Server 18 LTS)

"ansible_facts.packages": {
    "accountsservice": [
        {
            "arch": "amd64",
            "category": "gnome",
            "name": "accountsservice",
            "origin": "Ubuntu",
            "source": "apt",
            "version": "0.6.45-1ubuntu1"
        }
    ],
    (中略)
    "zlib1g": [
        {
            "arch": "amd64",
	    "category": "libs",
	    "name": "zlib1g",
	    "origin": "Ubuntu",
	    "source": "apt",
	    "version": "1:1.2.11.dfsg-0ubuntu2"
        }
    ]
}

 

get_packages.yml の 11 〜 16 行目は、パッケージ情報の出力部分となります。 template モジュールを使用しansible_factsに格納されたパッケージ情報をコントロールノード上にファイルとして出力します。 ここで inventory_hostname_short 変数はドメイン部分を除いたホスト名です。 例えば、host1.example.cominventory_hostname_shorthost1 になります。

get_packages.yml:
11:     - name: Output packages
12:       template:
13:         src: ./package_list.j2                             # テンプレートファイルの指定
14:         dest: "./{{ inventory_hostname_short }}/packages"  # 出力先の指定
15:         mode: '0644'                                       # パーミッションの指定
16:       delegate_to: localhost                               # 管理対象ノードでなくコントロールノード上で実行

 

Jinja2 テンプレートファイル - package_list.j2

Ansible では Jinja2 テンプレートエンジンを利用できます。今回の Playbook もデータの整形に Jinja2 を使用しています。Jinja2 の文法については こちら をご参照ください。それではテンプレートファイルの内容を見てみましょう。

2 行目でos_family変数によって Red Hat 系と Debian 系で処理を分岐させています。 その他のos_familyの取りうる値は こちら を参照してください。

パッケージ情報の内容を出力する部分は 5 行目と 11 行目です。

package_list.j2:
 1: # {{ inventory_hostname_short }}
 2: {% if ansible_facts.os_family == 'RedHat' %}                    # Red Hat 系の場合
 3: {%   for package_name in ansible_facts.packages.keys()|sort %}  # パッケージ名でソート
 4: {%     for package in ansible_facts.packages[package_name] %}
 5: {{       package['name'] }}-{{package['version']}}-{{package['release']}}.{{package['arch']}}
 6: {%     endfor %}
 7: {%   endfor %}
 8: {% elif ansible_facts.os_family == 'Debian' %}                  # Debian 系の場合 
 9: {%   for package_name in ansible_facts.packages.keys()|sort %}  # パッケージ名でソート
10: {%     for package in ansible_facts.packages[package_name] %}
11: {{       [package['name'], package['version']] | join('_') }}
12: {%     endfor %}
13: {%   endfor %}
14: {% endif %}

host1.example.com (CentOS 7) のパッケージ情報ansible_facts.packagesは次のようになります。

"ansible_facts.packages": {
    (中略)
    "zlib": [
        {
            "arch": "x86_64", 
            "epoch": null, 
            "name": "zlib", 
            "release": "18.el7", 
            "source": "rpm", 
            "version": "1.2.7"
        }
    ]
}

上記のデータを整形します。パッケージ名やバージョンなどを取り出し、文字列として連結します。

package_list.j2:
 5: {{       package['name'] }}-{{package['version']}}-{{package['release']}}.{{package['arch']}}

このような出力となります。

host1/packages:
zlib-1.2.7-18.el7.x86_64

host2.example.com (Ubuntu Server 18 LTS) も同様の処理を行います。

"ansible_facts.packages": {
    (中略)
    "zlib1g": [
        {
            "arch": "amd64",
	    "category": "libs",
	    "name": "zlib1g",
	    "origin": "Ubuntu",
	    "source": "apt",
	    "version": "1:1.2.11.dfsg-0ubuntu2"
        }
    ]
}

整形部分。こちらは取り出したデータを join で連結しています。

package_list.j2:
11: {{       [package['name'], package['version']] | join('_') }}

出力結果です。

host2/packages:
zlib1g_1:1.2.11.dfsg-0ubuntu2

まとめ

Linux サーバ (CentOS/Ubuntu) にインストール済みのパッケージを取得する Ansible Playbook をご紹介しました。 この Ansible Plabook を利用することで、脆弱性対策における対象ソフトウェアの把握が比較的容易になります。


SIDfm

SIDfm™ VM は、脆弱性情報を起点として、情報資産(ホスト)内のソフトウェア脆弱性の特定を行う機能を備えます。脆弱性の検出から対処まで記録は自動的に行い、脆弱性別やホスト別に脆弱性への対処状況・危険性を包括的に視認でき、運用管理機能も備えています。詳細は、SIDfm™ VM のホームページを御覧ください。

SIDfm VM での脆弱性管理の具体的なプロセス

最新の記事

脆弱性と対策のことなら

SIDfm™

  • お問い合わせ

    SIDfm™ 各種サービスの使用法や購入において気になる点などお気軽にお問い合わせ下さい。

    お問い合わせ
  • 無料でトライアル

    SIDfm™ の VM、RA、Biz を実際に無料で使ってみてください。

    無料で試してみる