Home

日記
そういやEdit

Zend FrameworkがOracleにいつ対応するのか質問していた人がいたけど、現時点でもZend_DbにはZend_Db_Adapter_Oracleとか入っているけど、これがちゃんと動かないってことなのかな? PDO OracleドライバーもすでにPHP 5.1.4とかには入っているはずだけど、Zend_Db_Adapter_Oracleがあるから、わざわざZend_Db_Adapter_Pdo_Oracleの方は作られないんじゃないかなー。

Published At2006-08-19 00:00Updated At2006-08-19 00:00

日記
PHP 6のUnicodeサポートEdit

昼飯の時に「たぶんバイナリ文字列とUTF文字列という二つの文字列型が追加されて、文字列処理関数は型を見て処理を振り分けるんじゃない?」と100%想像で言ってしまったんで、気になってPHP6-devのソースを見てみた。

zend_variables.cの

   switch (Z_TYPE_P(zvalue) & ~IS_CONSTANT_INDEX) {
case IS_CONSTANT: {
TSRMLS_FETCH();
if (UG(unicode)) goto dtor_unicode;
}
case IS_STRING:
CHECK_ZVAL_STRING_REL(zvalue);
free(Z_STRVAL_P(zvalue));
break;
case IS_UNICODE:
dtor_unicode:
CHECK_ZVAL_UNICODE_REL(zvalue);
free(Z_USTRVAL_P(zvalue));
break;
case IS_ARRAY:
case IS_CONSTANT_ARRAY:
case IS_OBJECT:
case IS_RESOURCE:
zend_error(E_CORE_ERROR, "Internal zval's can't be arrays, objects or resources");
break;
case IS_LONG:
case IS_DOUBLE:
case IS_BOOL:
case IS_NULL:
default:
break;
}

あたりを見ると、やっぱり従来型のバイナリ文字列とUNICODE文字列の二つの文字列型になるみたいだね。で、各文字列処理関数ではzend_builtin_functions.cから適当に抜き出した、

ZEND_NAMED_FUNCTION(zend_if_strlen)
{
zval **str;
if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &str) == FAILURE) {
ZEND_WRONG_PARAM_COUNT();
}
switch (Z_TYPE_PP(str)) {
case IS_UNICODE:
RETVAL_LONG(u_countChar32(Z_USTRVAL_PP(str), Z_USTRLEN_PP(str)));
break;
case IS_STRING:
RETVAL_LONG(Z_STRLEN_PP(str));
break;
default:
convert_to_text_ex(str);
RETVAL_LONG(Z_UNILEN_PP(str));
break;
}
}

みたいな感じで、やっぱりUNICODE文字列かどうかで単純に処理を振り分けているみたい。UNICODE文字列とバイナリ文字列が入り交じった状態の比較とか、文字列結合とかがどうなるかまでは追ってない。

Published At2006-08-19 00:00Updated At2006-08-19 00:00

日記
自転車買ったEdit

っつーか、オクサンに2ヶ月遅れの誕生日プレゼントとして買ってもらったんだけど。サギサカのRSXとしか書いてなくて、サギサカってメーカーはWeb上に公式な情報がほとんどないんで、いまいちよくわからん。近所のオリンピックに置いてあった中から機能ベース(前後サスペンション、オートライト、前後泥よけ)で選んだだけなんで。

ちょっとだけ近所をぐるっと回ってきたところ、一応スポーツサイクル向けだけあって、今まで乗ってきた通勤通学自転車と比べると、ずいぶんちゃんと走るなー。車道を走ってもそんなにきつくない。ただ、バックミラーがないと路駐避けとかのときに危ないんで、そのうちつけよう。あと、スピードメーターがついていると思っていたんだけど、二つあるメーターは前後のギア表示用のメーターで、スピードメーターはついてなかった。スピード+トリップメーターも後で買おう。

しばらくは近所を走って体を(特にけつの痛みを)慣らして、そのうちときどき通勤で使えるようになるといいなー。ただ、会社まで25kmもあるから、常時通勤に使うってのは無理だろうけど。時間もどうやったって電車よりかかるはずだし。

Published At2006-08-21 00:00Updated At2006-08-21 00:00

日記
1470.netメモの一覧表示時の可読性向上Edit

メモを一覧表示する際、メモのパーマリンク表示時以外では、255バイト以上は省略表示していたのを、1000バイトまで省略せずに表示するようにしました。また、省略表示時にも改行は有効になるようにしました。

また、省略表示時に「続きを読む」をクリックすると、従来はメモのパーマリンクに遷移していましたが、Ajaxが有効な環境の場合はその場でコメント全文を表示するように変更しました。

Published At2006-08-23 00:00Updated At2006-08-23 00:00

日記
String.replace(/\n/g, '<br />')の挙動の違いEdit

↑の2番目の機能を実現するために、Ajaxで取ってきたメモのJSONデータ中に含まれるプレーンテキスト形式のコメント本文を、

comment.escapeHTML().replace(/\n/g, '<br />')

してからinnerHTMLにセットしているんだけど(escapeHTML()はprototype.jsの拡張ね)、FirefoxとOperaでは想定通り表示されるのに、IEではなぜか連続した改行が1個の<br />になってしまい、空行が表現できていない模様。これって何が原因なんだ?

String.escapeHTML()のせいだった

IEでString.escapeHTML()した段階で、連続改行が消えてしまうらしい。確かprototype.jsのesacpeHTML処理ってブラウザ依存の特殊な書き方だった気がするし、そのせいだろうな。

というわけで、自前で置換を使ったescapeHTML()相当の処理に書き直して対応。ただ、非Windows環境ではまた別の問題が出るのかも?

Published At2006-08-23 00:00Updated At2006-08-23 00:00

日記
残り容量が数十MバイトになっていたEdit

PCがなんかくそ遅いなーと思ってふと空きディスク容量をみたら、残り数十Mバイトまで減っていた。Folder Size for Windowsで各ディレクトリ単位のディスク使用量をながめてみたところ、

  • Thunderbirdでimapでアクセスしているアカウントのデータフォルダに、なぜか1GバイトオーバーのINBOXファイルがあった。なにこれ? 削除したけど別に動作には支障はなし。
  • puttyのlogが無限に追記されたよ……。数Gバイト。
  • 昔ダウンロードしたCD/DVD-ROMのisoイメージファイルが、そこかしこに消されず残ってたよ。10Gバイトオーバー。

あと、細かいテンポラリディレクトリの中身とか消したら、30Gバイトくらい空いた。そこまでやって久しぶりにデフラグを起動したら、表示が真っ赤(ほとんど全部断片化されている)だったので、最適化実行中。これで使わないファイルの圧縮とかが走ればさらに余裕ができるかな。

Published At2006-08-25 00:00Updated At2006-08-25 00:00

日記
PHPで安全なセッション管理を実現する方法Edit

なんかこの辺の情報って、ちゃんとまとまっているのを見たことがない気がするんで、まとめておく。特にPHPのセッションを標準設定で使った場合の問題とその対策について。ツッコミ歓迎。

まずセッションの仕組みの基本から。

Webアプリケーションは、通常ステートレス(=状態がない)である。ステートレスというのは何かというと、ユーザー(ブラウザ)が連続的に複数回のアクセス(Webページの表示)しても、サーバー側はそれを特定のユーザーの連続したアクセスと認識せず、単に誰ともしらない複数のユーザーが複数回アクセスしたものとして処理すること。

ステートレスの反対のステートフル(状態を持つ)ってのは、ユーザーがページA→ページB→ページCなんてアクセスをしたり、あるいはページBでフォームからデータを投稿したりしたときに、そのアクセス履歴やら入力したデータなどの情報(=状態)を保持した上で、次にそのユーザーがアクセスしたときに、そういう状態を持つユーザーからのアクセスであることをサーバーが(認識しようと思えば)認識できることを表す。

ステートレスなWebアプリケーションにステートを持たせるための機能が、いわゆるセッション管理機能。これは、

  • Webアプリケーションにアクセスするユーザーを特定する
  • そのユーザーに関する状態(情報)を、サーバー側で保持する

という二つの仕組みを組み合わせて実現している。

ユーザーを特定するための仕組みとしては、ユーザーに対して自動的に(通常初回アクセス時に)識別コードを割り振り、その識別コードを使って特定する。この識別コードのことをセッションIDという。ユーザー(ブラウザ)は、そのWebアプリケーションにアクセスするたびに毎回必ずセッションIDを送信する必要がある。そうしないと、サーバーは同一のユーザーだと識別できない。

セッションIDは通常Cookieとしてブラウザに記憶され、Webアプリケーション(というか、特定のドメインの特定のパス以下)にアクセスするときには、ブラウザが自動的に送信してくれる。Cookieに対応していないブラウザ(携帯とか)では、あらゆるリンクのQUERY_STRINGにセッションID情報をくっつけて送ったりする場合もある。

このセッションIDというのは、ユニークでありさえすれば機能的(ユーザーの識別)には問題ないんだけど、セキュリティまで考えると、ユニークであればなんでもいいという訳ではない。

たとえばセッションIDを、アクセスしてきた人に連番で割り振るような仕組みになっているとする。自分のセッションIDが100だったときに、試しにセッションIDを99とか98に書き換えてアクセスしてみたら、他人(=自分の直前にそのWebアプリケーションを利用した人)のセッションIDを使ってアクセスできてしまうかもしれない。

その場合、そのセッションIDに結びつけられた情報には、他人の個人情報などが含まれていたりして、そういうものが表示されてしまうかもしれない。あるいは、そういう他人の情報を書き換えてしまったり、あるいはその人の権限で何らかの処理を実行できてしまうかもしれない。

それを考えると、セッションIDは第三者が推測可能な内容であってはいけない。そのため一般的には、ランダム(=ロジックで推測されない)かつ十分に大きい(=可能性のある値を順次試す(=ブルートフォースアタック)ことで、実用的な時間内に正解に行き当たることがない)識別コードを生成して利用する。たとえば、マイクロ秒単位の現在時間+乱数発生器+サーバー固有の値+ハッシュ関数の組み合わせを使って、数十桁の16進数文字列を生成したり、とか。

ともかくそうやってセッションIDを使ってユーザーを識別できるようにしたら、そのセッションIDをキーにサーバー側で情報を保存する場所を用意する。どこに保存してもいいんだけど、PHPのデフォルトのセッション機能では、指定されたディレクトリ(/tmpとか)の下にセッションIDをファイル名の一部に持つファイルを作成し、その中にシリアライズ(バイト列表現に変換)されたPHPの配列を保存するようになっている。セッションハンドラーを変えれば、DBに保存したり、メモリキャッシュに保存したり、いろいろできる。

これで、

  • ユーザーがセッションIDをサーバーに送る
  • セッションIDにマッチするセッション保存ファイルを読み込む
  • セッション保存ファイルの内容を復元(unserialize)して、$_SESSION変数に入れる

といった形でPHPのセッション機能が実現されるようになる。

って、セッションの仕組みの基本を説明しているだけで、ずいぶん長くなったな。ここまでは前振り。次からが本論。

PHPのセッション機能は、セッション固定攻撃(session fixation)に対して脆弱だ。セッション固定攻撃っつーのは、攻撃者が用意したセッションIDを強制的に使わせることによって、本来推測不可能なはずのセッションIDの、推測(というかあらかじめ知ること)を可能にしてしまう方法。

PHPのセッション機能は通常、Cookie経由のセッションIDもQUERY_STRING経由のセッションIDも、どちらも認識できる(session.use_only_cookiesオプションでQUERY_STRING経由のセッションIDを認識しないようにできる)。また、セッションIDが外部から渡された場合、そのセッションIDに対応するセッションデータ(通常はファイル)が存在しない場合は、自動的にそのセッションIDに対応するセッションファイルを生成し、正常にセッションを開始してしまう。

つまり、あるユーザーがあるWebアプリケーションにアクセスする入り口のところで、何らかの方法(Cookieのドメインによる有効範囲をうまく利用したり、セッションID付きリンクを踏ませたり)でセッションIDを固定してしまえば、そのユーザーはそのアプリケーションで、既知のセッションIDを使ってセッションを開始してしまうことになる。

今のところPHPには、セッションデータが生成されていないセッションIDが渡されたときに、そのセッションIDを利用したセッションを有効にさせない、というようなオプションがない(パッチはある)ので、外部から渡されたセッションIDでセッションが開始されてしまうことは避けられない。

じゃあどうすればいいかというと、

  • セッション変数の内容を見て、その正当性を確認する
  • セッションIDを変更することによって、危険なセッションIDを無効にしてしまう

あたりの組み合わせが対策となる。たとえば、

session_start();
if (!isset($_SESSION['_SESSION_CHECK']) {
session_regenerate_id();
$_SESSION['_SESSION_CHECK'] = array(
'startTime' => time(),
);
}

などとする。

これで、もしも正しく初期化されていないセッションが開始された場合は、session_regenerate_id()関数を使って、新しいセッションIDを生成しなおすことによって、もしかしたら外部から渡されたのかもしれないセッションIDは使わないことになる。

また、次のようにすることで、より安全性を高めることもできる。

session_start();
if (
!isset($_SESSION['_SESSION_CHECK']) ||
($_SESSION['_SESSION_CHECK']['REMOTE_ADDR'] != $_SERVER['REMOTE_ADDR']))
{
session_regenerate_id();
$_SESSION['_SESSION_CHECK'] = array(
'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'],
'startTime' => time(),
);
}

これは、セッション変数に入っている(=セッション初期化時の)ユーザーのIPアドレスと、今回アクセスしたユーザーのIPアドレスがマッチしない場合も、同様にsession_regenerate_id()関数を使って新しいセッションIDが生成されるようにするコードだ。

ただし、複数のゲートウェイを持つネットワークからのアクセスなどの場合は、正当なユーザーでもアクセスのたびに別のIPアドレスが使われることもあるので、このような書き方はいつでも使えるわけではない。利用するユーザーの環境が限定できる場合のみ利用できる方法となる(あと、リバースプロキシとか使っている環境でもこれだと無意味)。

また、上記のような特定の(不正な可能性のある)条件にマッチした場合に限らず、たとえば、

session_start();
if (!isset($_SESSION['_SESSION_CHECK']) || (rand(0, 100) > 99)) {
session_regenerate_id();
$_SESSION['_SESSION_CHECK'] = array(
'startTime' => time(),
);
}

のように、ランダムに1%の確率でセッションIDの変更を行うという方法もありだろう。これによって、未知の(準備したロジックでは判別できない)攻撃に対しても、ある程度安全性を高めることができるようになる。

しかし、実際には上記のコードはあまり役に立たない。

というのは、session_regenerate_id()関数は、セッションIDは付け替えるが、古いセッションIDに結びつけられたセッションデータ自体はそのまま残してしまう(PHP 5.1.0以降ならば、session_regenerate_id(true)とすることで、古いセッションIDに結びつけられたデータを破棄してくれるので、以下の話は関係なくなる)。たとえば、元のセッションIDがxxxxxxで、session_regenerate_id()後のセッションIDがyyyyyyyだった場合、セッションデータファイルとしてはその両方が残ってしまう。

そのため、せっかくセッションIDを変更しても、古いセッションデータに有効な情報(たとえばあるユーザーのログイン権限と結びつけられた情報など)が含まれていた場合、せっかくセッションIDを付け替えても、古いセッションIDの方が悪用されてしまう可能性がある。

単純に、session_regenerate_id()でセッションIDを変更するだけで安全性が確保できるのは、あくまでもsession_regenerate_id()する前のセッションデータに、有効な情報が含まれていなかった場合のみなのだ。

そこで、

if (isLoginOk()) { // 認証が通った
session_regenerate_id();
$_SESSION['userId'] = [ユーザーID];
}

のような形で、ログイン処理の後など、セッション内に重要なデータを登録するタイミングで、session_regenerate_id()を実行してセッションIDを付け替えることによって、セッションの安全性を高めることになる。

たとえそれまで使っていたセッションIDが危険なものだったとしても、それには重要な情報は含まれておらず、重要な情報は必ずサーバー側で新しく生成したセッションIDに結びつけられることになるからだ。

しかし、未知の危険性に対策のために、すでにセッションデータに有効な情報が含まれている状態で、セッションIDを変更したい場合もあるだろう。その場合はどうすればいいだろうか。

セッションを継続する必要がないのならば、session_destroy()を実行することで、現在のセッションIDに結びつけられたセッションデータは破棄される。しかし、それでは新しいセッションIDで今までの情報を引き継ぐことができない。

そこで、次のようなコードを使うことになる。

session_start();
$tmp = $_SESSION;
session_destroy();
session_id(md5(uniqid(rand(), true)));
session_start();
$_SESSION = $tmp;

テンポラリ変数に現在のセッション変数を待避してから、現在のセッションデータを破棄し、新しいセッションIDをsession_regenerate_id()を使わずに独自のコードで生成してから、再びセッションを開始し、先ほどテンポラリ変数に待避してあったセッション変数を、新しいセッション変数にセットし直す、という方法だ(なぜsession_regenerate_id()ではなくsession_id(md5(uniqid(rand(), true)))を使っているかについてはコメント欄参照)

これで、すでにセッションデータに有効な情報が含まれている場合でも、セッションIDを変更して安全性を高めることができることになる。


何か問題がありそうな記述があったらツッコミください。修正しますんで。


2006/11/20 http://tdiary.ishinao.net/20061120.html#p01に追加情報を書きました

Published At2006-08-25 00:00Updated At2006-08-25 00:00

日記
またマルチアカウントなspammerが来てるなーEdit

xdjfgbuhg vburhnem y564jf8mg m3nf72b6 x6j37fhga ldi9ebguf cgytxu3y91 wyebfts3 p4n56122 f6hfuye6 a4hr63g2a fjf74fhd62 hhdyn36 v4hte53hd dkduen7g dkduen3ygt jduye73bg ikkifjeu3 due7e63h9 y93je7fw jsyd74nbg cvcxrdrk4 pdur73b1 shs71h36f aaudyrng fjryu4hgoo lseujg84 skisu47gh xcefry391 s7sbh37gh dkdugu4ng duu3bgpg hmbnvye43 dud71s9d sis8gj00j djr74ngidy p4kgu7gber x7rh4ht6 nsy8fjt3e firjtu36 irjtufjr74 suryru74 dkdue732a vkrirh47 soed74se lkgjfnruv n47rhw93 p12ns7fe du37eu zbceu39 g4n5r7rh1 ir74nhft35 ogjrnfyu djyu37gfe so4tyr632 f2314det aofjrur73h fjfysbsfq i3he51f dhe351hs lkgurndg vncmdh3 h4nd63n27 o4f6ebwy sksye63bg ffye73bg kis3hgy73 z9f843j gkfj4gh98 f9f84jh7 d8485jgb apf93jes d9h4jh84h d9h84gg5 d74j0d84j dk3udj37 mvlwp9jg dlod94jh9 dod84jtff d47j8ed2 ptihjruf fgifdnrf8 r4t7461 a0z38gk e47tfjri sv84jf75 zlfi4jgy prltmfjs jfu5ke84 xmbjfnri sk3ls81j y47fj5uro dlritj4nbh kkdirj48 jfmtt273 plfjrntyu n5hyrf74t tt84jt849s4 c02f450t woitr74n9r lorgor834 orm9tr4n4 lainj1349 mie8t02f7 pa9ktcmr7 shrbur348j inr4jr8t34 aru94giuu z244838en ma83n8eer an4hnq wer8932 owkre934r4 bnur734r7 kfoe8u343 ojf9w4t7 mc48n5t85 o4u45h4t5 nvty5vb5wp wovu8544jr iwpvu857 ju58w0vw vktu58w mv587tw8v signr74j5 gkvirng8 so48jg8 pwirnbi7 afkjugt84 mf4mf84 g84ns8t l8fm48a n77a4jr9 nv4ka94l8 w83940t p93ja8aa ms94ng94 l884ma4 krkan93 lar8an34r a0023r4b oi38r8gc anfiarar3 mvosiru9a oiur9vfrf rryr83n8a aoinar8 vkr8a46a wfiejroaer7 m83n8rn8r kdfjae84 lkdjgsproi kd83jnga9 kfmru33 nvutks7e nvwpf6b pmxurai ma38q4js zqjldf9ua oa8ka349rk bndkeyr6 mce8eu kurjr7ea krfa8ra la3rjfna aeiefhai7 nfloeua7 oqm7f4kg8a v72mvks nbbcye363 zarq03q zlaia23r ashape3 zmar131 apaura098 vkoar9a3 arakea8 kru63nf7 nciaor81 xveyq734q aeiapr7a bspiru8 jgp49jd fjbg3i fjgblwlp3 gl3oyfks gugj4649 gggfjggk nfl02840 kawausa jrh84839 lhgjro49 jgle6389 zmzcmcor2 mrhyfk820 aieial78 ieje745s amaoei3 lvue7rtr ja38jcfie7 e8fe73fre eue7jee3 cmdua759 e3jfaviez zoe74tf zndgt258 cbsger8r cmfi73ak mchr629 ovneyxmg laien6394 m81kfur7 hfirg3p9 jglfidge62 hfltgoege0 hekrhd6 h94jgklfhw j5b2740rkf pp4jgjdjfj hoglnvbcm ncvbmrju bcieibup3 vbcx840 bhgbjnc93 bmchv3902 nvbwfhi6 bcmo38a jflgh079jdl ofklsb6 nbk730hr20 kfjbwmdj0 ghdlkht9 nhflir0 b639flwh i8394heiii kdj26r983 kdoud993 hlriyt89 jfrketl2g7 kflypfwo93 lkhkglwk ldfor0legk krhebi7f hwu042003 hfi6tfh wrhfl38 hgkwugtl8 rkhw73 lhbwlfu34 jfrjglw7 hkrugo48 j4hlkwlt jhg37tlw jkegflw94 lrfhtp3r59 it89tg1tlw hlbicge3 ker3lg83h hfkrgh0 jfoehjf20 hek39fl isjdkkif3 h83y3r xiu94575 e23i84her egtp2i4 hgo300200 jf73pgj21 gh2906sji hfo39t hi3y693k hfi39t2k lejp4ug0 gkp3ut9 pfjgp37lwsp lkpjvgii8 kpehs605 kgoe305jg h495kd3o oghdo93 loehf972 loh04jhpe jhoehgi4o rhornfl8 jpemhjru korkho47 fhofki47k3 jpyti7639t joehdmn83 khrofnr9 heoemj83ki djvo370fh jfpwgfk6k hfwkdjkv2 tttflehwo jgprhfpo370 yeorjf73 kgoirhdkd6 jroeheo9 fjfrigh8

前回の情報とあわせて、spammerがどういうことをやっている&やりたいのか分析する対象として使わせていただきます。飽きたらアカウントごと消します。

それにしても

前回も今回も、基本的に1アカウントごとに専用ドメインを持つ1サイトのみを登録していってるけど、最近ドメインが安くなっているとはいえ、それなりに投資はしているんだろうなー。それともどこかにただ同然でドメイン+レンタルスペースを配っているサービスとかがあって、そういうのを使っているんだろうか? 内容は良くありがちなキーワードポータル系で、できはいい方ではないけど、こんなんでもそれなりの成果はあるのかなー?

Published At2006-08-29 00:00Updated At2006-08-29 00:00

日記
PlaggerをインストールしてみたEdit

死ぬほど依存モジュールが多かったけど、基本的にCPANでinstall Plaggerときどきforceな感じでインストールできた。で、よく分からんけどまず基本はBloglinesのGmail転送だよねと、YAMLファイルを書いて、plagger --config bloglines2gmail.ymlとかしてみたら、gmail_notify.ttがないとかエラー終了して、Bloglinesの未読が消えた。がっでむ。結局、

svn export http://svn.bulknews.net/repos/plagger/trunk/plagger/assets/

でテンプレートファイルをレポジトリから持ってきて、global/assets_pathにそのディレクトリを指定して対応したけど、テンプレートファイルってこうやって別に入れるものなの? どこかにインストールされていそうな気がするんだけど、よくわからん。

Published At2006-08-29 00:00Updated At2006-08-29 00:00

日記
サーバートラブル中ですEdit

セッション&キャッシュサーバーでディスクフルをやらかしてしまいまして、1470.net関連のサービスが不安定になっています。


セッション&キャッシュを試行錯誤(ファイル、memcached)の末、MySQLバックエンドに切り替えたんだけど、セッション&キャッシュ用MySQLでbinlogを取る設定になっていたんで、セッション&キャッシュの大量の更新ログを出力してしまい、想定外の速さでディスクを食いつぶしていった、ということだった。

Published At2006-08-31 00:00Updated At2006-08-31 00:00