感想: 体系的に学ぶ 安全なWebアプリケーションの作り方

読んでます。

まだ途中だけど、主にJava/Tomcatユーザとしての備忘メモ。ただし、セキュリティの専門家でも何でもないので間違いは大いにあると思われます。

HttpOnly 属性の指定(p.103)

Tomcat では、Tomcat7以降(Servlet 3.0以降)なら単純に web.xml か web-fragment.xml

<session-config>
  <cookie-config>
    <http-only>true</http-only>
  </cookie-config>
</session-config>

と書けばOK。Tomcat6の場合は、context.xml や server.xml の Context 要素に

<Context useHttpOnly="true" ...>

のように useHttpOnly 属性を設定する*1

URL埋め込みの防止 (p.171)

URL Rewriting を使わないようにする対策として、Javaについては

J2EEの場合はセッションIDをURL埋め込み(J2EEではURLリライティングと呼ばれます)にするには、HttpServletResponseインタフェースのencodieURLメソッドまたはencodeRedirectURLメソッドを用いて明示的にURLを書き換える必要があるので、これらによる処理を記述しなければ、URL埋め込みのセッションIDとなることはありません。

とあるが、JSTLのc:urlやStrutsの類似のタグ等、多くのフレームワークの内部で encodeURL などが使われているため*2、実際には回避は難しいような気がする。

encodeURL を使わざるを得ない場合の対策としては、Servlet 3.0 以降なら HttpOnly の時と同様、web.xml か web-fragment.xml

<session-config>
  <tracking-mode>COOKIE</tracking-mode>
</session-config>

でOK。Servlet 2.5以前の場合は標準の設定は用意されておらず、Tomcat6でもできないので*3、個人的には以下のようなフィルタ(書き方は Servlet 3.0だけどやり方は2.5でも同じ)を適用してごまかしていた。

package example;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

@WebFilter("/*")
public class URLRewritingFilter implements Filter {

    @Override
    public void destroy() {
        // do nothing
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (response instanceof HttpServletResponse) {
            response = new HttpServletResponseWrapper((HttpServletResponse)response) {
                @Override
                public String encodeURL(String url) {
                    // 書き換えないで、無理やりそのまま返す
                    // 初期化パラメータでオンオフ切り替えられると良いかも
                    return url;
                }
            };
        }
        chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig fConfig) throws ServletException {
        // do nothing
    }
}

ログイン前のセッションIDの固定化攻撃の対策(p.182)

ログイン前にセッション変数を使っていると、セッションIDの固定化攻撃に完全に対策することは困難です。

とあるのだけど、理由がよくわからない(わかりました。tetz42さんのコメント参照)。例えば Spring Security (2.x の時代だけど)では、session-fixation-protection を有効にすると、ログイン時に

  1. 新しいセッションを作る
  2. ログイン前のセッションから、新しいセッションにセッション変数をコピーする
  3. ログイン前のセッションを破棄する

としていて、セッション固定化を回避すると同時に、ログイン前のセッションも引き継げるようになっていたんだけど、これだとまずいんだろうか?

  1. 攻撃者がショッピングカートに注文を入れる。Orderオブジェクトのリストがセッションに入る
  2. セッション固定化攻撃で、被害者が攻撃者のセッションIDを引き継いでログインする
  3. セッションIDは変更され、攻撃者はログインできない
  4. しかし、「Orderオブジェクトのリスト」は(deep copyされていない限り)共通のインスタンスなので、攻撃者が変更前のセッションから内容を変更できる可能性がある

とか?*4 でも、これも、セッション変数のコピー後に、古いセッションを破棄していれば問題ないような気がするんだけど、どうなんだろう。

攻撃者の作成したセッション変数を引き継ぐ可能性があること自体がセキュリティリスクである、ということなのかな。

セッションCookieにSecure属性をつける(p.215)

Apache Tomcatの場合は、HTTPS接続されたリクエストに対して、セッションIDのクッキーには自動的にセキュア属性が設定されます。

とあるが、SSLアクセラレータやWebサーバをリバースプロキシとしている場合など、Tomcatにリクエストが来た時点では HTTP になってしまっていてSecure属性が付かないケースがあるので、この仕様に頼るのはやめた方が良いと思う。

対策としては、Servlet 3.0 以降なら、例によって web.xml か web-fragment.xml

<session-config>
  <cookie-config>
    <secure>true</secure>
  </cookie-config>
</session-config>

で、かならずSecure属性が付くようになる。Tomcat6以前の場合は、server.xml などの Connector 要素に secure 属性を true に指定する*5

<!-- HTTP -->
<Connector port="8080" protocol="HTTP/1.1" secure="true" ...
<!-- AJP -->
<Connector port="8009" protocol="AJP/1.3" secure="true" ...

これで、HttpServletRequest#isSecure() が常に true を返すようになり、HTTPSでリクエストが来た場合と同じ動作にすることができる。

まとめ

セキュリティも地味に強化された Servlet 3.0 を使いましょう。

*1:Tomcat7以降でも使える http://tomcat.apache.org/tomcat-7.0-doc/config/context.html#Common_Attributes

*2:フレームワークが勝手にURL書き換えをやめてしまうと逆に書き換えが不可能になるので、これは正しい。設定できない仕様や実装の問題

*3:WebLogicではできたような気もする

*4:インスタンス固定化攻撃とでも言うのだろうか

*5:これも、Tomcat7でも使える。http://tomcat.apache.org/tomcat-7.0-doc/config/http.html#Common_Attributes