主にプログラミングに関して。Python, .NET Framework(C#), JavaScript, その他いくらか。
記事にあるサンプルやコードは要検証。使用に際しては責任を負いかねます

スポンサーサイト

                
tags:
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Python-Tornado: セキュアなアプリケーションを考える

                
 GoogleAppEngineから自サーバでのTornadoアプリケーションへの移行を前回に考えてみて、リクエストハンドラの定義やテンプレートなどの単純なところの違いを書きだした。今回はTornadoアプリケーションで認証管理をセキュアにすることを考えてみる。



 認証済みのユーザにはユーザIDをそのままでクッキーに仕込むという悪手がある。認証が済んだクライアントのクッキーに、ユーザIDをセットする。以降の通信ではクッキーに入れられたユーザIDを読むことで認証済みかを確認するという手段。
 デモコードを用意した。正規のログインハンドラはここでは使わないのでなんらか認証処理を書かなければならないようになっている。不正ななりすましを再現したいので、ユーザIDがそれぞれ'suzuki416' 'ito333' 'sato416'のアカウントが登録されているとした。どれも単純なIDで、ユーザ側でIDを決められるサービスを続けていれば、そのうち取得者が出るだろうIDだ。
import tornado.auth
import tornado.ioloop
import tornado.web
import tornado.gen
import tornado.httpserver


login = set(['suzuki416',
'ito333',
'sato416'])

############################################# admin

class GoogleLoginHandler(tornado.web.RequestHandler, tornado.auth.GoogleMixin):
@tornado.gen.coroutine
def get(self):
global login
if self.get_argument("openid.mode", None):
user = yield self.get_authenticated_user()
## authenticate
self.set_cookie("user", str(id_))
self.redirect(r"/admin")
else:
yield self.authenticate_redirect()


class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
return self.get_cookie("user")

class ManageHandler(BaseHandler):
@tornado.web.authenticated
def get(self):
""""""
self.write("authenticated")



############################################# initialize and run
if __name__ == "__main__":
settings = dict(cookie_secret="foooooooooooooooooooooooooooooo",
debug=True,
)


http_server = tornado.httpserver.HTTPServer(
tornado.web.Application([('/login', GoogleLoginHandler),
('/admin', ManageHandler),
],
**settings),
)
http_server.listen(8890)
tornado.ioloop.IOLoop.instance().start()

 これはIDが手に入れられれば、簡単になりすましができてしまう。クッキーはクライアント側でもいじれるものだから、たとえばuser=suzuki416というクッキーをクライアントがセットすれば、パスワードも知らずに成りすましができる。suzuki416というIDが登録済みだと知らなくても、下記の総当たりコードですぐにある程度の範囲の登録済IDを調べて成りすましができる。
import urllib2

family_names = set(['sato',
'suzuki',
'takahashi',
'tanaka',
'watanabe',
'ito',
'yamamoto'])

def d_attack():
for name in family_names:
for i in xrange(0, 1232):
opener = urllib2.build_opener()
session_id = name + str(i)
opener.addheaders.append(('Cookie', 'session=' + session_id))
try:
f = opener.open('http://localhost:8890/admin')
yield session_id
except urllib2.HTTPError, e:
pass

for user in d_attack():
print 'user ID detected: ' + user

 予測されやすいIDをクッキーにセットして認証管理をするなということ。最近でもECサイトでそういうものがあったらしい。
とあるECサイトのアクセス制御不備
 こういう総当たりの力技に対抗する手段として、Tornadoではクッキーにセットする値を複雑なものにエンコードするメソッドが用意されている。これを使えばクッキーにセットされる値は予測不可能なものになりながらも、サーバではセットされた値を元の値に簡単にもどせるので、適当だったり総当たりによるクッキー改変の成りすまし狙いに耐性ができる。
self.set_secure_cookie('foo', 'suzuki416')
self.get_secure_cookie('foo')
Cookies and secure cookies

 でもなんらかの手段で正規のクライアントのブラウザが持っているユーザIDがエンコードされているクッキー値を手に入れられたとする。そうするとまたずっとなりすましができるようになりかねない。ユーザIDをクッキーに入れるのはやめて、クッキーにはセッション変数をセットする。それでなりますましの永続化が防げる。


 クッキーの値を知る一つの手段として、通信路上にいる第三者の盗聴がある。これは端末のモバイル化が進んで、外で無線を使うようになった今ではよく考えるべきところだ。通信をSSL化して通信路上の盗聴は防ぐ。
Tornado SSL certs

 クロスサイトリクエストフォージェリというのもある。第三者のサイトが認証済みのユーザにリンクを踏ませたりして、データの改変や削除をユーザが意図していないのに行わせてしまうものだ。認証済みユーザは正規のセッション変数をクッキーに持っているので、そのままだと少なくとも自データの改変権限の有無のクッキーによる確認などはパスしてしまうだろう。これはTornadoで専用の対策が用意されている。
Cross-site request forgery protection


 とりあえず考え付くのは以上。様々な脅威への対策を検討して練り込んで、安心して使用できるWebアプリを。
            

コメントの投稿

非公開コメント

プロフィール

hMatoba

Author:hMatoba
Github

最新記事
リンク
作ったものなど
月別アーカイブ
カテゴリ
タグリスト

検索フォーム
Amazon
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。