在 Perl 中验证路径

Validating a path in Perl

我正在做一个 class 的实验室,我有一个关于检查特定输入的 Perl 字符串的问题。

基本上我想做的是确保我从用户那里收到的输入类似于:

/home/[anything is valid]/memo

本实验的重点是防止教师提供给我们的简单程序中的路径名攻击。所以我想在使用它之前检查以确保用户提供的路径名符合这种格式。

我目前正在使用 Perl 中的 abs_path() 方法来获取传入字符串的绝对路径,但现在我需要确保绝对路径包含我上面的内容。

这是我想要实现的目标:

my $input = "localhost:8080/cgi-bin/memo.cgi?memo=/home/megaboz/memo/new_CEO";
my $memo = '/home/megaboz/memo/new_CEO';
my $pathName = abs_path($memo);
if($pathName ne "/home/[anything works here]/memo/[anything works here]") {
       #throw an error
}
else {
       #process input
}

有什么指点吗?

欢迎来到 regular expressions 的精彩世界,这是 Perl 擅长的领域。

让我们来看看如何构建其中之一。首先,我们通常使用正斜杠来表示正则表达式,即

/some-expression/

但是由于您的路径中有正斜杠,这样做会涉及一些混乱的字符串转义,因此我们将使用 m 替代分隔符。

m(some-expression)

现在,我们要从 /home/ 开始,到 /memo 结束。您可以在上面的 link 中阅读所有关于不同语法的信息,但是在正则表达式中我们使用 ^$ (称为锚点)分别表示字符串的开头和结尾.所以我们的正则表达式看起来像

m(^/home/SOMETHING/memo$)

现在开始中间的部分。我们希望一切都过去。您的通用“任何”正则表达式是一个点 .,它匹配任何单个字符。我们可以应用 Kleene 星 *,它表示“之前出现的任何东西的零个或更多”。所以 .* 一起说“零个或更多的东西”。

m(^/home/.*/memo$)

这是我们的正则表达式。要应用它,我们使用 =~ 来询问“是否匹配”,或使用 !~ 来询问“是否失败”。您的代码结构方式,我们要检查失败。

if ($pathName !~ m(^/home/.*/memo$)) {
    ...
} else {
    ...
}

正则表达式相当普遍,基本上可以在任何编程语言中使用,因此它绝对是一项值得拥有的技能(尽管 Perl 以其强大的正则表达式支持而闻名,因此您是正确的工具字符串匹配功能)。

你的问题遗漏了很多东西,所以我不得不做一些猜测。而且,由于 Whosebug 主要是关于其他人在阅读这些答案时遇到类似问题,因此其中一些可能不适用于您。此外,其中大部分是关于网络安全的,而不是 Perl 特有的。你想用任何语言经历同样的事情。

首先,你说“这里什么都能用”。不要让那成为事实。考虑虚拟父目录 .. 指定围绕目录结构移动:

/home/../../memo/../../../target.pl

您最终得到了一个您不想公开的文件。不仅如此,如果他们能够通过其他方式在正确的位置创建 memo 符号链接,他们也可以使用它来四处移动。也就是说,您无法仅通过查看路径来真正判断您将获得什么文件,因为符号链接(我猜也是硬链接)可以完全改变一切。如果 memo/ 的符号链接怎么办?

其次,永远不要让远程 CGI 用户告诉您文件的位置。这对他们来说太多了,无法为您做出决定。相反,看起来您将允许他们提供两样东西。第二个位置的目录和最后的东西。让他们单独指定这两件事:

https://localhost:8080/cgi-bin/memo.cgi?user=megaboz&thing=NewCEO

你仍然需要验证这两件事,但分开做比在一堆其他事情中间做要容易得多。而且,由于您正在从用户那里获取输入并将其映射到文件系统,因此您应该使用污点检查 (perlsec),这有助于您捕获在程序外部使用的用户输入。要清除值,请使用匹配项并捕获您允许的内容。我建议你不要试图在这里挽救任何坏数据。如果它不符合您的预期,return 一个错误。此外,最好指定您允许的内容,而不是提出您将禁止的所有内容:

#!perl -T

my( $user  ) = however_you_get_CGI_params( 'user' ) =~ m/\A([a-z0-9]+)\z/i;
my( $thing ) = however_you_get_CGI_params( 'thing' ) =~ m/\A([a-z0-9]+)\z/i;

unless( defined $user and defined $thing ) { ... return some error ... }

现在,这并不意味着您现在在 $user$thing 中的值是正确的。它们只是有效值。将它们映射到您需要获取的任何内容。由于您已经构建了一条路径,因此检查路径是否存在可能就足够了:

use File::Spec::Functions;
my $path = catfile( '/home', $user, 'memo', $thing );

unless( -e $path ) {  ... return some error ... }