如何使用system()避免在Perl中意外逃逸?

时间:2010-04-19 21:52:20

标签: perl system

我想使用system()命令运行一些命令,我​​这样做:

execute_command_error("trash-put '/home/$filename'");

execute_command_error将报告运行的system命令是否出错。我知道我可以使用Perl命令取消链接文件,但我想使用trash-put删除内容,因为它是一种回收程序。

我的问题是$filename有时会有撇号,引号和其他奇怪的字符,这些字符会弄乱system命令或Perl本身。

2 个答案:

答案 0 :(得分:5)

将命令名和参数生成为数组,并将其传递给system:

my(@command) = ("trash-put", 'home/$filename');
system @command;

这意味着Perl不会调用shell来执行任何元字符扩展(或I / O重定向,或命令管道,或......)。它确实意味着它完全按照你的要求去做。

sub execute_command_error
{
    system @_;
}

从大量评论中借用信息:

  

perldoc -f systemperldoc.perl.org/functions/system.html(@Ether)中明确记录了哪些内容。

(另见下面关于'exec'的讨论密切相关。)

  

你的意思是将$ filename放在单引号中吗? (@mobrule)。

我确实打算使用单引号 - 我正在证明$filename不会被Perl或Shell扩展...在我的测试脚本中,我使用了'my.$file',并且给了我一个名字中带有$的文件 - 按照我的意图。

  

我认为如果你想调用shell(例如,如果你想要一些管道),所需的引用是$ command_line =“\”$ command \“\”$ arg1 \“\”$ arg2 \“.. “。 (@Jefromi)。

在参数周围添加双引号对嵌入式$,反引号 1 ,“$(...)”和相关表示法没有帮助。你几乎需要单引号,但是你需要将嵌入的单引号重写为“'\''”,它会生成一个单引号来终止当前的单引号参数,一个反斜杠引用组合来表示单引号,以及另一个单引号来恢复单引号。

  

如果我直接使用系统命令,这将是一个很好的解决方案;但是我使用webmin的execute_command函数,这有点过头了,所以我不知道如何编辑它以允许数组。您是否可以将嵌入式单引号的重写扩展为“'\''”...这就是我现在要使用的内容。 (@布赖恩)

粗略地说,(Unix)shell处理单引号的方式是“从第一个单引号到下一个引用的所有内容都是文字文本,没有元字符”。因此,要使shell将某些内容视为文字文本,请将其括在单个字符中。这涉及除单引号本身以外的所有内容。正如我的评论所说,你必须使用4个字符的替换字符串来将单个引号嵌入到单个引用参数的中间。

可能有一种比这更简洁的方法(可能使用一个或两个map操作),但这应该有效:

for (my $i = 0; $i < scalar(@command); $i++)
{
    $command[$i] =~ s/'/'\\''/g; # Replace single quotes by the magic sequence
    $command[$i] = "'$command[$i]'"; # Wrap value in single quotes
}

然后,您可以加入数组以生成单个字符串,以便传输到execute_command


  

最好将其写为系统{$ command [0]} @command来处理@command有一个元素的情况。这是我在 Mastering Perl 的“安全编程技术”一章中讨论的内容之一。 (@briandfoy)。

作为一般规则,我会接受这种更正。但是,我不确定在这种情况下它是至关重要的,其中命令名称由程序提供,并且它只是可能为用户提供的参数。命令名称'trash-put'对于shell扩展是安全的(shell启动时IFS会被shell重置为默认值,因此攻击途径不可用)。

'perldoc -f exec'手册页中讨论了这个问题:

  

如果你真的不想执行第一个参数,但是想要对你正在执行的程序撒谎,你可以指定你真正想要作为“间接对象”运行的程序(没有逗号)在LIST前面。 (这总是强制将LIST解释为多值列表,即使列表中只有一个标量。)

     

示例:

   $shell = '/bin/csh';
   exec $shell '-sh'; # pretend it's a login shell
  

或更直接地

   exec {'/bin/csh'} '-sh'; # pretend it's a login shell
  

当参数通过系统shell执行时,结果受其怪癖和能力的影响。有关详细信息,请参阅perlop中的“STRING”。

     

使用带有exec或system的间接对象也更安全。这种用法(对于system()也可以正常工作)强制将参数解释为多值列表,即使列表只有一个参数。这样你就可以安全地从shell扩展通配符或拆分带有空格的单词。

   @args = ( "echo surprise" );
   exec @args;              # subject to shell escapes
                            # if @args == 1
   exec { $args[0] } @args; # safe even with one-arg list
  

第一个版本,没有间接对象的版本,运行了echo程序,将“惊喜”传递给它。第二个版本没有;它试图运行一个名为“echo surprise”的程序,没找到它,并设置$?指示失败的非零值。


1 如何在Markdown中显示后退?

答案 1 :(得分:2)

perldoc -f system中所述:

  

如果LIST中有多个参数,或者LIST是一个数组   如果有多个值,则使用列表的其余部分给出的参数启动列表的第一个元素给出的程序。 如果有的话   只有一个标量参数,检查参数是否为shell元字符,如果有,则将整个参数传递给系统的   用于解析的命令shell(在Unix平台上是“/ bin / sh -c”,但在其他平台上有所不同)。如果没有shell元字符   参数,它被分成单词并直接传递给“execvp”,这更有效。

我喜欢使用IPC::System::Simple版本的system(),它可以提供更多控制,例如能够捕获各种异常并将某些错误代码处理为“坏”,将其他错误代码处理为“好”:< / p>

use IPC::System::Simple qw(system);
system("cat *.txt");   # will die on failure