如何避免在 Perl 中使用 system() 意外转义? [英] How can I avoid escaping by accident in Perl using system()?

查看:48
本文介绍了如何避免在 Perl 中使用 system() 意外转义?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

解决方案

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

my(@command) = ("trash-put", 'home/$filename');系统@命令;

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

sub execute_command_error{系统 @_;}

<小时>

从大量评论中借用信息:

<块引用>

perldoc -f systemperldoc 中有明确记录.perl.org/functions/system.html (@Ether).

(另请参阅下面对 'exec' 的讨论,这是密切相关的.)

<块引用>

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

我确实打算使用单引号 - 我正在证明 $filename 不会被 Perl 或 Shell 扩展......在我的测试脚本中,我使用了 'my.$file',这给了我一个名称中带有 $ 的文件 - 正如我所希望的那样.

<块引用>

我认为,如果您确实想调用外壳程序(例如,如果您想要一些管道),所需的引用是 $command_line = "\"$command\" \"$arg1\" \"$arg2\"...".(@Jefromi).

在参数周围添加双引号对嵌入的 $、反引号1、'$(...)' 和相关符号.您几乎需要单引号围绕事物,但是您需要将嵌入的单引号重写为'\''",它会生成一个单引号来终止当前的单引号参数,一个反斜杠引号组合表示单引号,另一个单引号恢复单引号参数.

<块引用>

如果我直接使用系统命令,这将是一个很好的解决方案;但是我正在使用 webmin 的 execute_command 函数,这有点超出我的想象,所以我不知道如何编辑它以允许数组.您能否将嵌入单引号的重写扩展为'\''"...这就是我现在将使用的.(@Brian)

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

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

for (my $i = 0; $i 

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

<小时><块引用>

最好把它写成 system { $command[0] } @command 来处理 @command 有一个元素的情况.这是我在掌握 Perl 的安全编程技术"一章中谈到的内容之一.(@briandfoy).

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

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

<块引用>

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

示例:

 $shell = '/bin/csh';执行 $shell '-sh';# 假装它是一个登录shell

<块引用>

或者更直接地

 exec {'/bin/csh'} '-sh';# 假装它是一个登录shell

<块引用>

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

使用带有 exec 或 system 的间接对象也更安全.这种用法(也适用于 system())强制将参数解释为多值列表,即使列表只有一个参数.这样,您就可以避免 shell 扩展通配符或拆分其中包含空格的单词.

 @args = ("回声惊喜");执行@args;# 受 shell 转义影响# 如果@args == 1exec { $args[0] } @args;# 即使使用单参数列表也是安全的

<块引用>

第一个版本,没有间接对象的版本,运行 echo 程序,传递给它一个惊喜"的参数.第二个版本没有;它试图运行一个名为回声惊喜"的程序,没有找到它,然后设置 $?表示失败的非零值.

<小时>

1 如何让反引号在 Markdown 中显示?

I want to run some commands using the system() command, I do this way:

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

Where execute_command_error will report if there was an error with whatever system command it ran. I know I could just unlink the file using Perl commands, but I want to delete stuff using trash-put as it's a type of recycling program.

My problem is that $filename will sometimes have apostrophes, quotes, and other weird characters in it that mess up the system command or Perl itself.

解决方案

Generate the command name and arguments as an array, and pass that to system:

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

This means that Perl does not invoke the shell to do any metacharacter expansion (or I/O redirection, or command piping, or ...). It does mean it does exactly what you told it to do.

sub execute_command_error
{
    system @_;
}


Borrowing information from the copious collection of comments:

Which is clearly documented in perldoc -f system or at perldoc.perl.org/functions/system.html (@Ether).

(See also the discussion of 'exec' below which is closely related.)

Did you mean to put $filename in single quotes? (@mobrule).

I did intend to use single quotes - I'm demonstrating that the $filename does not get expanded by Perl or Shell...In my test script, I used 'my.$file', and that gave me a file with a $ in the name - as I intended.

I think the desired quoting if you do want to invoke the shell (for example if you want some piping) is $command_line = "\"$command\" \"$arg1\" \"$arg2\"...". (@Jefromi).

Adding double quotes around the arguments won't help with embedded $, backtick1, '$(...)' and related notations. You more nearly need single quotes around things, but then you need to rewrite embedded single quotes as "'\''" which generates a single quote to terminate the current single-quoted argument, a backslash-quote combination to represent a single quote, and another single quote to resume the single-quoted argument.

This would be a good solution if I used system command directly; however I am using webmin's execute_command function, which is a bit over my head so I wouldn't know how to edit it to allow for arrays. Could you expand on the rewrite of embedded single quotes as "'\''"...This is what I will use, for now. (@Brian)

Roughly speaking, the way the (Unix) shells treat single quotes is "everything from the first single quote up to the next is literal text, no metacharacters". So, to get the shell to treat something as literal text, enclose it in single characters. That deals with everything except single quotes themselves. As my comment says, you have to use the 4-character replacement string to get a single quote embedded into the middle of a single quoted argument.

There is probably a neater way to do it than this (using one or two map operations, perhaps), but this should work:

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
}

You can then join the array to make a single string for transmission to execute_command.


It's better to write that as system { $command[0] } @command to handle the case where @command has one element. This is one of the things I talk about in the "Secure Programming Techniques" chapter of Mastering Perl. (@briandfoy).

As a general rule, I'll accept this correction. I'm not sure it is crucial in this instance, though, where the command name is provided by the program and it is only the arguments that are possibly provided the user. The command name 'trash-put' is safe from shell expansions (IFS is reset to default by the shell when it starts, so that avenue of attack is not available).

This issue is discussed in the 'perldoc -f exec' man page:

If you don't really want to execute the first argument, but want to lie to the program you are executing about its own name, you can specify the program you actually want to run as an "indirect object" (without a comma) in front of the LIST. (This always forces interpretation of the LIST as a multivalued list, even if there is only a single scalar in the list.)

Example:

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

or, more directly,

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

When the arguments get executed via the system shell, results are subject to its quirks and capabilities. See "STRING" in perlop for details.

Using an indirect object with exec or system is also more secure. This usage (which also works fine with system()) forces interpretation of the arguments as a multivalued list, even if the list had just one argument. That way you're safe from the shell expanding wildcards or splitting up words with whitespace in them.

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

The first version, the one without the indirect object, ran the echo program, passing it "surprise" an argument. The second version didn't; it tried to run a program named "echo surprise", didn't find it, and set $? to a non-zero value indicating failure.


1 How do you get a back-tick to display in Markdown?

这篇关于如何避免在 Perl 中使用 system() 意外转义?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆