Perl

October 27, 2010

XSSに強いウェブサイトを作る – テンプレートエンジンの選定基準とスニペットの生成手法 (第1回神泉セキュリティ勉強会発表資料)

発表資料は以下のとおり。春山様はじめECナビの皆様、ありがとうございました。

October 16, 2010

My Slides at YAPC::Asia 2010 (Writing Prefork Servers / Workers, Unix Programming with Perl)

The slides I used for my presentations at YAPC::Asia 2010 are now available from the links below. Thank you to those who came, and JPA and the volunteer stuff for running the event. I hope you enjoyed (will enjoy) my slides!

October 01, 2010

Shibuya.pm でセキュアコーディングの話をしてきた件

昨日は、Shibuya Perl Mongersテクニカルトーク#14 に参加してきました。

パネラーとしてウェブサイトのセキュリティに関するディスカッションに加えていただいて、いろいろ上から目線で大局的な話をしたり。一方、ライトニングトークでは具体的な事例として、既にブログに書いた Twitter の XSS に絡んで構造化テキストの処理手法について話をさせていただきました (参照: 構造化テキストの正しいエスケープ手法について, String::Filter っていうモジュール書いた)。

とはいえ、既にブログに書いたことを繰り返すのも芸がないので、正しい設計が何か、という切り口ではなく、どういう設計をすれば「安全」か、という話になっています。スライドは以下にありますので、興味のある方はご覧ください。

主催の shibuya.pm の皆様、パネルディスカッションに参加する機会をいただいた IPA の皆様、会場をご提供いただいた IIJ の皆様、ありがとうございました。

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);

September 22, 2010

(Twitter の XSS 脆弱性に関連して) 構造化テキストの正しいエスケープ手法について

昨日の Twitter の XSS 騒ぎは、まだ皆さんの記憶に新しいことと思います。いい機会なので、ツイートのような構造化テキストのエスケープ手法について触れておきたいと思います。

Twitter のメッセージは、単なる平文(プレインテキスト)ではなく、「@英数字」のような他のユーザーへの言及と「http://〜」のような URL を自動的にハイパーリンク化する構造化テキストです。

このような複数のルールをもつ構造化テキストを HTML 化する際には、どのようなコードを書けばいいのでしょう? まず「@〜」をリンク化してから、URL をリンク化すればいいのでしょうか? それだと、@〜 のをリンク化した A HREF タグの中の URL がさらにリンク化されていまいますね。

では、URL をリンク化してから @〜 をリンク化すればいいのでしょうか? それだと、@ を含む URL があった場合に、やはり HTML が壊れてしまいます。

正しいアプローチは、全てのルールを同時に適用することです。

Perl で書くなら、以下のように、「@〜」と URL を検出するパターンを単一の正規表現にまとめてトークナイズ (split) し、切り出されたトークンがどのタイプかを判定しながら処理していきます。

my $html = '';
for my $token (split m{(http://[0-9A-Za-z_\.\%\?\#\@/]+|\@[0-9A-Za-z]+)}, $tweet) {
    if ($token =~ m{^http://}) {
        $html .= '<a href="' . encode_entities($token) . '">'
            . encode_entities($token) . '</a>';
    } elsif ($token =~ m{^\@(.*)$}) {
        my $user = $1;
        $html .= '<a href="http://twitter.com/' . encode_entities($user) . '">'
            . encode_entities($token) . '</a>';
    } else {
        $html .= encode_entities($token);
    }
}

でもルールが複雑になってくると面倒なので... 続きはあとで書くString::Filter っていうモジュール書いた - 続: (Twitter の XSS 脆弱性に関連して) 構造化テキストの正しいエスケープ手法について」に続く

August 27, 2010

テストケースの実行にあわせて Apache を起動・終了する方法

ウェブアプリケーションやライブラリの結合テストを行う段階になると、実際に Apache を起動してテストを実行したくなります。しかし、そのためにいちいち Apache の設定ファイルを修正して httpd を再起動して、とやっていては面倒です。特に複数のプログラムを同時に開発していると、あっちをテストしたらこっちが動かなくなって… なんてなったりして嫌気がさしてきます。

そこで、テストを実行する際に、環境毎に異なる以下のような問題を吸収しつつ、テスト専用に設定された Apache を自動的に起動終了してくれる Perl モジュール:Test::Httpd::Apache2 を書きました。

  • 環境によって、インストールパスが違う (/usr/local/apache/bin だったり /usr/sbin だったり)
  • 環境によって LoadModule の要不要や、ロードするパスが違う
  • 環境によってプログラム名が違う (Debian 系では httpd ではなく apache2)
  • Apache/2.0 と 2.2 でモジュール名が変わっているケースがある (mod_access と mod_authz_host)

使い方は簡単。テストコードの先頭で、Test::Httpd::Apache2 のインスタンスを生成すると、自動的に TCP の空きポートを探して Apache が起動するので、そのアドレスに HTTP クライアントからアクセスするだけです。起動された Apache は、Test::Httpd::Apache2 のインスタンスが解放されるタイミングで終了され、一時ファイルも自動的に消去されます。

use Test::Httpd::Apache2;

my $httpd = Test::Httpd::Apache2->new(
    required_modules => [ qw(perl) ],  # mod_perl をロード
    custom_conf => << 'EOT',
<Location />
  SetHandler perl-script
  ...
</Location>
EOT
);

my $root_url = "http://" . $httpd->listen . "/"; # 起動した host:port から URL 生成

# あとはテスト
...

そして、テスト本体が Perl のコードである必要はないので、PHP や他の言語のテストを実行するためのラッパーとして使うこともできます。

use Test::Httpd::Apache2;

my $httpd = Test::Httpd::Apache2->new(
    required_modules => [ qw(php5) ],  # mod_php をロード
    custom_conf => << 'EOT',
DocumentRoot "docroot"
SetHandler php5-script
EOT
);

# 起動した host:port から URL を生成して、環境変数に登録
$ENV{ROOT_URL} = "http://" . $httpd->listen . "/";

# my_test.php を実行
system('php', 'my_test.php') == 0
    or die "my_test.php failed: $?";

Test::mysqldTest::postgresql と組み合わせると、データベースを使うウェブアプリの結合テストが気軽に書けそうで夢が広がりまくりんぐ。

まだ荒削りなモジュールなので、改善提案やパッチ等お待ちしております。インストールは cpan -i Test::Httpd::Apache2、リポジトリは github.com/kazuho/p5-test-httpd-apache2 にあります。

それでは、have fun!

August 17, 2010

Releasing Starlet 0.10, now runs faster than ever

Last Sunday my friend told me that Starman is now way faster than Starlet and I started to wonder why.  There should not be such a difference between the two servers, sicne they share a lot in common.  So I looked into the code yesterday, found the bottleneck and fixed them, as well as adding some tweaks.  Here's what I did, and the chart[1] shows how the performance changed.

Starlet

1) optimize the calculation of Content-Length

Plack::Middleware::ContentLength was the larget bottleneck.  Instead of calling the module Starlet now calculates the Content-Length header by itself in a faster way, when and only when necessary.

2) use TCP_DEFER_ACCEPT on linux

TCP_DEFER_ACCEPT is a flag that suppresses the return of accept(2) until any data arrives on a newly-established TCP socket.  By using the flag, same number of HTTP requests can be handled by a smaller set of workers, since they do not need to poll waiting for HTTP requests to arrive on non-keepalive connections.  The fact leads to less memory pressure and higher performance.  Starlet turns on the flag by default.

3) switch from alarm to select-based polling

Apps can now use alarm for their own purposes.

4) reuse the fake psgi.input

An optimization copied from Starman.

5) merge short content-length with response headers

It is sometimes faster to merge data within perl and send them as a single packet than sending the two separately.  BTW, anyone going to write a wrapper for writev(2)?

The conclusion is that Starlet is still a good choice if you are looking for an application server that runs behind a reverse proxy, maybe better than Starman until miyagawa-san copies the optimizations back :-p

NOTE 1: The benchmark was taken on linux-2.6.32 (x86_64), using a single core of Core 2 Duo running at 3GHz.  The options used were "ab -c 50", "plackup -s Starlet --max-reqs-per-child=10000", "--workers=64" for keepalive and "--workers=16" for non-keepalive.

July 03, 2010

Parallel::Scoreboard でワーカープロセスをモニタリングする方法

 cho45 さんの Plack::Middleware::ServerStatus (Starman や Starlet で Apache の mod_status 相当の情報を得られるようにする - 冬通りに消え行く制服ガールは、夢物語にリアルを求めない。 - subtech) に続き、昨日 kazeburo さんが「StarmanやStarletでmod_statusっぽい情報を得る簡易版Plack::Middleware::ServerStatus - blog.nomadscafe.jp」というエントリを書かれていらっしゃいましたが、ウェブアプリケーションサーバに限らず、複数のワーカープロセスが動作するシステムにおいて、それらの状況をモニタリングするためのスコアボードがほしい、というケースはよくあることだと思います。

 また、プロセス名を使う方法は、他の監視ツールとの相性が悪い、プロセス名の取得方法にポータビリティがない、あまり長いステータスを保存できない、バッファオーバーフローの可能性がある、といった問題があります。

 なので昨日、特定のミドルウェアや特定の規格 (Plack) に依存しない、より汎用的で確実な実装があるといいよね、という話をしていたのですが、気が向いたので書いてみました。新幹線ハック!

 使い方は簡単。なんでも保存できちゃうので JSON で構造化された情報を保存したり、あるいは HTTP の全リクエストを書いておいてモニタリングに活用する、といったことも可能です。

use Parallel::Scoreboard;

# 初期化
my $scoreboard = new Parallel::Scoreboard(
    base_dir => '/tmp/my_scoreboard',
);

# ワーカープロセス側からステータスを保存
$scoreboard->update('this is my current status');

# 全ワーカープロセスのステータスを読み込み
my $stats = $scoreboard->read_all();
for my $pid (sort { $a <=> $b } keys %$stats) {
    print "status for pid:$pid is: ", $stats->{$pid}, "\n";
}

 内部的な仕組みとしては、プロセス毎にステータスファイルを作り、flock を使って死活監視を行うようになっています。

 コードは github/kazuho/p5-Parallel-Scoreboard にあります。また CPAN にもあげておいたので、もうじき seach.cpan.org/dist/Parallel-Scoreboard からダウンロードできるようになると思います。それでは have fun!

注: 「革命の日々! setproctitle(3)のカーネルサポートが-mmに入りました」等を参照

July 02, 2010

Writing a Fast and Secure HTTP Parser (in the case of HTTP::Parser::XS)

In May, I had a chance to give a talk at Tsukuba.xs Beer Talks #1.  I forgot to upload the slide since then, but finally, here it comes :-p

The slide titled "HTTP::Parser::XS - writing a fast & secure XS module" covers the design and implementation of HTTP::Parser::XS: an XS HTTP Parser that is used by a number of Plack-compatible web servers, and picohttpparser: its underlying HTTP parser written in C.

My intention was to introduce to programmers who mostly use Perl the fun of writing tight (fast and beautiful) code using the C programming language.  It would be grateful if you could feel some of my addiction from the slides.  Enjoy!

PS. And if you find any security holes or bugs, please let me know. Thank you in advance.

February 24, 2010

既製品の管理ツールを使わないことでウェブサービスの TCO を下げる話について hbstudy#8 で話してきた件

 昨日、hbstudy#8 で話をする機会をいただくことができたので、Nagios や Amanda といった既製品の管理ツールやバックアップツールを使わずに内製したことで「パストラック」の運用コストを下げた、という話をしてきました。

 もちろん、「既製品を使わない」というのもひとつの手段にすぎませんから、それを無闇にお勧めするつもりはありません。ただ、小回りの効くツールを組み合わせる手法にも十分な競争力があるという点、あるいはその事例として参考になれば幸いです。

 スライドはこちら。hbstudy 運営の皆様、話を聞いてくださった皆様、ありがとうございました。