perl-security
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePerl 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 () tracks data from external sources and prevents it from being used in unsafe operations without explicit validation.
-TPerl的污染模式()会跟踪来自外部源的数据,防止其在未经过显式验证的情况下用于不安全操作。
-TEnabling 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)};
undefinedUntainting 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
}
undefinedsub bad_untaint($input) {
$input =~ /^(.*)$/s;
return $1; # 接受任意内容——毫无作用
}
undefinedInput 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;
}
undefinedsub bad_validate($input) {
die "无效内容" if $input =~ /[<>"';&|]/; # 无法检测编码后的攻击
return $input;
}
undefinedLength 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;
}
undefineduse 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;
}
undefinedSafe 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
}
undefinedsub bad_read($path) {
open my $fh, $path; # 如果$path = "|rm -rf /",会执行命令!
open my $fh, "< $path"; # Shell元字符注入
}
undefinedTOCTOU 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 = ; # Shell injection risk
ls $user_dir
Also use `Capture::Tiny` for capturing stdout/stderr from external commands safely.my $output = ; # 存在Shell注入风险
ls $user_dir
也可以使用`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;
}
undefinedsub bad_find($dbh, $email) {
my $sth = $dbh->prepare("SELECT * FROM users WHERE email = '$email'");
# 如果$email = "' OR 1=1 --",会返回所有用户
$sth->execute;
return $sth->fetchrow_hashref;
}
undefinedDynamic 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!
}
undefinedsub bad_order($dbh, $column) {
$dbh->prepare("SELECT * FROM users ORDER BY $column"); # SQL注入!
}
undefinedDBIx::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,
});
undefinedmy @users = $schema->resultset('User')->search({
status => 'active',
email => { -like => '%@example.com' },
}, {
order_by => { -asc => 'name' },
rows => 50,
});
undefinedWeb 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>
}
undefinedsub bad_html($input) {
print "<div>$input</div>"; # 如果$input包含<script>,会触发XSS
}
undefinedCSRF 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');
});
undefinedOutput Encoding
输出编码
Always encode output for its context: for HTML, for URLs, for JSON.
HTML::Entities::encode_entities()URI::Escape::uri_escape_utf8()JSON::MaybeXS::encode_json()始终根据输出上下文进行编码:HTML上下文使用,URL上下文使用,JSON上下文使用。
HTML::Entities::encode_entities()URI::Escape::uri_escape_utf8()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
undefinedini
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
undefinedRunning perlcritic
运行perlcritic
bash
undefinedbash
undefinedCheck 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
undefinedperlcritic --severity 4 --theme security --quiet lib/ || exit 1
undefinedQuick Security Checklist
快速安全检查表
| Check | What to Verify |
|---|---|
| Taint mode | |
| Input validation | Allowlist patterns, length limits |
| File operations | Three-arg open, path traversal checks |
| Process execution | List-form system, no shell interpolation |
| SQL queries | DBI placeholders, never interpolate |
| HTML output | |
| CSRF tokens | Generated, verified on state-changing requests |
| Session config | Secure, HttpOnly, SameSite cookies |
| HTTP headers | CSP, X-Frame-Options, HSTS |
| Dependencies | Pinned versions, audited modules |
| Regex safety | No nested quantifiers, anchored patterns |
| Error messages | No stack traces or paths leaked to users |
| 检查项 | 验证内容 |
|---|---|
| 污染模式 | CGI/Web脚本是否启用 |
| 输入验证 | 是否使用白名单模式、长度限制 |
| 文件操作 | 是否使用三参数open、路径遍历检查 |
| 进程执行 | 是否使用列表形式的system、无Shell插值 |
| SQL查询 | 是否使用DBI占位符、绝不直接插值 |
| HTML输出 | 是否使用 |
| CSRF令牌 | 是否生成并在状态变更请求中验证 |
| 会话配置 | 是否使用Secure、HttpOnly、SameSite cookie |
| HTTP头 | 是否设置CSP、X-Frame-Options、HSTS |
| 依赖 | 是否固定版本、经过审计的模块 |
| 正则安全 | 无嵌套量词、锚定模式 |
| 错误信息 | 是否向用户泄露堆栈跟踪或路径 |
Anti-Patterns
反模式
perl
undefinedperl
undefined1. 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占位符,根据上下文编码所有输出。采用纵深防御——绝不依赖单一防护层。