古いCGIスクリプトを安全に運用するための設定例
長期間更新されていないCGIスクリプトを、現在のサーバーでそのまま公開するのは危険です。Perlやシェルを使った掲示板、アクセスカウンター、フォーム処理などは、当時の実行環境を前提に作られているため、現行のApacheやPHP-FPM環境へ単純に移すだけでは安全性と互換性の両方に問題が生じます。
古いフレーム構成のサイトや、文字コードが崩れたレガシーHTMLでは、画像、タイトル、リンク先が正しく表示されないことがあります。運営主体や主要な機能を特定できない場合は、スクリプトを先に動かすのではなく、ファイルの出所、保存データ、外部通信、管理画面の有無を調査することが重要です。
ここでは、既存のCGIを廃棄せず検証する必要がある場合を想定し、Apacheの公開設定、ファイル権限、Perl互換性、入力値検証、ログ監視までを具体例で整理します。公開前には必ず隔離環境で確認し、動作することよりも被害を限定できることを優先してください。
既存ファイルと実行範囲を調査する
最初に行うのは、CGIファイルを実行せずに内容と構成を確認する作業です。拡張子が.cgiや.plであっても、内部で外部コマンドを呼び出しているとは限りません。反対に、拡張子が通常のHTMLでも、SSIやJavaScriptを通して危険な処理へ接続している場合があります。
次のような項目を台帳に記録します。
- ファイル名、所有者、更新日時、ハッシュ値
- 使用しているインタープリターとモジュール
- 書き込みを行うデータファイルやテンポラリファイル
- メール送信、外部URLへの接続、OSコマンドの呼び出し
- 管理画面、認証情報、Cookie、セッション保存先
- 文字コード、改行コード、想定しているデータベース形式
公開サーバーとは別に、ネットワーク接続を制限した検証用ホストを用意します。バックアップされたデータに本番の個人情報が含まれているなら、匿名化したコピーを使います。古いサイトの所有者や目的が確認できない場合は、処理内容が分からないファイルをCGI実行ディレクトリへ置かないことが安全です。
Apacheで実行対象を限定する
Apacheでは、CGIを必要なディレクトリだけで有効にします。サイト全体にExecCGIを適用すると、アップロードされたファイルや誤って配置されたスクリプトまで実行される可能性があります。専用のcgi-binを作り、静的コンテンツと実行ファイルを分離してください。
設定例は次のようになります。環境によってモジュール名や記述場所が異なるため、反映前に構文検査を行います。
<Directory "/var/www/example/cgi-bin">
Options +ExecCGI -Indexes
AllowOverride None
Require all granted
AddHandler cgi-script .cgi .pl
</Directory>
<Directory "/var/www/example/public">
Options -ExecCGI -Indexes
AllowOverride None
Require all granted
</Directory>
Options -Indexesは、インデックスファイルがないディレクトリの一覧表示を防ぎます。AllowOverride Noneにすると、利用者が置いた.htaccessによって実行権限や認証設定を変更されにくくなります。共有ホスティングで変更できない場合は、管理画面で同等の設定を確認します。
Apache 2.4系では、古いAllow from allやOrder allow,denyだけを残す設定は避けます。アクセス制御はRequireへ統一し、管理用CGIにはIP制限や追加認証を設定します。エラーページやサーバーのバージョン情報を外部へ表示しないことも、攻撃者への手掛かりを減らします。
ファイル権限と実行ユーザーを分離する
CGIスクリプトは、Webサーバーの実行ユーザーが必要なファイルだけを読み取れる状態にします。一般的な目安は、スクリプトを750、設定ファイルを640、公開データを640程度にし、所有者とグループを慎重に指定する方法です。実際の値は、Apacheの実行方式とホスティング環境に合わせます。
chown -R cgiuser:webgroup /var/www/example/cgi-bin
find /var/www/example/cgi-bin -type f -name '*.cgi' -exec chmod 750 {} \;
find /var/www/example/cgi-bin -type f -name '*.pl' -exec chmod 750 {} \;
chmod 640 /var/www/example/cgi-bin/config.pl
パスワード、APIキー、SMTP認証情報をスクリプトと同じ公開領域へ置かないでください。設定ファイルをドキュメントルートの外へ移し、環境変数や権限付きの読み取りで渡します。バックアップファイル、エディターの一時ファイル、.oldや.bakもWebから取得できない場所に保管します。
CGIに書き込み権限が必要な場合は、サイト全体ではなく専用ディレクトリだけに限定します。アップロード先は実行ディレクトリから分離し、そこへ.cgiや.plを保存できないようにします。Apacheのmod_suexecや、コンテナ、専用のシステムユーザーを使えば、侵害時に被害が広がる範囲を抑えられます。
Perlの互換性と起動方法を確認する
古いPerl CGIでは、先頭行のShebang、モジュールの場所、標準出力のヘッダーが動作を左右します。実行ファイルの先頭には、実際に使用するPerlの絶対パスを記述します。
#!/usr/bin/perl
use strict;
use warnings;
use CGI qw(:standard);
use Encode qw(decode encode);
print header(-type => 'text/html', -charset => 'UTF-8');
現在のPerlでは、古い構文や非推奨モジュールが警告またはエラーになることがあります。strictとwarningsを有効にし、変数の未宣言、未定義値、文字コード変換の失敗を確認します。モジュールを無制限に追加するのではなく、必要な依存関係を固定し、CPANから取得する場合もバージョンと配布元を記録します。
日本語処理では、入力データの文字コードを判定せずに連結しないことが大切です。古いページがShift_JIS、保存データがEUC-JP、HTTPレスポンスがUTF-8という構成もあります。受け取った値を適切にデコードし、HTMLへ出力する前にエスケープします。変換できない文字を黙って削除すると、検索条件や識別子が変わるため、エラーとして処理する方が安全です。
CGIの標準入力や環境変数に依存する処理も点検します。GETとPOSTの両方を受け付ける古いフォームでは、想定外のメソッドを拒否し、リクエストサイズに上限を設定します。処理が停止したときに詳細なパスやSQL文を画面へ出さず、利用者には一般的なエラーだけを返します。
入力値を検証し危険な処理を置き換える
フォーム、Cookie、URLパラメーター、アップロード名は、すべて信頼できない入力として扱います。メールアドレスなら形式と長さ、数値なら範囲、選択項目なら許可リストで検証します。正規表現だけに頼らず、空文字、改行、NULLバイト、長大な文字列を個別に確認します。
OSコマンドの実行は、古いCGIで特に注意が必要な部分です。次のようなコードは、入力値にシェルの記号が混入するとコマンドインジェクションにつながります。
my $name = param('name');
system("convert $name /tmp/output.png");
必要であれば、シェルを介さないリスト形式で呼び出し、値を許可されたファイル名へ変換します。ただし、最も安全なのは外部コマンド自体を使わず、Perlモジュールやサーバー側の固定処理へ置き換える方法です。ファイルパスは利用者から受け取らず、IDからサーバー側で対応するパスを選びます。
HTMLへ値を戻すときは、コンテキストに応じたエスケープが必要です。本文、属性値、JavaScript、URLでは安全な処理が異なります。PerlのHTMLエスケープ機能を使い、手作業で<や>だけを置き換える方法は避けます。SQLを使う場合はプレースホルダーによるパラメーター化を採用し、文字列連結で検索文を作らないでください。
認証とセッションを現代化する
管理画面を古いCGIのまま公開する場合、推測しやすい初期パスワードや固定Cookieを必ず確認します。パスワードは平文や単純なMD5で保存せず、利用可能な環境でArgon2id、bcrypt、または適切なコスト設定のPBKDF2を使います。移行期間だけ旧形式を検証し、ログイン成功後に新しい方式へ更新する設計も選択肢になります。
セッションIDは予測できない乱数で生成し、URLへ埋め込まないようにします。CookieにはSecure、HttpOnly、適切なSameSite属性を設定し、ログイン後にはセッションIDを再発行します。ログアウト時やパスワード変更時には、既存セッションを無効化します。管理操作にはCSRF対策用のトークンを付け、状態を変更する処理をGETで実行しないでください。
ログイン試行回数、管理画面へのアクセス、失敗した入力検証、権限エラーを記録します。ただし、パスワード、セッションID、個人情報をログへそのまま残してはいけません。ログファイルの権限、保存期間、ローテーションを設定し、ログがディスクを使い切ってCGI全体を停止しないようにします。
隔離環境で検査し段階的に公開する
検証時は、外部メール送信、決済、ファイル削除、DNS変更などの副作用を無効にします。テスト用のSMTPサーバーやダミーデータを使い、危険な処理が実際の利用者や外部サービスへ届かないようにします。コンテナを利用する場合も、特権モードやホストのソケット共有を安易に有効にしないでください。
確認項目には、正常系だけでなく異常系を含めます。空のフォーム、極端に長い入力、二重送信、不正な文字コード、存在しないID、権限のない利用者からの操作を試します。レスポンスヘッダー、HTTPステータス、エラーログ、CPUとメモリの使用量を同時に確認し、タイムアウトが設定されていることも検証します。
公開する場合は、最初から全利用者へ開放せず、IP制限やBasic認証を付けた状態で短期間運用します。監視に問題がないことを確認してから対象範囲を広げます。古いCGIに修正を加えたときは、変更前のハッシュと差分を保存し、いつでも元の状態へ戻せるようにします。
更新計画と停止基準を決める
古いスクリプトは、一度動けば管理が終わるものではありません。OS、Apache、Perl、依存モジュールの更新によって、昨日まで動いていた処理が停止したり、危険な互換モードが無効になったりします。実行環境を可能な範囲で固定し、更新前に同じテストを自動実行できるようにします。
次のような状態になった場合は、公開を継続せず停止を検討します。
- 所有者や処理目的を確認できない
- 依存する古い暗号方式や未修正ライブラリを外せない
- 管理者パスワードや個人情報が平文で保存されている
- 入力検証とアクセス制御を追加できない
- ログを取得できず、侵害の有無を調べられない
- バックアップから安全に復旧できない
代替機能へ移行するまで停止画面を表示し、保存データを読み取り専用で保全する方法もあります。CGIを残すことが目的になると、技術的負債と攻撃面だけが拡大します。必要な機能を新しい実装へ移し、古いエンドポイントを段階的に閉鎖する計画を作成します。
まずはコピーしたファイルの一覧化、隔離環境の準備、Apache設定の限定、権限の見直しから始めてください。実行ログと変更履歴を残し、検証を通過したCGIだけを最小範囲で公開します。安全性を確認できないスクリプトは、動かすのではなく停止して保全する判断を選んでください。