APIセキュリティのハードニング
![](https://thinkit.co.jp/sites/default/files/styles/main_image_730x/public/main_images/17685_main.jpg?itok=o1u8mdoL)
第三回は、第二回で構築したシステム(図1)をOAuth 2.0のセキュリティベストプラクティスに則って、より堅牢にしていきます。
攻撃の側面の把握
セキュリティを考えるにあたって重要なのは、攻撃の側面について知ることです。まずは、攻撃者がどこを攻撃してくるのかを把握します(図2、表1)。
表1:攻撃の側面と攻撃の例
# | 攻撃の側面 | 攻撃の例 |
---|---|---|
① | 通信経路 | 通信を盗聴し、ユーザの秘密情報(ユーザ名/パスワード)やトークンの情報を不正取得する |
② | 外部アプリ | 攻撃者の作成した外部アプリにユーザを誘導し、秘密情報を不正取得する |
③ | APIゲートウェイ | 攻撃者の作成したAPIゲートウェイにユーザ/クライアントを誘導し、APIコールに付与されたアクセストークンを不正取得する |
④ | ブラウザ | スクリプトなどを実行し、認可リクエスト/認可レスポンスを操作して、アクセストークンを不正取得する |
⑤ | 認可サーバ | 攻撃者の作成した認可サーバにユーザ/クライアントを誘導し、秘密情報を不正取得する |
ベストプラクティスによると、上記攻撃の側面は、1箇所のみならず、複数箇所(例えば外部アプリとAPIゲートウェイなど)が攻撃者のコントロール下にあることも想定する必要があります。以降では、攻撃の側面ごとに、代表的な攻撃方法とその対応方法を説明します。なお、ここではFintechアプリを例に用います。つまり、APIゲートウェイおよびAPIサーバを銀行が提供し、外部アプリは銀行の口座を管理する(残高照会や送金ができる)Fintechアプリであると仮定します(図3)。
攻撃の側面①:通信経路
まずは、通信経路が攻撃者のコントロール下にある場合を考えます。通信経路が攻撃者のコントロール下にある場合、通信が暗号化されていない限り、すべての通信は盗聴される危険性があります。例えばResource Owner Password Credentials Grantの場合、以下のようなコマンドでトークンエンドポイントをコールするので、通信からはユーザの秘密情報(ユーザ名/パスワード)を取得できます。
リスト1:
$ curl http://<認可サーバhostname>:8080/auth/realms/sample_service/protocol/openid-connect/token -d "grant_type=password&client_id=sample_application&client_secret=<client secret>&username=sample_user&password=<password>&scope=openid"
攻撃者は取得したユーザの秘密情報を自身の使用する外部アプリに入力することで正規ユーザになりすまし、正規ユーザの口座から攻撃者自身の口座への送金要求ができます(図4)。
この攻撃の対応方法として、通信をTLSで暗号化する方法があります(図5)。通信をTLSで暗号化することで、盗聴が格段に難しくなります。
攻撃の側面②:外部アプリ
次に、外部アプリが攻撃者のコントロール下にある場合を考えます。外部アプリが攻撃者のコントロール下にある場合、当該アプリに入力したユーザの秘密情報が盗まれる危険性があります。また、ユーザが許可していない操作を、ユーザのリソースに対して実行される危険性があります。まずは、ユーザの秘密情報を不正取得する例です。攻撃者は取得したユーザの秘密情報を自身の使用する外部アプリに入力することで正規ユーザになりすまし、正規ユーザの口座から攻撃者自身の口座への送金要求ができます(図6)。
この攻撃の対応方法として、OAuth 2.0の認可フローをResource Owner Password Credentials GrantからAuthorization Code Grantに変更する方法があります(図7)。Authorization Code Grantに変更することで、ユーザの秘密情報が外部アプリを経由することがなくなるので、外部アプリからユーザの秘密情報を取得できなくなります。
次に、ユーザが許可していない操作を外部アプリが実行する例です。攻撃者は、正規ユーザから送金要求の同意を得ていないにも関わらず、認可サーバから払い出されたアクセストークンを付与することで、正規ユーザから攻撃者自身の口座への送金要求ができます(図8)。
この攻撃の対応方法として、アクセストークンのscopeクレームを用いる方法があります(図9)。scopeクレームを用いることで、外部アプリは許可された範囲外のリソースを操作できなくなります。
攻撃の側面③:APIゲートウェイ
次に、APIゲートウェイが攻撃者のコントロール下にある場合を考えます。APIゲートウェイが攻撃者のコントロール下にある場合、当該APIゲートウェイに対するAPIコールに付与されたアクセストークンが盗まれる危険性があります。攻撃者は取得したアクセストークンを用いて正規のAPIゲートウェイを通じ、正規ユーザの口座から攻撃者自身の口座への送金要求ができます(図10)。
この攻撃の対応方法として、アクセストークンのaud(Audience)クレームを用いる方法があります(図11)。audクレームを用いることで、ユーザが許可していないAPIゲートウェイはリソースを操作しなくなります。
audクレームを用いたアクセス制御を導入することで、リソースを操作できるAPIゲートウェイを特定のAPIゲートウェイのみに限定することはできますが、裏を返せばaudクレームで指定されてさえいれば、指定されたAPIゲートウェイは攻撃者の指示によってリソースを操作してしまいます。
そこで登場するのが、リソース操作の要求元、つまり外部アプリを限定する方法です。その一例として、OAuth MTLS(OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens: RFC8705)を用いる方法があります(図12)。OAuth MTLSを用いることで、トークンを受け取った外部アプリ以外からのAPIコールに対して、APIゲートウェイはリソースを操作しなくなります。
攻撃の側面④:ブラウザ
次に、ブラウザが攻撃者のコントロール下にある場合を考えます。ブラウザが攻撃者のコントロール下にある場合、スクリプトなどを実行することで、認可リクエストや認可レスポンスを盗聴/改ざんされる危険性があります。
まずは、認可リクエストを改ざんする例です。例えばAuthorization Code Grantの場合、以下のようなURLで認可エンドポイントにアクセスします。
リスト2:
https://<認可サーバhostname>/auth/realms/sample_service/protocol/openid-connect/auth?response_type=code&client_id=sample_application&redirect_uri=https://<外部アプリhostname>/callback&scope=openid remittance sample_api_gateway
上記URLのclient_idとredirect_uriを以下のように書き換えることで、攻撃者は自身の使用する外部アプリに認可コードを飛ばすことができ、最終的に正規ユーザの口座から攻撃者自身の口座への送金要求ができます(図13)。
リスト3:
https://<認可サーバhostname>/auth/realms/sample_service/protocol/openid-connect/auth?response_type=code&client_id=evil_application&redirect_uri=https://<偽外部アプリhostname>/callback&scope=openid remittance sample_api_gateway
次に、認可レスポンスを盗聴する例です。例えばAuthorization Code Grantの場合、以下のような認可レスポンスがブラウザに返ってきます。
リスト4:
302 Found Location: https://<外部アプリhostname>/callback?code=<authorization_code>
上記Locationヘッダから、認可コード(authorization_code)を取得し、自身の使用する外部アプリに送ることで、最終的に正規ユーザの口座から攻撃者自身の口座への送金要求ができます(図14)。
このような攻撃への対応方法としては、stateパラメータを用いる方法、nonceパラメータを用いる方法、そしてPKCE(Proof Key for Code Exchange:RFC7636)を用いる方法があります。
まずは、stateパラメータを用いる方法です(図15)。stateパラメータを用いることで、外部アプリは認可リクエストと認可レスポンスを同一セッションとしてバインドすることができ、外部アプリは対応する認可リクエストのない認可レスポンスを処理しなくなります。
次に、nonceパラメータを用いる方法です(図16)。nonceパラメータを用いることで、外部アプリは認可リクエストとトークンレスポンスを同一セッションとしてバインドすることができ、外部アプリは対応する認可リクエストのないトークンレスポンスを用いてAPIをコールしなくなります。
最後に、PKCEを用いる方法です(図17)。PKCEを用いることで、認可サーバは認可リクエストとトークンリクエストを同一セッションとしてバインドすることができ、認可サーバは対応する認可リクエストのないトークンリクエストに対し、トークンを返さなくなります。
上述したstateパラメータを用いる方法、nonceパラメータを用いる方法、PKCEを用いる方法は、すべて認可コードを不正取得されたときの対応方法です。それに対し、そもそも認可コードの不正取得自体を困難にする方法として、OAuth 2.0 Form Post Response Modeを用いる方法があります(図18)。Form Post Response Modeを用いることで、クエリ部分ではなく、リクエストボディ部分に認可コードが記載されるため、認可コードが盗聴されにくくなります。
攻撃の側面⑤:認可サーバ
最後に、認可サーバが攻撃者のコントロール下にある場合を考えます。認可サーバが攻撃者のコントロール下にある場合、当該サーバに送ったユーザの秘密情報、認可コード、クライアントの秘密情報が盗まれる危険性があります。ここではブラウザも攻撃者のコントロール下にあることを想定します。
まずは、ユーザの秘密情報を盗聴する例です。例えばAuthorization Code Grantの場合、以下のようなURLで認可エンドポイントにアクセスします。
リスト5:
https://<認可サーバhostname>/auth/realms/sample_service/protocol/openid-connect/auth?response_type=code&client_id=sample_application&redirect_uri=https://<外部アプリhostname>/callback&scope=openid remittance sample_api_gateway
上記URLの認可サーバhostnameを以下のように書き換えることで、攻撃者は自身の使用する認可サーバに認可リクエストを飛ばすことができ、最終的に正規ユーザの口座から攻撃者自身の口座への送金要求ができます(図19)。
リスト6:
https://<偽認可サーバhostname>/auth/realms/sample_service/protocol/openid-connect/auth?response_type=code&client_id=sample_application&redirect_uri=https://<外部アプリhostname>/callback&scope=openid remittance sample_api_gateway
この攻撃の対応方法として、一般的なフィッシング対策があります(図20)。フィッシング対策は認証部分の技術であり、今回ターゲットとしているOAuth 2.0やOIDCの範囲からは外れるため詳細は割愛しますが、例えば、ユーザが正規認可サーバであることに気付きやすくする仕組み(ユーザが登録した画像を表示するなど)をログイン画面に施したり、2要素認証を導入したりする方法などがあります。
次に、トークンリクエストを盗聴する例です。例えばAuthorization Code Grantの場合、以下のような認可レスポンスがブラウザに返ってきます。
リスト7:
302 Found Location: https://<外部アプリhostname>/callback?code=<authorization_code>
上記Locationヘッダのコールバックエンドポイントを以下のように書き換えることで、認可コードが攻撃者の使用する認可サーバから届いたと外部アプリに錯覚させ、攻撃者は自身の使用する認可サーバにトークンリクエストを飛ばすことができ、最終的に正規ユーザの口座から攻撃者自身の口座への送金要求ができます(図21)。
リスト8:
302 Found Location: https://<外部アプリhostname>/callback_for_evil_auth_server?code=<authorization_code>
この攻撃への対応方法としては、コールバックエンドポイントと認可サーバとの突き合わせをする方法、OAuth MTLSを用いる方法があります。
まずは、コールバックエンドポイントと認可サーバとの突き合せをする方法です(図22)。外部アプリが使用する認可サーバが1つであれば問題ありませんが、やむなく2つ以上の認可サーバを使用する場合、redirect_uri(つまりコールバックエンドポイント)は認可サーバごとに変えて、どの認可サーバから来た認可レスポンスかを区別できるようにします。その上で、認可リクエストの送り先、認可レスポンスの送り元、加えてトークンリクエストの送り先の認可サーバがすべて一致するかを確認することで、攻撃者のコントロール下にある認可サーバにトークンリクエストを送らないようにします。
次に、OAuth MTLSを用いる方法です(図23)。こちらはAPIゲートウェイが攻撃者のコントロール下にある場合の攻撃の対策方法として説明したものと同じです。OAuth MTLSを用いることで、たとえ攻撃者がトークンリクエストを盗聴できたとしても、トークンリクエストのTLSネゴシエーションの時に、攻撃者は認可サーバに対し正規外部アプリのクライアント証明書を提示できないため、認可サーバはトークンを返さなくなります。
次回は、今回紹介したハードニング方法を、実際にKeycloakを用いて設定し、動作を確認していきましょう。
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- Keycloakを用いたハードニングの実装方法
- Keycloakのインストールと構築例
- KeycloakによるAPIセキュリティの基本
- FAPI 1.0に準拠したクライアントアプリケーションと リソースサーバの作り方
- Oracle Cloud Hangout Cafe Season4 #4「マイクロサービスの認証・認可とJWT」(2021年7月7日開催)
- FAPIとKeycloakの概要
- 3scaleのAPIゲートウェイの機能を拡張してみよう!
- 3scaleの基本的な使い方
- コンテナ上のマイクロサービスの認証強化 ~IstioとKeycloak~
- コンテナ上のマイクロサービスの認証強化 ~StrimziとKeycloak~