舞台裏

Qiita が表でこっちが裏。こっそりやっていく。

Formログインに関する微調整とかを調査中

ログインURL の変更とか、そういうのをこまごま検証中。

assertjGen にバグ報告があったりとか、 JDT2017 の登録とか、ぴあの情報流出問題の確認とか、明日の関Java の準備とか色々別作業で手を取られたのであまり進んでない。

同時セッション制御についていろいろ試した

<concurrency-control>error-if-maximum-exceededtrue にした場合で、 Remember-Me を組み合わせた場合にうまくエラー制御ができない問題を色々試した。

結局、回避方法は分からなかった。

実装見る限り、回避は無理っぽい気がするが、、、

6.3.3 Session Management

Detecting Timeouts

You can configure Spring Security to detect the submission of an invalid session ID and redirect the user to an appropriate URL.

あなたは、無効なセッションIDが渡されたことを検出し、適切な URL にリダイレクトするために Spring Security を設定することができます。

This is achieved through the session-management element:

これは、 session-management 要素を通じて実現できます。

<http>
...
<session-management invalid-session-url="/invalidSession.htm" />
</http>

Note that if you use this mechanism to detect session timeouts, it may falsely report an error if the user logs out and then logs back in without closing the browser.

もしあなたがセッションタイムアウトを検出するメカニズムを使っている場合は注意してください。 もしユーザがログアウトしたあとにブラウザを閉じる前に「戻る」などでアクセスしてきた場合に間違ってエラーの報告するかもしれません。

This is because the session cookie is not cleared when you invalidate the session and will be resubmitted even if the user has logged out.

これは、あなたがセッションを無効にしてログアウトした状態で再サブミットした場合にセッションクッキーがクリアされないのが原因です。

You may be able to explicitly delete the JSESSIONID cookie on logging out, for example by using the following syntax in the logout handler:

あなたは明示的に JSESSIONID クッキーをログアウトのときに削除することができます。
例えば、ログアウトハンドラーのなかで次のようなシンタックスを使います。

<http>
<logout delete-cookies="JSESSIONID" />
</http>

Unfortunately this can’t be guaranteed to work with every servlet container, so you will need to test it in your environment

残念ながら、これは全てのサーブレットで動作することは保証できません。
よって、あなたがあなたの環境でテストする必要があります。

If you are running your application behind a proxy, you may also be able to remove the session cookie by configuring the proxy server.

もしあなたがプロキシを経由してアプリケーションを走らせているなら、あなたはプロキシサーバーの設定でセッションクッキーを削除することができるかもしれません。

For example, using Apache HTTPD’s mod_headers, the following directive would delete the JSESSIONID cookie by expiring it in the response to a logout request (assuming the application is deployed under the path /tutorial):

たとえば、 Apache HTTPDmod_headers を使っているなら、次のようなディレクティブで JSESSIONID クッキーをログアウトリクエストのレスポンスで無効にできます(アプリケーションが /tutorial パスの下にデプロイされていると想定してください)。

<LocationMatch "/tutorial/logout">
Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT"
</LocationMatch>

Concurrent Session Control

If you wish to place constraints on a single user’s ability to log in to your application, Spring Security supports this out of the box with the following simple additions.

もしあなたが単一のユーザがログインできるよう制約を設けたいのであれば、 Spring Security は次のシンプルな追記によってこれをサポートします。

First you need to add the following listener to your web.xml file to keep Spring Security updated about session lifecycle events:

最初に、あなたは Spring Security がセッションライフサイクルイベントを維持するために次のリスナーを web.xml に追加する必要があります。

<listener>
<listener-class>
    org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>

Then add the following lines to your application context:

次の行をアプリケーションコンテキストに追加してください。

<http>
...
<session-management>
    <concurrency-control max-sessions="1" />
</session-management>
</http>

This will prevent a user from logging in multiple times - a second login will cause the first to be invalidated.

これは、ユーザが複数回ログインすることを防ぎます。
2回目のログインで、1回目のログインが無効になります。

Often you would prefer to prevent a second login, in which case you can use

しばしば、あなたは2回目のログインを防ぎたいと思うかもしれません。
その場合、次のようにすればできます。

<http>
...
<session-management>
    <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
</http>

The second login will then be rejected.

2回目のログインは拒否されます。

By “rejected”, we mean that the user will be sent to the authentication-failure-url if form-based login is being used.

拒否されるとは、 Form ログインを使っている場合は authentication-failure-url で指定した URL にユーザを送ります。

If the second authentication takes place through another non-interactive mechanism, such as “remember-me”, an “unauthorized” (401) error will be sent to the client.

もし2回目の認証が他の非対話的なメカニズムを通じてやってきた場合(例えば Remember-Me など)は、 “unauthorized” 401 エラーがクライアントに送信されます。

If instead you want to use an error page, you can add the attribute session-authentication-error-url to the session-management element.

もしエラーページを指定したい場合は、 session-authentication-error-url 属性を session-management 要素に追加することでできます。

If you are using a customized authentication filter for form-based login, then you have to configure concurrent session control support explicitly.

もしあなたが Form ログインにカスタマイズした認証フィルターを使っている場合、同時セッションサポートを明示的に設定する必要があります。

More details can be found in the Session Management chapter.

さらなる詳細については、 Session Management の章 で見つけることができます。

Session Fixation Attack Protection

Session fixation attacks are a potential risk where it is possible for a malicious attacker to create a session by accessing a site, then persuade another user to log in with the same session (by sending them a link containing the session identifier as a parameter, for example).

セッション固定化攻撃は、悪意ある攻撃者がサイトにアクセスして作ったセッションで、他のユーザがログインできる可能性があるというリスクです(例えば、リンクに含まれたセッションを識別するパラメータで他のユーザに送信します)。

Spring Security protects against this automatically by creating a new session or otherwise changing the session ID when a user logs in.

Spring Security は、ユーザがログインしたときに新しいセッションを作るかセッションIDを変更することで、これを自動的に防ぎます。

If you don’t require this protection, or it conflicts with some other requirement, you can control the behavior using the session-fixation-protection attribute on <session-management>, which has four options

もしあなたがこの防御を必要としないか、もしくは他の要求と衝突する場合は、 <session-management> 要素の session-fixation-protection 属性の4つのオプションで、振る舞いを制御することができます。

  • none - Don’t do anything. The original session will be retained.

none は、何もしません。オリジナルのセッションが保持されます。

  • newSession - Create a new “clean” session, without copying the existing session data (Spring Security-related attributes will still be copied).

newSession - 既存のセッションデータをコピーせずに、新しいきれいなセッションを作成します(Spring Security に関係する属性はコピーされます)。

  • migrateSession - Create a new session and copy all existing session attributes to the new session. This is the default in Servlet 3.0 or older containers.

migrateSession - 既存のセッションが持つ属性を全てコピーして新しいセッションを作成します。これは Servlet 3.0 以前のコンテナではデフォルトです。

  • changeSessionId - Do not create a new session. Instead, use the session fixation protection provided by the Servlet container (HttpServletRequest#changeSessionId()). This option is only available in Servlet 3.1 (Java EE 7) and newer containers. Specifying it in older containers will result in an exception. This is the default in Servlet 3.1 and newer containers.

changeSessionId - 新しいセッションを作成しません。
代わりに、サーブレットコンテナが提供するセッション固定化攻撃対策(HttpServletRequest#changeSessionId())を使用します。
このオプションは、 Servlet 3.1 (Java EE 7) かそれ以上のコンテナで有効です。
それ以前のコンテナで指定した場合は、例外が発生します。
これは Servlet 3.1 以上のコンテナではデフォルトです。

When session fixation protection occurs, it results in a SessionFixationProtectionEvent being published in the application context.

セッション固定の防御が実行されると、 SessionFixationProtectionEvent がアプリケーションコンテキストに通知されます。

If you use changeSessionId, this protection will also result in any javax.servlet.http.HttpSessionIdListener s being notified, so use caution if your code listens for both events.

もしあなたが changeSessionId を使用しているのであれば、この防御は javax.servlet.http.HttpSessionIdListener にも通知されます。
よって、あなたのリスナーは両方のイベントを受け取ることに注意してください。

See the Session Management chapter for additional information.

追加の情報は Session Management の章 を見てください。

SessionManagementFilter の価値がようやくわかった気がする

Session Management について勉強開始。

翻訳は以前していた。

SessionManagementFilter

セッション管理するフィルター。

  • SecurityContextRepositorySecurityContext が無かったら何もしない
  • あったら Authentication を取り出す
  • Authenticaiton が匿名認証でない場合は、 SessionAuthenticationStrategyonAuthentication() が実行される
  • Authentication が null だったり匿名認証だったりした場合は、セッションタイムアウトしてないかチェックして、タイムアウトしてたら専用のハンドラ(InvalidSessionStrategy)に処理を委譲する

SessionAuthenticationStrategy

認証が行われた時のセッションに関する処理を提供する。
典型的なのはセッション固定化攻撃対策のためにセッションIDを変更する処理とか。

リファレンスにはデフォルト実装は SessionFixationProtectionStrategy って書いているけど、実際は実行環境のサーブレットのバージョンによって変わるっぽい。

3.1 未満なら SessionFixationProtectionStrategy で、3.1 以上なら ChangeSessionIdAuthenticationStrategy になるっぽい。
ChangeSessionIdAuthenticationStrategy の方は、 3.1 で追加された HttpServletRequest.changeSessionId() メソッドを使用している。

2つのクラスは AbstractSessionFixationProtectionStrategy を継承していて、結局メインの処理はこっちにある。

SessionManagementFilter の価値は?

SessionAuthenticationStrategyUsernamePasswordAuthenticationFilter でログインに成功したときにも利用されている。
セッション固定化攻撃対策のためのセッションID振り直しはそのときに行われていて、 SessionManagementFilter では結局行われていない。
じゃあ、 SessionManagerMentFilter の価値は???

リファレンスには、 Remember-Me や Pre-Authentication のような非対話型のログイン処理が行われた時に働くっぽい。
たしかに、 Remember-Me のときは動作していた。

※Pre-Authetication とは、別の認証システムによってすでに認証済みのユーザがアクセスしてきたようなケース

SessionManagementFilter の価値

以下は、全て推測

  • なぜ SessionStorategy の処理は UsernamePasswordAuthenticationFilterSessionManagementFilter の2か所で実行されているのか?
    • UsernamePasswordAuthenticationFilter の方は、ログインに成功するとすぐにリダイレクトを実行してしまい、後続処理が実行されない(SessionManagementFilter が呼ばれない)ため。
  • なぜ Remember-Me の認証が完了したあとに、 UsernamePasswordAuthenticationFilter のように独自で SessionStorategy の処理が呼ばれないのか?
    • UsernamePasswordAuthenticationFilter がリダイレクトするという点で特殊なため
    • Remember-Me や Pre-Authentication は認証終了後リダイレクトはしないっぽい
    • よって後続の SessionManagementFilter が実行される
    • なので、個々の処理で個別に SessionStorategy を呼ぶのではなく、 SessionManagementFilter で漏れなく呼ぶようにしていると思われる
  • UsernamePasswordAuthenticationFilter の前に SessionManagementFilter がくるように並べれば、 UsernamePasswordAuthenticationFilter もカバーできるのでは?
    • ログイン成功後に実行しなければならない処理なので、それより前にあっては本末転倒
  • Remember-Me でもセッション固定化攻撃ってあるの?
    • 実際やってみたら、できた
    • 攻撃者が A のブラウザでログイン画面にアクセス(セッションID_1 が発行される)
    • 被害者が B のブラウザで Remember-Me を有効にしてログイン
    • 被害者のブラウザに保存されたセッションID を、攻撃者がセッションID_1 で置き換える
    • 何も知らない被害者がアプリにアクセスすると、 Remember-Me による自動ログインが実行される
      • アプリはセッション情報のないセッションID が渡ってきたと思い、普通に Remember-Me の認証を実行する
    • Remember-Me 認証が成功して、セッションID_1 が正規のセッションID になる
    • 攻撃者はセッションID_1 でアプリにアクセスする
    • 攻撃者のブラウザは被害者がログインしたときのセッションで表示される
  • ということで、 Remember-Me の自動認証が成功したときもセッションID は振りなおす必要があり、 SessionManagementFilter がその役割を担っていることになっている

と思う。

HPKP について調べてたけどあきらめた

HPKP (HTTP Public Key Pinning) について調べてたが、あきらめた。

pin の設定がよくわからんし、上手くブロックされたところを調べるための環境を用意するのが面倒そう。

それに 自堕落な技術者の日記 : HPKP(HTTP Public Key Pinning)公開鍵ピニングについて考える - livedoor Blog(ブログ) とかを見るとあまり推奨される機能では無さげ(後述の通り、実際に使っているサイトがほとんどなかった)。

ということで記事内容から削除して闇に葬る。

以下没内容


Public-Key-Pins

SSL(TLS) でサーバーが送ってくる公開鍵の信憑性は、公開鍵をラップしている証明書を発行した認証局(CA)をブラウザが信頼していることによって成り立っている。

もし攻撃者が認証局を乗っ取ることに成功すると、攻撃者は任意のサーバーの公開鍵を偽造できるようになってしまう。 (公開鍵が偽物でも、証明書がブラウザの信頼する認証局(攻撃済)の物だと、ブラウザは証明書を信頼してしまう)

そうなると、攻撃者は中間者攻撃を仕掛けることができるようになってしまう。

Public-Key-Pins をレスポンスヘッダーに乗せると、ブラウザは最初の HTTPS 通信の際にサーバーと公開鍵のハッシュ値の組み合わせを記録する。 そして、二回目以降は保存している公開鍵のハッシュ値と実際に送られてきた公開鍵のハッシュ値が一致することをチェックするようになる。 もし比較の結果に差がある場合、ブラウザは公開鍵に問題があることを検知できることになる。

この仕組み上、当然のことながら一番最初のリクエストの時点ですでに認証局が乗っ取られていて公開鍵が偽造されていた場合は意味をなさない。

実際に使ってるの?

認証局が乗っ取られた場合というかなりのレアケースに備えたヘッダーだが、実際に使っているサイトはあるのだろうか?

Google, Amazon, Twitter, Youtube, Wikipedia, Pixiv, ニコニコ動画, 食べログ, 三井住友銀行, 楽天 などなどを適当に確認してみたが、このヘッダーを使っているところは確認できかった(見逃してたらごめんなさい)。

結局このヘッダーを確認できたのは、自分が確認した中では Facebook だけだった。

あんまり一般的ではないということだろうか。


参考

Spring Security メモを分割したった

Spring Security のメモが自分でも引くほど長くなってきたので、いくつかの記事に分割した。
スクロールバーが現実的な長さになった気がする。

Qiita はとくに文字数制限とかはないっぽいが、あまりにも長くなってくるとプレビューがまともに動かなくなってくるので編集がしづらくなるのがつらい(長い記事書くなって話だが)。

f:id:opengl-8080:20170417224806j:plain