如何验证我的Perl CGI脚本的输入,以便我可以安全地将其传递给shell?

tpa*_*r44 1 regex security perl cgi

我是Perl和复杂正则表达式的新手.我的意思是我之前使用过正则表达式的*,但没有比这更复杂.在下面的脚本中,我知道有一个非常大的安全漏洞,可以在其中注入和运行perl代码,以便即使是shell也可以执行任何命令.在试图阻止这种注射时,我逐渐意识到正则表达式比我想象的要困难得多.我正在使用的书说要使用它的组合

die "The specified user contains illegal characters!"
      unless($user =~/^\w+$/);
Run Code Online (Sandbox Code Playgroud)

我相当肯定这意味着来自用户的输入必须以多个单词开头,但我不确定这是如何阻止注入命令的,因为它从不检查分号.我认为除非条款应该更像

unless($user=~/^\w+;\w$/);
Run Code Online (Sandbox Code Playgroud)

但是,似乎都不起作用.对此的任何帮助都会很棒,因为我真的很想理解这一点.谢谢!

#!/usr/bin/perl

use CGI;
use CGI::Carp qw(fatalsToBrowser);
$q = new CGI;

print $q->header,
    $q->start_html('Finger User'),
    $q->h1('Finger User'),
print "<pre>";

$user = $q->param("user");

#die "the specified user contains illegal characters!"
#   unless ($user =~ /ls/);
if (!($user =~ /^\w*;\w*$/)){
    print `/usr/bin/finger -s $user`;
}

print "</pre>";
print $q->end_html;
Run Code Online (Sandbox Code Playgroud)

Sin*_*nür 5

首先,让我们看一下给你带来麻烦的陈述:

die "The specified user contains illegal characters!"
      unless($user =~/^\w+$/);
Run Code Online (Sandbox Code Playgroud)

这是另一种写作方式:

 if ( $user !~ /^\w+$/ ) {
     die "...";
 }
Run Code Online (Sandbox Code Playgroud)

模式是什么意思?

  ^                        the beginning of the string
 \w+                       one or more word characters
  $                        before an optional \n, and the end of the
                           string

因此,代码将被视为有效的用户名字符串,除了单词字符和可能的换行符之外什么都没有.有两个问题:

首先,我怀疑你打算用换行符接受字符串.对此的修复很容易:用于\z表示字符串的结尾而不是$.

其次,\w匹配一个比大大更大的集合[A-Z_a-z0-9].没有其他开关,它可以匹配各种语言中的许多其他单词字符.请参阅最近的**字符perlrecharclass:

\w matches a single alphanumeric character (an alphabetic character, or a decimal digit) or a connecting punctuation character, such as an underscore ("_"). It does not match a whole word. To match a whole word, use \w+ . This isn't the same thing as matching an English word, but in the ASCII range it is the same as a string of Perl-identifier characters.

    If the /a modifier is in effect ...

    \w matches the 63 characters [a-zA-Z0-9_].
    otherwise ...
        For code points above 255 ...

        \w matches the same as \p{Word} matches in this range. That is, it matches Thai letters, Greek letters, etc. This includes connector punctuation (like the underscore) which connect two words together, or diacritics, such as a COMBINING TILDE and the modifier letters, which are generally used to add auxiliary markings to letters.
        For code points below 256 ...
            if locale rules are in effect ...

            \w matches the platform's native underscore character plus whatever the locale considers to be alphanumeric.
            if Unicode rules are in effect or if on an EBCDIC platform ...

            \w matches exactly what \p{Word} matches.
            otherwise ...

            \w matches [a-zA-Z0-9_].

因此,在5.14获得更广泛接受之前,最明确地说明[a-z_A-Z0-9]这些是您想要匹配的唯一字符是最安全的.

$user=~/^\w+;\w$/

考虑到上面的讨论,现在应该清楚的是, $user =~ /^\w+;\w$/只匹配包含单词字符,分号和尾随单词字符以及可能是换行符的输入.

至于你的代码,

#!/usr/bin/perl

use CGI;
use CGI::Carp qw(fatalsToBrowser);
$q = new CGI;
Run Code Online (Sandbox Code Playgroud)

首先,你错过了

use strict; 
use warnings;
Run Code Online (Sandbox Code Playgroud)

如果你想拯救自己,并且可能让世界其他地方感到头疼,那些pragma 不是可选的.

其次,use CGI::Carp qw(fatalsToBrowser);如果您无法访问Web服务器日志,则应仅用作短期离合器.

第三,

$q = new CGI;
Run Code Online (Sandbox Code Playgroud)

应该

my $q = CGI->new;
Run Code Online (Sandbox Code Playgroud)

new CGI被称为间接对象表示法,让你完全 perl放弃你的代码最终做什么.CGI->new明确地调用由... new提供的方法CGI.顺便说一句,我讨厌$q$query作为持有CGI对象的变量的名称.只是简单$cgi就更有意义了.

最后,看看:

print $q->header,
    $q->start_html('Finger User'),
    $q->h1('Finger User'),
print "<pre>";
Run Code Online (Sandbox Code Playgroud)

因此,您使用CGI 手工提供的HTML生成方法打印一些HTML .这种大杂烩风格和一些笨拙的纠结混乱最终会放入代码,这是避免使用HTML提供的HTML生成方法的一个很好的理由CGI.

切换到CGI :: Simple并使用模板包(如 HTML :: Template)将代码与HTML内容分开.以下未经测试的 脚本应该有效.请记住,您始终可以使用以下两种调试模式之一来测试CGI::Simple:

#!/usr/bin/env perl

use strict;
use warnings;

use CGI::Simple;
use HTML::Template;

run();

sub run {
    my $cgi = CGI::Simple->new;
    my $tmpl = HTML::Template->new(filehandle => \*DATA);

    my $user = $cgi->param('finger_user');

    unless (defined $user) {
        show_form($cgi, $tmpl);
        return;
    }

    if (($user) = ($user =~ /^([A-Z_a-z0-9]{1,40})\z/)) {
        show_output($cgi, $tmpl, $user);
    }
    else {
        show_error($cgi, $tmpl, "Invalid user name");
    }

    return;
}

sub show_form {
    my ($cgi, $tmpl) = @_;

    $tmpl->param(FORM => 1);

    print $cgi->header(
        -type    => 'text/html',
        -charset => 'utf-8',
    ), $tmpl->output;

    return;
}

sub show_error {
    my ($cgi, $tmpl, $msg) = @_;

    $tmpl->param(ERRORMSG => $msg);

    print $cgi->header(
        -type    => 'text/html',
        -charset => 'utf-8',
    ), $tmpl->output;

    return;
}

sub show_output {
    my ($cgi, $tmpl, $user) = @_;

    $tmpl->param(
        USER => $user,
        OUTPUT => scalar `finger -s $user`,
    );


    print $cgi->header(
        -type    => 'text/html',
        -charset => 'utf-8',
    ), $tmpl->output;

    return;
}


__DATA__
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>finger
<TMPL_IF USER>
<TMPL_VAR USER>
<TMPL_ELSE>
a user
</TMPL_IF>
on our system</title>
</head>

<body>

<TMPL_IF ERRORMSG>
<p syle="color:#e11"><TMPL_VAR ERRORMSG></p>
</TMPL_IF>

<TMPL_IF OUTPUT>
<h1>finger <TMPL_VAR USER></h1>
<pre><TMPL_VAR OUTPUT></pre>
</TMPL_IF>

<TMPL_IF FORM>
<form id="finger_form" name="finger_form" method="GET">
<p><label for="finger_user"><input id="finger_user" name="finger_user" type="text"
size="51"><input type="submit" value="finger" id="finger_submit"
name="finger_submit"></p>
</form>
</TMPL_IF>

</body>
</html>
Run Code Online (Sandbox Code Playgroud)