TL;DR
- 某サービスに「ソースコードからIPアドレスが流出してしまう」という脆弱性が発見され、世間を騒がせていますね。
- そもそもなぜソースコードからIPアドレス流出したの?という問題について語ってみます。
- 筆者は某サービスの開発に関わっていたわけではないので、当記事で語るパターンでの流出だったとは断言できません。
- そのため、起こりうる一つの「怖いパターン」の事例共有として、ご参考までによろしくお願いいたします。
- 初学者にもわかるように少し噛み砕いてご説明します。
前提
まず大前提ですが、統計のためやバグの追跡のためにサービス利用者のIPアドレスを収集すること自体は不思議なことではありません。
問題は、__なぜそれがフロントから見える状態になっていたのか__です。
統計を取るにせよ、サービスに嫌がらせをしていた投稿者を特定してそのIPアドレスをブロックするにせよ、DBに値が残っていれば十分で、それをフロントに持ってくる意味ってありませんよね?
少なくとも私はパッと思いつきません。
それにフロントに持ってくるということは、今回のような「流出のリスク」があるということでもあります。
要は、__「DBにさえあれば良いはずのIPアドレスがなぜフロントから見えてしまったのか?」__が今回理解すべきことになります。
サーバサイドの動きを考えてみる
その前提の上でDBに思いを馳せてみましょう。
例えば__「ブログ投稿サービスで、記事の情報を保存する『Article』テーブルが存在していた」__と考えてみます。
正規化がどうのこうのという難しいことは考えなくて良いです。
以下の要件で考えてみましょう。
- Articleテーブルは1記事の情報を1レコードで保存している
- Articleテーブルには「記事タイトル」「本文」「投稿日付」などのカラムがある
- そのカラムの中に「IPアドレス」もあった
不思議な仮定ではないはずです。
さて、お話が変わって現在三大フルスタックWebフレームワークといえば
- Rails
- Laravel
- Django
です。本題ではないのでなぜこれが三大なのかという議論は避けますが、少なくともよく使われているフレームワークであることには間違いないでしょう。
参考:どれ使うべき?3大WebフレームワークRails・Django・Laravelを徹底比較してみた
その中で、RailsとLaravelのORM(DBへのアクセスを簡単にしてくれる仕組みのことですね) には共通の特徴があります。
Active Record__という特徴です。
ざっくりといえば、「DBの1レコードをひとつのオブジェクトとして扱おう!」__という考え方です。
賛否両論、メリットデメリットがある仕組みではあるのですが、個人的には大好きな仕組みです。
詳しくはこちらもどうぞ (宣伝)
開発への異常な愛情 または私は如何にして嫉妬を止めてActive Recordを愛するようになったか
閑話休題。
さて、例えばLaravelの場合、その"Active Record"を利用すると、このようなことができます。
// ログインしているユーザーの最新の記事のIDを特定
$currentArticleId = $this->getCurrentArticleIdByLoginUser();
// 最新の記事(1レコード ≒ 1オブジェクト)を取得
$currentArticle = Article::find($currentArticleId);
// オブジェクトをビューに渡す
return view('current_article.index',compact('currentArticle'));
分かる人は「コントローラーでそんな処理するな」とか「なんでコントローラーで明らかに特定のModel専用の独自処理を実装してるの?怖……」とか言いたくなったかもしれませんが、とりあえず今は無視してください。
要するに、__「Articleテーブルの、ログインユーザーによる最新の1レコード ≒ 1オブジェクト」を取得して、フロントにパスしている__ということだけ伝われば十分です。compact('currentArticle')
はそのためのおまじないです。
さて、このオブジェクトが格納されている変数$currentArticle
は、当たり前ですが1レコードの情報全てが格納されています。
フロント側……Laravelは.blade.php
というファイルにHTMLとPHPが融合したようなものを記載していくのですが……このbladeファイルの中では、$currentArticle
が持っている情報全てにアクセスすることができます。
<h1>{{ $currentArticle->title }}</h1>
こう書くだけでWebページにタイトルが表示されます。とても便利です。
話を戻しましょう。なぜフロントでIPアドレスが見えてしまったのでしょうか?
仮説1。
bladeファイル(あるいは他のフレームワークにおける同様の機能)の中で
{{ $currentArticle->ipaddress }}
と書いてしまったのでしょうか?
その可能性は低いと思われます。意味がありませんし、レビュー文化があればすぐ気付くでしょう。
仮説2。
{{ $currentArticle }}
と書いてしまった?これもあまり意味がなく、すぐ気付くため考えにくいです。
ちなみに実際にこれをやってみると確かに流出はするのですが、そもそもフロントに$currentArticle
の中身を展開した文字列がズラーッと表示されますw ソースコード上の流出どころの騒ぎではありませんね。
仮説3。
<h1>{{ $currentArticle->title }}</h1>
と書くだけでもフロントに$currentArticle
の情報は引き渡っているから、$currentArticle
のメンバ変数は全て可視化されてしまっていた。
こう考えた人、ほぼ正解!
ただ少なくともLaravelくんはとても賢いので、そのようなことにはなりません。
仮説4。
フロント専用のフレームワークを使っていたことが原因。
このパターンについてご説明いたします。
JSフレームワークを使っていたとしたら?
フロントの三大フレームワーク、これは議論を待たずに
- React
- Vue
- Angular
ですね。これらはいわゆる「三大フロントフレームワーク」と呼ばれるものですが、言い換えると「三大JavaScriptフレームワークです」
JavaScriptのフレームワークなんです。
さて、JavaScriptがPHPのオブジェクトを使う方法はあるでしょうか?
PHPでなくても、RubyでもPythonでも同じです。
使えるように変換する必要がありますよね?
そう、JSON(JavaScript Object Notation) です。
……厳密に言えば、JavaScriptのオブジェクトに変換するために、一度JSONに変換する必要があるというお話です。他にも色々方法はありますが、一番良く使われるのがJSONでしょう。
そういうわけで、
$currentArticle = Article::find($currentArticleId);
$currentArticleJson = $currentArticle->toJson();
のようにしてあげるとJSON(文字列)に変換できます。
あとはこのJSON文字列$currentArticleJson
をフロントにそのまま引き渡せば、JavaScriptがその文字列を処理する際に流出してしまう……というオチです。
ちなみにLaravelではJSONに変換する際に「この項目はJSONに変換した際に変換の対象にしない!」と指定するためのフィールドがあります。
モデルから変換する配列やJSONに、パスワードのような属性を含めたくない場合があります。それにはモデルの$hiddenプロパティに定義を追加してください
本題ではないので詳細は省きますが、この記載から属性:"ipaddress"の指定が漏れてしまうということは十分にあり得ることです。
結論
繰り返しますが、この記事で書いたのはあくまでもあり得るパターンの一つであり、今回世間を騒がせた件は別の理由である可能性もあります。例えばそれこそ5chではIPアドレスをフロントで表示する機能もあるので、そのような機能がどこかにあったのかもしれません。
ただ、ここで書いた事例は他のIT企業でも十分に「起こりうる」ことです。
ここで紹介したパターンでは、そのリスクはフレームワークを他のフレームワークに適合させるために = 汎化させるために、「JSONへの明示的な変換」をしたことで発生するものでした。
「こうするとリスクは防げる」という銀の弾丸ははなかなかないものなのですが、少なくともJSONへの変換 = いわゆるシリアライズした上でのデータの受け渡しはときにリスクを伴う、ということは覚えておいても良いかもしれません。
お読み頂きありがとうございました。
ではまたどこかで。