ここにあるドキュメントは古いものです。 より新しいものが perldoc.jpにあります → perlsec - Perl のセキュリティ


NAME

perlsec - Perl security


DESCRIPTION

Perlは、プログラムが setuidや setgidされるような特別な権限を付加 されて実行されたときでもセキュリティ保持が容易になるように設計さ れています。スクリプトの一行ごとの多重置換を行うことに基づいてい るような大部分のコマンドラインシェルとは違って、Perlは隠れた障害 が少ないような、より便利な評価手法を用いています。それに加えてPerl はより多くの組み込み関数を持っているので、ある目的を達成するため に(信頼できないかもしれないような)外部プログラムを使うことが少な くてすむのです。

Perlは、そのプログラムが異なる実ユーザーIDや実効ユーザーID、実グ ループID、実効グループIDがを使って実行されることを検出したときに、 自動的に taint modeと呼ばれる特別なセキュリティチェックのセッ トを有効にします。UNIXパーミッションにおけるsetuidビットはモード 04000で、setgidビットはモード02000です。これらは重複してセットす ることもできます。taint modeは、コマンドラインフラグ -Tを使っ て陽に有効にすることもできます。このフラグはサーバープログラムで あるとか、CGIスクリプトのような、他の誰かにすりかわって実行される プログラムに使うことを強く勧めます。

このモードで動作しているとき、Perlは明白なわなと隠れたわなの両方 に対処するためにtaint checkと呼ばれる特別な警戒を行います。 これらのチェックの幾つかは、単純です。pathディレクトリが他から書 き込み可能でないことを検査するといったことがそうです。注意深いプ ログラマーは常にこれらのことはチェックしています。このほかのチェ ックはしかしながら、言語自身によって最も良くサポートされます。 そして、これらのチェックは特にset-idされたPerlプログラムを 対応するCプログラムよりも安全にするのに貢献するのです。

自分のプログラムの外側から来たデータをプログラムの外の何かに影響 を及ぼすために使うことは、少なくともアクシデントででもなければ、 できません。すべてのコマンドライン引数、環境変数、ロカール情報 (perllocaleを参照)、幾つかのシステムコール(readdir, readlink, getpw*呼び出しのgecosフィールド)の結果、すべてのファイル入力とい ったものは“汚染された”(tainted)と目印が付けられます。汚染され たデータは直接、間接を問わずサブシェルを起動するコマンドに使うこ とも、ファイルやディレクトリ、プロセスに変更を加えるようなコマン ドに使うこともできません (重要な例外: systemあるいはexecに対する引数リストの 要素として渡した場合には、その要素に対する汚染検査は行われません)。 以前に汚染されていた値を参照していた式 において設定された値はすべて汚染されます。これはたとえ、論理的に 汚染された値が、式の値に影響を及ぼさないと言い切れる場合であって も変わりません。汚染は各スカラー値に結び付けられるので、配列の幾 つかの要素が汚染されていて、そのほかの要素はそうではないというこ ともありえます。

For example:

    $arg = shift;               # $arg は汚染された
    $hid = $arg, 'bar';         # $hid も汚染された
    $line = <>;                 # 汚染された
    $line = <STDIN>;            # これも汚染された
    open FOO, "/home/me/bar" or die $!;
    $line = <FOO>;              # まだ汚染されている
    $path = $ENV{'PATH'};       # 汚染されていますが、下のほうを見てください
    $data = 'abc';              # 汚染されていない

    system "echo $arg";         # 安全ではない
    system "/bin/echo", $arg;   # 安全です (shを使いません)
    system "echo $hid";         # 安全ではない
    system "echo $data";        # PATHを設定するまでは安全ではありません

    $path = $ENV{'PATH'};       # $path が汚染されました

    $ENV{'PATH'} = '/bin:/usr/bin';
    delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

    $path = $ENV{'PATH'};       # $path は汚染されていません
    system "echo $data";        # これで安全!

    open(FOO, "< $arg");        # OK - 読み込みのみのファイル
    open(FOO, "> $arg");        # Not OK - 書き込みしようとしている

    open(FOO,"echo $arg|");     # Not OK, but...
    open(FOO,"-|")
        or exec 'echo', $arg;   # OK

    $shout = `echo $arg`;       # 安全でない。$shoutは汚染されました。

    unlink $data, $arg;         # 安全でない
    umask $arg;                 # 安全でない

    exec "echo $arg";           # 安全でない
    exec "echo", $arg;          # 安全です (シェルを使いません)
    exec "sh", '-c', $arg;      # 安全と解釈されます。ああ!

    @files = <*.c>;             # 常に安全でありません (cshを使います)
    @files = glob('*.c');       # 常に安全でありません (cshを使います)

安全でないことをやろうとすると、``Insecure dependency'' や ``Insecure $ENV{PATH}''のような致命的エラーとなるでしょう。安全でない system もしくは execを書くことはできるけれども、それは 先の例で``安全である''あったようなことを陽にやることによっ てのみだということ に注意してください。


Laundering and Detecting Tainted Data

ある変数が汚染されたデータを保持しているかどうかを検査するため、 そして、``Insecure dependency'' メッセージの引き金になる可能性があ るかどうかを検査するために、あなたの最も身近にあるCPANのミラーサ イトでTaint.pmモジュールを探してみてください。これは1997年の11 月に入手できるようになるでしょう。あるいは、以下のような関数 is_tainted() を使うことができます。

    sub is_tainted {
        return ! eval {
            join('',@_), kill 0;
            1;
        };
    }

この関数はある式のどこかにある汚染されたデータが式全体を汚染して しまうことを利用しています。これはすべての演算子に対して、そのす べての引数が汚染されているかどうかの検査をするので効率は良くない でしょう。その代わりに、一部の式において汚染された値にアクセスし て式全体が汚染されたとみなされるような場合には、もっと効率が良く て保守的な(conservative)方法が使われます。

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

以下に示す例は、データに“語”(アルファベット、数字、アンダース コア)のキャラクター、ハイフン、アットマーク、ドット以外のものが 入っていないことを検査するものです。

    if ($data =~ /^([-\@\w.]+)$/) {
        $data = $1;                     # $data は汚染されていません
    } else {
        die "Bad data in $data";        # これをどこかで記録する
    }

これはかなり安全です。なぜなら\w+は通常シェルのメタキャラクタ ーにはマッチしませんし、ドットやダッシュなどのシェルにとって特別 な意味を持つようなものにもマッチしないからです。/.+/ を使うの は、これはすべてを通してしまうのにPerlはそれをチェックしませんか ら、理論的には安全ではありません。汚染を取り除くときには、自分の パターンについて十二分に注意せねばなりません。正規表現を使ったデ ータの洗浄は、先に説明したより低い特権度の子プロセスをforkするた めの戦略を使うまでは汚れたデータの汚染除去のみの機構です。

先の例では、use localeが有効であるときには$dataの汚染除去を行 いません。なぜなら、\wにマッチするキャラクターはロカールによ って決定されるからです。Perlは、ロカールで決まることを、それがプ ログラムの外から来たデータから構成されているという理由によって信 用できないものとみなします。もしロカールを考慮したプログラムを書 いていて、\wを含んだ正規表現でデータの洗浄を行いたいというの なら、式の置かれたのと同じブロックの前の部分にno localeを置き ます。perllocale に詳しい説明と例があります。


Switches On the "#!" Line

自分の作ったスクリプトをコマンドのように使えるようにしたとき、シ ステムはperlに対して、スクリプトの#!の行からコマンドラインスイッ チを渡します。Perlは、setuid(あるいはsetgid)されたスクリプトに与 えられたコマンドラインスイッチが#! 行にあるものと本当に一致する かどうかを検査します。一部のUNIXやUNIXに似た環境では #! 行には一 つのスイッチしか置けないので、そういったシステムでは-w -Uとい った形式ではなく-wUのようにする必要があるでしょう(これは#!を サポートしていて、setuidやsetgidスクリプトが使えるUNIX環境やUNIX に似た環境でのみ行なわれることです)。


Cleaning Up Your Path

``Insecure $ENV{PATH}'' メッセージに対処するために、$ENV{'PATH'} に既知の値を設定する必要があります。そしてpathに含まれている各デ ィレクトリは、そのディレクトリの所有者やグループ以外からの書き込 みを禁じていなければなりません。実行しようとしているファイルをフ ルパスで書いたとしてもこのメッセージがでるので、びっくりすること があるかもしれません。このメッセージはプログラムのフルパスを書か なかったから出力されるではなく、環境変数PATHを設定しなかったり安 全でない値を設定したりしたために出力されるのです。Perlは対象とな っている実行ファイルが自分自身をturn aroundしたり、PATHを参照し て別のプログラムを起動したりするかどうかを知ることができないので、 確実に自分でPATHを設定するようにします。

この問題を引き起こす環境変数はPATHだけではありません。 一部のシェルでは、IFS、CDPATH、ENV、BASH_ENVのような環境変数を 使っていますから、Perlはこれらの変数がからであるかあるいは サブプロセスが起動したときに汚染されていないかどうか チェックします。setidしていたり、汚染検査をするスクリプトに 以下のような行を付け加えたくなるかしれません。

    delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};   # %ENVを安全にする

このほかの、汚染された値を使っているかどうかに注意を払わないよう な操作によってトラブルに巻込まれる可能性もあります。ユーザーが使 うようなファイル名を扱うファイル検査の使用を賢明なものにします。 可能であれば、ファイルをオープンしたその後で適切にスペシャル ユーザー(グループも!)の特権を落とします。Perlはあなたが読み出し のために汚染されたファイル名を使ってファイルをオープンすることを 妨げませんから、出力の際には注意しましょう。汚染検査機構 (tainting mechanism)はばかばかしいミスに対応するためのものであって、必要な ことを取り除くものではありません。

Perlは、systemexecに対してシェルのワイルドカードがあるか もしれないような文字列ではなく陽にパラメータリストを渡した場合に は、ワイルドカードの展開のためにシェルを呼び出したりしません。残 念なことに、openglob、backtickといったものはそういった別 の呼び出し手順を提供していないので、より多くのごまかしが必要とさ れます。

Perlは、setuidやsetgidされたプログラムから安全にファイルやパイプ をオープンする方法を提供しています。これは単に、汚れ仕事をするた めの制限された権利を持った子プロセスを生成するというものです。ま ず最初に、パイプによって親プロセスと子プロセスとを繋ぐ構文の特別 なopenを使って子プロセスをforkします。このとき、子プロセスは そのIDセットをリセットしさらにその他のプロセス毎の属性をリセット して、オリジナルの、もしくは安全な既知の値へと戻します。それから もはや何の特別のパーミッションも持っていない子プロセスがopen などのシステムコールを実行します。ファイルやパイプは親プロセスよ りも低い特権の元で実行されている子プロセスでオープンされたので、 すべきではないようなことをごまかしておこなうことはできません。

以下に示すのは、安全に backtickを行う方法です。どのようにして execはシェルが展開するかもしれない文字列を伴って呼び出されな いようになっているかに注目してください。これはシェルをエスケープ する目的には最善の方法というわけではありません。これは単に、シェ ルを呼び出さないというだけです。

    use English;
    die "Can't fork: $!" unless defined $pid = open(KID, "-|");
    if ($pid) {           # 親プロセス
        while (<KID>) {
            # 何かを行う
        }
        close KID;
    } else {
        my @temp = ($EUID, $EGID);
        $EUID = $UID;
        $EGID = $GID;    #      initgroups() が呼ばれない
        # 特権が確実になくなるようにする
        ($EUID, $EGID) = @temp;
        die "Can't drop privileges" 
                unless $UID == $EUID  && $GID eq $EGID; 
        $ENV{PATH} = "/bin:/usr/bin";
        exec 'myprog', 'arg1', 'arg2' 
            or die "can't exec myprog: $!";
    }

readdirを代わりに使うことができるにしても、同様の戦略がglob を通じたワイルドカードの展開でも有効です。

汚染検査は、農場をくれてやる (give away the farm)ためのプログラ ムを記述することを自分自身に任せないということではなくて、最終的 にそれをつかって良からぬなにかを行おうとしているだれかを信頼する 必要がないというときに最も便利なものです。これは、set-idプログラ ムや、CGIプログラムのように誰かにすり変わって起動されるようなプ ログラムに便利なセキュリティチェックです。

しかしながら、これは良からぬなにかを行おうはしないコードの作者を 信用しないということとは明らかに違います。これは誰かが、プログラ ムをあなたが今まで見たことのないようにいじって“Here, run this” といわせるようなときに必要な種類の信用です。この種の安全性のため に、Perlの配布パッケージに標準で含まれているSafeモジュールをチェ ックしてみてください。このモジュールはプログラマーがすべてのシス テム操作をトラップし、名前空間のアクセスが注意深く制御されるよう な特別な仕切り(compartment)をセットアップすることを許します。


Security Bugs

スクリプトと同じくらい柔軟に特別な権限をシステムに与えて しまう類の明白な問題の他に、多くのUNIXでは、set-idされたスクリプ トは本質的に安全でない権利を最初から持っています。その問題とは、 カーネルにおける race conditionです。インタープリターを実行する ためにカーネルがファイルをオープンするのと、(set-idされた)インタ ープリターが起動してファイルを解釈するために再度オープンするその 間に、問題のファイルが変更されるかもしれません。特に、使っている システムがシンボリックリンクをサポートしている場合には。

幸運なことに、このカーネル“仕様”(feature)は使用禁止にできるこ ともあります。残念なことに禁止には二つのやり方があります。システ ムはset-idビットがセットされているスクリプトを単純に禁止すること ができますが、このときはなにもできません。もう一つ、スクリプトに 付けられたset-idビットを単純に無視してしまうことができます。後者 の場合、Perlスクリプトにあるsetuid/gidビットが無用なものではない とPerlが認識したときに、Perlはsetuidやsetgidの仕掛けを模倣(emulate) することができます。この機能は、必要とされるときに自動的に起動さ れるsuidperlと呼ばれる特別な実行ファイルを通じて行なわれます。

しかし、kernel set-idスクリプト機能が禁止されていなければ、Perl はあなたのset-idスクリプトは安全ではないとやかましく主張すること でしょう。このとき、あなたはkernel set-id スクリプト機能を禁止す るか、スクリプトをCのラッパーで包んでしまうかのいずれかが必要で す。C ラッパーは、Perlプログラムを呼び出すことを除いては何もしな いプログラムです。コンパイルされたプログラムはset-idされたスクリ プトに関するカーネルのバグには影響されません。次の例は、Cで書い た単純なラッパーです。

    #define REAL_PATH "/path/to/script"
    main(ac, av)
        char **av;
    {
        execv(REAL_PATH, av);
    }

このラッパーをコンパイルして実行ファイルにし、スクリプトではなく この実行ファイルをsetuidしたりsetgidします。

あなたの持っているsetuidされた Perlプログラムに対して上記のこと を自動的に行う便利なやり方が、Perlの配布パッケージのgpという ディレクトリにあるwrapsuidというプログラムにあります。これは、 setuidされたスクリプトを同じ名前で先頭にドットと付いたファイルに 移動し、それから各ファイルに対して上で示したようなラッパーを使っ てコンパイルします。

近年、ベンダーはこのようなセキュリティバグに対する耐性を備えたシ ステムを提供しはじめました。そのようなシステムでは、インタープリ ターを起動するためにカーネルにset-idスクリプトが渡されたときにそ のパス名をそのまま使うのではなく、代わりに/dev/fd/3を渡します。 これはスクリプトでは、あらかじめオープンされている特別なファイル ですから、邪悪なスクリプトをこじ入れるためにつかうことはできませ ん。こういったシステムにおいては、Perlは -DSETUID_SCRIPTS_ARE_SECURE_NOWを付加してコンパイルすべきでし ょう。Perlを構築するConfigrueプログラムは自分自身でこれを見つ け出そうとするので、あなたが特別な何かをしなければならない、とい うことはありあません。SysVr4の最近のリリースのほとんどやBSD4.4は このアプローチをカーネルの race conditionを避けるために使ってい ます。

リリース 5.003以前のPerlでは、suidperlにあったバグによって、 厳密にPOSIXに従ってコンパイルされているシステムにおいて セキュリティホールが持ち込まれる可能性がありました。


Protecting Your Programs

ここで挙げるのは、あなたのPerlプログラムのソースコードをさまざま な“セキュリティ”のレベルで隠す方法です。

しかしまず最初にいっておきますが、ソースコードのリードパーミッシ ョンを落とすことはできません。なぜなら、ソースコードは、コン パイルやインタープリットするために読めるようになっていなければな らないからです(これは、CGIスクリプトのソースがwebの利用者から見 ることができないというのとは違います)。このため、パーミッション は0755レベルにしておかなければならないのです。これによって あなたのローカルシステム上のユーザーはあなたのソースを見ること だけになります。

一部の人達はこれをセキュリティ上の問題であると考えています。あな たのプログラムが安全でないことを行っていて、他人がそういったセキ ュリティの隙間をこじ開ける方法を知らないことに頼っているのなら、 それは安全ではないのです。これはある人が安全でないことがらを見つ けだし、ソースを見ることなしにそれをこじ開けることの要因となりま す。明快さを通したセキュリティはバグを直すのではなく隠すことに比 べれば、セキュリティをほんの少しだけしか傷つけません。

ソースフィルター(CPANにある Filter::*)を通して暗号化しようとする ことはできますが、クラッカーがそれを複号化することは可能でしょう。 先に説明したバイトコードコンパイラーとインタープリターを使うこと もできますが、クラッカーはそれを逆コンパイルすることができるかも しれません。ネイティブコードコンパイラーを使おうとしても、クラッ カーはそれを逆アセンブルできるかもしれません。こういったことは、 他人があなたのプログラムを手に入れようとすることを難しくしたりし ますが、プログラムを決定的に隠すことは誰にもできないのです(この ことは、Perlに限らずすべての言語にあてはまります)。

他人があなたのプログラムから受ける利益について気にしているのであ れば、restrictive licenceがあなたに法的な安全を与えるでしょう。 あなたのソフトウェアのライセンスに、“本ソフトウェアは XYZ Corp. による、公表されていない独占的ソフトウェアです。あなたが使用する ためにこれにアクセスすることは許可されておらず云々”のような脅し 文句を付けておきます。あなたのライセンスが確実に有効なものとなる ように、弁護士と相談したほうが良いでしょう。


SEE ALSO

perlrun for its description of cleaning up environment variables.