ID とかについて
ID
Identification
特定の個人やエンティティを一意に認識・識別するために使われる情報。
そもそも「識別」とは。
「身元を識別する」と言う時、それは「個人やエンティティが実際に主張している通りのものであることを確認するプロセス」のことを指す。
識別の目的には以下のようなものが考えられる
- セキュリティの確保: 不正アクセスや情報漏洩を防ぐために、権限のないユーザーが重要な情報やリソースにアクセスすることが出来ないようにする。
- アクセス管理: 適切なユーザーにのみ特定のデータやシステム機能へのアクセスを許可することで、機密情報の保護を図る。
- パーソナライズされたユーザーエクスぺリンスの向上
身元を識別するプロセスにおいて、ユーザーは自身の身元を証明するための情報を提供する。
この認証のための情報に関して、認証の三要素という定義がある。
認証の三要素は以下
- 知識情報(パスワード・秘密の質問のこたえなど、知っているもの e.g. Gmail アカウントのメールアドレスとパスワード)
- 所有情報(身分証明書やデジタルデバイスなど、持っているもの e.g. SMS が送信されるスマートフォン)
- 生体情報(指紋・虹彩・静脈など、生体的な特徴 e.g. 顔認証や指紋認証)
「メールアドレス(あるいはユーザー名)とパスワードを使ったログイン」は「知識情報を用いたユーザーの身元の識別」と表すことが出来る。
ユーザーは「メールアドレス」と「パスワード」という知識情報について、これらの情報を所有しているのは自分だけ = この情報を知っているのは自分だけ(自分以外の誰も知らない)、という前提に基づいて自身の身元の主張を行う。
身元の主張のために受け取った情報を検証する側のプロセスを確認する。
保存されている情報の中から、メールアドレスと、そのメールアドレスの所有者しか知り得ない(という前提に基づいている)パスワードの値が、ユーザーが提供(主張)する値(メールアドレスとパスワード)と一致しているかどうかを検証し、一致していた場合、その主張を正しいものだとする。
このプロセスについて、検証する側からすると、ユーザーが提供してきた知識情報に基づいて認証のプロセスを行うものの、実際に知識情報の本来の所有者が認証を要求してきたかどうかは分からない。
知識情報は、その所有者以外は誰も知らない、という前提で扱われるので、盗み出されたパスワードを使った「なりすまし」を検出することは出来ない。
知識情報は、ユーザーが忘れてしまったり、第三者に推測・盗難されてしまったりするリスクが存在している。
所有情報は、単なる記憶や知識である知識情報とは違い物理的に存在するデジタルデバイスに依存するので、知識情報よりは盗まれにくい(なりすましの耐性が高い)。もちろん、デバイスそのものを紛失してしまったり、盗まれてしまったりといったリスクは存在している。
生体情報は、その人間自身が持つ固有の特徴になるので、所有情報よりもさらになりすましの耐性が高くなる。だが、生体情報はプライバシーに対する懸念事項が存在したりするようだ。
これらの認証情報を複数組み合わせて認証を行う認証である多要素認証(MFA)を行うことで、なりすましに対するリスクを大幅に低減させることが出来る。
ただし、現実的かどうかはおいておいて究極的に考えると、生体情報を含んだ多要素認証を行なっていたとしても 100% なりすましを防ぐことは出来ない。
とはいえ、適切な多要素認証を行うことでほとんどのケースで十分なセキュリティを確保することが出来るのだろう。
ID は個人の身元を識別するためにユーザーが提供する情報のことだった。
この情報は知識情報・所有情報・生体情報(認証の三要素)のいずれか、あるいは、これらの組み合わせによって表される。
IDaaS
自前で ID を管理し、認証に関する仕組みを実装することは可能。
だが、認証システムの実装は複雑で困難なものであり(程度によるのだろうが)、また ID というデータの性質上、プライバシーへの配慮なども欠かすことが出来ない。
そうした ID を扱う上での困難さ・複雑さを外部のサービスとして利用出来るようにしたものが IDaaS 。
簡単なシナリオで整理する。
管理者により IDaaS によってメールアドレスとパスワードという知識情報が ID として払い出される。
この払い出された ID は組織の従業員などに共有される。
知識の従業員は企業の管理下にあるデータやシステムにアクセスするために、自身の身分を識別するよう ID を提出する。
ID を受け取ったシステムは、この ID を払い出した IDaaS へと問い合わせを行い、身元の識別結果を行う。
管理者が発行した発行したパスワードは一時的なものなので、ID の持ち主となったユーザーによってパスワードの更新が行われる。(パスワードを更新しないと「自分以外誰も知らない」という ID の前提が確保出来ないので)
「ユーザー」と「ID」を 混同してしまっているかもしれない。
ソーシャルログイン
多少知識がある Auth0 の使用を想定して書いている
Auth0 などの IDaaS ではソーシャルログインを行うことが出来る。
ユーザーが新しいサービスの利用を開始するとき、従来ではユーザー名とパスワードを新たに作成する必要があった。
ソーシャルログインを使うと、Google アカウントなどの既に所有しているアカウントを使うことが出来るので、ユーザーはアカウント作成の手間を省くことが出来るようになる。
ソーシャルログインを行うと、IdP が管理するユーザーアカウントの ID が Auth0 のユーザーと紐づくことになる。
OAuth2.0 のフローを使って、IdP から IdP が管理するユーザープロファイルへのアクセス権が Auth0 に送られるという。
フローを考える
Auth0 が提供するユーザー認証の UI から Google でログインを選択する。
Google アカウントの認証情報の入力画面に遷移される。
Google アカウントの OAuth サーバーで認証情報の検証が行われ、正常な場合アクセストークンと交換するための認証コード付き でAuth0 にリダイレクトされる。
認証コードを受け取った Auth0 は認証コードとアクセストークンを交換し、アクセストークンを使ってユーザープロファイルを取得し、Auth0 が管理するユーザー情報と Google アカウントの認証情報の紐付けを完了させる。
このログインフローを行うエンドユーザーは Google アカウントを ID として扱う。また、その ID によって識別されるアプリケーションがプロファイルを取得するユーザーは Auth0 によって管理されるユーザーとなる。
この OAuth のフローにおいて、保護されたリソースへのアクセスを行う RP(Relying Party)は Auth0 になる。
アプリケーションは Auth0 と OAuth プロセスを介して Google アカウントから連携されたプロフィール情報を参照するが、直接 Google アカウントを管理するサーバーとやり取りをすることはない。
OAuth というか OIDC になるのかな? この辺りの理解は何度調べても曖昧だ。
ID トークン
Auth0 などの IDaaS は、ログインフォームからエンドユーザー提示した ID を受け取る。
IDaaS はその ID を使って「身元の識別」を行う。この ID の持ち主はこのユーザーだ、と特定する。
Google アカウントなどを使ったサードパーティによるソーシャルログインを行う場合は、サードパーティの IdP が ID の検証を行う。
Auth0 自身はエンドユーザーが提示した ID の検証が正常に完了したことを分かっているが、エンドユーザーがアクセスを試みアプリケーションはまだその事実を知らない。
誰が認証を試みたのか、をアプリケーションは検知しない。というより、誰かが認証を試みたかどうかも検知し得ない。
Auth0 などの IDaaS からアプリケーションに対し「誰々が正常に認証されましたよ」という事実を自身の「お墨付き」と共に共有する。
この時に使われるのが ID トークン。
アプリケーションは受け取った ID トークンの内容を検証することで、ID トークンに含まれるエンドユーザーのメールアドレスのプロファイル情報を参照することが出来る。
このアプリケーションが受け取った ID トークンがもしかしたら改竄されたものかもしれない。
アプリケーションが受け取るのは「誰々が正常に認証されましたよ」という情報のみなので、これだけではそれが本当なのかどうかまでは分からない。
もしかしたら、その情報が嘘なのかもしれないし、あるいは内容が改竄されているかもしれない。
ID トークンを渡してきた相手が信用出来ないということもあるだろう。
現実世界でも見ず知らずの相手に「これは本物ですよ」と言われても、それをそのまま信じるというのは難しいだろう。
アプリケーションは ID トークンの内容が本当なのかどうかを検証する手段を持っている。
ID トークンには、そのトークンの発行元(issuer)である認証サーバーの情報が含まれている。発行元を確認し、信頼出来る認証サーバーが発行したものなのであれば、その ID トークンの信頼性は増すだろう。
しかし、まだ改竄されている可能性が残る。
ある ID トークンの発行元を確認すると、いかにも信頼出来そう発行元の情報が含まれていたとする。
だが、この ID トークンは、実はまるで違う発行元が作成したもので、トークンの発行後に発行元の情報だけが書き換えられていたものだった。
アプリケーションはこのような場合に改竄の有無を調べるために ID トークンの署名を確認することが出来るようになっている。
(秘密鍵を用いたデジタル署名を、ID トークンを発行した認証サーバーが提供する公開鍵によって複合することで検証することが出来る)
要するに、改竄は検出出来る。
また、ID トークンにはそのトークンの発行先(audience)の情報も含まれている。
すなわち、ID トークンが発行されるときには「この ID トークンはどれどれのアプリケーション専用ですよ」という意図が込められているということになる。
ID トークンを利用するアプリケーションは自身を表すユニークなクライアントID を IDaaS に伝えておく。
IDaaS が ID トークンを発行する時には、その ID トークンが誰宛なのかを発行先として含めておく。
ID トークンを受け取ったアプリケーションは、発行先情報を確認することで ID トークンが自分自身に宛てられて発行されたものであることを確認する。
アプリケーションが受け取った ID トークンが本当に自分自身に宛てて発行されたものなのかどうかを検証しなかった場合どうなるかを考える。
同じ ID トークンの発行元となる IDaaS を利用する2つのサービス A と B があるとする。
サービス A に対して、あるエンドユーザーがログインを行い、認証に成功すると ID トークンが発行される。
この ID トークンは改竄されていない正真正銘正しいトークンである。
ここで、悪意のある第三者により、何らかの手段で ID トークンが盗まれたとする。
悪意のある第三者はこの ID トークンを使いサービス B へアクセスをする。
サービス B は受け取った ID トークンの検証を行うが、発行元は信頼出来るし改竄も検出出来なかったので、悪意のある第三者に対して正常にアクセス権の提供を行う。
サービス B が ID トークンの発行先を検証する場合、自身宛に作られた ID トークンではないことが分かる。
ID トークンに含まれる発行先が、トークンを受け取ったサービスを識別する値と一致しない場合、そのトークンは無効としなければならない。
とはいえ、改竄が行われていないし発行先も一致しているといって、それで完全に不正な使われ方をしていないと言い切れるわけではない。
あるサービス向けに発行されたトークンを盗み取った場合、トークンの検証をしたところで問題が検出出来ないので同じサービスでそのまま使われてしまうこともある。
これはリプレイアタックというらしい。
nonce などの OAuth フローに登場する属性については別セクションに分ける。
OAuth とか
Auth0 などの IDaaS がソーシャルログインを行う時 IdP との間で OAuth 2.0 フローのプロセスが開始される。
この OAuth 2.0 フローでは、アプリケーションではなく IDaaS が RP として IdP とやり取りを行い、Client ID と Client Secret を Auth0 のサーバーが安全に管理する認可コードフローになる。
Google Cloud で OAuth 2.0 の同意画面を組み込む時には明示的にクライアントアプリケーションを登録して ClientId を確認したりしていたが、ふと Auth0 では Client ID を明示的に設定したことがないのにソーシャルログインが出来ていたことに気づいた。
調べてみたら、デフォルトで有効になっている Auth0 の Google の Social Connection では組み込みで Client ID の発行がサポートされているようだった。
IDaaS を介さずに、SPA などで直接 IdP とやり取りをするのがインプリシットフローになる。
インプリシットフローは IdP による ID 検証が正常に完了すると認証リクエスト元のアプリケーションへリダイレクトをするが、この時に URL にアクセストークンがそのまま乗ってしまうのでセキュリティ上のリスクが高いという懸念があった。
Auth0 と違い、バックエンドサーバーで Client Secret を安全に保護できないような SPA を公開クライアントと呼ぶ。
インプリシットフローはセキュリティ上のリスクが高いので非推奨で、その代わりに認証後にアクセストークンではなく認可コードを返すようにする PKCE(Proof Key for Code Exchange)の使用が推奨されている。
アクセストークンが漏洩してしまうと、そのアクセストークンが認められている範囲で任意のリソースへアクセスすることが出来てしまう。
PKCE の場合だと、アクセストークンを直接返す代わりに認可コードを返すようにし、さらに追加の検証ステップを追加することで、認可コードが漏洩しても問題がないようなメカニズムになっている。
PKCE だと、Google などの IdP の認証サーバーへ認証フローの開始を表すリクエストを送る前に、手元でランダムな文字列を作成する。
このランダムな文字列のことを Code Verifier と呼ぶ。
次にこの Code Verifier をハッシュ関数を使いハッシュ化された値を生成する。
このハッシュ化された値のことを Code Challenge と呼ぶ。
クライアントはこの認証サーバーに認証フロー開始のリクエストを送る時に、この Code Challenge も一緒に送る。
さらに、この Code Challenge を生成した時に使用したハッシュ化アルゴリズムも一緒に伝える。
IdP はユーザーが入力する ID の検証を行う。この時、受け取った Code Challenge は保存しておく。
ID の検証が正常に行われると、クライアントに対して認可コード付きでリダイレクトされる(インプリシットフローではここがアクセストークンになっていた)
認可コードを受け取ったクライアントは、その認可コードとアクセストークンを交換するために IdP の認証サーバーへリクエスト送るのだが、この時に Code Verifier を一緒に送る。
認可コードと Code Verifier を受け取った認証サーバーでは、受け取った Code Verifier に対して事前にクライアントから伝えられていたハッシュ化アルゴリズムを使ってハッシュ化を行う。
ハッシュ化された結果が、クライアントから事前に受け取っていた Code Challenge の結果と一致していた場合「これからアクセストークンを返そうとしているクライアントはこの認証フローを開始したクライアントと一致している」ことが分かる。
もし、認可コードが漏洩してしまった場合でも、その認可コードとアクセストークンを交換するためには「認証フローが開始された時に認証サーバーに渡された Code Challenge の値とハッシュ化された結果が一致するような文字列」を指定する必要がある。
ハッシュ化アルゴリズムの数学的な性質により、この文字列の特定はとても困難なものになっている。
この特定の困難さにより、正しい Code Verifier の持ち主 = 認証フローを開始したクライアントだということが明らかになり、安全にアクセストークンの受け渡しが可能になる。
ただし、認可コードと一緒に Code Verifier も漏洩してしまうとどうしようもなくなる。
OAuth フロー全体のセキュリティ目的のためのパラメーター
state と nonce は OAuth 2.0 や OIDC のプロトコルで共通的に使われるセキュリティ向上を目的としたパラメーター
どちらもクライアントが認証サーバーに対して認証フローを開始するリクエストに含まれるパラメーターになる
state
認証サーバーに対して送られるリクエストに含まれた state は、認証サーバーがトークンを返す時に一緒に返ってくる。
CSRF 攻撃は前提として、何らかのサービスに対して既に認証が完了しているユーザーを標的とする。
OAuth の文脈でも同様に、何らかの認証サーバー上で既に認証が完了しているユーザーに対して攻撃が行われるシナリオが存在する。
参考にさせてもらった Stackoverflow での回答をベースに確認していく。
ブリリアントフォトというサービスがあり、このサービスはエースブックというサードパーティの IdP を使ってユーザーの認証を行なっている。
攻撃者であるマロリーはブリリアントフォトにアクセスする。
ブリリアントフォトはエースブックの認証サーバーへマロリーのブラウザをリダイレクトさせる。
マロリーはエースブックの認証サーバー上でユーザー名やパスワードなど、自身の認証情報の入力を行う。
認証に成功すると、認可コード付きの URL へマロリーのブラウザがリダイレクトされようとするが、マロリーはこのフローが行われないよう処理を中断させ認証コード付きの URL を控えておく。
この認可コードは、マロリーのために発行されたもので、マロリー固有のアクセストークンと交換するために使われるものとなっている。
マロリーは攻撃のターゲットであるアリスを、どうにかして先ほど発行させた URL へアクセスさせようとする。
アリスがブラウザでマロリーが用意した攻撃用のリンクをクリックしたとする。
認可コードを受け取ったブリリアントフォトは、その認可コードを発行するに至った認証フローを開始したのがアリスが操作しているブラウザからだったのかどうかを関与しない。
認可コードを受け取ったブリリアントフォトは、そのままマロリーの認可コードを使いエースブックへアクセストークンとの交換リクエストを実行する。
アリスがブリリアントフォトを使って何らかの操作を行う時、アプリケーションではマロリーのアクセストークンが有効になっている。
この状態でアリスが写真をアップロードしようとすると、それはマロリーのアカウントに紐づく形で保存されてしまうことになる。
このシナリオにおける問題点は、認可コード付きのリクエストを受け取ったブリリアントフォトが、その認可コードの発行と、認可コードの発行に至るまでの認証フローを開始したユーザー(ブラウザ)が同一かどうかを検証出来ていなかった点。
state パラメーターを使うと、認証フローの開始時点でブラウザは自身が作成したランダムな値(state)を控えておき、認証フローのプロセスが進んだ後、認証コードと共に返ってきた state が自身が控えておいた値と一致するかどうかを検証することで、ブラウザを操作するユーザーの意図通りに認証フローが行われたことが分かるようになる。
サービスを利用するエンドユーザーに対して、ユーザーが意図していないアカウントとして認証されないように state は使われる。
OAuth を利用するクライアントとしてユーザーを適切に保護するため state の検証はとても重要なプロセスになる。
OAuth の文脈における CSRF 攻撃は『エンドユーザーを「自身が意図していない権限をもったユーザー」としてサービスを利用させ、意図しない操作を実行させてしまう』というもの、と認識した。
state パラメーターを使った値の検証を行うことで、OAuth を利用してクライアントがアクセストークンを受け取った時に、そのアクセストークンを使ったサービス上の操作がブラウザを操作するユーザーの意図通りかどうか、を確認することが出来る。
Auth0 などの IDaaS では、state パラメーターを付与する処理は内部的に行われている。
参考
- https://stackoverflow.com/a/35988614
- https://auth0.com/docs/secure/attack-protection/state-parameters
nonce
Number + Once で nonce。
non + ce だと認識していたが、正しくは n + once。
nonce パラメーターも state パラメーターと同様に認証フローを開始するリクエストに含まれるパラメーターになる。
state と同様に nonce も予想が難しいランダムな値を使う。
nonce は state とは異なりリダイレクト時に一緒に返ってくるものではなく、発行される ID トークンに含まれることになる。
OIDC の仕様にある ID Token のセクションから拝借。
{
"iss": "https://server.example.com",
"sub": "24400320",
"aud": "s6BhdRkqt3",
"nonce": "n-0S6_WzA2Mj",
"exp": 1311281970,
"iat": 1311280970,
"auth_time": 1311280969,
"acr": "urn:mace:incommon:iap:silver"
}
Auth0 などの IDaaS では ID トークンが発行されると同時にセッションを開始する。
セッションはいわゆるログイン状態を管理するための仕組み。
nonce があることで、発行された ID トークンが特定の認証セッションに紐づいているかどうかを検証することが出来るとのこと。
nonce パラメーターを使わなかった場合のリスクとなるリプレイアタックのシナリオを考える。
Auth0 のドキュメントによるとインプリシットフローでリプレイアタックのリスクがあるらしい。
攻撃者であるイブは、アリスが何らかのソーシャルサイトへアクセスし、認証後に発行される ID トークンをネットワークを盗聴し傍受する。
イブは盗み取った ID トークンを使いソーシャルサイトへのリクエストを試みる。
ID トークンを受け取ったソーシャルサイトは ID トークンが正当なものだと判断し、イブをアリスとして迎え入れる。
これによりイブはソーシャルサイト上でアリスのプライベートな情報を閲覧することが出来たり、悪意のある投稿を行うことが出来るようにある。
nonce パラメーターがあった場合、イブが傍受した ID トークンを使いアリスになりすまそうとした時、ソーシャルサイトは ID トークンに含まれる nonce の値と認証フローの開始時に手元で控えていた値を突き合わせることで、その ID トークンが現時点でブラウザを操作しているユーザーによって開始されたものかどうかを検証することが出来る。
nonce を使うことで、現在のセッションが開始された時の認証要求が正当に機能しているかどうかのセキュリティチェックを行うことが出来る。
クライアントが ID トークンを扱う時、その ID トークンに含まれる nonce の値と、内部的に保持している nonce の値が一致しているかどうかが検証されることで、正当な認証フローによるユーザー操作が行われていることが保証されるようになっている、と理解した。
state パラメーターと同様に Auth0 を使う場合だと SDK が内部的に nonce に関わる処理を行ってくれる。