CSRF脆弱性対策

CSRF対策のtokenはセッションIDで良い

セキュリティ的にワンタイムトークン>セッションIDではない。
という話が、この辺の記事に書かれています。
高木浩光@自宅の日記 - クロスサイトリクエストフォージェリ(CSRF)の正しい対策方法
高木浩光@自宅の日記 - CSRF対策に「ワンタイムトークン」方式を推奨しない理由, hiddenパラメタは漏れやすいのか?


肝はこういう事のようです

  • tokenは外部のサイトから知り難い(実質知り得ない)ものでないといけない
    • セッションIDはcookieに格納される
    • セッションIDは『暗号学的に安全な擬似乱数生成系で生成されているはず』(引用)
      • 推測も事実上できない


補足すると、セッションIDを使用したCSRF対策tokenが漏洩している状態(大体XSS)だと、

ので、セッションIDが漏洩しうる状況だとワンタイムトークンもバシバシ突破されます。

はてなCSRF対策

はてな各サービスの CSRF 脆弱性対策について - はてなダイアリー日記
パッと見先程の話に準拠した対策に見えますが、
id:malaの指摘(livedoor クリップ)によると問題があるようです。
実は先程の話とは暗黙の前提に違いがあるのだと思います。
おそらく、1サイト(=1Webプリ)、1ドメインにつき、1セッションIDというのを前提にしているのでしょう。


はてなの場合は、セッションIDはhatena.ne.jpで設定されていて、
そのサブドメインの各サイトで共有しています。
1リクエストで完了する処理なら、XSS脆弱性のあるはてなのどれかのサイトから、
XSS脆弱性のないはてなの別のサイトにtoken付きのリクエストを飛ばして強制的に実行できてしまいます。


もちろん、セッションハイジャックをしてしまえばtokenがサイト間で共有されてなくても、
いろいろできてしまいますが、それについてはこういう事のようです。

原則を補足してみる

tokenは外部のサイトから知り難い(実質知り得ない)ものでないといけない

tokenは(同一団体が運営する親ドメインが同じサイトであっても)外部のサイトから知り難い(実質知り得ない)ものでないといけない


この原則を満たさないと、XSS脆弱性のあるサイトから兄弟サイトへのカジュアルなCSRFは止められませんよ、と。


ワンタイムトークンを使うとしても保存先が兄弟サイトで共有されるセッション変数であれば、

  1. 脆弱性のあるサイトAでワンタイムトークンのhidden fieldを含むページのHTMLをXHRで取得
  2. スクレイピングでワンタイムトークンを取得
  3. 脆弱性のない兄弟サイトBに先程のワンタイムトークンを含むリクエストを送信

とかできちゃうので、結局、「セキュリティ的にワンタイムトークン>セッションIDではない。」のも同じですよ、と。

GreasemonkeyでクロスドメインiframeのcontentWindowにアクセスするとエラーになる件の回避方法

http://from.example.org/ のページでほげほげした結果を
iframeで開いたhttp://to.example.com/ のページにwindow.postMessageで渡して、
そっちのGreasemonkeyで何か処理をさせるみたいなGreasemonkeyを書いててハマったのでメモ。


こんな感じのコードだったのだが、Firebugのcosoleでなら動くのに
Greasemonkeyの中で動かそうとすると動かなかった。

function executeBid(ids){
  var $iframe = $('<iframe/>',{
    src:'http://to.example.com/'
  });
  $iframe.bind('load',function(){
    this.contentWindow.postMessage(JSON.stringify({command:'hoge',data:ids}),'http://to.example.com/');
  });
  $iframe.appendTo('body');
}


これによると、
Greasemonkey0.8系の段階では、クロスドメインiframeのcontentWindowにアクセスするとエラーになるというのが原因らしい。
ついでに、0.9系では直っているらしい。

0.8系のままでも動くようにするにはどうすればいいのか?
contentWindowにアクセスするところをGreasemonkeyの外にしてしまえばいい。

function executeBid(ids){
  var iframeId = 'iframe-'+Date.now();
  var $iframe = $('<iframe/>',{
    src:'http://to.example.com/',
    id:iframeId
  });
  $iframe.bind('load',function(){
    location.href='javascript:('+
    (function(){
      document.getElementById('__iframeId__')
        .contentWindow.postMessage(JSON.stringify({command:'hoge',data:__ids__}),'http://to.example.com/');
    }).toString()
    .replace('__iframeId__',iframeId)
    .replace('__ids__',ids.toSource())
    +')()';
  });
  $iframe.appendTo('body');
}

昨日騒ぎになったTwitterのXSS脆弱性によって実際に受けそうな被害とその対策

何が出来るのか

どんな脆弱性かの詳細はこちらを参照2010 年 9 月 21 日現在のツイッターのバグ(脆弱性)について
外部JavaScriptを読み込むコードを仕込めたので、
どんなJavaScriptでも実行できる状態でした。
以下、JavaScriptの実行によってTwitter上で出来る事。

セッションハイジャック

JavaScriptからcookieを参照できるのでログイン状態のセッションIDも参照できます。
また、任意のJavaScriptが実行できる以上、参照したセッションIDを攻撃者のサーバ等に送信することも出来ます。
攻撃者は奪ったセッションIDをcookieに設定すれば、被害者のアカウントでtweet,direct messageの閲覧・送信が出来ます。
確認してみたところ、ログイン状態のセッションIDはログアウトしても無効にはなりません
設定→パスワードからパスワード変更操作をすれば(変更前後のパスワードが同一でも)以前のセッションIDを無効にできます。

セッションハイジャックで出来る事・出来無い事

本来のユーザがログインしている状態と同じなので大抵のことは出来ますが、以下のことは出来ません。

  • 現在のパスワードを参照する
    • そもそもWebから現在のパスワードを参照する機能がありません
  • ユーザ情報の変更
  • パスワードの変更
    • これらは変更実施時に現在のパスワードを入力する必要があるので、パスワードを知らない状態では出来ません

ブラウザにパスワードを覚えさせている場合のパスワード奪取の可能性

ブラウザにパスワードを覚えさせている場合、JavaScriptで偽のログインフォームを作成し、
usernameに現在ログインしているユーザ名=いつもログインしているユーザ名を入れ、
ブラウザに入力させたパスワードを取得するということができそうです。


ただ、最近のブラウザの場合、最初からHTMLに含まれているformについてのみパスワード入力補完の対象になるようで、
JavaScriptで動的に作成したフォームにはパスワードを入れないようです。
Firefox3.6,Google Chrome6,Safari5については大丈夫なのを確認しました。
また、IE6はダメ(JavaScriptで動的に作成したフォームにはブラウザが覚えているパスワードが入ってしまう)なことも確認しました。


http://twitter.com/ の右上のログインフォームからログインしてパスワードを覚えさせている状態で、
以下をアドレスバーに貼りつけて実行し、一番下に表示されたログインフォームにユーザ名を入れて、
パスワードが自動入力されなければOKです。

javascript:$('<form method="post" action="https://twitter.com/sessions" id="signin"><input name="session[username_or_email]" id="username" type="text"><input name="session[password]" id="password" type="password"><input id="signin_submit" type="submit"></form>').appendTo('body');void(0);

今からやっておくといい対策

セッションハイジャックされてたらログアウトしても無駄なので、
同じパスワードでいい=実際には変更しなくていいのでパスワード変更操作をしておく。
IE6等パスワード奪取の可能性があるブラウザで踏んでしまった場合は、
ちゃんとパスワードを違うものに変更しておく。

git rebaseでよくやること

ticket1というtopic branchで作業していて、
ひとつ前のcommitに対する修正をしたくなったとする。

git checkout HEAD^

でひとつ前に戻って、修正してcommit,commit場合によってはcommit --amend。
この時のhash値(1234ab等とする)をコピーしておいて、

git checkout ticket1
git rebase 1234ab

とやってやると、

o---0---1  ticket1
    \
     2---3---4 

みたいのが、

o---0---2---3---4---1  ticket1

みたいになる。
あまり真面目にtopic branch切ってなかったり、
topic branchの用途に対して不まじめでも、
割と扱いやすいchangesetsが作れて嬉しい。

若手IT勉強会 secrets of the JavaScript Ninja 読書会(第5章 prototypeの話)

注意

基本的に自分が気になったことしか此処には書かないので、
これだけ読んでもNinja本読んだ代わりには全くならないですよ、っと。

HTML Prototypes

HTMLElement.prototypeに生やしたmethodが、
document.getElementById等で取得したDOM要素のobjectで使える。
多分、document.createElementで作ったDOM要素もそうだと思う。

var elem = new HTMLElement(); 

みたいなことはできない。
IE8以降とモダンブラウザで使えるらしい。

ネイティブオブジェクトの拡張

便利だけど、Objectの拡張は気をつけて行うべき、
Numberの拡張はやめたほうがいい。
なんでNumberの拡張をやめておいたほうがいいかというと、
Numberはprimitiveのnumberのラッパーなんだけど、

  • numberを裸のリテラルで書くとprimitiveとしてしか扱われない
  • リテラル表記を()で括るとラッパーオブジェクトとしても扱われる
  • numberを代入した変数は、ラッパーオブジェクトとしても扱われる

というように、prototypeに追加して生やしたmethodを使えるかどうかが
分かりにくいからとかそういう話。

prototypeを使って継承可能なclassを定義する話

基本的にJohn Resig - Simple JavaScript Inheritanceの話
sub classでoverrideしたmethodの中からthis._super methodとして、
super classの同名methodを呼ぶことが出来る。
これを実現するからくりが面白い。

var Person = Class.extend({
  init: function(isDancing){
    this.dancing = isDancing;
  },
  dance: function(){
    return this.dancing;
  }
});

var Ninja = Person.extend({
  init: function(){
    this._super( false );
  },
  dance: function(){
    // Call the inherited version of dance()
    return this._super();
  },
  swingSword: function(){
    return true;
  }
});

こんな感じでclassの定義を行っている(本の方だとextendでなくsubClassだった)。


この時、extendに渡されたobjectが持っているmethodが、
そのままそのclassのprototypeに追加されるのではなくて、

  1. this._superをローカル変数に退避
  2. this._superにsuper classのprototypeの同名を代入
  3. 定義時に渡されたmethodを呼び出し
  4. this._superを元に戻す

という処理のラッパーmethodをclassのprototypeに追加されるようにしている。


this._superという同じ名前のmethodなのに、
呼び出し側のmethod毎に違うmethodが呼ばれるのはこういうわけ。


さらに、効率化のために、super classにoverrideされるmethodがない場合、
sub classで定義されたmethodの中でthis._super methodを呼んでいない場合は、
ラッパーmethod作成処理をスキップしている。


短いコードだけどかなり興味深いので、じっくり読んでみるといい。

旧態然とした会社と最新技術をおいかけまわす会社、以外の会社

それが本当に「間違っている」のであればそれはいずれ淘汰されます。だけれども旧態然とした会社と最新技術をおいかけまわす会社、5年後にどちらが存続しているかは結構微妙な勝負なのではないかと思います。

現在もそんなやり方をつづけられているのであれば、やり方としては間違っているけれども全体としては最善手ではないにしても適手なのでしょう。

http://d.hatena.ne.jp/kuippa/20100814/1281767643

思うに非効率なプロレスをうまくやっている会社=旧態然とした会社は、
最新技術をおいかけまわす会社と5年後の存続可能性が五分五分だとしても、
その時点で主流の言語で非効率なプロレスをやらされてる下請けは
五分五分じゃなくて高確率で消えてたりするんじゃないの?
旧態然とした会社はその時代その時代で別の下請けを使って、
上手くプロレスをやるから存続できるって話なんじゃないのかなー。
とすると、そのプロレスをやらされている下請けは、
この構造に乗っかり続けても得しないよね。

rebase時に順番変わっても元のcommit日時は保持されるのね

画像はMercurial Rebase Extensionのhg rebaseを使った後のlogをTortoiseHGで表示したもの。
もちろんchangesetのhash値は変わっている。
なお、Gitも同じ。