CGIの安全性

Perlで書くCGIスクリプトは、手軽にフォームを処理したり掲示版を作るのに 便利です。オープンソースで出回っているスクリプトもたくさんあり、それらをダウンロード して自分なりにカスタマイズして使っている方も多いでしょう。 しかし、CGIスクリプトでは、不注意なプログラミングが即セキュリティーホールに結び付く ことがあります。以下、自分が普段気をつけている事項をメモしておきます。 他に「ここに気をつけたほうが良い」ということがありましたら、 メイルまたは 掲示版 にて教えて頂ければ幸いです。

やってはいけないこと

× パイプ構文 open "| something" を使う

メイル送信フォームで、open "| $sendmail $email" ($emailは クライアントから来るフォームのパラメータ) などとしている例を見かけましたが、 これは絶対にダメです。玄関を開けっ広げにして出かけているようなものです。 たとえ$emailをregexpでチェックしても、完全に侵入をシャットアウト するのは困難です。

駱駝本 "Programming Perl" の第6章にも解説がありますが、ここは open "|-" || exec($sendmail, $email) のように、パイプとexec()コールを使うのが良いでしょう。 エラーチェックも考慮すると、だいたいこんな感じになるでしょうか。

    my $pid = open PIPE, "|-";
    die "Fork failed.\n" unless (defined($pid));
    $SIG{ALRM} = sub { die "Subprocess died prematurely.\n" };

    if ($pid) {
        print PIPE, .... # sendmailに渡すデータ
        close(PIPE) or warn $! ? "Error closing pipe: $!"
                               : "Subprocess exit with status $?";
    } else {
        exec('/usr/sbin/sendmail', $email)
           or die "Couldn't exec sendmail.\n";
    }

× クライアントから提供されるパラメータでファイルをオープンする

例えば、掲示版スクリプトで、既にある記事の編集が出来るようになっていると します。記事はarticleディレクトリの下に記事番号を名前として 保存されるとしましょう。

編集リクエストをフォームパラメータ article_no で受け取って、そのまま open "> article/$article_no" などと していたらアウトです。$article_no"../../cgi-bin/bbs.cgi" のような文字列を入れられた場合を 考えて下さい。

$article_no =~ tr/0-9//cd のようにして怪しい文字を除去するか、 regexpでチェックをかけて不正なパラメータをはじくようにします。

    $article_no =~ /^[0-9]+$/ or die "Invalid article number.\n";
    

× CGIプログラムを置くディレクトリのパーミッションが777

Unixでは、ファイルそのものに書き込み権限が無くても ディレクトリに書き込み権限があればファイルを上書き出来ます。 これはロジカルな仕様なのですが、慣れるまでは直観的にわかりにくいようです。 ディレクトリのパーミッションが777ということは、誰でもそこにcgiスクリプトを 追加したり、既存のスクリプトを上書きしたり出来ると言うことです。

それでも、CGIプログラムがセキュリティホールの無い完璧なものならば、 Webでアクセスしてくるクラッカーには手も足も出ません。 しかし、上に述べたような穴が一箇所でもあった場合、芋蔓式に侵入経路を 広げてしまうことに鳴り兼ねません。CGIが置いてあるディレクトリのパーミッションは 755にしておき、CGIプログラムが書き込む必要のあるデータは別のディレクトリを 作ってそこに置くのが良いでしょう (データディレクトリは全員に書き込み権限を 出しておきます)。

なお、httpサーバのセッティングによっては、CGIスクリプトがそのオーナーの 権限で実行される場合があります。この場合、他人のCGIスクリプトの穴から 侵入される恐れは減りますが、自分のCGIスクリプトに穴があると防ぎ切れません。 500くらいにしておけば多少は安全でしょうが…

× 環境変数HTTP_USER_AGENT, HTTP_REFERER等をノーチェックで使う

HTTP_USER_AGENTHTTP_REFERERは外部からアクセス している人が提供する情報です。自前でクライアントをでっちあげれば、 任意に文字列を送信することが簡単にできます。

やった方が良いこと

オプション-wTをつける

Perlには、外部から提供される信頼のおけないデータを追跡して、 それが危ないオペレーションに使われたらエラーを出すという機能(tainting、「汚れ」) があります。 サーバーの設定によっては、CGIスクリプトはすべてtaint機能をオンにして 実行されているかもしれません。確実にtaintingを有効にするには、 実行時にオプション-Tをつけてperlを起動します。CGIスクリプトの場合、 一行目をこんなふうにすると良いでしょう。

    #! /usr/bin/perl -T
    

この機能はかなり「うるさい」です。例えば、PATH環境変数を 自分でセットしていないと怒られます。ユーザ提供のパラメータをチェックせずに 使ってファイルをオープンしようとしても怒られます。しかし、taintingで 文句を言われないように作るのが最低ラインと思ってがんばってください。

Taintなパラメータは、regexpなどでチェックをかけることで綺麗にすることが できます。ただし、regexpが確実に不正な入力をチェックできるものでなければ 意味が無いことに注意してください。Perlにはデータの意味はわからないので、 そのチェックが有効であるかどうかを判断することはできません。

また、どうせオプションをつけるなら-wオプションもつけてやると 良いです。プログラム作法上好ましくない箇所を警告してくれます。 こいつもかなり「うるさい」ですが、実用的なプログラムを作るには この警告を黙らせるくらいの気遣いは最低限必要です。

エラーはしつこい程にチェック

あるCGIスクリプトで、クライアントが提供する名前でテンポラリファイルを 作った後、renameでCGIが管理する名前にリネームしているという 操作がありました。しかし、そのスクリプトはrenameが成功したか どうかをチェックしていませんでした。

あるハッカー(*)が、 自分の望む箇所にファイルを作成し、さらにrename の呼出しを失敗させることができるようなパラメータを渡すことに成功しました。 テンポラリファイルはそのまま残り、それを踏み台としてそのハッカーはシステムへの 侵入に成功したのです。

本来のプログラムロジックでは起こり得ない状態というのが、プログラムのバグや 予期せぬ入力によって起こる場合があります。起こってはならない状態の部分に チェックルーチンを入れて、プログラムを安全な状態に押し戻すなり、管理者に 通報するなりしておくと良いでしょう。


ハッカー : 通常、悪意を持ってシステムに 侵入し、破壊活動や情報を盗む輩はクラッカーと呼ばれます。ハッカーはむしろ、 計算機に深い造詣を持ち、複雑な仕事をさくっとやってのけてしまう技術者に 対し、しばしば尊敬の念を伴って使われます。

ここで例にあげたケースは、実は侵入者は悪意を持っていたわけではなく、 セキュリティーの検証のために侵入したものでした。したがってここでは 「ハッカー」の呼称を用いました。


Shiro Kawai
Last modified: Thu Oct 21 04:11:33 HST 1999