Perl 模棱两可的命令行选项,以及带有 -i 的 eval 的安全隐患?

Posted

技术标签:

【中文标题】Perl 模棱两可的命令行选项,以及带有 -i 的 eval 的安全隐患?【英文标题】:Perl ambiguous command line options, and security implications of eval with -i? 【发布时间】:2012-05-29 08:42:36 【问题描述】:

我知道这是不正确的。我只是想知道 perl 是如何解析这个的。

所以,我在玩 perl,我想要的是 perl -ne 我输入的是 perl -ie 行为有点有趣,我想知道发生了什么。

$ echo 1 | perl -ie'next unless /g/i'

所以 perl Aborted (core dumped) 。阅读perl --help 我看到-i 需要扩展备份。

-i[extension]     edit <> files in place (makes backup if extension supplied)

对于那些不知道-e 只是评估的人。所以我在想三件事中的一件可能会发生,要么它被解析为

    perl -i -e'next unless /g/i' i 得到 undef,其余的作为参数传递给 e perl -ie 'next unless /g/i' i 得到参数 e,其余的像文件名一样挂起 perl -i"-e'next unless /g/i'" 作为 i 的参数

当我跑步时

$ echo 1 | perl -i -e'next unless /g/i'

程序没有中止。这让我相信'next unless /g/i' 没有被解析为-e 的文字参数。毫无疑问,上面的内容会以这种方式解析,并且会产生不同的结果。

那是什么?好吧,再玩一点,我得到了

$ echo 1 | perl -ie'foo bar'
Unrecognized switch: -bar  (-h will show valid options).

$ echo 1 | perl -ie'foo w w w'
... works fine guess it reads it as `perl -ie'foo' -w -w -w`

玩弄上面的,我试试这个...

$ echo 1 | perl -ie'foo e eval q[warn "bar"]'
bar at (eval 1) line 1.

现在我真的很困惑.. 那么 Perl 是如何解析这个的呢?最后,看起来你实际上可以从-i 中获得一个 Perl eval 命令。这有安全隐患吗?

$ perl -i'foo e eval "warn q[bar]" '

【问题讨论】:

看起来 Perl 正在吞噬 -i 开关作为其参数之后的所有内容。 @JRFerguson 对我来说不是,看起来 perl 只是假设 -i 将所有字符占用到第一个空格,然后它似乎在其余部分运行自己的命令行解析形式输入。 确实是我要说的。 顺便说一句,没有安全隐患。 perl -e'...' 不能做任何你不能用 perl somefile.pl 做的事情。 但是,能够将参数设置为 perl -i 不应为您提供与将参数设置为 perl -eperl $file 相同的能力。 【参考方案1】:

快速解答

Shell 引用处理正在折叠和连接它认为的所有参数。您的调用相当于

$ perl '-ienext unless /g/i'

它立即中止,因为 perl 将此参数解析为包含 -u,这会触发核心转储,您的代码将开始执行。这是一个旧功能,曾经用于创建伪可执行文件,但如今它在本质上已经退化了。

eval 的调用似乎是-e 'ss /g/i' 的错误解析。

第一条线索

B::Deparse 你的朋友可以吗,前提是你碰巧在没有dump 支持的系统上运行。

$ echo 1 | perl -MO=Deparse,-p -ie'next unless /g/i'
dump is not supported.
BEGIN  $^I = "enext"; 
BEGIN  $/ = "\n"; $\ = "\n"; 
LINE: while (defined(($_ = <ARGV>))) 
    chomp($_);
    (('ss' / 'g') / 'i');

那么为什么unle 会消失呢?如果你运行的是 Linux,你可能还没有达到我的水平。上面的输出来自 Cygwin 上的 Perl,关于 dump 不受支持的错误是一个线索。

下一条线索

来自perlrun documentation的注意事项:

-u

此开关会导致 Perl 在编译您的程序后转储内核。然后,理论上您可以使用 undump 程序(未提供)将此核心转储并转换为可执行文件。这会以牺牲一些磁盘空间为代价来加快启动速度(您可以通过剥离可执行文件来最小化)。 (不过,在我的机器上,“hello world”可执行文件的大小约为 200K。)如果您想在转储之前执行程序的一部分,请改用 dump 运算符。注意:undump 的可用性是特定于平台的,可能不适用于特定的 Perl 端口。

工作假设和确认

Perl 的参数处理将整个块视为单个选项集群,因为它以破折号开头。 -i 选项使用下一个单词 (enext),正如我们在 implementation for -i processing 中看到的那样。

case 'i':
    Safefree(PL_inplace);
    [Cygwin-specific code elided -geb]
    
        const char * const start = ++s;
        while (*s && !isSPACE(*s))
            ++s;

        PL_inplace = savepvn(start, s - start);
    
    if (*s) 
        ++s;
        if (*s == '-')      /* Additional switches on #! line. */
            s++;
    
    return s;

对于备份文件的扩展名,来自 perl.c 的上述代码最多使用第一个空白字符或字符串结尾,以先到者为准。如果还有字符,第一个必须是空格,然后跳过它,如果下一个是破折号,那么也跳过它。在 Perl 中,你可以把这个逻辑写成

if ($$s =~ s/i(\S+)(?:\s-)//) 
  my $extension = $1;
  return $extension;

那么,-u-n-l-e 都是有效的 Perl 选项,因此参数处理会吃掉它们并留下无意义的选项

ss /g/i

作为-e 的参数,perl 将其解析为一系列划分。但在执行开始之前,古老的-u 导致 perl 转储内核。

意外行为

更奇怪的是,如果您在 nextunless 之间放置两个空格

$ perl -ie'next  unless /g/i'

程序尝试运行。 Back in the main option-processing loop我们看到了

case '*':
case ' ':
    while( *s == ' ' )
      ++s;
    if (s[0] == '-')        /* Additional switches on #! line. */
        return s+1;
    break;

额外的空格会终止对该参数的选项解析。证人:

$ perl -ie'next 废话 -garbage --foo' -e die
死于 -e 第 1 行。

但没有我们看到的额外空间

$ perl -ie'next 废话 -garbage --foo' -e die
无法识别的开关:-onsense -garbage --foo(-h 将显示有效选项)。

但是,如果有一个额外的空格和破折号,

$ perl -ie'next -unless /g/i'
不支持转储。

设计动机

正如 cmets 所指出的,逻辑是为了 harsh shebang (#!) line constraints 而存在的,perl 会尽力解决这个问题。

解释器脚本

解释器脚本是一个启用了执行权限的文本文件,其第一行的格式为:

#! interpreter [optional-arg]

解释器必须是本身不是脚本的可执行文件的有效路径名。如果execve 的文件名参数指定了一个解释器脚本,那么解释器将使用以下参数调用:

interpreter [optional-arg] filename arg...

其中 arg... 是由execveargv 参数指向的一系列单词。

对于便携使用,optional-arg 应该不存在,或者指定为单个单词(ie,它不应包含空格)......

【讨论】:

如果 shell 认为这只是一个参数'-ienext unless /g/i' 为什么 perl 会在空格上分解它?为什么 Perl 会假设如果 asdf jkl 作为一个参数发送,它应该被读作 -asdf -jkl? shell 不应该为 perl 做准备参数的工作,而 perl 只担心处理这些参数吗? 不幸的是,在这种情况下,在运行 Perl 5.14.2 的 Mac OS X (10.6.8) 上使用 Deparse 并没有显示出来。无论有没有它,都会得到“中止陷阱”。 @Evan Carroll,关于“为什么 perl 在空白处分解它?”我怀疑这与 Perl 对 #! 选项的处理有关。线。某些操作系统(例如 Linux)将命令之后的所有内容作为单个参数传递。分割空格将允许#!/usr/bin/perl -w -i 工作。 @Greg Bacon,我不会说 Linux 是一个古老的系统。 @ikegami 很有趣!我不知道shebang was executed so awkwardly 缺乏标准化。【参考方案2】:

要知道的三件事:

    '-x y' 对 Perl 表示 -xy(对于某些任意选项“x”和“y”)。

    -xy 与 unix 工具一样,是一个代表 -x -y 的“捆绑包”。

    -i-e 一样,吸收了其余的论点。与-e 不同,它认为空格是参数的结尾(根据上面的#1)。

意思是

-ie'next unless /g/i'

这只是一种奇特的写作方式

'-ienext unless /g/i'

解绑到

-ienext -u -n -l '-ess /g/i'
  ^^^^^             ^^^^^^^
----------         ----------
val for -i         val for -e

perlrun 文件-u 为:

此开关会导致 Perl 在编译程序后转储内核。然后,理论上您可以使用 undump 程序(未提供)将此核心转储并转换为可执行文件。这会以牺牲一些磁盘空间为代价来加快启动速度(您可以通过剥离可执行文件来最小化)。 (不过,在我的机器上,“hello world”可执行文件的大小约为 200K。)如果您想在转储之前执行程序的一部分,请改用 dump() 运算符。注意:undump 的可用性是特定于平台的,可能不适用于特定的 Perl 端口。

【讨论】:

注意:我不知道#3,我忘记了#1,所以这确实是一个非常棘手的问题,特别是因为简单地添加-MO=Deparse 对这个系统没有帮助。 '-x y' means -xy to Perl (for some arbitrary options "x" and "y"). 这是真的吗? perl '-w edie' 不起作用。也许你的意思是'-x y' means -xy to Perl (for some arbitrary options "x" that requires an argument, and an arbitrary argument "y").,就像perl '-e die'的情况一样 @Evan Carroll,啊,所以它是特定于 -i 的?奇怪!

以上是关于Perl 模棱两可的命令行选项,以及带有 -i 的 eval 的安全隐患?的主要内容,如果未能解决你的问题,请参考以下文章

3-Perl 基础语法

Perl模块 Getopt::Long 解析

如何将强制和可选命令行参数传递给 perl 脚本?

在 vim 编辑中使用 perl 单行命令

在 Perl 中使用 Getopt 时如何对参数进行分组?

使用 getopts 处理长短命令行选项