2013年1月14日月曜日

[node.js, express]express で CSRF 対策

実行環境
express v3.0.6

csrfミドルウェア


express アプリケーションでCSRFの脆弱性に対応するためには、 csrf ミドルウェアを導入します。(CSRF対策が必要な場面では、必ず通信をSSLやTLSで暗号化する必要がありますが、この記事ではそこには触れません。)

csrf ミドルウェアでは、以下の処理を行います。
  • セッション開始時にトークンを生成し、セッション変数に保存する
  • GET, HEAD, OPTIONS 以外のメソッドでHTTPリクエストを受け取ったときに、リクエストbodyで渡されたトークンの値と,セッションに保存した値を照合する

ミドルウェアの設定


csrfミドルウェアはセッションにトークンを保存するので、必ずsession ミドルウェアの後に呼び出します。(14行目)
また以下の例では、ビューからセッションの_csrfの値にアクセスできるように、res.locals._csrf に値をコピーしています。(16行目)

app.js
//~
app.configure(function(){
  app.set('port', process.env.PORT || 3000);
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.favicon());
  app.use(express.logger('dev'));
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(express.cookieParser('your secret here'));
  app.use(express.session());


  app.use(express.csrf());

  app.use(function(req, res, next){
    res.locals._csrf = req.session._csrf;
    next();
  })


  app.use(app.router);
  app.use(express.static(path.join(__dirname, 'public')));
});
//~

ビューの設定


HTMLフォームに、セッションの _csrf の値を保存するhiddenフィールドを追加します。(2行目)

form.jade
form(method="POST", action="/")
  input(type="hidden", name="_csrf", value= _csrf) 
  br
  input(type="submit") 

Errorのハンドリング


フォームで送信した_csrf の値とセッションに保存しているトークンの値が一致しないと、Errorオブジェクトが生成されるので、Errorをハンドルする処理を追加します。
以下の例では、レスポンスステータスに403をセットし、403エラー用のエラーページを表示させています。(20行目)

app.js
//~
app.configure(function(){
  app.set('port', process.env.PORT || 3000);
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.favicon());
  app.use(express.logger('dev'));
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(express.cookieParser('your secret here'));
  app.use(express.session());
  app.use(express.csrf());
  app.use(function(req, res, next){
    res.locals._csrf = req.session._csrf;
    next();
  })
  app.use(app.router);
  app.use(express.static(path.join(__dirname, 'public')));

  app.use(function(err, req, res, next){
    if(err.name === "Forbidden"){
      res.status(403).render('403')
    } else {
      next(err)
    }
  })
});
//~

0 件のコメント:

コメントを投稿