Perl Security

作者 affaan-mef648e01899b無授權條款275K 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫3 天前更新

テイントモード、入力バリデーション、安全なプロセス実行、DBIパラメータ化クエリ、Webセキュリティ(XSS/SQLi/CSRF)、perlcriticセキュリティポリシーを網羅する包括的なPerlセキュリティ。

AI 產生的概覽

撰寫安全 Perl 程式的指南,涵蓋汙染模式、輸入驗證、注入防護與 perlcritic 政策。

功能
此技能提供一套 Perl 安全模式參考:汙染模式與去汙、白名單輸入驗證、防 ReDoS 的正規表達式、安全的檔案與行程處理、DBI 參數化查詢,以及針對 XSS、CSRF 與回應標頭的網頁防護。它也包含 perlcritic 安全設定、快速檢查清單與反模式清單。它產出的是指引與程式範例,而非執行工具。
適用情境
適用於在 Perl 中處理使用者輸入、建置或審查 CGI、Mojolicious、Dancer2、Catalyst 等 Perl 網頁應用程式,或撰寫 DBI 查詢的情境。也適合審查 Perl 程式中的檔案操作、系統指令執行與輸出編碼。
執行需求
不需要指令稿或執行環境,僅為說明性參考。實作範例假定具備 Perl 環境及 DBI、HTML::Entities、URI::Escape、JSON::MaybeXS、File::Temp、IPC::Run3 等模組,政策檢查可選用 perlcritic。

Perlセキュリティパターン

入力バリデーション、インジェクション防止、セキュアコーディングプラクティスを網羅するPerlアプリケーションの包括的なセキュリティガイドライン。

アクティベートするタイミング

  • Perlアプリケーションでユーザー入力を処理するとき
  • PerlのWebアプリケーション(CGI、Mojolicious、Dancer2、Catalyst)を構築するとき
  • セキュリティ脆弱性についてPerlコードをレビューするとき
  • ユーザー指定パスでファイル操作を実行するとき
  • PerlからシステムコマンドをExecuteするとき
  • DBIデータベースクエリを書くとき

仕組み

テイント対応の入力境界から始め、次に外側に移動する: 入力をバリデートしてアンテイントし、ファイルシステムとプロセス実行を制約内に保ち、どこでもパラメータ化されたDBIクエリを使用する。以下の例は、ユーザー入力、シェル、またはネットワークに触れるPerlコードをリリースする前に適用することが期待されるデフォルトを示す。

テイントモード

Perlのテイントモード(-T)は外部ソースからのデータを追跡し、明示的なバリデーションなしに安全でない操作で使用されることを防ぐ。

テイントモードの有効化

perl
#!/usr/bin/perl -Tuse v5.36;
# テイントされた: プログラム外からのものmy $input    = $ARGV[0];        # テイントされたmy $env_path = $ENV{PATH};      # テイントされたmy $form     = <STDIN>;         # テイントされたmy $query    = $ENV{QUERY_STRING}; # テイントされた
# PATHを早期にサニタイズ(テイントモードで必要)$ENV{PATH} = '/usr/local/bin:/usr/bin:/bin';delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};

アンテイントパターン

perl
use v5.36;
# Good: 特定の正規表現でバリデートしてアンテイントsub untaint_username($input) {    if ($input =~ /^([a-zA-Z0-9_]{3,30})$/) {        return $1;  # $1はアンテイントされている    }    die "Invalid username: must be 3-30 alphanumeric characters\n";}
# Good: ファイルパスをバリデートしてアンテイントsub untaint_filename($input) {    if ($input =~ m{^([a-zA-Z0-9._-]+)$}) {        return $1;    }    die "Invalid filename: contains unsafe characters\n";}
# Bad: 過度に許可的なアンテイント(目的を無効化する)sub bad_untaint($input) {    $input =~ /^(.*)$/s;    return $1;  # 何でも受け入れる — 無意味}

入力バリデーション

ブロックリストよりアローリスト

perl
use v5.36;
# Good: アローリスト — 許可されるものを正確に定義sub validate_sort_field($field) {    my %allowed = map { $_ => 1 } qw(name email created_at updated_at);    die "Invalid sort field: $field\n" unless $allowed{$field};    return $field;}
# Good: 特定のパターンでバリデートsub validate_email($email) {    if ($email =~ /^([a-zA-Z0-9._%+-]+\@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/) {        return $1;    }    die "Invalid email address\n";}
sub validate_integer($input) {    if ($input =~ /^(-?\d{1,10})$/) {        return $1 + 0;  # 数値に強制変換    }    die "Invalid integer\n";}
# Bad: ブロックリスト — 常に不完全sub bad_validate($input) {    die "Invalid" if $input =~ /[<>"';&|]/;  # エンコードされた攻撃を見逃す    return $input;}

長さ制約

perl
use v5.36;
sub validate_comment($text) {    die "Comment is required\n"        unless length($text) > 0;    die "Comment exceeds 10000 chars\n" if length($text) > 10_000;    return $text;}

安全な正規表現

ReDoS防止

壊滅的なバックトラッキングは重複するパターンにネストされた量詞が使用されるときに発生する。

perl
use v5.36;
# Bad: ReDoSに脆弱(指数的バックトラッキング)my $bad_re = qr/^(a+)+$/;           # ネストされた量詞my $bad_re2 = qr/^([a-zA-Z]+)*$/;   # クラスにネストされた量詞my $bad_re3 = qr/^(.*?,){10,}$/;    # 繰り返される貪欲/怠惰な組み合わせ
# Good: ネストなしで書き直すmy $good_re = qr/^a+$/;             # 単一の量詞my $good_re2 = qr/^[a-zA-Z]+$/;     # クラスに単一の量詞
# Good: バックトラッキングを防ぐためにpossessive量詞またはアトミックグループを使用my $safe_re = qr/^[a-zA-Z]++$/;             # Possessive (5.10+)my $safe_re2 = qr/^(?>a+)$/;                # アトミックグループ
# Good: 信頼されていないパターンにタイムアウトを適用use POSIX qw(alarm);sub safe_match($string, $pattern, $timeout = 2) {    my $matched;    eval {        local $SIG{ALRM} = sub { die "Regex timeout\n" };        alarm($timeout);        $matched = $string =~ $pattern;        alarm(0);    };    alarm(0);    die $@ if $@;    return $matched;}

安全なファイル操作

3引数open

perl
use v5.36;
# Good: 3引数open、レキシカルファイルハンドル、戻り値チェックsub read_file($path) {    open my $fh, '<:encoding(UTF-8)', $path        or die "Cannot open '$path': $!\n";    local $/;    my $content = <$fh>;    close $fh;    return $content;}
# Bad: ユーザーデータを使った2引数open(コマンドインジェクション)sub bad_read($path) {    open my $fh, $path;        # $pathが"|rm -rf /"なら、コマンドを実行!    open my $fh, "< $path";   # シェルメタキャラクターインジェクション}

TOCTOU防止とパストラバーサル

perl
use v5.36;use Fcntl qw(:DEFAULT :flock);use File::Spec;use Cwd qw(realpath);
# アトミックファイル作成sub create_file_safe($path) {    sysopen(my $fh, $path, O_WRONLY | O_CREAT | O_EXCL, 0600)        or die "Cannot create '$path': $!\n";    return $fh;}
# パスが許可されたディレクトリ内に留まることをバリデートsub safe_path($base_dir, $user_path) {    my $real = realpath(File::Spec->catfile($base_dir, $user_path))        // die "Path does not exist\n";    my $base_real = realpath($base_dir)        // die "Base dir does not exist\n";    die "Path traversal blocked\n" unless $real =~ /^\Q$base_real\E(?:\/|\z)/;    return $real;}

一時ファイルにはFile::Temp(tempfile(UNLINK => 1))を使用し、レースコンディションを防ぐためにflock(LOCK_EX)を使用する。

安全なプロセス実行

リスト形式のsystemとexec

perl
use v5.36;
# Good: リスト形式 — シェル補間なしsub run_command(@cmd) {    system(@cmd) == 0        or die "Command failed: @cmd\n";}
run_command('grep', '-r', $user_pattern, '/var/log/app/');
# Good: IPC::Run3で安全に出力をキャプチャuse IPC::Run3;sub capture_output(@cmd) {    my ($stdout, $stderr);    run3(\@cmd, \undef, \$stdout, \$stderr);    if ($?) {        die "Command failed (exit $?): $stderr\n";    }    return $stdout;}
# Bad: 文字列形式 — シェルインジェクション!sub bad_search($pattern) {    system("grep -r '$pattern' /var/log/app/");  # $patternが"'; rm -rf / #"なら}
# Bad: 補間のあるバッククォートmy $output = `ls $user_dir`;   # シェルインジェクションリスク

外部コマンドからstdout/stderrを安全にキャプチャするためにはCapture::Tinyも使用する。

SQLインジェクション防止

DBIプレースホルダー

perl
use v5.36;use DBI;
my $dbh = DBI->connect($dsn, $user, $pass, {    RaiseError => 1,    PrintError => 0,    AutoCommit => 1,});
# Good: パラメータ化クエリ — 常にプレースホルダーを使用sub find_user($dbh, $email) {    my $sth = $dbh->prepare('SELECT * FROM users WHERE email = ?');    $sth->execute($email);    return $sth->fetchrow_hashref;}
sub search_users($dbh, $name, $status) {    my $sth = $dbh->prepare(        'SELECT * FROM users WHERE name LIKE ? AND status = ? ORDER BY name'    );    $sth->execute("%$name%", $status);    return $sth->fetchall_arrayref({});}
# Bad: SQLでの文字列補間(SQLi脆弱性!)sub bad_find($dbh, $email) {    my $sth = $dbh->prepare("SELECT * FROM users WHERE email = '$email'");    # $emailが"' OR 1=1 --"なら、すべてのユーザーが返される    $sth->execute;    return $sth->fetchrow_hashref;}

動的カラムアローリスト

perl
use v5.36;
# Good: アローリストに対してカラム名をバリデートsub order_by($dbh, $column, $direction) {    my %allowed_cols = map { $_ => 1 } qw(name email created_at);    my %allowed_dirs = map { $_ => 1 } qw(ASC DESC);
    die "Invalid column: $column\n"    unless $allowed_cols{$column};    die "Invalid direction: $direction\n" unless $allowed_dirs{uc $direction};
    my $sth = $dbh->prepare("SELECT * FROM users ORDER BY $column $direction");    $sth->execute;    return $sth->fetchall_arrayref({});}
# Bad: ユーザー選択カラムを直接補間sub bad_order($dbh, $column) {    $dbh->prepare("SELECT * FROM users ORDER BY $column");  # SQLi!}

DBIx::Class(ORM安全性)

perl
use v5.36;
# DBIx::Classは安全なパラメータ化クエリを生成するmy @users = $schema->resultset('User')->search({    status => 'active',    email  => { -like => '%@example.com' },}, {    order_by => { -asc => 'name' },    rows     => 50,});

Webセキュリティ

XSS防止

perl
use v5.36;use HTML::Entities qw(encode_entities);use URI::Escape qw(uri_escape_utf8);
# Good: HTMLコンテキスト用に出力をエンコードsub safe_html($user_input) {    return encode_entities($user_input);}
# Good: URLコンテキスト用にエンコードsub safe_url_param($value) {    return uri_escape_utf8($value);}
# Good: JSONコンテキスト用にエンコードuse JSON::MaybeXS qw(encode_json);sub safe_json($data) {    return encode_json($data);  # エスケープを処理}
# テンプレートの自動エスケープ(Mojolicious)# <%= $user_input %>   — 自動エスケープ(安全)# <%== $raw_html %>    — 生の出力(危険、信頼されたコンテンツのみ)
# テンプレートの自動エスケープ(Template Toolkit)# [% user_input | html %]  — 明示的なHTMLエンコード
# Bad: HTMLの生の出力sub bad_html($input) {    print "<div>$input</div>";  # $inputが<script>を含む場合XSS}

CSRF保護

perl
use v5.36;use Crypt::URandom qw(urandom);use MIME::Base64 qw(encode_base64url);
sub generate_csrf_token() {    return encode_base64url(urandom(32));}

トークンを検証するときは定数時間比較を使用する。ほとんどのWebフレームワーク(Mojolicious、Dancer2、Catalyst)には組み込みのCSRF保護がある — 手作りのソリューションよりそれらを優先する。

セッションとヘッダーセキュリティ

perl
use v5.36;
# Mojolicousセッション + ヘッダー$app->secrets(['long-random-secret-rotated-regularly']);$app->sessions->secure(1);          # HTTPSのみ$app->sessions->samesite('Lax');
$app->hook(after_dispatch => sub ($c) {    $c->res->headers->header('X-Content-Type-Options' => 'nosniff');    $c->res->headers->header('X-Frame-Options'        => 'DENY');    $c->res->headers->header('Content-Security-Policy' => "default-src 'self'");    $c->res->headers->header('Strict-Transport-Security' => 'max-age=31536000; includeSubDomains');});

出力エンコード

常に出力をそのコンテキスト用にエンコードする: HTML用にはHTML::Entities::encode_entities()、URL用にはURI::Escape::uri_escape_utf8()、JSON用にはJSON::MaybeXS::encode_json()。

CPANモジュールセキュリティ

  • cpanfileでバージョンをピン留め: requires 'DBI', '== 1.643';
  • メンテナンスされたモジュールを優先: MetaCPANで最近のリリースを確認
  • 依存関係を最小化: 各依存関係は攻撃面積

セキュリティツーリング

perlcriticセキュリティポリシー

ini
# .perlcriticrc — セキュリティ重視の設定severity = 3theme = security + core
# 3引数openを要求[InputOutput::RequireThreeArgOpen]severity = 5
# チェックされたシステムコールを要求[InputOutput::RequireCheckedSyscalls]functions = :builtinsseverity = 4
# 文字列evalを禁止[BuiltinFunctions::ProhibitStringyEval]severity = 5
# バッククォート演算子を禁止[InputOutput::ProhibitBacktickOperators]severity = 4
# CGIでテイントチェックを要求[Modules::RequireTaintChecking]severity = 5
# 2引数openを禁止[InputOutput::ProhibitTwoArgOpen]severity = 5
# 裸のファイルハンドルを禁止[InputOutput::ProhibitBarewordFileHandles]severity = 5

perlcriticの実行

bash
# ファイルをチェックperlcritic --severity 3 --theme security lib/MyApp/Handler.pm
# プロジェクト全体をチェックperlcritic --severity 3 --theme security lib/
# CI統合perlcritic --severity 4 --theme security --quiet lib/ || exit 1

クイックセキュリティチェックリスト

チェック確認事項
テイントモードCGI/Webスクリプトの-Tフラグ
入力バリデーションアローリストパターン、長さ制限
ファイル操作3引数open、パストラバーサルチェック
プロセス実行リスト形式のsystem、シェル補間なし
SQLクエリDBIプレースホルダー、補間しない
HTML出力encode_entities()、テンプレート自動エスケープ
CSRFトークン生成され、状態変更リクエストで検証される
セッション設定Secure、HttpOnly、SameSiteクッキー
HTTPヘッダーCSP、X-Frame-Options、HSTS
依存関係ピン留めされたバージョン、監査されたモジュール
正規表現の安全性ネストされた量詞なし、アンカーされたパターン
エラーメッセージスタックトレースやパスがユーザーに漏れない

アンチパターン

perl
# 1. ユーザーデータを使った2引数open(コマンドインジェクション)open my $fh, $user_input;               # CRITICAL脆弱性
# 2. 文字列形式のsystem(シェルインジェクション)system("convert $user_file output.png"); # CRITICAL脆弱性
# 3. SQL文字列補間$dbh->do("DELETE FROM users WHERE id = $id");  # SQLi
# 4. ユーザー入力でのeval(コードインジェクション)eval $user_code;                         # リモートコード実行
# 5. サニタイズせずに$ENVを信頼するmy $path = $ENV{UPLOAD_DIR};             # 操作される可能性があるsystem("ls $path");                      # 二重脆弱性
# 6. バリデーションなしにテイントを無効化($input) = $input =~ /(.*)/s;           # 怠惰なアンテイント — 目的を無効化
# 7. HTMLでの生のユーザーデータprint "<div>Welcome, $username!</div>";  # XSS
# 8. 未バリデートのリダイレクトprint $cgi->redirect($user_url);         # オープンリダイレクト

忘れないこと: Perlの柔軟性は強力だが規律が必要。Webに面したコードにはテイントモードを使用し、アローリストですべての入力をバリデートし、すべてのクエリにDBIプレースホルダーを使用し、すべての出力をそのコンテキスト用にエンコードする。多層防御 — 単一の層に依存しない。

來源與署名

來源:affaan-m/ecc位於docs/ja-JP/skills/perl-security提交ef648e0

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架