« (Twitter の XSS 脆弱性に関連して) 構造化テキストの正しいエスケープ手法について | Main | Shibuya.pm でセキュアコーディングの話をしてきた件 »

September 23, 2010

String::Filter っていうモジュール書いた - 続: (Twitter の XSS 脆弱性に関連して) 構造化テキストの正しいエスケープ手法について

先のエントリ「(Twitter の XSS 脆弱性に関連して) 構造化テキストの正しいエスケープ手法について」の続き。

弾さんが「404 Blog Not Found:DHTML - 構造化テキストは構造化するのがやっぱ正しい」で示されているような DOM ベースの操作を行えば、原理的に XSS 脆弱性を防ぐことができます。ただ、クライアントサイド JavaScript によるレンダリングはウェブの構造を破壊するという点で筋が悪い(テーブルと FONT タグを利用したページレイアウトが批判されていた頃を覚えていらっしゃいますでしょうか。JavaScript によるレンダリングはウェブのリンク構造も破壊するので一層たちが悪いというのが自分の考え)ですし、サーバサイドでの DOM 操作は重たいので、できれば避けたいところです。

構造化テキストの HTML への変換は、よほど複雑な記法でない限りはワンパスで処理可能ですし、であれば、わざわざ DOM のような構造を作らなくても、SAX のようにイベントベース(あるいはコールバックベース)で処理すれば済むことなのですから。

このような変換の必要は、Twitter に限らず、しばしば耳にするものです。最近自分が目にしたものだと「Template中のURLを自動で賢くアンカーテキストにしたい - すぎゃーんメモ」。このケースでは URL さえリンク化できれば良かったので tokuhirom さんのコメントのとおりでいいんですが、この URI::Find を使う手法だと URL 以外のものも処理したくなった時に対応できないんだよなーと思って見ていたのでした。

そしたらちょうど Twitter の件があったので、複数の変換ルールを合成可能な文字列変換モジュール String::Filter を書いて CPAN にアップロードしました。このモジュールを使えば、ツイートの変換ルールは下の例のように書くことができます。(@user や #hashtag の前には空白が必要といったルールも入っているため)やや長く感じられるかもしれませんが、意図は明確ですし、ルールを後から追加・変更できるので使いやすいのではないでしょうか。

このような HTML スニペット生成手法と、Text::MicroTemplateText::Xslate のような型ベースの自動エスケープ機能(詳細は「Kazuho@Cybozu Labs: Text::MicroTemplate - テンプレートエンジンのセキュリティと利便性」を参照)を備えたテンプレートエンジンを組み合わせることで、自然と、より安全なウェブアプリケーションを開発できるようになっていけばいいな、と思うところです。

use HTML::Entities (encode_entities);
use String::Filter;

# デフォルトの変換ルールを指定してフィルタオブジェクトを生成
my $sf = String::Filter->new(
    default_rule => sub {
        my $text = shift;
        encode_entities($text);
    },
);

# URL の変換ルールを登録
$sf->add_rule(
    'http://[A-Za-z0-9_\\-\\~\\.\\%\\?\\#\\@/]+',
    sub {
        my $url = shift;
        sprintf(
            '<a href="%s">%s</a>',
            encode_entities($url),
            encode_entities($url),
        );
    },
);

# @user の変換ルールを登録
$sf->add_rule(
    '(?:^|\\s)\\@[A-Za-z0-9_]+',
    sub {
        $_[0] =~ /^(.*?\@)(.*)$/;
        my ($prefix, $user) = ($1, $2);
        sprintf(
            '%s<a href="http://twitter.com/%s">%s</a>',
            encode_entities($prefix),
            encode_entities($user),
            encode_entities($user),
        );
    },
);

# #hashtag の変換ルールを登録
$sf->add_rule(
    '(?:^|\\s)#[A-Za-z0-9_]+',
    sub {
        $_[0] =~ /^(.?)(#.*)$/;
        my ($prefix, $hashtag) = ($1, $2);
        sprintf(
            '%s<a href="http://twitter.com/search?q=%s">%s</a>',
            encode_entities($prefix),
            encode_entities(uri_escape($hashtag)),
            encode_entities($hashtag),
        );
    },
);

# tweet を HTML に変換する
my $html = $sf->filter($tweet);

TrackBack

TrackBack URL for this entry:
http://bb.lekumo.jp/t/trackback/404050/25104625

Listed below are links to weblogs that reference String::Filter っていうモジュール書いた - 続: (Twitter の XSS 脆弱性に関連して) 構造化テキストの正しいエスケープ手法について:

Comments

Post a comment