勘違いしてたかも<Taint mode2007年02月05日 10時40分58秒

なお、このtaintperlやPerlの-Tオプションを指定した場合、この手のブラックリスト方式のメタキャラクタ漏れによる脆弱性は発生しない(記号を削るという方式では汚染は除去されていないとみなされる)。

$a = $ARGV[1];
$a =~ s/\W//g; # 汚染されている
$a =~ s/^([a-z]+)$//;
$a = $1; # 汚染されていない

というわけで、Perlでは、CGIというものが普及する以前から、

そのため、記号を全部削るという対策が普及しました。

のような対策は「言語として」推奨されていない(それでもそういう対策をしちゃう人は別問題として)。

ホントかな? ホントかな?

試しに、以下のスクリプトを書いてみましたよ。

#!/usr/bin/perl -T
use strict;

my $file = $ARGV[0];
open FH, $file;

コマンドライン引数の値をそのまま引数 2個形式 open の第2引数に渡しちゃっているという、明らかにダメなプログラム例。話をわかりやすくするため、サンプルも敢えてシンプルにいきますよ。

これ、例えば taint.pl なんて名前で保存して実行権限付加しておいて、以下のように呼び出したりしても、Perl は例外を発生しません

murachi@maha ~ $ ./taint.pl hoge.txt
murachi@maha ~ $

で、以下のように、open 命令の第2引数に渡す文字列がモードを指定することになるような値を、コマンドライン引数に指定すると、例外を発生します。

murachi@maha ~ $ ./taint.pl '>hoge.txt'
Insecure dependency in open while running with -T switch at ./taint.pl line 5.
murachi@maha ~ $

それではおさかなラボの人が「これでは汚染が除去されたとは看做されないよ」とおっさられている方法でサニタイズした場合、どうなるでしょうか? (ここでは敢えて、悪意をこめてこの言葉を使用します。もちろん、このようなコーディングを推奨するわけではありません。) スクリプトを以下のように修正してみましょう。

#!/usr/bin/perl -T
use strict;

my $file = $ARGV[0];
$file =~ s/\W//g;   # ←この行を追加
open FH, $file;

ここで、先ほどと同じコマンド呼び出しで試してみると、例外は発生することなく、スクリプトは実行されます

murachi@maha ~ $ ./taint.pl '>hoge.txt'
murachi@maha ~ $

それじゃあ置換操作が行われちゃっているとことごとく汚染フラグは下ろされちゃうのかっていうとそういうわけでもなくて、例えば以下のように、状況的に間違っちゃっているフィルタリングを行っているような場合、

#!/usr/bin/perl -T
use strict;

my $file = $ARGV[0];
$file =~ s/%([\da-f][\da-f])/pack 'H2', $1/eg;  # ←この状況では無意味な置換
open FH, $file;

以下のように呼び出すことで、やはり例外は発生する。

murachi@maha ~ $ ./taint.pl '%3ehoge.txt'
-bash: ./taint.pl: /usr/bin/perl: bad interpreter: Text file busy
murachi@maha ~ $

何故なら間違った置換操作によって %3e> に置換されるから。そして、その状態で open の第2引数に渡すと読み込みモード以外のモードになってしまうので、安全では無いとして例外が発生する、と。

つまり、Taint mode における Perl は、外部入力由来の値について、適切な置換操作による汚染の除去が行われたかどうかに関わらず、その値の使用時に安全性のチェックを行うと。そして、その値を利用する状況に応じて、その値が安全では無いと看做されるのであれば、例外を発生する、ということなわけだわね。

(Mon Feb 5 23:02:22 JST 2007 追記) とりあえず、致命的に間違っている上記の行に取り消し線を入れておきます。詳細は追記以降をご参照ください。

で、ここまで調べてみて、思ったんだけど、Taint mode は安全では無い値の利用について、その値を利用する (すなわち、出力する) 際の水際で、状況に応じたチェックを行い、安全かどうかを検証する。これが意味するところは、すなわち、プログラマーが状況ごとに正しい値の形式を把握していなければ、Taint mode を利用する意味はあんまりなくなっちゃうんじゃないか? っていうこと。

何が言いたいのかっていうと、つい最近、おいらはこんな記事を書いた。驚くべきことに、taint mode でぐぐると、この記事は結構上位に表示されちゃう。で、この中でおいらは、以下のように書いてしまったわけなんだけれども、

で、実際の、(恐らく) 正しい (と思われる) 運用方法ってのは、一通りシステムを作り終えた段階で最終チェック的なテストの一つとして、あるいはある程度組み終えたところで設計の正当性を確認する目的で、んー、どっちかっていうと後者の意味合いの方が重要になってくるのかな、とにかくそんなタイミングで Taint mode を ON にしてみて、動くことをチェックする。それだけ。で、そこで動かなかった場合に、原因と、それに起因する設計上の欠陥を調査し、設計上の semantics を考慮したうえで、修正を反映する。つまり、Taint なデータが流れている場所を見つけたからといって、その場所だけを局所的に直すんではなくて、それを元に (というかヒントの一つ、ぐらいの目安で) 設計自体を見直す、っていう使い方ができれば、いいんでないかなぁとか思うわけです。

実際に、「結果として危険な値」を流すことができなければ、つまりそういうケースを検証するテストデータを用意することができなければ、設計上の欠陥を見つけることもできないのではないか? ついでに言えば、優秀なテスト設計者がテストを作ってくれるならともかくとしても、仮に設計者自らがテストデータを作成するんであれば、危険な値が流れる可能性のあるデータを用意できるという時点で、プログラムの設計上のキーポイントを把握していると言うことでもあるわけで、そう考えると、上記のような目的で Taint mode を利用するってのは、実はナンセンスなんじゃないかと思えてきてしまったのです。

そうすると、やっぱり Taint mode の正しい使い方は、本番環境で使用する、なのかなぁ。なんだろうなぁ。

あ、ちなみに、推奨されるべき実装は、そもそも 2引数形式の open は使用せず、3引数形式の open を使用するか、または sysopen を使用する、なのであったりします。

#!/usr/bin/perl -T
use strict;

my $file = $ARGV[0];
open FH, '<', $file;    # そもそも 3引数形式を使えと

# いちおうこいつにもリンクしとこ。。。


Mon Feb 5 23:02:22 JST 2007 - 追記

かなだまさかつさん、コメントにてご指摘ありがとうございます。

追試してみました。

#!/usr/bin/perl -T
use strict;

my $file = $ARGV[0];
open FH, '>', $file;

3 引数 open を書き込みモードで呼び出す場合、上記では当然例外になります。これは、以下のような呼び出しであっても例外になります。

murachi@maha ~ $ ./taint.pl hoge.txt
Insecure dependency in open while running with -T switch at ./taint.pl line 5.
murachi@maha ~ $

んで、じゃあサニタイズしましょーってな感じで (←こらこら)

#!/usr/bin/perl -T
use strict;

my $file = $ARGV[0];
$file =~ s/\W/_/g;      # ←とりあえず置換してみた
open FH, '>', $file;

とかやっても、まだ値は「危険」と看做されて例外が発生します。これがかなだまさかつさん曰くところの、記号を削るという方式では汚染は除去されていないとみなされる、ということだわね。

んで、以下のように記述すると、

#!/usr/bin/perl -T
use strict;

my $file = $ARGV[0];
$file =~ s/\W/_/g;
($file) = $file =~ /(\w+)/;     # ←ワード構成文字のみ抽出
open FH, '>', $file;

これなら例外発生せずスクリプトは必ず実行されます。Perl は正規表現のキャプチャを用いた値の抽出を、そのデータが取るべきフォーマットとの整合の結果選択される値と看做して「安全」になったのであろうと (つまり、「洗浄」されたと) 解釈するのですね。

てゆか、追記前の文末でリンクしたこいつにもその辺ちゃんと書いてあるじゃん。読めよ>ヲレ (以下、強調は T.MURACHI による)

しかし、汚染の検査は面倒です。あなたのデータの汚染を取り除くだけと いうこともあるでしょう。汚染検査機構をバイパスするためのただ一つの方法は、 マッチした正規表現のサブパターンを参照することです。 Perl は、あなたが $1、$2 などを使って部分文字列を参照したときに、 あなたがパターンを記述したときに何を行うのかを知っていたと仮定します。 つまり、汚染されていないものを束縛しないか、機構全体を無効にするということです。 これは、変数がなんらかの悪いキャラクターを持っているかどうかを 検査するというのではなく、変数が良いキャラクターのみを持っていることの 検査には都合が良いです。 これは(あなたが考えもしないような)悪いキャラクタを見失うことがあまりにも 簡単であるからです。

ちなみに、以下のようなスクリプトを書いた場合でも、例外は発生せず、スクリプトは実行されてしまいます。

#!/usr/bin/perl -T
use strict;

my $file = $ARGV[0];
($file) = $file =~ /(.+)/;  # ←これはひどいw
open FH, '>', $file;

ちうわけで、Taint mode はわかっていて使う分にはそれなりに保険にはなるけど、サニタイズ的な悪しき習慣を改善するほどの特効薬とはなりえず、わかってないで使おうとするとどつぼに。。。いやいや、ちゃんと理解してから使いましょうってことなんでしょう多分 (^_^; 。

そして、先日の記事で書いたような、設計上間違っている部分がないことを確認するための手段の一つとして利用するっていう考え方も、あながち間違いでは無かったのかな、ととりあえず思ってみる。。。いやでも、まぁ、そう結論付けるのはもうちょっとこの辺いろいろとさらってみてからにしよう。m(_ _;)m


Tue Feb 6 01:15:04 JST 2007 - さらに追記

Taint モジュールを利用すれば、汚染チェックを行う出力レイヤーを自作することもできるわけか。。。例えばこんな感じ?

use Taint;
sub safePrint {
    die 'Insecure request'  if tainted @_;
    print @_;
}

明日試してみよう。今日はもう寝るっす。。。