在 Perl 中验证路径

问题描述

我正在为我正在上课的课程做实验室工作,我有一个关于检查特定输入的 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 结束。您可以在上面的链接中阅读有关不同语法的所有信息,但在正则表达式中,我们使用 ^$(称为锚点)分别表示字符串的开头和结尾。所以我们的正则表达式看起来像

m(^/home/SOMETHING/memo$)

现在是中间的部分。我们想要任何事情过去。您的通用“任何”正则表达式是一个点 .,它匹配任何单个字符。我们可以应用 Kleene 星 *,它表示“零个或多个之前的任何内容”。所以 .* 一起表示“零个或多个”。

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

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

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

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

,

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

首先,您说“这里什么都行”。不要让那是真的。考虑..,虚拟父目录,指定在目录结构中的移动:

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

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

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

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

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

#!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 ... }