West Gate Laboratory

人生を少しでも,面白く便利にするモノづくり

ラズベリーパイを使ったIoTシステムに脆弱性があると指摘があったためセキュリティ対策を施した話

概要

先日のブログで記事を書いた、ラズベリーパイを使って作ったIoTシステムに「脆弱性がある」と指摘を受けたので、素人ながら調べつつ最低限のセキュリティ対策を施した話。

westgate-lab.hatenablog.com

(ちなみに、上の記事ははてなブログの週間ランキング2位になってしまった)

今週のはてなブログランキング〔2020年2月第1週〕 - 週刊はてなブログ

背景

先日上記のブログ記事を公開したところ、「システムに脆弱性がある」という指摘を多々頂いた。システムというのは、ラズベリーパイでインターホンを監視して、呼出音を検知したら条件に応じて解錠ボタンを押す」というものである。

もともとは、予定された配達か否かで2通りの解錠方法を考えていた。

f:id:kaname_m:20200126140827p:plain
予定された配達の場合(廃止済み)

f:id:kaname_m:20200126140822p:plain
予定していなかった配達の場合(現行版は常にこれ)

受けた指摘は主に2つ。

  • もともと予定された配達時間帯に万が一悪意を持った人物が我が家のインターホンを鳴らすと中央玄関が開いて、中へ入れてしまう

  • 解錠コマンド用URLにアクセスした相手を識別しておらず、悪意を持った人物がコマンドを送ることが可能

というものである。

1つ目に関しては、確かにそうなので、「予定された配達の場合」のモードは廃止した。一方で、2つ目に関しては何らか認証が必要そうだ。しかしやり方がわからない。

そんなときに、IPA情報処理推進機構から連絡があった。どうやら脆弱性関連情報の届出受付システムを通じてこのシステムの脆弱性を届出てくれた人がいたようだ。

脆弱性関連情報の届出受付:IPA 独立行政法人 情報処理推進機構

届出られた内容を読むと脆弱性の詳細が事細かに書いてあり、素人の自分にとっては非常にありがたい内容だった。届出内容には対処方法も書いてあり、どうやらアクセス制御機能を実装する必要がありそうだ。

「アクセス制御・・・?」状態だった私はまずそれを調べるところから始め、ようやくID/パスワードによる認証ができるようになったため、その過程を述べる。今回はDigest認証を使用した。

f:id:kaname_m:20200209114348p:plain
アクセス制御機能を追加したシステム。Digest認証を使用

Basic認証、Digest認証

アクセス制御とは、要はあるシステムに対し誰か何をしていいのかを設定したり識別したりして、それに基づいて操作を拒否したり許可したりすることである。

思いつくのはID/パスワードによる認証だが、ID/パスワードによる最も基本的な認証方法にBasic認証Digest認証がある。

私はアクセス制御については素人なので、詳細な説明は他の記事を読んでもらうとして、ここではごくごく簡単に説明する。

Basic認証:実装が容易で、簡易的な認証方法。ログインIDとパスワードを入力すると、それらがBase64エンコードされてサーバへ送信される。サーバはその情報をデコードし、ID/パスワードが一致していたらOKとする。

Basic認証はとても実装が楽だが、認証情報がただBase64エンコードされているだけで、誰でもデコードできるので、実質平文で送っていることになる。公衆Wifiで認証情報を入力するな、というのはこういうことのようだ。なので、一般的に通信を暗号化するSSLHTTPS)と一緒に使うようである。

Digest認証Basic認証が実質平文でID/パスワードを送っているのに対し、Digest認証ではパスワードをハッシュ化して送信する。ハッシュ化、というのは一方向関数を使ってある固定長のメッセージに置き換えることで、ハッシュ値からもとの値(ID/パスワード)を解析することを困難にするもの。サーバ側ではID/パスワード情報があるので、同様にハッシュ値を計算して、それらが一致すれば許可する、といった具体のようである。

→今回はDigest認証を使ってアクセス制御を実装することとした。

なお、Digest認証ではアクセス先のURLやユーザ名はハッシュ化されず平文で送られるため、それらも隠したい場合はHTTPSによる通信暗号化をする必要がある。

最低限の対策

ポート変更

このシステムではラズベリーパイ側のサーバはFlaskで実装している。Flaskはデフォルトで5000番ポートを使用するが、まずはこれを変更する。49513~65535が自由に使えるポート番号のようだ。

もちろん、これだけでは対策にはなり得ないわけだが、デフォルトポートを狙ってくるようなアクセスには効果があるだろう。

Flaskでポート番号を変更する場合は、起動時の引数にport=(好きなポート番号)を加えてやれば良い。 (以下の例ではFlaskの処理をスレッド化して起動している、これについては過去記事参照)

    rest_service_thread = threading.Thread(name='rest_service', target=app.run, args=('0.0.0.0',), kwargs=dict(debug=False,port=PORT_NUM))
    rest_service_thread.start()

URL変更

以前の記事ではモザイクを掛けていたとはいえほとんどURL丸見えで公開してしまったため、そりゃ見る人が見ればアクセスしてくるよね、という状態だった。(実際にアクセスログを見るといくつか外部からのアクセスが見られた。もちろんインターホンが鳴った時以外は何も反応しないわけだが)

なので、URLはより複雑な名前に変更した。(個人使用のシステムなのでURLは非公開)

(ただ、先程述べたように今回実装したDigest認証はURLは平文で送られるため、もしパケットキャプチャなどされた場合にはURLはわかってしまう)

Digest認証の実装

FlaskにDigest認証を実装する。今回は以下のページを参照した。

qiita.com

Digest認証はFlask拡張を入れれば簡単に実装できる。

pip3 install flask-httpauth

で、スクリプト上でインポートする。

from flask_httpauth import HTTPDigestAuth

Flaskインスタンスを生成する際は以下の通り。

# Flaskインスタンス生成
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your secret key'
auth = HTTPDigestAuth()

で、実際にアクセスがあったときの処理部分に以下を記述。

@app.route("/YOUR_URL/")
@auth.login_required
def your_function():

次に、ユーザネームを引数とし、あらかじめサーバに保存してある認証用の情報からパスワードを返す関数を実装する。ID/パスワードはDictionaryで持っておくのが楽で、スクリプト上に直接書いてもよいが、今回は外部ファイルから読み込んでいる。

@auth.get_password
def get_pw(username):
    # ユーザデータ読み込み
    users = pickle_load('./users.pickle');
    if username in users:
        return users.get(username)
    return None

ID/パスワードなどの認証用情報は以下の記事を参照してあらかじめ外部ファイルに出力しておいた。

neuryo.hatenablog.com

ここまで実装すると、http://[IPaddress]:[PORT_NUM]/[YOUR_URL]/にアクセスした際にID/パスワードの入力を要求される。

f:id:kaname_m:20200209135658p:plain
認証画面

これで最低限のアクセス制御が実装できた。なお、アクセスする度ID/パスワードを打っていては配達員が帰ってしまうため、それらの情報はブラウザに保存した。

その他

セキュリティ対策にはならないのだが、私はこのシステムにアクセスする端末を決めているので、その機種以外でのアクセスをhttpヘッダのUser-Agentを使って弾いてみた。

User-Agentについては以下の記事などが詳しいので、詳細はそちらを参照のこと。

qiita.com

from Flask import requestして、アクセスがあった際にrequest.headers.get('User-Agent')すると、例えば以下のような文字列が得られる。

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/xxx.xx (KHTML, like Gecko) Chrome/xx.x.xxx.xxx Safari/xxx.xx

(xxxはバージョン番号)

文字列を見るとわかるように、アクセス元のOSなどがここからわかる(上の例の場合はWin10 64bit)。

今回はこのシステムにアクセスする端末を限定し、それ以外の種類のものからのアクセスは弾くことにした。 (もちろん、これは個人を識別できるIDでは全く無いため、気休めである)

その他の例として、iPadから接続すると以下のようなUser-Agentが得られる。

Mozilla/5.0 (iPad; CPU OS 13_3 like Mac OS X) AppleWebKit/xxx.xxx (KHTML, like Gecko) CriOS/xxx.xxx Mobile/xxxx Safari/xxx.xxx

iPad」の文字列が入っており、iPadからのアクセスであることがわかる。

まとめ

ラズベリーパイを使った再配達撲滅システムに最低限のセキュリティ対策を行い、アクセス制御(Digest認証)を実装した。

個人で使うシステムとしてはこれで十分と思うが、もし足りない部分があれば追加で実装していこうと思う。

余談

暗号の歴史について、以前読んだこの本が非常に面白かったのでここで紹介しておく。

暗号解読―ロゼッタストーンから量子暗号まで

暗号解読―ロゼッタストーンから量子暗号まで

  • 作者:サイモン シン
  • 出版社/メーカー: 新潮社
  • 発売日: 2001/07/31
  • メディア: 単行本