perl-security

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Perl Security Patterns

Perl安全编程模式

Comprehensive security guidelines for Perl applications covering input validation, injection prevention, and secure coding practices.
这是一份针对Perl应用的全面安全指南,涵盖输入验证、注入防护以及安全编码实践。

When to Activate

适用场景

  • Handling user input in Perl applications
  • Building Perl web applications (CGI, Mojolicious, Dancer2, Catalyst)
  • Reviewing Perl code for security vulnerabilities
  • Performing file operations with user-supplied paths
  • Executing system commands from Perl
  • Writing DBI database queries
  • 在Perl应用中处理用户输入时
  • 构建Perl Web应用(CGI、Mojolicious、Dancer2、Catalyst)时
  • 检查Perl代码中的安全漏洞时
  • 使用用户提供的路径执行文件操作时
  • 从Perl中执行系统命令时
  • 编写DBI数据库查询时

How It Works

工作原理

Start with taint-aware input boundaries, then move outward: validate and untaint inputs, keep filesystem and process execution constrained, and use parameterized DBI queries everywhere. The examples below show the safe defaults this skill expects you to apply before shipping Perl code that touches user input, the shell, or the network.
从感知输入边界的污染模式开始,向外扩展:验证并解除输入的污染限制,约束文件系统和进程执行,并且在所有场景下使用参数化DBI查询。以下示例展示了在发布涉及用户输入、Shell或网络操作的Perl代码前,你应该遵循的安全默认规范。

Taint Mode

污染模式(Taint Mode)

Perl's taint mode (
-T
) tracks data from external sources and prevents it from being used in unsafe operations without explicit validation.
Perl的污染模式(
-T
)会跟踪来自外部源的数据,防止其在未经过显式验证的情况下用于不安全操作。

Enabling Taint Mode

启用污染模式

perl
#!/usr/bin/perl -T
use v5.36;
perl
#!/usr/bin/perl -T
use v5.36;

Tainted: anything from outside the program

受污染的内容:所有来自程序外部的数据

my $input = $ARGV[0]; # Tainted my $env_path = $ENV{PATH}; # Tainted my $form = <STDIN>; # Tainted my $query = $ENV{QUERY_STRING}; # Tainted
my $input = $ARGV[0]; # 受污染 my $env_path = $ENV{PATH}; # 受污染 my $form = <STDIN>; # 受污染 my $query = $ENV{QUERY_STRING}; # 受污染

Sanitize PATH early (required in taint mode)

提前清理PATH(污染模式下必需)

$ENV{PATH} = '/usr/local/bin:/usr/bin:/bin'; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
undefined
$ENV{PATH} = '/usr/local/bin:/usr/bin:/bin'; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
undefined

Untainting Pattern

解除污染的模式

perl
use v5.36;
perl
use v5.36;

Good: Validate and untaint with a specific regex

正确方式:使用特定正则表达式验证并解除污染

sub untaint_username($input) { if ($input =~ /^([a-zA-Z0-9_]{3,30})$/) { return $1; # $1 is untainted } die "Invalid username: must be 3-30 alphanumeric characters\n"; }
sub untaint_username($input) { if ($input =~ /^([a-zA-Z0-9_]{3,30})$/) { return $1; # $1已解除污染 } die "无效用户名:必须是3-30位字母数字或下划线\n"; }

Good: Validate and untaint a file path

正确方式:验证并解除文件路径的污染

sub untaint_filename($input) { if ($input =~ m{^([a-zA-Z0-9._-]+)$}) { return $1; } die "Invalid filename: contains unsafe characters\n"; }
sub untaint_filename($input) { if ($input =~ m{^([a-zA-Z0-9._-]+)$}) { return $1; } die "无效文件名:包含不安全字符\n"; }

Bad: Overly permissive untainting (defeats the purpose)

错误方式:过度宽松的解除污染(失去意义)

sub bad_untaint($input) { $input =~ /^(.*)$/s; return $1; # Accepts ANYTHING — pointless }
undefined
sub bad_untaint($input) { $input =~ /^(.*)$/s; return $1; # 接受任意内容——毫无作用 }
undefined

Input Validation

输入验证

Allowlist Over Blocklist

白名单优先于黑名单

perl
use v5.36;
perl
use v5.36;

Good: Allowlist — define exactly what's permitted

正确方式:白名单——明确定义允许的内容

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; }
sub validate_sort_field($field) { my %allowed = map { $_ => 1 } qw(name email created_at updated_at); die "无效排序字段:$field\n" unless $allowed{$field}; return $field; }

Good: Validate with specific patterns

正确方式:使用特定模式验证

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; # Coerce to number } die "Invalid integer\n"; }
sub validate_email($email) { if ($email =~ /^([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,})$/) { return $1; } die "无效邮箱地址\n"; }
sub validate_integer($input) { if ($input =~ /^(-?\d{1,10})$/) { return $1 + 0; # 转换为数字类型 } die "无效整数\n"; }

Bad: Blocklist — always incomplete

错误方式:黑名单——永远存在遗漏

sub bad_validate($input) { die "Invalid" if $input =~ /[<>"';&|]/; # Misses encoded attacks return $input; }
undefined
sub bad_validate($input) { die "无效内容" if $input =~ /[<>"';&|]/; # 无法检测编码后的攻击 return $input; }
undefined

Length Constraints

长度限制

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;
}
perl
use v5.36;

sub validate_comment($text) {
    die "评论不能为空\n"        unless length($text) > 0;
    die "评论长度超过10000字符\n" if length($text) > 10_000;
    return $text;
}

Safe Regular Expressions

安全正则表达式

ReDoS Prevention

防止ReDoS攻击

Catastrophic backtracking occurs with nested quantifiers on overlapping patterns.
perl
use v5.36;
当嵌套量词作用于重叠模式时,会发生灾难性回溯。
perl
use v5.36;

Bad: Vulnerable to ReDoS (exponential backtracking)

错误方式:易受ReDoS攻击(指数级回溯)

my $bad_re = qr/^(a+)+$/; # Nested quantifiers my $bad_re2 = qr/^([a-zA-Z]+)$/; # Nested quantifiers on class my $bad_re3 = qr/^(.?,){10,}$/; # Repeated greedy/lazy combo
my $bad_re = qr/^(a+)+$/; # 嵌套量词 my $bad_re2 = qr/^([a-zA-Z]+)$/; # 对字符类使用嵌套量词 my $bad_re3 = qr/^(.?,){10,}$/; # 贪婪/惰性量词组合重复

Good: Rewrite without nesting

正确方式:重写为无嵌套的正则

my $good_re = qr/^a+$/; # Single quantifier my $good_re2 = qr/^[a-zA-Z]+$/; # Single quantifier on class
my $good_re = qr/^a+$/; # 单个量词 my $good_re2 = qr/^[a-zA-Z]+$/; # 对字符类使用单个量词

Good: Use possessive quantifiers or atomic groups to prevent backtracking

正确方式:使用占有量词或原子组防止回溯

my $safe_re = qr/^[a-zA-Z]++$/; # Possessive (5.10+) my $safe_re2 = qr/^(?>a+)$/; # Atomic group
my $safe_re = qr/^[a-zA-Z]++$/; # 占有量词(5.10+) my $safe_re2 = qr/^(?>a+)$/; # 原子组

Good: Enforce timeout on untrusted patterns

正确方式:对不可信的正则设置超时

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; }
undefined
use POSIX qw(alarm); sub safe_match($string, $pattern, $timeout = 2) { my $matched; eval { local $SIG{ALRM} = sub { die "正则超时\n" }; alarm($timeout); $matched = $string =~ $pattern; alarm(0); }; alarm(0); die $@ if $@; return $matched; }
undefined

Safe File Operations

安全文件操作

Three-Argument Open

三参数Open

perl
use v5.36;
perl
use v5.36;

Good: Three-arg open, lexical filehandle, check return

正确方式:三参数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; }
sub read_file($path) { open my $fh, '<:encoding(UTF-8)', $path or die "无法打开'$path':$!\n"; local $/; my $content = <$fh>; close $fh; return $content; }

Bad: Two-arg open with user data (command injection)

错误方式:带用户数据的双参数open(命令注入)

sub bad_read($path) { open my $fh, $path; # If $path = "|rm -rf /", runs command! open my $fh, "< $path"; # Shell metacharacter injection }
undefined
sub bad_read($path) { open my $fh, $path; # 如果$path = "|rm -rf /",会执行命令! open my $fh, "< $path"; # Shell元字符注入 }
undefined

TOCTOU Prevention and Path Traversal

防止TOCTOU和路径遍历

perl
use v5.36;
use Fcntl qw(:DEFAULT :flock);
use File::Spec;
use Cwd qw(realpath);
perl
use v5.36;
use Fcntl qw(:DEFAULT :flock);
use File::Spec;
use Cwd qw(realpath);

Atomic file creation

原子化文件创建

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 create_file_safe($path) { sysopen(my $fh, $path, O_WRONLY | O_CREAT | O_EXCL, 0600) or die "无法创建'$path':$!\n"; return $fh; }

Validate path stays within allowed directory

验证路径是否在允许的目录内

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; }

Use `File::Temp` for temporary files (`tempfile(UNLINK => 1)`) and `flock(LOCK_EX)` to prevent race conditions.
sub safe_path($base_dir, $user_path) { my $real = realpath(File::Spec->catfile($base_dir, $user_path)) // die "路径不存在\n"; my $base_real = realpath($base_dir) // die "基础目录不存在\n"; die "路径遍历已阻止\n" unless $real =~ /^\Q$base_real\E(?:/|\z)/; return $real; }

临时文件使用`File::Temp`(`tempfile(UNLINK => 1)`),并使用`flock(LOCK_EX)`防止竞争条件。

Safe Process Execution

安全进程执行

List-Form system and exec

列表形式的system和exec

perl
use v5.36;
perl
use v5.36;

Good: List form — no shell interpolation

正确方式:列表形式——无Shell插值

sub run_command(@cmd) { system(@cmd) == 0 or die "Command failed: @cmd\n"; }
run_command('grep', '-r', $user_pattern, '/var/log/app/');
sub run_command(@cmd) { system(@cmd) == 0 or die "命令执行失败:@cmd\n"; }
run_command('grep', '-r', $user_pattern, '/var/log/app/');

Good: Capture output safely with IPC::Run3

正确方式:使用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; }
use IPC::Run3; sub capture_output(@cmd) { my ($stdout, $stderr); run3(@cmd, \undef, $stdout, $stderr); if ($?) { die "命令执行失败(退出码$?):$stderr\n"; } return $stdout; }

Bad: String form — shell injection!

错误方式:字符串形式——Shell注入!

sub bad_search($pattern) { system("grep -r '$pattern' /var/log/app/"); # If $pattern = "'; rm -rf / #" }
sub bad_search($pattern) { system("grep -r '$pattern' /var/log/app/"); # 如果$pattern = "'; rm -rf / #" }

Bad: Backticks with interpolation

错误方式:带插值的反引号

my $output =
ls $user_dir
; # Shell injection risk

Also use `Capture::Tiny` for capturing stdout/stderr from external commands safely.
my $output =
ls $user_dir
; # 存在Shell注入风险

也可以使用`Capture::Tiny`安全捕获外部命令的标准输出/错误。

SQL Injection Prevention

SQL注入防护

DBI Placeholders

DBI占位符

perl
use v5.36;
use DBI;

my $dbh = DBI->connect($dsn, $user, $pass, {
    RaiseError => 1,
    PrintError => 0,
    AutoCommit => 1,
});
perl
use v5.36;
use DBI;

my $dbh = DBI->connect($dsn, $user, $pass, {
    RaiseError => 1,
    PrintError => 0,
    AutoCommit => 1,
});

Good: Parameterized queries — always use placeholders

正确方式:参数化查询——始终使用占位符

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({}); }
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: String interpolation in SQL (SQLi vulnerability!)

错误方式:SQL中字符串插值(SQL注入漏洞!)

sub bad_find($dbh, $email) { my $sth = $dbh->prepare("SELECT * FROM users WHERE email = '$email'"); # If $email = "' OR 1=1 --", returns all users $sth->execute; return $sth->fetchrow_hashref; }
undefined
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; }
undefined

Dynamic Column Allowlists

动态列白名单

perl
use v5.36;
perl
use v5.36;

Good: Validate column names against an allowlist

正确方式:针对白名单验证列名

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({});
}
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 "无效列名:$column\n"    unless $allowed_cols{$column};
die "无效排序方向:$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: Directly interpolating user-chosen column

错误方式:直接插入用户选择的列

sub bad_order($dbh, $column) { $dbh->prepare("SELECT * FROM users ORDER BY $column"); # SQLi! }
undefined
sub bad_order($dbh, $column) { $dbh->prepare("SELECT * FROM users ORDER BY $column"); # SQL注入! }
undefined

DBIx::Class (ORM Safety)

DBIx::Class(ORM安全)

perl
use v5.36;
perl
use v5.36;

DBIx::Class generates safe parameterized queries

DBIx::Class会生成安全的参数化查询

my @users = $schema->resultset('User')->search({ status => 'active', email => { -like => '%@example.com' }, }, { order_by => { -asc => 'name' }, rows => 50, });
undefined
my @users = $schema->resultset('User')->search({ status => 'active', email => { -like => '%@example.com' }, }, { order_by => { -asc => 'name' }, rows => 50, });
undefined

Web Security

Web安全

XSS Prevention

XSS防护

perl
use v5.36;
use HTML::Entities qw(encode_entities);
use URI::Escape qw(uri_escape_utf8);
perl
use v5.36;
use HTML::Entities qw(encode_entities);
use URI::Escape qw(uri_escape_utf8);

Good: Encode output for HTML context

正确方式:针对HTML上下文编码输出

sub safe_html($user_input) { return encode_entities($user_input); }
sub safe_html($user_input) { return encode_entities($user_input); }

Good: Encode for URL context

正确方式:针对URL上下文编码

sub safe_url_param($value) { return uri_escape_utf8($value); }
sub safe_url_param($value) { return uri_escape_utf8($value); }

Good: Encode for JSON context

正确方式:针对JSON上下文编码

use JSON::MaybeXS qw(encode_json); sub safe_json($data) { return encode_json($data); # Handles escaping }
use JSON::MaybeXS qw(encode_json); sub safe_json($data) { return encode_json($data); # 自动处理转义 }

Template auto-escaping (Mojolicious)

模板自动转义(Mojolicious)

<%= $user_input %> — auto-escaped (safe)

<%= $user_input %> — 自动转义(安全)

<%== $raw_html %> — raw output (dangerous, use only for trusted content)

<%== $raw_html %> — 原始输出(危险,仅用于可信内容)

Template auto-escaping (Template Toolkit)

模板自动转义(Template Toolkit)

[% user_input | html %] — explicit HTML encoding

[% user_input | html %] — 显式HTML编码

Bad: Raw output in HTML

错误方式:HTML中直接输出原始内容

sub bad_html($input) { print "<div>$input</div>"; # XSS if $input contains <script> }
undefined
sub bad_html($input) { print "<div>$input</div>"; # 如果$input包含<script>,会触发XSS }
undefined

CSRF Protection

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));
}
Use constant-time comparison when verifying tokens. Most web frameworks (Mojolicious, Dancer2, Catalyst) provide built-in CSRF protection — prefer those over hand-rolled solutions.
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防护——优先使用框架自带的方案,而非自行实现。

Session and Header Security

会话和Header安全

perl
use v5.36;
perl
use v5.36;

Mojolicious session + headers

Mojolicious会话 + Header设置

$app->secrets(['long-random-secret-rotated-regularly']); $app->sessions->secure(1); # HTTPS only $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'); });
undefined
$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'); });
undefined

Output Encoding

输出编码

Always encode output for its context:
HTML::Entities::encode_entities()
for HTML,
URI::Escape::uri_escape_utf8()
for URLs,
JSON::MaybeXS::encode_json()
for JSON.
始终根据输出上下文进行编码:HTML上下文使用
HTML::Entities::encode_entities()
,URL上下文使用
URI::Escape::uri_escape_utf8()
,JSON上下文使用
JSON::MaybeXS::encode_json()

CPAN Module Security

CPAN模块安全

  • Pin versions in cpanfile:
    requires 'DBI', '== 1.643';
  • Prefer maintained modules: Check MetaCPAN for recent releases
  • Minimize dependencies: Each dependency is an attack surface
  • 固定版本在cpanfile中:
    requires 'DBI', '== 1.643';
  • 优先选择维护中的模块:在MetaCPAN上检查最近的发布记录
  • 最小化依赖:每个依赖都是一个攻击面

Security Tooling

安全工具

perlcritic Security Policies

perlcritic安全策略

ini
undefined
ini
undefined

.perlcriticrc — security-focused configuration

.perlcriticrc — 安全聚焦的配置

severity = 3 theme = security + core
severity = 3 theme = security + core

Require three-arg open

要求使用三参数open

[InputOutput::RequireThreeArgOpen] severity = 5
[InputOutput::RequireThreeArgOpen] severity = 5

Require checked system calls

要求检查系统调用的返回值

[InputOutput::RequireCheckedSyscalls] functions = :builtins severity = 4
[InputOutput::RequireCheckedSyscalls] functions = :builtins severity = 4

Prohibit string eval

禁止字符串形式的eval

[BuiltinFunctions::ProhibitStringyEval] severity = 5
[BuiltinFunctions::ProhibitStringyEval] severity = 5

Prohibit backtick operators

禁止反引号操作符

[InputOutput::ProhibitBacktickOperators] severity = 4
[InputOutput::ProhibitBacktickOperators] severity = 4

Require taint checking in CGI

CGI脚本要求启用污染检查

[Modules::RequireTaintChecking] severity = 5
[Modules::RequireTaintChecking] severity = 5

Prohibit two-arg open

禁止双参数open

[InputOutput::ProhibitTwoArgOpen] severity = 5
[InputOutput::ProhibitTwoArgOpen] severity = 5

Prohibit bare-word filehandles

禁止裸字文件句柄

[InputOutput::ProhibitBarewordFileHandles] severity = 5
undefined
[InputOutput::ProhibitBarewordFileHandles] severity = 5
undefined

Running perlcritic

运行perlcritic

bash
undefined
bash
undefined

Check a file

检查单个文件

perlcritic --severity 3 --theme security lib/MyApp/Handler.pm
perlcritic --severity 3 --theme security lib/MyApp/Handler.pm

Check entire project

检查整个项目

perlcritic --severity 3 --theme security lib/
perlcritic --severity 3 --theme security lib/

CI integration

CI集成

perlcritic --severity 4 --theme security --quiet lib/ || exit 1
undefined
perlcritic --severity 4 --theme security --quiet lib/ || exit 1
undefined

Quick Security Checklist

快速安全检查表

CheckWhat to Verify
Taint mode
-T
flag on CGI/web scripts
Input validationAllowlist patterns, length limits
File operationsThree-arg open, path traversal checks
Process executionList-form system, no shell interpolation
SQL queriesDBI placeholders, never interpolate
HTML output
encode_entities()
, template auto-escape
CSRF tokensGenerated, verified on state-changing requests
Session configSecure, HttpOnly, SameSite cookies
HTTP headersCSP, X-Frame-Options, HSTS
DependenciesPinned versions, audited modules
Regex safetyNo nested quantifiers, anchored patterns
Error messagesNo stack traces or paths leaked to users
检查项验证内容
污染模式CGI/Web脚本是否启用
-T
标志
输入验证是否使用白名单模式、长度限制
文件操作是否使用三参数open、路径遍历检查
进程执行是否使用列表形式的system、无Shell插值
SQL查询是否使用DBI占位符、绝不直接插值
HTML输出是否使用
encode_entities()
、模板自动转义
CSRF令牌是否生成并在状态变更请求中验证
会话配置是否使用Secure、HttpOnly、SameSite cookie
HTTP头是否设置CSP、X-Frame-Options、HSTS
依赖是否固定版本、经过审计的模块
正则安全无嵌套量词、锚定模式
错误信息是否向用户泄露堆栈跟踪或路径

Anti-Patterns

反模式

perl
undefined
perl
undefined

1. Two-arg open with user data (command injection)

1. 带用户数据的双参数open(命令注入)

open my $fh, $user_input; # CRITICAL vulnerability
open my $fh, $user_input; # 严重漏洞

2. String-form system (shell injection)

2. 字符串形式的system(Shell注入)

system("convert $user_file output.png"); # CRITICAL vulnerability
system("convert $user_file output.png"); # 严重漏洞

3. SQL string interpolation

3. SQL字符串插值

$dbh->do("DELETE FROM users WHERE id = $id"); # SQLi
$dbh->do("DELETE FROM users WHERE id = $id"); # SQL注入

4. eval with user input (code injection)

4. 使用用户输入的eval(代码注入)

eval $user_code; # Remote code execution
eval $user_code; # 远程代码执行

5. Trusting $ENV without sanitizing

5. 未清理就信任$ENV

my $path = $ENV{UPLOAD_DIR}; # Could be manipulated system("ls $path"); # Double vulnerability
my $path = $ENV{UPLOAD_DIR}; # 可能被篡改 system("ls $path"); # 双重漏洞

6. Disabling taint without validation

6. 未验证就解除污染

($input) = $input =~ /(.*)/s; # Lazy untaint — defeats purpose
($input) = $input =~ /(.*)/s; # 草率的解除污染——失去意义

7. Raw user data in HTML

7. HTML中直接输出用户数据

print "<div>Welcome, $username!</div>"; # XSS
print "<div>欢迎,$username!</div>"; # XSS

8. Unvalidated redirects

8. 未验证的重定向

print $cgi->redirect($user_url); # Open redirect

**Remember**: Perl's flexibility is powerful but requires discipline. Use taint mode for web-facing code, validate all input with allowlists, use DBI placeholders for every query, and encode all output for its context. Defense in depth — never rely on a single layer.
print $cgi->redirect($user_url); # 开放重定向

**谨记**:Perl的灵活性强大但需要自律。Web相关代码启用污染模式,使用白名单验证所有输入,所有查询都使用DBI占位符,根据上下文编码所有输出。采用纵深防御——绝不依赖单一防护层。