最近書かれた記事5件分を表示します。
更新状況をRSS 1.0形式でご覧になれます。
htmlspecialchars()の使い方を間違えてはいないか。1月16日に書いた記事(続・「サニタイズ言うなキャンペーン」)を読み返していて疑問に思ったため、プログラムをもう一度見直すことにする。
htmlspecialchars()は、「&」「<」「>」を一般実体参照に変換するPHPの関数であり、クロスサイトスクリプティング脆弱性(鳩丸ぐろっさり)への対策として用いるものだ。閲覧者が入力したデータを表示させる場合には必須である。
以下、htmlspecialchars()を使っている部分を抜き出して検討する。
HTTP環境変数は、次のようにして参照している。
$useragent = htmlspecialchars(strip_tags($_SERVER['HTTP_USER_AGENT']), ENT_QUOTES);
if (ereg("Mozilla/4\.0 \(compatible; MSIE", $useragent)) {
$client = "msie";
}
$accept = htmlspecialchars(strip_tags($_SERVER['HTTP_ACCEPT']), ENT_QUOTES);
if (ereg("application/xhtml\+xml", $accept)) {
header("Content-Type: application/xhtml+xml; charset=UTF-8");
} else {
header("Content-Type: text/html; charset=UTF-8");
}
特定の文字列が含まれているか否かを調べているだけであり、HTTP環境変数そのものを出力したり他の処理で使ったりすることはない。よく考えれば、別段htmlspecialchars()やstrip_tags()に通す必要はなかった。
それぞれの1行目を次のように変更した。
$useragent = $_SERVER['HTTP_USER_AGENT'];
$accept = $_SERVER['HTTP_ACCEPT'];
リクエストされたURIは、次のようにして取得している。
RewriteEngine On
RewriteRule ^(.*)\.html$ foo.php?id=/$1
$request = substr(htmlspecialchars(strip_tags($_GET['id']), ENT_QUOTES), 0, 30).".html";
.htaccessを用いてリクエストを整形してからfoo.phpへ渡している。渡されたid($_GET['id'])は、処理するXML文書・XSLTスタイルシートを選択する際に使われる。
細かい話はさておき、条件分岐の最後では$_GET['id']をそのまま使っている。
(前略)
} else {
$xml = $root.$request.".xml";
}
問題があるとすれば、おそらくこの部分だと思う。しかしながらクライアントに直接返される文字列ではないため、htmlspecialchars()は不適である(そもそもが「サニタイズ」の発想だ)。
危険な文字を削除したり変換したりするという発想は「サニタイズ」であるから、エラーを返すことを検討したい。やはり、削除や変換だけでは解決になっていない気がする。
考慮の末、$_GET['id']を受け取る部分を次のように変更した。
$request = substr($_GET['id'], 0, 30);
if (ereg("[^[:lower:][:digit:]/_-]", $request)) {
http404();
}
$request .= ".html";
$_GET['id']にアルファベットの小文字([:lower:])、数字([:digit:])、スラッシュ、アンダースコア、ハイフン以外の文字が含まれていれば、HTTP 404を返す関数(別途用意)へ飛ばす。$_GET['id']には拡張子が含まれないため、ピリオドすらエラーの対象にしている(「../」対策のつもり)。
入力段階・出力段階云々は別として、エラーを返す処理の重要性を思い知った。今までは「サニタイズすればよい」と当然のように思っていたものである。
ここまでにいくつかの勘違いを重ねているが、最後にはもやもや感が消え去ったように思う。
続・「サニタイズ言うなキャンペーン」とは(高木浩光@自宅の日記)にて、12月29日に書いた記事(「サニタイズ言うなキャンペーン」)への指摘をいただいた。
CGI入力に対するサニタイズのコードになっている。おそらく、変更前のプログラムでは、記号の削除をしていたのであろう。「『サニタイズ』=文字削除」と理解されているケースと思われる。
続・「サニタイズ言うなキャンペーン」とは - 高木浩光@自宅の日記より引用
仰る通り、変更前のプログラムでは(CGI)入力に対して記号の削除を行なっていた。「いわゆる危険な文字をプログラムへ渡してはまずいのではないか」という認識を持っていたからだ。その認識が文字の削除・変換といった姑息な手段を連想させ、エラーを出して例外処理を行なうところへ行き着かないという意味だろうか。
それはともかく、12月29日に書いた記事では勘違いをしている。次の引用をしておきながら、htmlspecialchars()を使うことばかり考えていたようだ。
元々、HTMLを出力するときは、その出力全体に対して「<」「>」「&」のエスケープ処理の検討が要求されているのであって、CGI入力に依存しているかどうかは無関係である。
「サニタイズ言うなキャンペーン」とは何か - 高木浩光@自宅の日記より引用
「出力」と「入力」を見落としていた。しっかり引用しておきながらの見落としは恥ずかしい。
「サニタイズ言うなキャンペーン」とは何か(高木浩光@自宅の日記)の見出しすぐ下で挙げられているソースコードは、閲覧者が入力したテクストあるいはHTTP環境変数を出力するものだ。テクストやHTTP環境変数、つまり、いわゆるタグを埋め込める部分を出力する際に、先程引用したようなエスケープ処理が要求されているという認識でよいだろうか。
そうであれば、現在使っているプログラムとは直接的に関係ないのかも知れない。外部から受け取るパラメータと出力するものは次の通りだ。念のためhtmlspecialchars()とstrip_tags()に通している。
念のためというのは、出力段階ではエスケープ処理を行なえないからだ。現在使っているプログラム(PHP/XSLTプロセッサ)の出力内容はXHTML文書そのものであり、出力直前にエスケープ処理を行なうと表示すらできなくなる。例えば、<h1>は<h1>と出力される。
2006-01-25 15:55追記
処理を分岐する際に参照する。例えば、MSIEにはXML宣言を出力しない。
$useragent = htmlspecialchars(strip_tags($_SERVER['HTTP_USER_AGENT']), ENT_QUOTES);
if (ereg("Mozilla/4\.0 \(compatible; MSIE", $useragent)) {
$client = "msie";
}
送信するContent-Type(MIME type)を決定する際に参照する。
$accept = htmlspecialchars(strip_tags($_SERVER['HTTP_ACCEPT']), ENT_QUOTES);
if (ereg("application/xhtml\+xml", $accept)) {
header("Content-Type: application/xhtml+xml; charset=UTF-8");
} else {
header("Content-Type: text/html; charset=UTF-8");
}
返すコンテンツをURIから判断する。/foo.htmlというリクエストは、mod_rewriteを用いてbar.php?id=/foo.htmlに変換してから渡す。
渡されたidは、出力に使うXML文書とXSLTスタイルシートを選択する際に参照する。
$request = substr(htmlspecialchars(strip_tags($_GET['id']), ENT_QUOTES), 0, 30);
外部から受け取るパラメータは以上だ。次に示すのは出力するものである。
GETデータによって選択したXML文書とXSLTスタイルシートのパスを、XSLTプロセッサ(xslt_process())へ渡してXHTML文書へ変換する。変換後はそのまま出力する。
$xslt = xslt_create();
$result = xslt_process($xslt, $xml, $xsl, NULL, NULL, $parameters);
xslt_free($xslt);
echo $result;
XSLTプロセッサへ渡すパラメータを除けば、出入りするものは以上である。閲覧者が入力しうる文字列は一切出力されない。また、変数resultにはXHTML文書そのものが格納されているため、これをhtmlspecialchars()に通すことはできない。
以上の理由から入力段階でエスケープ処理を行なうようにしている。まだ誤読している感は否めないが、少しは前進しただろうか。
ここまでは理解している。だが、外部から受け取ったパラメータを一切出力しない場合、エスケープ処理は本当に必要なのか。
もっとも、万が一に脆弱性があるかもしれないことを想定しての保険として、CGIの入力段階でパラメタを洗浄する(サニタイズする)ことには賛成だ。(中略)「本来の正しいプログラム方法 + 万が一のための保険としてのサニタイズ処理」という整理を常に心がけるべきである。
「サニタイズ言うなキャンペーン」とは何か - 高木浩光@自宅の日記より引用
本来の正しいプログラム方法(出力段階におけるエスケープ処理)を組み込めないため、入力段階の処理に頼っているという状況だ。本来は必要ないとは思うものの、(意味合いが違うが)万が一のための保険として組み込んでいる。
2006-01-25 15:55追記
筋が通っていない。htmlspecialchars()は本当に必要かへ続く。
2006-01-26 02:15追記
書名に惹かれ、轡田隆史氏の『「国語力」をつける本』を読んだ。国語(日本語)には大変に興味があり、日本人であるならばしっかり身に付けたいと常々思っている。
何故、日本語力ではなく国語力なのか。著者は、国語という言葉を、非常に身近な日常性の響きが強い言葉として捉えている。生き方を磨くとは、国語力を磨くことだそうだ。
文章を書く際に「5W1Hを意識せよ」とは、何度も聞いたことがある。when(いつ)、where(どこで)、who(誰が)、what(何を)、why(何故)、how(どのように)したのか。
最も重要なものはwhyである。何故そうなったのか。言わば原因の部分であり、これが欠落すると単なる報告になってしまう。例えば事件の報道は、whyにあたる原因を知らせることで注意を促す意味がある。原因が載っていなければ意味をなさない。
日常の記録であれば、「何故、そのような行動を取ったのか」に相当する。ありふれた行動を記した文章にwhyが付加されると、それはこの世に二つとない自分だけの文章となる。
要するに、読者が知りたいのはwhyなのだ。whyを除けば、同じような文章は数多く存在することだろう。文章を特徴付けるのは、他でもない自分だけのwhyである。
普段から日常の記録、つまり日記を書いている身としては、書くことの重要性を改めて認識した。Webに公開する文章などほんの一握りであり、毎日、数記事分は何かしら書いている。すべての文章を公開しないのは、まだまだ未熟だと感じているが故のことである。
本書は、一般に認識されている「国語力」というより、国語と切って離せない「生き方」について書かれている。生き方を磨くこと、すなわち(著者のいう)国語力を磨くことである。
ATOK主催の全国一斉!日本語テストを受験した。2006年2月28日までの期間限定で公開されている。
問題は、漢字力(9問)・表記力(5問)・文法(3問)・敬語(4問)・手紙の常識(4問)・語彙力(5問)の6項目30問から成る。私は、30問中20問正解の66点であった。普通らしい。
誤答は問題3、7、8の3つ。問題3は本当に知らなかったが、問題7と8は考えすぎて間違えた。問題9は完璧に答えられてよかった。
誤答は問題12、13の2つ。問題13はともかく、問題12の正解(模範解答)には驚いた。意外である。
誤答なし。細かい使い分けには普段から気を遣っているため、3問正解できて自信がついた。
誤答なし。敬語は私の得意とするところであり、この項目は落とせない。
誤答は問題23、24、25の3つ。正直なところ、全然分からなかった。社会に出れば必ず必要になるだろうから、身につけておきたいところではある。
誤答は問題27、28の2つ。正解した問題26と29は知っていたが、他は全然知らなかった。問題30は勘で正解したと言ってよい。
手紙の常識については妥当な結果だと思っている。それはともかく、語彙力の増強が急務だと再認識した。知らない表現が数多く存在するため、読書によって少しずつ知識を増やしていきたい。
寺院と神社の違いが分からない。「聞くは一時の恥、聞かぬは末代の恥」という言葉の通り、知らないままでいるのがもっとも厄介だ。
広辞苑では次のように定義されている。
神道の神を祀るところ。一般には神殿と付属の施設から成る。やしろ。おみや。もり。
仏像を安置し、僧・尼が居住し、道を修し教法を説く建物。中国で「寺」はもと役所の意。伽藍。蘭若。
寺院(Wikipedia)には、現在の日本語では、神道を除く諸宗教の宗教施設を指す語として誤用され広く用いられている。
ともある。神社が神道の施設、寺院はそれ以外の宗教施設、一般的にはこの認識でよいらしい。ただし、寺院の本来の意味は、仏教僧の居場所とのことだ。
お寺の歴史,お寺と神社というリソースもある。
gzip圧縮転送はローカルサーバにおいても有効である。転送量を削減(圧縮)することにより、PHPの実行時間を減らすことができる。
他のプロセスがCPU負荷をほぼ掛けない状況において、gzip圧縮転送の有無による処理時間の差異を調査した。処理内容は、xslt_process()関数によるXML文書からXHTMLへの変換である。
結果は次の通りである。なお、行数はXML文書の行数を示し、ファイルサイズ(KB)は出力結果のサイズ(無圧縮時)を示している。
80行 5KB・400行 28KBの時点では、gzip圧縮を含めることで実行時間が僅かに増大している。ただし、0.005秒の差など本当に僅かなものだ。
他方、2000行・110KBの時点では実行時間の長短に逆転が見られ、10000行 680KBでは0.2秒もの差異が見られる。この0.2秒の差異は、PHPの実行時間としては非常に大きい。
転送量の多少などローカルサーバでは気にしないものだが、PHP実行時間の短縮がこれだけできるなら利用しない手はない。