Perl は安全な setuid スクリプトや setgid スクリプトを書くことが簡単にで きるように設計されています。シェルはスクリプト内の各々の行に対して置換を 何回も行ないますが、Perl は見えない「内部動作」を少なくした、より伝統的 な評価機構を使っています。さらに、この言語では、より多くの組み込み機能を 備えており、目的達成のために、外部の (そして、信頼のおけない可能性のある) プログラムに頼らなければならないことが、少なくなっています。
多くの OS で、スクリプトのような柔軟なシステムに対して、特別な権限を与え ることによる明らかな問題以前に、setuid スクリプトは、スタート時点から本 質的に安全ではありません。これは、特にシステム上にシンボリックリンクがあ る場合には、カーネルが何を実行するのかを調べるためにファイルをオープンし てから、setuid インタプリタが変更を行なって、そのファイルを解釈するため に再オープンするまでに、何かが変更される可能性があるからです。
幸いにも、カーネルの「機能」は、禁止することができる場合があります。不幸 にも、禁止する方法は2通りあります。一つは、システムが setuid ビットが設 定されているスクリプトを扱えないようにするもので、こちらは余り役に立つこ とがありません。もう一つは、単にスクリプトに付いた setuid ビットを無視す るというものです。もし、後者であれば、Perl スクリプトに付けた、意味のな くなった setuid/setgid ビットを Perl が見つけたときに、setuid と setgid の機構をエミュレートすることができます。これは、必要なときに自動的に呼ば れる、suidperl という特別な実行ファイルによって行なわれます。
しかし、カーネルの setuid スクリプトの機能が禁止されていなければ、Perl は、この setuid スクリプトが安全ではないと警告を発します。カーネルの setuid スクリプトの機能を禁止するか、スクリプトに C ラッパーを被せる必要 があります。どのようにこれを行なうかについては、Perl 配布キットの eg ディ レクトリの wrapsuid プログラムを参照してください。
setuid スクリプトが、この本質的な非安全性のバグから免れているシステムも 存在します。たとえば、Solaris の最近のリリースでは大丈夫なようです。この ようなシステムでは、カーネルがインタプリタへオープンする setuid スクリプ トの名前を渡すとき、そのものを表わすパス名を渡すのではなく、代わりに /dev/fd/3を渡します。これは、既にスクリプトに対してオープンされた特殊ファ イルですから、横取りをするような悪意を持った競合条件を避けることになりま す。こういったシステムでは、Perl をコンパイルするときに -DSETUID_SCRIPTS_ARE_SECURE_NOW をつけておくとよいでしょう。Perl を作る Configure プログラムは、自分でこれを見つけようとします。
Perl が setuid スクリプトを実行するとき、明らかな罠に填ったりしないよう に、特別な予防策がとられます。(いくつかの点で、Perl スクリプトは同じこと をする C プログラムよりも安全であるといえます。)すべてのコマンドラインの 引数、環境変数、入力は、「汚染」マークが付けられ、サブシェルを起動するコ マンドや、ファイルやディレクトリやプロセスに修正を加えるコマンドには、直 接的にも間接的にも使用できないようになっています。汚染マークが付けられた 値を含んだ式で、設定を行なった変数もすべて、(たとえ、論理的に汚染された 値が、その変数に影響を与えることが不可能であっても) 汚染されたことになり ます。たとえば:
$foo = shift; # $foo は汚染された $bar = $foo,'bar'; # $bar も汚染された $xxx = <>; # 汚染された $path = $ENV{'PATH'}; # 汚染されたが、以下も参照 $abc = 'abc'; # 汚染されていない system "echo $foo"; # 安全ではない system "/bin/echo", $foo; # 安全 (sh を使いません) system "echo $bar"; # 安全ではない system "echo $abc"; # PATH が設定されるまでは、 # 安全ではない $ENV{'PATH'} = '/bin:/usr/bin'; $ENV{'IFS'} = '' if $ENV{'IFS'} ne ''; $path = $ENV{'PATH'}; # 汚染されていない system "echo $abc"; # これで安全 ! open(FOO,"$foo"); # OK open(FOO,">$foo"); # OK ではない open(FOO,"echo $foo|"); # OK ではありませんが ... open(FOO,"-|") || exec 'echo', $foo; # OK $zzz = `echo $foo`; # 安全ではなく、zzz は汚染 unlink $abc,$foo; # 安全ではない umask $foo; # 安全ではない exec "echo $foo"; # 安全ではない exec "echo", $foo; # 安全 (sh を使いません) exec "sh", '-c', $foo; # 安全と解釈されてしまいます
汚染状況は、スカラ値ごとに管理されますから、配列要素の一部の要素だけが汚 染され、他は大丈夫ということもあります。
もし、何か安全とはいえないことをしようとしたならば、"Insecure
dependency" とか "Insecure PATH" といった致命的エラーになります。それで
も、安全でないシステムコールや exec
を書くことができることに注意
しなくてはなりませんが、実行するためには、上記の最後の例のように、明示的
に行なうことが必要になっています。サブパターンを参照することでも、この汚
染チェックの仕組みの裏をかくことができます。$1
, $2
といっ
た部分文字列を使うときには、Perl は、使っているみなさん自身が、パターン
を書く時点で、自分で何をしているのかが分かっているものと、仮定するのです:
$ARGV[0] =~ /^-P(\w+)$/; $printer = $1; # 汚染されません
これは、`\w+' がシェルのメタ文字にマッチしませんから、かなり安全で
す。`/.+/' は安全とはいえないものですが、Perl はそれをチェックして
くれませんから、パターンの内容にも注意しないといけません。これは、
($>
を $<
に合わせないのであれば) ファイル名を与えて、その
ファイルに何か操作を行ないたい場合に、汚染のマークを取り除く「唯一」の手
段でもあります。
"Insecure PATH" というメッセージには、$ENV{'PATH'
} に既知の値を設
定し、パスの中のディレクトリは、一般の権限では書き込みができないようになっ
ていることが必要です。よく言われる文句には、実行ファイルのパス名をフルパ
スで書いても、このメッセージが出るというものがあります。しかし Perl は、
その実行ファイルが PATH に依存して別のプログラムを実行しないと判断
することはできないのです。
汚染された値を使うことには関与しない操作で、問題が起こることもあります。
ユーザが指定したファイル名を扱うときには、ファイルテストを賢く利用してく
ださい。可能ならば、open
などは、$> = $<
を設定してから行
なってください。(グループ ID の方も忘れないでください。) Perl は、汚染さ
れたファイル名を読み込みのために open
することは咎めませんので、
出力に気を付けてください。汚染チェックの仕組みは、setuid の過ちを防ぐこ
とを目的としているのであって、何も考えなくてもよいということではありませ
ん。
Go to the first, previous, next, last section, table of contents.