背景の詳細
最近、ユーザー A が、ユーザー A と (ほぼ) 同時に、コントローラーが生成したダウンロードにアクセスしようとしていたユーザー B のセッションを意図せず乗っ取る可能性があるという問題が発生しました。
これが発生するために必要なすべての条件について、まだ 100% 確信しているわけではありませんが、本番環境およびステージング環境で問題を確実に再現できます。これらの環境の重要な詳細は次のとおりです。
環境の詳細
アプリケーション サーバー: Phusion Passenger 5.0.21 または 5.0.24 (両方のバージョンを試し、両方とも問題を再現したことを意味します)
フレームワーク: Rails 4.2.4
言語: Ruby 2.2.3
オペレーティング システム: CentOS 6
興味深いことに、 Phusion Passenger 4.0.53を使用してこの問題を再現することはできません。
ハイジャックを再現する手順
単純すぎて真実とは思えないかもしれませんが、必要なことはこれだけです。
- ユーザー A がシステムにログインする
- ユーザー B がシステムにログインする
- ユーザー A と B の両方が同じダウンロード ボタンを (ほぼ) 同時にすばやくクリックします
誰かのセッションが意図せずハイジャックされるのに必要なのはそれだけです。(A と B のどちらのセッションがハイジャックされるかは、ルーレットのように思えますが、見かけほどランダムではありません。)
ページに表示されている現在のセッションのユーザー名と姓を確認できるため、ユーザーのセッションがハイジャックされたことがわかります。
毎回、1 人のユーザーが別のユーザーに「なります」。
ユーザー アクセス ロールが異なる場合は、異なるレベルのアクセス権を持つ可能性があることも意味します。たとえば、誰かが意図せずにセッションをハイジャックした場合、突然管理者になる可能性があります....
必要なコード
バージョン 4 に戻すと、この問題は発生しなくなったため、最初は Phusion Passenger がこの問題を引き起こしている唯一のもののように見えました。
ただし、いくつかのコードを変更した後、コントローラー コードのメソッドがこの問題の発生に寄与しているように見えると判断しました。
Phusion Passenger 5.0.21 または 5.0.24 でこの問題を発生させる Controller メソッドの例を次に示します。
def sample_method
respond_to do |format|
format.csv {
headers.merge!({'Cache-Control'=>'must-revalidate, post-check=0, pre-check=0'})
render :text => proc { |response, output|
100.times do |i|
output.write("This is line #{i}\n")
end
}
}
end
end
私たちの Cache-Control の修正は、この問題に非常にうまく作用しているようです。
おそらく、これを変更するべきではありませんでしたが、キャッシュ制御パラメーターが突然別のセッションに突入する力をどのように持つことができるかについて、誰かが洞察を持っていることを願っています.
これをテストするには、Controller#sample_method にマップするルートが必要であり、クリックしてこのファイルをダウンロードできるボタンが必要です。
CSV が必要であり、CSV を返さないことを指定していることに気付きましたが、この場合、CSV は別のクラスで生成されるため、実際の CSV を proc に置き換えました。
上記の環境で上記のコードを実行すると、問題が再現されます。
その他の依存関係
ユーザー認証にはDevise gemを使用しています。この問題を再現するためにテスト アプリケーションをセットアップする場合は、Devise と 2 つのアカウントのセットアップが必要です。
ちなみに、これをテストするには、2 台の別々のコンピューターで 2 人が必要になります。両方が同時にシステムにログインし、ボタンを同時に何度もクリックする必要があります。
この問題はとてつもないことのように思えますが、実際に私たちの環境で明らかになりました。それを実現するには、Phusion Passenger の特定のバージョン、特定のヘッダー セット、およびレンダー ブロックが必要ですが、実際にそうなります。(特定のコードは、「必要なコード」セクションにリストされています。)
修正
幸いなことに、コードを使用してこの問題を回避する方法があります。format.csv ブロック内で #send_data メソッドを使用できました。
コードの他のブロックの代わりに、次の行に沿って何かを実行しているだけです。
format.csv {
send_data data_here, filename: filename, type: 'text/csv', disposition: 'attachment'
}
これはよりクリーンなコードであり、より優れたコードです。しかし、Passenger やコード自体に何らかの大きな問題があるのではないかと心配しています。
アイデア?
コミュニティの専門家が、このような意図しないセッション ハイジャックがどのように可能であるかを説明できるかもしれません。
おそらく、セッション Cookie が適切に送受信されていないようです。(セッションにはデータベースを使用していません。)
この問題の特定のインスタンスに対する修正はありますが、この問題が最初に現れる可能性がある他の根本的な問題 (おそらくパッセンジャーにあるでしょうか?) があるかどうかはわかりませんでした.
非常に奇妙な問題のようです。
一方で、ヘッダーで行っていたことが悪い考えだっただけかもしれません。
あなたの洞察は大歓迎です!