2019年8月29日木曜日

ユーザログイン(ユーザ認証)の歴史

https://qiita.com/YukiMiyatake/items/4c2162f85fe3c9c203a7
シェアしました。

はじめに

Webアプリに限らず、ほとんどのサービスではユーザ名とパスワードを入力し、ログインをしてから
サービスを使う
OSのログインしかり、メールアプリ、Webサービスなど
ユーザ名とパスワードの入力という基本部分はほぼ変わってないが
時代とともにより安全な仕組みへと進化しているので
私の知る限りの説明したいとおもう

認証、認可の違い

まずややこしいのが、認証、認可(承認)のちがいである
認証(Authentication) は、ユーザーが正しいかどうかの確認(パスワード認証)で
認可、承認(Authorization)は、アクセス権の確認である
昔はこの認証、認可を同時に行っていたが、OAuth等の登場から厳密に分ける必要が出てきた
ただ、いまだにHTTPヘッダでも混同して使われていたりする

歴史

ただし順番に並んでいるともかぎらないし、抜けもある

平文ベーシック認証

いわゆるBasic認証と称して、ユーザ名とパスワードが平文で送られていた
今考えると恐ろしい世界だ
悪意ある人が一度その情報を取得すると、簡単に乗っ取ることが可能
また、パスワードは他のサイトでも使い回す事が多いので、下手すると全てのサービスが乗っ取られる
初期の頃は、フォームからPOSTなどで平文パスワードをログイン時に送っていた
(なかにはGETでクエリパラメータでURLにパスワードが平文でのってる恐ろしいサービスもあった)
Postで送らない方法もある。たとえばHTTPヘッダに乗せるなど
また、TextではなくJson等で送る方法もある
が、平文であればいずれも問題である

ハッシュ

平文でパスワードを送ると盗まれると危険である
そのためパスワードをHashし、それを送ることで平文よりは安全になる
Hashとは、不可逆な一方向変換である
https://qiita.com/chroju/items/3ddae568206b8bc3d8f9
これで、パスワード自体が取られる事はなくなった
passwordより5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8 の方がわかりにくいでしょ?
sha1やmd5のような暗号強度の低いものはレインボーテーブルという、Hashの逆引きデータが販売されている
レインボーテーブル
https://dev.classmethod.jp/security/rainbowtable/
ので、より強度な暗号アルゴリズムを使い、さらに ソルトやストレッチングを行い
暗号強度を増やすべきである

ランダムソルト&ストレッチング

パスワードをハッシュ化したので、パスワードを知られる事はなくなった(解読されなければ)
しかし、ハッシュ値を知っていればなりすましログインも可能だし、同じハッシュのユーザは同じパスワードだとわかる
ゆえに、ユーザーごとに異なるSaltをつければ上記の問題がなくなり、安全性もあがる
Saltとはパスワードに追加する追加文字列である
また、Saltと同時にストレッチング(Hashの重ね掛け)を行うと強度が増す
上記処理には PHPの password_hash 関数などが有名

セッション&クッキー

通信のたびにユーザ名とパスワードを送るのはハッキングを受ける確率も増えるし
毎回認証のオーバーヘッドは避けたい
そこでセッションを使う
はじめにサーバはクライアントにランダムな値のセッションIDを送る。
サーバはセッションIDと紐付ける事でクライアントの状態を保持できるため
認証が成功すれば、その後はセッションIDの確認だけで認証が不要になる
セッションIDは盗まれても、ワンタイムなのでそこまで痛くない。一定期間で無効になるし、パスワード情報が何もない
また、セッションには認証だけでなく、それまでのやり取りのステート(例えばショッピングカートにアイテムを入れる)も保存することが可能
https://qiita.com/7968/items/ce03feb17c8eaa6e4672

セッションハイジャック

URLパラメータにセッションIDを入れると簡単にセッションを乗っ取られる
セッションIDは Cookieに保存することで、簡単かつ比較的安全にセッション管理が出来る
(もちろんCookieを盗んだりも可能であるが、URLパラメータに表示するよりは圧倒的にましである)

セッション・フィクセーション

攻撃者が有効なセッションIDを対象のCookieに送り込んで、以後そのセッションIDが対象とひも付き
攻撃者が自由に乗っ取れる
これは、セッションIDがユーザ固有ではなく、セッションIDがランダムでかわらないサイトだとおこりうるが
ユーザごとにセッションIDを発行していればおきない

CSRF(Cross Site Request Forgery)

https://qiita.com/maruloop/items/e14d02299bd136f4b1fc
ログインとは少し話がずれるが、気をつける必要がある
何かしらの方法で悪意のあるURLやJavaScriptを踏ませる
具体的にはimageタグやメール等をクリックさせる
そうすると、偶然裏でサービスにログインしていた場合、悪意のあるURLが正規呼び出しだと勘違いされ
実行されてしまう
例えばブログへの書き込みだったり、銀行振り込みかもしれない
単純な攻撃ではあるが、防ぐには少し厄介だ
Referで判定できればいいのだが、そうもいかない
そこで最も有効だと思われるものに
Hiddenにワンタイムトークンを仕込む事である
トークン(CSRFトークンと呼ぶ)を識別することで、認証を行ったページからのアクセスを判断可能
c-yanさんの指摘で、Hiddenではなくヘッダに入れる実装もあるとのことですが、そのあたり他の要素もからみ纏められなかったのでリンクにて。
https://www.scutum.jp/information/waf_tech_blog/2017/11/waf-blog-051.html

Authorizationヘッダ

Webページの場合、セッションをCookieに入れると良いとかいたが
WebAPIではCookieが使えないため、Cookieに依存すべきではない
その場合はAuthorizationヘッダを使うべきだ
間違ってもURLにクエリパラメータとして置いてはいけない。
下記のHTTPSで保護しても無駄になるし

HTTPS

TLSレイヤーにて暗号化を行っている
設定によっては脆弱性があるが非常に強力で
基本的にはHTTPSを使えばBasic認証で送ってもかなり安全ではある
ただし、古いバージョンは脆弱性があるので注意
下記のまとめは個人的に有用だと思った
https://qiita.com/mpyw/items/bb8305ba196f5105be15

OAuth、OpenID

色々あり今使われているのは
OAuth2とOpenID Connect
簡単にいうと、OpenID Connectは、OAuth2 + User情報取得 である。
だが実際はFacebook等のOAuth2のサーバでもUser情報は取得している(/meへリダイレクト)
やはり認証と認可をセットにすることが多い。
つまり、OpenID Connectだと、取得ページへのリダイレクトがないぶん効率良く認証が出来る
どちらも肝の部分は
認証サーバ(たとえばFacebookやTwitter)を使い、認証サーバにログインし、権限のトークンをもらう(認可)
そのトークンの権限を使い、認証サーバの操作が可能になる。
たとえばFacebookのウォールの読み書きをしたいなら、読み書き許可のあるトークンを使いFacebookにアクセスする
ここで素晴らしいのは、ログイン(認証)はFacebookにFacebookで行う事ができるため
自サーバにFacebookのログイン情報を保持しなくてよい

JWT

Json Web Token
OpenIDのトークンで使われる
https://qiita.com/TakahikoKawasaki/items/8f0e422c7edd2d220e06
OAuth2でも使われていると思ったが、アクセストークンは定義されてないっぽい??
JWSで出来る事は、改ざん防止だ(暗号化はない)
仕組み的には、ヘッダ、ペイロード、署名から成り立つ
署名にはメッセージのハッシュ値が入っているので、改ざんすることは不可能だ
ただし、暗号化されていないので、だれでも内容を見ることはできる
一般的にはJWSのペイロードに ユーザIDとトークンの有効期間を置く
JWSは署名により改ざん不可能なため、このユーザしか使えない、期間限定トークンだ
セッションIDに近いが少し違う
セッションIDはランダムな値なため、サーバ上のDBと照らし合わせて判定が必要だ
Webサーバがスケールした時、異なるサーバへ割り当てられた場合セッションの照合が出来ない
(そのため、複数のWebサーバで運用するには DBなりKVSでのセッション共有必要)
ところがJWSの場合は、サーバの秘密鍵で署名の有効性を確認できるので
KVSもDBも不要です(DBについては後述・・)
JWEは暗号化も含めた規格だが、あまりドキュメントがないし
HTTPSがあれば大丈夫(とか思ってはいかんのだろうか?)

JWTの危険性

前に仕事で、JWTは危険だという記事を見せられこれについてどう思うか?と言われた
JOSEというのは、JWTの中の署名(JWS)と暗号化(JWE)の総称である
一般的にJWTといえばJWSの事だと思っていい
JOSEの脆弱性は、暗号化方式を noneやHMAC(共有鍵方式)
に書き換えて改ざんが出来ることだ
大抵のフレームワークでは既に対応されてると思いたいが
暗号化方式の noneとHMACは弾くようにすべきだ
(これらがアルゴリズムと設定出来る事が脆弱性だ)
なので、上記を気をつければ、積極的に使うべきだと思う
JWTはBase64urlエンコードされているためURLセーフだ
OAuth等でよく使われる bearerトークンは Token68を満たす必要があるが
Base64(url)はToken68要件を満たすため、JWTトークンはそのままbearerトークンとして送る事ができる
HTTPヘッダに
Authorization: bearer 
と送ってしまおう

JWTのsalt

上記、JWSを使えばセッションIDのようにDBを参照する必要がない
と言ったが、ちょっとウソでした
改ざん防止してあるので、DBなしで認証出来るが
同じトークンを使い回す事が可能である
(有効期間をつければ、一定時間で無効にはなるが)
よりセキュアに、トークンを1回で使えなくするために
ペイロードにユーザ事、通信のたびに変動する乱数のSaltを入れるべきである
結果として、SaltをユーザごとにDB管理する必要がある

まとめ

HTTPSを使い
パスワードは十分にセキュアなHash方式を採用し
ユーザーごとに異なるSalt付与やストレッチングを行なう
DBにはHashされたパスワードを保存し、ログイン時に認証を行なう
その後の通信は、SaltつきのJWT等でワンタイムトークンにて認証する
HTTPSで保護されてるが、通信内容を暗号化したい場合はJWEなり別途暗号化を入れる
ぐらいしておけば、普通の用途には十分と思われる
暗号化とデータ改ざんは、基本的には強固になればCPU負荷が増えるため
セキュリティ要件とのトレードオフになる事が多い

0 コメント:

コメントを投稿