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模式

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

Enabling Taint Mode

启用Taint模式

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]; # Tainted my $env_path = $ENV{PATH}; # Tainted my $form = <STDIN>; # Tainted my $query = $ENV{QUERY_STRING}; # Tainted

Sanitize PATH early (required in taint mode)

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

$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

会话与头部安全

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

Mojolicious session + headers

Mojolicious会话 + 头部配置

$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
检查项验证内容
Taint模式CGI/Web脚本是否添加
-T
标记
输入验证白名单模式、长度限制
文件操作三参数open、路径遍历检查
进程执行列表形式的system、无Shell插值
SQL查询DBI占位符、无直接插值
HTML输出使用
encode_entities()
、模板自动转义
CSRF令牌已生成、在状态变更请求中验证
会话配置Secure、HttpOnly、SameSite cookies
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>Welcome, $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的代码启用taint模式,使用白名单验证所有输入,所有查询都使用DBI占位符,并根据上下文对所有输出进行编码。采用纵深防御——绝不依赖单一防护层。