Home

へぼへぼCTO日記

正月早々DBD::mysqlのデバッグやってた

DBD::mysqlのベンチマークとってたらメモリリークが発生。

どうやらサーバサイドprepareなStatementHandlerを使いまわしているとリークする模様。rt.cpan.orgとどっちが反応よいのかよくわからなかったがとりあえずbugs.mysql.comのほうにバグリポート。Bug #41815

というわけで、今年もよろしくおねがいします。

はてぶタグとともに2008年を振り返る

はてぶのタグを年と月ごとにカウントするやつつくりました。
明らかに車輪の再発明ぽいですね。誰かやってるんでしょうけどXML::LibXMLのリハビリがてらに作ってみました。
2007年と2008年のぶんをとってくるです。

$tags = {
    year => {
        month => {
            'TAG' => count,
        },
    },
};

↑とってくるデータ構造はこんなかんじ。

んでコードと。

#!/usr/bin/perl
use strict;
use warnings;

use feature qw/say/;
use utf8;

use Encode;
use LWP::Simple qw/get/;
use XML::LibXML;
use XML::LibXML::XPathContext;

use DateTime;
use DateTime::Format::ISO8601;

use Storable;

my $base_url = 'http://b.hatena.ne.jp/nihen/atomfeed';
my $parser = XML::LibXML->new();
my $xc = XML::LibXML::XPathContext->new();
my $dt_fmt = DateTime::Format::ISO8601->new;

my $page = 0;
my $tags = {
    2007 => {map { $_ => +{} } 1..12 },
    2008 => {map { $_ => +{} } 1..12 },
};
SEARCH_2007_2008:
    while (1) {
        my $content = get($base_url . '?of=' . ( ++$page - 1 ) * 20);
        say 'entry of ' . ($page - 1) * 20 . '-' . $page * 20;
        my $doc = $parser->parse_string($content);

        my $namespace = $doc->documentElement()->getAttribute('xmlns');
        my $namespace_dc = $doc->documentElement()->getAttribute('xmlns:dc');
        $xc->registerNs('x', $namespace);
        $xc->registerNs('xdc', $namespace_dc);

         my @entries = $xc->findnodes('/x:feed/x:entry', $doc);

        if ( !@entries ) {
            last SEARCH_2007_2008;
        }

        foreach my $entry ( @entries ) {
            my ($issued) = $xc->findnodes('x:issued', $entry);
            my $dt = $dt_fmt->parse_datetime($issued->textContent);

            if ( $dt->year < 2007 ) {
                last SEARCH_2007_2008;
            }

            my ($title) = $xc->findnodes('x:title', $entry);
            my @tags = $xc->findnodes('xdc:subject', $entry);
            for my $tag_el ( @tags ) {
                my $tag = $tag_el->textContent;
                if ( !defined $tags->{$dt->year}{$dt->month}{$tag} ) {
                    $tags->{$dt->year}{$dt->month}{$tag} = 1;
                }
                else {
                    $tags->{$dt->year}{$dt->month}{$tag}++;
                }
            }
        }
        store $tags, 'tagsstorefile';
    }

store $tags, 'tagsstorefile';

WALL・Eをみてきた

昨日、ポケモンの映画を見たいと言っていた子供たちを誘って、pixarの最新作「WALL・E」を見てきた。

劇場は新宿ピカデリーさんのスクリーン2。新宿バルト9さんと違いDLP上映なのかはっきりしないのだが、おそらくDLP上映であった。

pixar映画は実はそんなに見ておらず「トイ・ストーリー」「トイ・ストーリー2」「バグズ・ライフ」「ファインディング・ニモ」あたりを見ただけだったりする。しかしどれも好きな作品で、特に「トイ・ストーリー」はdebian使いとしては外せない。ちなみにディズニーランドにいくとバズライトイヤーのアトラクションに最低2回は乗ることも付け加えておく。

内容はまさに大人も子供も楽しめるSF映画であり親子共々楽しませてもらった。もちろんpixar映画特有のCG映像美はDLP上映ということもあり十分満足できた。そして当初無機質に見えて、性別なんてものも意識するような対象じゃなかったイブが、見た目は何一つとして変わらないのにも関わらず、だんだんとかわいく見えてきて、明らかに「女の子」に見えてしまうのであるから演出というものは本当にすごいものである。

ところで、幼いころにLDで「風の谷のナウシカ」を繰り返し、繰り返し見てきた私としてはナウシカのオープニングとWALL・Eのエンドクレジットの類似性を指摘せざる負えないだろう。エンドクレジットを見た瞬間に、「なるほどWALL・Eの物語はナウシカへのオマージュという意味もあったのだなぁ」と一人納得してしまった。

これを機会に他のpixar映画も一通り見ておくとするかな。と思わせてくれる良作。

mysqlのパーサのわからないところ

ここんところ文字コードとmysqlの関係が頭から離れずにいろいろと調査中だったりします。

で。mysqlのパーサのコードを読んだりもしてるわけなのですがどうしてもわからないことが。

サーバサイドPrepareを使わないで、
character-set-clientが’sjis’でフィールドのキャラセットが’utf8′のときに
0×815f(\)が0×5c(\)ではいり
0×955c(表)が0xE8A1A8(表)
できちんと入ること。
SQLParse => utf8化
の順番であれば、0×955cのあとの’がエスケープされてエラーになりそうだし
utf8化 => SQLParse
の順番であれば、0×815f(=>0×5c)のあとの’がエスケープされてエラーになりそう。
でもエラーにならない。うーむ。ちなみにコード読んだ限りではParse後にutf8化してるように感じるけど・・・。
yaccがマルチバイトなencodingを認識してパースしてくれているんだろうか。ぐふう。

まぁはっきりいってコードの読みがまだまだ浅いのとyacc(bison)をちゃんと理解しないとわかるはずもないんだろうけど。

追記1: あ、おk。分かった。sql/sql_lex.ccのget_textでマルチバイトの処理やってた。

UTF-16時代のエスケープ処理

Connector/JのSQLインジェクション脆弱性

えーと、変換後がUCS-2とかだとマズいかな?

…(実験中)…

試作パッチ以前に、Connector/Jでcharacter_set_server=ucs2のサーバに繋がらないんですけど。
Connector/J側でcharacterEncoding=UTF-8などとしておけば繋がりますが、こんな仕様あったかな…。

そもそもucs2は現在クライアントキャラクタセットとしては使えないんじゃないでしょうか。

9.1.9. Unicode Support

UCS-2 cannot be used as a client character set,
which means that SET NAMES 'ucs2' does not work.
(See Section 9.1.4, “Connection Character Sets and Collations”.)

ちなみにUCS-2とかUTF-16のようにASCIIな部分までマルチバイトなキャラクタセットが使えるとなると、Cのmysql_real_escape_stringも問題が発生しそうですね。マルチバイトはエスケープ対象外という処理でShift_JIS(やGBK)の5c問題を解決しているようなので。これは現在のサポート文字セットに依存した脆弱な実装ということになるんでしょうか。

そんな時代がくるのか分かりませんが、perl的にUTF-16時代の正しいescape処理を考えてみるとこんな感じ?

#!/usr/bin/perl
use strict;
use warnings;

use utf8;
use Encode;

# from DBD::mysqlPP::quote
my @quote_target = (
    "\\"   => '\\\\',
    "\0"   => '\\0',
    "\n"   => '\\n',
    "\r"   => '\\r',
    "'"    => q{\\'},
    '"'    => '\\"',
    "\x1a" => '\\Z',
);

my $str = "I'm sorry\nok.";

print quote($str, 'utf-16LE');

sub quote {
    my ($str, $charset) = @_;

    my %quote_target_for_charset = map { Encode::encode($charset, $_) } @quote_target;

    $str =~ s{
        (.)
    }{
        my $bytes = Encode::encode($charset, $1);
        $quote_target_for_charset{$bytes} // $bytes;
    }exmsg;

    return $str;
}

Yen markのhtmlでのエスケープもなんかへんな感じになってるのでファイルごとup
1文字ずつencodeしてるのはなんかやーなかんじですね。試してないけどRubyだと$KCODEの切り替えを使うとencode的な事は1回で済むのかも。あとで試してみよう。

年末年始にやることりすと

  • 映画を映画館で2本みる(1本を子供とで1本は一人で)
  • 文芸書を2冊は読む
  • 詳説ActionScript3.0を読了する
  • DBD::mysqlのmysql_set_character_set対応パッチを書いてみる
  • ついでにDBD::mysqlPPの5C問題調査とか
  • Sennaを試す
  • OpenIDを試す
  • gitとgithubを試す

useServerPrepStmtsを使うのが根本解決だとはおもう。けど…?

UnicodeのU+00A5問題

JavaとMySQLの組み合わせでUnicodeのU+00A5を用いたSQLインジェクションの可能性
なるほど。この問題は初耳だったので驚いた。

しかも、PreparedStatementを使っても解決されないという。この部分に非常に驚いたのでどういうことなのか少し調べてみた。

PreparedStatementとは?

mysqlにおける話としてはPrepared Statement (訳)がとてもわかりやすい。
なかでも重要なのは以下の部分だ。

PerlとJava のユーザはかなり長い間prepared statementを使って
きました。しかし、これらはクライアント側のprepared
statementでした。
クライアント側のprepared statementは同じようなセキュリティの恩恵
をもたらしますが、性能向上には至りません。でも心配いりません。
MySQL Connector/J は3.1のリリースでサーバーサイドprepared statement
をサポートします。

そして実は現在のConnector/Jの最新版(5.1.7)ではデフォルトでは「サーバーサイドprepared statement」を使っていないのである。そしてクライアントサイドのprepared statementは結局のところエスケープ関連のセキュリティ脆弱性を潜在的に抱えてしまう存在なのである。そして実際に今回の問題があった。

Connector/JでサーバーサイドのPreparedStatementを使う

jdbcのURLにuseServerPrepStmts=trueをつけるだけである。

      Connection con = DriverManager.getConnection(
        "jdbc:mysql://localhost/tokumaru?user=xxx&password=xxxx&useUnicode=true&useServerPrepStmts=true&characterEncoding=" + charEncoding)

簡単ですね。こうすることにって、U+00A5問題は発生しません。エスケープ自体どの時点でもしないわけですから。めでだしめでたし。

しかし、useServerPrepStmtsのデフォルト変更問題が

useServerPrepStmtsのここの説明ではデフォルトがtrueになっているが、これは上述の通り嘘である。ちなみに英語マニュアルをみるときちんとfalseになっている。これにはuseServerPrepStmtsの登場時(Connector/J 3.1.0)にはデフォルトがtrueだったが、5.0.55.1.0においてfalseに変更されたという経緯があるためのようだ。そしてなぜfalseにされたかということの背景を察すると、trueにすることの弊害もありそうで、手放しでこれをtrueにすることを勧めることが少しはばかられる。これについて詳しい方がいたらぜひ説明をお願いしたいところである。

そろそろCakePHPについて一言言っておくか

  • 2008-12-07 (日)
  • php
  • hatena count

前回のエントリを見てくれた人がCakePHPのtracにこれ脆弱性じゃね?ってチケットを立ててくれたよ!で、見に行ってみたらCakePHPのエライ人がこんな回答をしてくれてたよ!

#5842(getClientIP() possiblly return false IP address)

this is not a security exploit in Cake,
but should certainly be something to be aware of when building an application.

超意訳: CakePHPの脆弱性ではありません。「仕様です」。だからアプリケーション構築するさいはちゃんと知っとくべきことだよね!

オーケー、分かった。確かにこれはCakePHPの脆弱性ではなかった。CakePHPを使っていた僕らの脆弱性だったんだ。オーケー、前言は撤回するよ。正しくはこうだったんだ。

CakePHPが許されるのは小学生までだよねー

CakePHPのgetClientIPを使っていいのは小学生までだよねー

  • 2008-12-03 (水)
  • php
  • hatena count

PHPで開発をすることが多くなりPerlの良さを再確認している今日この頃ですが皆さんいかがお過ごしでしょうか。

さて、今日は今もっともナウいPHPのWebアプリケーションフレームワークであるCakePHPのお話を一つ。

CakePHPには組み込みコンポーネントとしてリクエストハンドラ(RequestHandler)が備わっています。
リクエストハンドリング :: 組み込みのコンポーネント :: マニュアル :: 1.2 Collection :: The Cookbook:

このRequestHandlerのメソッドであるgetClientIPが小学生には危険そうだというお話。(あ、このエントリのタイトル逆w)

まずはgetClientIPの実装コードを。(1.2を例にとっているが1.1もほぼ一緒である)
https://trac.cakephp.org/browser/trunk/cake/1.2.x.x/cake/libs/controller/components/request_handler.php

function getClientIP() {
    if (env('HTTP_X_FORWARDED_FOR') != null) {
        $ipaddr = preg_replace('/(?:,.*)/', '', env('HTTP_X_FORWARDED_FOR'));
    } else {
        if (env('HTTP_CLIENT_IP') != null) {
            $ipaddr = env('HTTP_CLIENT_IP');
        } else {
            $ipaddr = env('REMOTE_ADDR');
        }
    }

    if (env('HTTP_CLIENTADDRESS') != null) {
        $tmpipaddr = env('HTTP_CLIENTADDRESS');

        if (!empty($tmpipaddr)) {
            $ipaddr = preg_replace('/(?:,.*)/', '', $tmpipaddr);
        }
    }
    return trim($ipaddr);
}

(envの実装はbasics.php。簡単にいってしまえば$_SERVERか$_ENVから指定されたkeyの値をとってくるというところ。)
ひええ。怖いですね。小学生には使いこなすのは厳しそうですね。それではみなさんご機嫌よう。

といって終わるのも不親切なので何がどう危険なのかを少し解説。

HTTP_X_FORWARDED_FORというのはhttpヘッダのFORWARDED_FORX-FORWARDED-FORのことで、これは一般的には、匿名ではないプロキシに接続している際にプロキシサーバがアクセス先のサーバにhttpリクエストのヘッダに勝手に送信元のIPアドレスを付与しちゃうものだ。

で、このコードではそのヘッダに何か値があればそれを送信元IPアドレスとして返してしまう。

ちなみにリバースプロキシを使っている時はmod_rpafmod_extract_forwardedを使ってこのコードと同様のことをapacheレベルでやったりはする(mod_rpafとmod_extract_forwardedは置き換えのフックのタイミングが違ってたりする)。しかし決定的に違うのは、mod_rpafにしろmod_extract_forwardedにしろ、置き換えを許す送信元のプロキシのアドレスを限定する設定があるのだ。mod_rpafではRPAFproxy_ips、mod_extract_forwardedではMEFacceptがそれにあたる。ところがこのgetClientIPにはそういったコードは見当たらない。

ここで一度整理しておきたいが、phpで取得できる$_SERVER['REMOTE_ADDR']はwebサーバに実際に接続してきたtcp/ipにおける送信元アドレスであり、3ウェイ・ハンドシェイクにより検証されている信頼性の高い送信元IPアドレスといえる(もちろんシーケンス番号の問題とかもあるが)。それに比べhttpリクエストヘッダに記述されるFORWARDED_FORX-FORWARDED-FORの値は、webサーバにとってはまったく検証を経ていない信頼性の低いアドレスなのである。

上記のことからわかるのはgetClientIPには任意のIPアドレスを注入できるということだ。要するにIP偽装(IP Spoofing)である。

ちなみにfirefoxであればアドオンのModify Headersなんかを使えば簡単にhttpヘッダは任意のものが作成できる。

ところで、WebアプリケーションにおいてIP偽装が問題になるのはどういう場面であろうか。それは認証に利用している場合であろう。日本に住んでいるwebエンジニアが真っ先に思いつくのは携帯の個体識別番号認証でのIPアドレスチェックではないだろうか。先日高木浩光氏に退化してゆく日本のWeb開発者と揶揄されていたが、個体識別番号認証自体を完全否定することは私にはできないが、少なくともCakePHPでモバイルサイトを構築されている方はこの件を確認していただくのがよいかとおもわれる。しかしたとえば携帯とPCでデザインを変えているだけなんて場合には影響は皆無であろう。

ちなみにこの件はCakePHP開発側に伝えるべきなのだろうか。しかしこれだけ確信的にコードに書かれていると「仕様です」という感じがしないでもない。

しかし、小学生が使うことも考慮して以下のマニュアル等には「よいこのみなさん、ここでえることのできるIPアドレスはほんとうのクライアントのIPアドレスとはちがうばあいがあるよ」ぐらいは書いておいてほしいものだ。
クライアントについての追加情報を取得する :: リクエストハンドリング :: 組み込みのコンポーネント :: マニュアル :

まぁとりあえず小学生は$_SERVER['REMOTE_ADDR']を普通に使っておくとよいとおもわれる。

追記1: $_SERVER['HTTP_X_FORWARDE_FOR']のhttpヘッダでのキーは”X-FORWARDED-FOR”ですた

情けは人のためならず

ここ最近自宅で料理をするようになった。

人からもらったケンタロウのレシピ本cookpadやニコ動の料理カテゴリを活用しながらなんとかやっている。

そんなある日レシピの中に「出汁200cc」と書かれていた。「出汁」がいったいなにものなのか分からなかった私は、本をくれた知人に「デジルを200ccってあるんだけどデジルってなに?」と聞いてみた。

「…。だし、と読みますが」

「…。あぁ…。」

それはそうと先日、ホテルのバー通いが話題になっている我が国の総理が踏襲を「ふしゅう」と読んで話題になっていたが、私も4-5年前まで同様に「ふしゅう」と読んでおり気づいた時には死にたくなるほど恥ずかしい思いをしたものである。

読み間違いを指摘するのは自分自身もなんとなく気恥ずかしい。けれど、その人のために指摘してあげられるようになりたいものだ。

出汁をデジルと読む国民的アイドル木村拓哉さん(1:31あたり)

※このメタ構造はやっぱ分かりづらいと思ったので一応補足しておくとタイトルは当然誤用である。

Home

Search
Feeds
Meta

Return to page top