setodaNote

忘れる用のメモ書き

サイバー脅威情報集約システム EXIST を構築する

EXIST は VirusTotal や Shodan、Twitte などの情報を API を通じて取得し、脅威情報(IPアドレスやハッシュ値)の関連情報を横断的に検索することができる「サイバー脅威情報集約システム」です。

MISP と連携可能なので、ここでは EXIST+MISP 環境を構築していきます。

なお、CentOS 7 に対して EXIST+MISP 環境を自動で導入できるスクリプトを github で公開しています。 手っ取り早く環境構築したい場合は以下からご利用ください。

EXIST+MISP 環境を自動で導入できるスクリプト

github.com

サイバー脅威情報集約システム EXIST とは

EXIST(EXternal Information aggregation System against cyber Threat)は NICT の NICTER 解析チームが開発しているサイバー脅威情報集約システムです。オープンソースソフトウェアとして公開されています。

blog.nicter.jp

EXISTは,サイバー脅威情報を集約し,様々な情報源を横断的に検索することができるWebアプリケーションです. 様々な情報源からサイバー脅威情報をフィードやAPI経由で取得し,EXIST上のデータベースに集約します. 利用者はWebUIもしくはWebAPIで,サイバー脅威情報を特定のキーワードで横断的に検索することが出来ます.

さっそく構築手順に進みます。

CentOS 7 のインストール

まず CentOS 7 を準備します。ここでは VMware Workstation 15.5 Pro 上に CentOS 7 を導入します。 以下では GUI ありのサーバで導入する場合も念頭に記載していますが、最小構成のサーバの場合でも概ね同様の手順で導入できます。

GUI ありの場合は画面ロックを無効化しておきます

インストール中に画面がロックされると煩わしいので、画面のロックを無効化しておきます。

[アプリケーション]>[システムツール]>[設定]>[プライバシー]>[画面ロック]:オフ
[アプリケーション]>[システムツール]>[設定]>[電源管理]>[ブランクスクリーン]:しない

MISP のインストール

ここから先の作業はすべて root 前提で進めます。

su -

以下のスクリプトを利用してインストールします。*1 なおスクリプトの処理の都合上、EXIST より先に実行する必要があります。

MISP インストールスクリプトの取得

wget https://raw.githubusercontent.com/vodkappa/misp-install-centos-7/master/misp.install.sh
wget https://raw.githubusercontent.com/vodkappa/misp-install-centos-7/master/misp.variables.sh 

MISP インストールスクリプトの修正

少し古いスクリプトのようで、そのままではうまく動かないので一部記載を修正します。同時に多少カスタマイズします。

以下のコマンドを入力します。

# 特にインストールには不要なのでホスト名の変更処理をコメントアウト
sed -i -e "s/hostnamectl/#hostnamectl/" misp.install.sh

# php のバージョンが古いままなので修正(rh-php56 -> rh-php72)
sed -i -e "s/rh-php56/rh-php72/g" misp.variables.sh
sed -i -e "s/rh-php56/rh-php72/g" misp.install.sh

# 不要な処理なのでコメントアウト
sed -i -e "s/yum install python-importlib/##yum install python-importlib/g" misp.install.sh

# DB の文字コードを UTF-8 に変更します
# これをしておかないと EXIST で日本語が文字化けします。
CHANGE_DB_CHARSET_TO_UTF8='sed -i -e "$(grep \\\\[mysqld\\\\] -n /etc/my.cnf.d/server.cnf | cut -d : -f 1)a character-set-server=utf8" /etc/my.cnf.d/server.cnf'
sed -i -e "$(grep 'systemctl enable mariadb.service' -n misp.install.sh | cut -d : -f 1)i $CHANGE_DB_CHARSET_TO_UTF8" misp.install.sh

作業の簡易化のためにDB設定周りの問い合わせ応答を自動化しておきます。

# DB のセキュリティ設定(mysql_secure_installation)の問い合わせ部分を自動化
sed -i -e "s/mysql_secure_installation/yum install expect -y\nexpect -c \"\nset timeout 5\nspawn mysql_secure_installation\nexpect \\\\\"Enter current password for root\\\\\"\nsend \\\\\"\\\n\\\\\"\nexpect \\\\\"Set root password\\\\\"\nsend \\\\\"y\\\n\\\\\"\nexpect \\\\\"New password\\\\\"\nsend \\\\\"\${DBPASSWORD_ADMIN}\\\n\\\\\"\nexpect \\\\\"Re-enter new password\\\\\"\nsend \\\\\"\${DBPASSWORD_ADMIN}\\\n\\\\\"\nexpect \\\\\"Remove anonymous users\\\\\"\nsend \\\\\"y\\\n\\\\\"\nexpect \\\\\"Disallow root login remotely\\\\\"\nsend \\\\\"y\\\n\\\\\"\nexpect \\\\\"Remove test database and access to it\\\\\"\nsend \\\\\"y\\\n\\\\\"\nexpect \\\\\"Reload privilege tables now\\\\\"\nsend \\\\\"y\\\n\\\\\"\nexpect \\'$\\\\\"\nexit 0\n\"/g" misp.install.sh

タイムゾーン設定を日本に合わせて修正します。

sed -i -e "s/Europe\/London/Asia\/Tokyo/g" misp.install.sh

MISP インストール用の環境設定

MISP のインストールに必要な環境変数一式を設定します。

. misp.variables.sh
# 実行結果(例)
# . misp.variables.sh
Admin (root) DB Password: 807d0fbcae7c4b20518d4d85664f6820aafdf936104122c5073e7744c46c4b87
User  (misp) DB Password: 2998b3232d29e8dc5a78d97a32ce83f556f3ed31b057077503df05641dd79158

実行した結果として DB Admin のパスワードが表示されます。 上の値は例示であり、実際の値はランダムに生成されます。 後で必要になるので必ずメモしておきます。

MISP インストールスクリプトの実行

MISP のインストールスクリプトを実行します。 スクリプトには「yum update -y」が含まれているので各種パッケージも最新化されます。 この処理は、通信環境などにもよりますが、概ね20分程度かかります。

bash misp.install.sh

以下のような表示になればインストールは完了です。

2019-10-20 16:23:52 Notice: Array to string conversion in [/var/www/MISP/app/Model/Log.php, line 205]
Setting "Session.cookie_timeout" changed to 3600

[root@localhost ~]# 

最後にログのローテートに失敗するのを防ぐための設定をしておきます。

sed -i -e "s/}/    su apache apache\n}/g" /etc/logrotate.d/misp

これで MISP の導入は完了です。

EXIST のインストール

MISP と同様に作業はすべて root 前提ですすめます。

su -

EXIST に必要な Python 環境の準備

まず EXIST を git clone しておきます。

cd /opt
git clone https://github.com/nict-csl/exist.git

次に EXIST の動作に必要な Python3.x 環境を導入します。

yum install -y https://centos7.iuscommunity.org/ius-release.rpm
yum install python3 python3-libs python3-devel python3-pip -y

また Python の仮想環境を作っておきます。 仮想環境にしておくと、後々のパッケージ名に関連するエラーの発生を防ぐ事ができます。

cd /opt/exist
python3 -m venv venv-exist
source venv-exist/bin/activate

プロンプトが以下のような表示になっていれば無事に仮想環境が有効になっています。 以降は仮想環境上での作業を前提として進めます。

(venv-exist) [root@localhost exist]# 

なお、この仮想環境から抜けるには deactivate と入力します。

EXIST に必要な各種パッケージをインストール

Python の仮想環境で以下のコマンドを実行します。

cd /opt/exist
pip install -r requirements.txt

EXIST に必要なデータベースの準備

EXIST に必要なデータベースとユーザを作成します。 DB へのログインパスワードは MISP 導入時に設定したものを使います(Admin (root) DB Password としてメモしてあるはずの16進数)。

mysql -u root -p 807d0fbcae7c4b20518d4d85664f6820aafdf936104122c5073e7744c46c4b87

以下のコマンドではユーザ名「exist」、パスワード「password」として設定しています。 あくまで例示なので、必要に応じて適切なものに置き換えて実行してください。

MariaDB [(none)]> create database intelligence_db;
MariaDB [(none)]> create user 'exist'@'localhost' identified by 'password';
MariaDB [(none)]> grant ALL on intelligence_db.* to exist;
MariaDB [(none)]> quit

次に EXIST の DB 周りの設定ファイルを修正します。

まずテンプレートから設定ファイルを作成します。

cp intelligence/settings.py.template intelligence/settings.py

修正や追記する項目は以下の通りです。

  • ALLOWED_HOSTS:localhost と EXIST が動いているマシンの IP アドレスを追加します
  • USER:先程 DB に作ったユーザ名に修正します
  • PASSWORD:先程 DB に作ったユーザのパスワードに修正します
  • HOST:localhost と入力します

例えば以下のように入力します。

vim intelligence/settings.py
ALLOWED_HOSTS = [
     'localhost',   # add
     'EXIST_Server_IP',   # add
#    '192.168.56.101',
]

(snip)

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'intelligence_db',
        'USER': 'exist',   # modify
        'PASSWORD': 'password',   # modify
        'HOST': 'localhost', # modify
        'PORT': '',
        'OPTIONS': {
            'charset': 'utf8mb4',
            'init_command': 'SET character_set_connection=utf8mb4;'
                            'SET collation_connection=utf8mb4_unicode_ci;'
                            "SET NAMES 'utf8mb4';"
                            "SET CHARACTER SET utf8mb4;"
        },
    }
}

次に Django マイグレーションをかけます。

python3 manage.py makemigrations exploit reputation threat threat_hunter twitter twitter_hunter news news_hunter vuln
python3 manage.py migrate

結果がすべて緑の OK になっていれば問題ありません。

EXIST に必要な Celery の準備

まず Celery に必要な Redis について確認しておきます。 以下のコマンドを入力してグリーンランプがついていれば OK です。

systemctl status redis

もしインストールされていなければ以下の通り入力します。

yum install redis
systemctl start redis
systemctl enable redis

次に sysconfig 配下に Celery の設定ファイルを新規に作成します。

vim /etc/sysconfig/celery
# Name of nodes to start
# here we have a single node
CELERYD_NODES="localhost"
# or we could have three nodes:
#CELERYD_NODES="w1 w2 w3"

# Absolute or relative path to the 'celery' command:
CELERY_BIN="/opt/exist/venv-exist/bin/celery"

# App instance to use
# comment out this line if you don't use an app
CELERY_APP="intelligence"
# or fully qualified:
#CELERY_APP="proj.tasks:app"

# How to call manage.py
CELERYD_MULTI="multi"

# Extra command-line arguments to the worker
CELERYD_OPTS="--time-limit=300 --concurrency=8"

# - %n will be replaced with the first part of the nodename.
# - %I will be replaced with the current child process index
# and is important when using the prefork pool to avoid race conditions.
CELERYD_PID_FILE="/var/run/celery/%n.pid"
CELERYD_LOG_FILE="/var/log/celery/%n%I.log"
CELERYD_LOG_LEVEL="INFO"

次に Celery をサービスとして登録します。 修正箇所は以下の通りです。

  • User:root
  • Group:root
  • WorkingDirectory:/opt/exist (EXIST へのパス)
vim /etc/systemd/system/celery.service
[Unit]
Description=Celery Service
After=network.target

[Service]
Type=forking
User=root
Group=root
EnvironmentFile=/etc/sysconfig/celery
WorkingDirectory=/opt/exist
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} \
-A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS}'
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODES} \
--pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} \
-A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS}'

[Install]
WantedBy=multi-user.target

Celery のログファイル用にディレクトリを作成します。

mkdir /var/log/celery; chown root:root /var/log/celery
mkdir /var/run/celery; chown root:root /var/run/celery

また設定ファイルを新規に作成します。

vim /etc/tmpfiles.d/exist.conf
#Type  Path               Mode  UID        GID         Age  Argument
d      /var/run/celery    0755  root  root  -

最後に Celery サービスを起動しておきます。

systemctl start celery.service
systemctl enable celery.service

Firewall を設定

EXIST で 8000 番ポートを利用するので開けておきます。

firewall-cmd --zone=public --add-port=8000/tcp --permanent
firewall-cmd --reload

ページスクリーンショット取得用の設定

EXIST で URL を検索した際にページのスクリーンショットがうまく表示されるように必要なパッケージの導入と設定を行います。

yum install wkhtmltopdf xorg-x11-server-Xvfb -y
cp scripts/url/url.conf.template scripts/url/url.conf
sed -i -e "s/path\/to\/your\/exist/opt\/exist/g" scripts/url/url.conf
sed -i -e "s/YOUR_DB_USER/exist/g" -e "s/YOUR_DB_PASSWORD/${DBPASSWORD_EXIST}/g" -e "s/YOUR_DB/intelligence_db/g" scripts/url/url.conf

# Japanese Font
yum install ipa-gothic-fonts ipa-pgothic-fonts
fc-cache -f

(オプション)ツイート表示画面にツイートへの直接リンクを追加

EXIST で収集したツイートについて、日時表記部分がツイートへのリンクになるように改修しておきます。これがあったほうが便利です。

cd /opt/exist
sed -i -e "s/{{ tw.datetime }}/\<a href=\"https:\/\/twitter.com\/{{ tw.screen_name }}\/status\/{{ tw.id }}\"\>{{ tw.datetime }}\<\/a\>/g" apps/twitter/templates/twitter/index.html
sed -i -e "s/{{ tw.datetime }}/\<a href=\"https:\/\/twitter.com\/{{ tw.screen_name }}\/status\/{{ tw.id }}\"\>{{ tw.datetime }}\<\/a\>/g" apps/dashboard/templates/dashboard/index.html
sed -i -e "s/{{ tw.datetime }}/\<a href=\"https:\/\/twitter.com\/{{ tw.screen_name }}\/status\/{{ tw.id }}\"\>{{ tw.datetime }}\<\/a\>/g" apps/dashboard/templates/dashboard/crosslist.html
sed -i -e "s/{{ tw.datetime }}/\<a href=\"https:\/\/twitter.com\/{{ tw.screen_name }}\/status\/{{ tw.id }}\"\>{{ tw.datetime }}\<\/a\>/g" apps/twitter_hunter/templates/twitter_hunter/tweets.html 

EXIST の起動設定

EXIST サービスの作成

EXIST をサービスとして登録します。 Celery サービスの起動後でないと正常に動作しないため、起動時の条件に記載しています。

vim /etc/systemd/system/exist.service
[Unit]
Description = EXIST
After = celery.service

[Service]
WorkingDirectory=/opt/exist
ExecStart=/opt/exist/venv-exist/bin/python3 manage.py runserver 0.0.0.0:8000
Restart=always
Type=simple
KillMode=control-group
Restart=on-failure

[Install]
WantedBy=multi-user.target

EXIST サービスを起動

systemctl start exist.service
systemctl enable exist.service

これで EXIST の導入は完了です。

EXIST や MISP へのアクセス

EXIST へのログイン

ブラウザから以下にアクセスすると EXIST の画面が表示されます。

http://localhost:8000/

f:id:soji256:20191021012544p:plain
EXIST のメインページ

MISP へのログイン

ブラウザから以下にアクセスすると MISP ログイン画面が表示されます。

http://localhost/users/login/
もしくは
http://{Your_VM_IP_address}/user/login/

f:id:soji256:20191021002715p:plain
MISP login

MSIP のデフォルトユーザ名とパスワードは以下の通りです。

Username: admin@admin.test
Password: admin

ログイン直後にパスワードの変更が求められます。 それが完了すると Admin ユーザのプロフィールページに遷移します。 左上にある「Home」をクリックすると MISP のダッシュボードが表示できます。

f:id:soji256:20191021003113p:plain
MISP Main page

EXIST の活用について

EXIST は各種 API などを設定することで情報収集プラットフォームとして機能します。 Twitter については Twitter API の挙動を理解していないとうまく情報収集ができないので注意が必要です。

これら設定周りについては別途、記事にしますが、現時点で調べた内容をまとめた記事を以下に投稿しています。 ご参考になれば幸いです。

サイバー脅威情報集約システム EXIST を活用する ~Twitter Hunter 周りの設定など~

soji256.hatenablog.jp

なお、API周りの設定については以下の記事がとても参考になりました。

(付録)トラブルシューティング

yum コマンドが自動アップデートとバッティングしてしまう

GUI ありの場合は CentOS のインストール直後に yum コマンドが自動アップデートとバッティングしてしまう場合があります。 以下の通り、自動更新を無効化にしておくことで回避できます。

# サービスの停止、無効化
sudo systemctl stop packagekit
sudo systemctl mask packagekit
 
# サービスの削除(必要なら)
#sudo yum remove PackageKit* -y

# 再起動
sudo reboot

yum のインストールが遅い

ファイルのダウンロードが遅い場合は、以下のコマンドを入力し yum の取得先を比較的速度の早い国内サイトに限ることで改善される場合があります。

echo include_only=.jp >> /etc/yum/pluginconf.d/fastestmirror.conf

scripts/threat/insert2db.py 実行時のエラー

# python3 scripts/insert2db/threat/insert2db.py
Traceback (most recent call last):
  File "scripts/insert2db/threat/insert2db.py", line 307, in <module>
    json_dict = fetchMispJson()
  File "scripts/insert2db/threat/insert2db.py", line 63, in fetchMispJson
    if res.status_code == 200:
UnboundLocalError: local variable 'res' referenced before assignment

tail でログを確認します。

tail scripts/insert2db/threat/logs/insert2db.log

コンフィグ(scripts/insert2db/conf/insert2db.conf)の MISP の URL 設定(MISP_URL)の最後に "/" がついていなかったために接続に失敗していました。

取得ツイートの日本語が文字化けする

日本語が「????????????????????????????」などと表示されてしまう場合がありました。 DB の文字コード設定が latin1 になっていたことが直接の原因でした。 通常の EXIST 導入では発生しませんが、MISP を先にインストールしたことによる副作用で発生していました。

以下はそうなった場合の対処です。

まず DB の設定を変更(追記)します。

sudo vim /etc/my.cnf.d/server.cnf
[mysqld]
character-set-server=utf8

設定反映のために DB を再起動します。

sudo systemctl restart mariadb

これで次からは UTF-8 で DB が生成されますが、 一度作ってしまったDBについては個別に文字コードの変更が必要です。

MariaDB [intelligence_db]> alter database intelligence_db character set utf8;

さらにテーブルのカラムを UTF-8 に修正します。

MariaDB [intelligence_db]> alter table テーブル名 default charset=utf8;

参考サイト:

Running setup.py install for uWSGI ... error

以下をインストールするとうまくいくかも。

sudo yum groupinstall "Development Tools"

参考サイト:

DB のセキュリティ設定(mysql_secure_installation) で「Switch to unix_socket authentication」と聞かれる

以下を参考に yes か no を決めて設定を進めます。

EXIST のページスクリーンショットがうまく取得できない

EXIST の画面上は画像ファイルが壊れたときのアイコン表示となっており、 ログは以下のようになっていました。

[ERROR]    2019-10-17 00:00:00,000    module:views    /opt/exist/apps/url/views.py    message:No wkhtmltoimage executable found: "b''"
If this file exists please check that this process can read it. Otherwise please install wkhtmltopdf - http://wkhtmltopdf.org

wkhtmltopdf をインストールするなどすると解消されました。

yum install wkhtmltopdf xorg-x11-server-Xvfb -y
cp scripts/url/url.conf.template scripts/url/url.conf
sed -i -e "s/path\/to\/your\/exist/opt\/exist/g" scripts/url/url.conf

ImportError:configparserというモジュールはありません

MISP を導入済みの状態で EXIST を導入しようとした際に発生したエラーです。 直接の原因は大文字小文字「Configparser」と「configparser」で見つかる見つからないというもののようです。 Python2 環境と Python3 環境が絡み合っているのか、どうにも解消できませんでしたが、 Python の仮想環境で EXIST を導入&実行することで解消できました。

参考サイト:

MISP のタイムゾーンがヨーロッパになっている(時刻表示がずれる)

MISP のインストールスクリプトでタイムゾーン設定がヨーロッパになっているため、 修正せずに利用すると時刻表示がずれてしまいます。

vim /etc/opt/rh/rh-php56/php-fpm.d/timezone.ini
date.timezone = "Asia/Tokyo"

変更を適用します。

systemctl restart rh-php72-php-fpm.service

EXIST にアクセスすると DisallowedHost と表示される

設定ファイル /opt/exist/intelligence/settings.py に EXIST が稼働しているサーバのIPアドレスが登録されていない場合に表示されます。 以下のようにIPアドレス(192.168.xxx.xxx の箇所)を設定すると解消されます。

ALLOWED_HOSTS = [
     'localhost',
     '192.168.xxx.xxx',   # <- Add the EXSIT Server IP adress
#    '192.168.56.101',
]

各種検索時に VirusTotal の情報が取得できていない

VirusTotal に関連するコンフィグファイルの読み込みに失敗している可能性があります。

  • コンフィグファイル(conf/exist.conf)の設定が正しいか
  • ログ(logs/django.log)を確認して message:No section: 'vt' といったエラーが出力されていないか
    • エラーが出ている場合は下記の「message:No section: というエラーが出る」をご参照ください

message:No section: というエラーが出る

ログ(logs/django.log)に message:No section: 'vt'message:No section: 'geoip' といったエラーが出る場合、 EXIST の実行スクリプト(manage.py)の作業ディレクトリが EXIST のインストール先と異なっている可能性があります。

EXIST が利用している lib/vt.py などにおいてコンフィグファイルへのパスが相対パスで記述されており、 作業ディレクトリが EXIST のインストール先であることを前提しているため、 それが異なっている場合はうまく動作しません。

カレントディレクトリを適切なものにしてから manage.py を実行するか、 サービスとして起動している場合は以下のように WorkingDirectory を指定すると解消されます。

vim /etc/systemd/system/exist.service
[Service]
WorkingDirectory=/opt/exist

変更を適用するためにはサービスの再起動が必要です。

systemctl daemon-reload
systemctl restart exist.service

参考文献

EXIST 関連

公式

構築周り

各種API

MISP 関連

公式

  • MISP/MISP: MISP (core software) - Open Source Threat Intelligence and Sharing Platform (formely known as Malware Information Sharing Platform)
    https://github.com/MISP/MISP

構築周り

MISP フィード設定

EXIST・MISP 共通

cron 設定周り

service 設定周り

DB 設定周り

その他

自動化スクリプト作成周り

更新履歴

  • 2019/10/23 新規作成
  • 2019/10/23 スクリプトの細かな不具合を修正。その他、一部表記を加筆修正。
  • 2019/10/24 トラブルシューティングに「EXIST にアクセスすると DisallowedHost と表示される」を追加。
  • 2019/10/24 自動化スクリプトについて追記・修正。
  • 2019/10/24 EXIST サービス設定に誤り(WorkingDirectory を設定していないと VirusTotal などの情報取得に失敗する)があったのを修正。トラブルシューティングと参考文献に関連を追記。
  • 2019/10/26 ログのローテートの設定と関連する参考文献を追加。
  • 2019/11/12 ツイートへの直リンク生成部分を一部追加。
  • 2019/12/02 EXIST のアップデートに合わせて記載内容を修正しました。表題や記載順序について整理しました。

*1:厳密に確認はしていませんが、記述内容からすると MISP の公式 github に掲載されているインストール手順をうまく自動化できるように組み上げたもののようです。