I Met A Var That Wasn’t There: Return of the Spectre of Unicode in Perl

jacoby

Dave Jacoby

Posted on November 17, 2021

I Met A Var That Wasn’t There: Return of the Spectre of Unicode in Perl

I can’t let it go.

So, let’s make an exploit!

#!/usr/bin/env perl

use strict;
use warnings;
use experimental qw{ say signatures state };

use utf8;

if (1) {
    my $timeout;
    ( $timeout, ㅤ() ) = poorly_trusted_code_from_elsewhere();
    my @array = ( 'curl -s http://perl.com/', 'ping -c 1 purdue.pl', ㅤ() );

    for my $cmd (@array) {
        say $cmd;
        sleep $timeout;
    }
    say join "\n", @array;
    exit;
}

sub ㅤ : lvalue {
    state @array;
    @array = 1 .. 10 unless scalar @array;
    if ( scalar @_ ) {
        @array = @_;

        # say instead of a try {exec()} catch {};
        # because I'm just trying to make a point
        say join ' - ', @array;
    }
    else { return @array }
}

sub poorly_trusted_code_from_elsewhere () {
    return ( 4, 'cat /etc/passwd' );
}

Enter fullscreen mode Exit fullscreen mode

(if (1){ ,,, ; exit} is a convention I use in my test files, where I work to see if I can do the code thing I’m thinking about, not actually solving the problem, and because there can be dozens of things I’m poking at, I put them in this simple if statement and exit out, occasionally turning it to if (0) and certainly stopping things at the end of the block.)

So, rather than actually stand up an untrustworthy server, I’m mockig it with poorly_trusted_code_from_elsewhere, and having it return a command that … well, we use shadow passwords, so passing /etc/passwd isn’t the horror it was in the 1990s, but still, don’t.

Then we have a for loop that handles all the commands, in this case saying them rather than running them, because I don’t really want to get the source code to Perl.com.

If I run this with use utf8:

$ ./exploit.pl
HOLY SIGNIFICANT WHITESPACE, BATMAN!
HOLY SIGNIFICANT WHITESPACE, BATMAN!
curl -s http://perl.com/
ping -c 1 purdue.pl
cat /etc/passwd
curl -s http://perl.com/
ping -c 1 purdue.pl
cat /etc/passwd

Enter fullscreen mode Exit fullscreen mode

If I comment it out:

$ ./exploit.pl
Unrecognized character \xE3; marked by <-- HERE after $timeout, <-- HERE near column 17 at ./exploit.pl line 11.

Enter fullscreen mode Exit fullscreen mode

And, if I run perlcritic:

Problem while critiquing "exploit.pl": Can't parse code: Encountered unexpected character '227'

Enter fullscreen mode Exit fullscreen mode

But then, you have sub ㅤ : lvalue { ... } right in your program. “Hey Joe” , you ask. “What’s the deal with this nameless subroutine you put in my code?”

So, instead, Joe writes BadMod, a bad module that does bad things.

package BadMod;

use strict;
use warnings;
use experimental qw{ say signatures state };
use utf8;

use Exporter qw{import};
our @EXPORT = qw{
    poorly_trusted_code_from_elsewhere
    ㅤ
};

sub ㅤ : lvalue {
    say 'HOLY SIGNIFICANT WHITESPACE, BATMAN!';
    state @array;
    @array = 1 .. 10 unless scalar @array;
    if ( scalar @_ ) {
        @array = @_;

        # say instead of a try {exec()} catch {};
        # because I'm just trying to make a point
        say join ' - ', @array;
    }
    else { return @array }
}

sub poorly_trusted_code_from_elsewhere () {
    return ( 4, 'cat /etc/passwd' );
}

1

Enter fullscreen mode Exit fullscreen mode

And there’s nothing too noticable in the main code, beyond () in places that kinda smell, and a warning on perlcritic.

#!/usr/bin/env perl

use strict;
use warnings;
use experimental qw{ say signatures state };
use utf8;

use lib '.';
use BadMod;

if (1) {
    my $timeout;
    ( $timeout, ㅤ() ) = poorly_trusted_code_from_elsewhere();
    my @array = ( 'curl -s https://perl.com/', 'ping -c 1 purdue.pl', ㅤ() );

    for my $cmd (@array) {
        say $cmd;
        sleep $timeout;
    }
    say join "\n", @array;
    exit;
}


$ ./exploit.pl
HOLY SIGNIFICANT WHITESPACE, BATMAN!
HOLY SIGNIFICANT WHITESPACE, BATMAN!
curl -s https://perl.com/
ping -c 1 purdue.pl
cat /etc/passwd
curl -s https://perl.com/
ping -c 1 purdue.pl
cat /etc/passwd

Enter fullscreen mode Exit fullscreen mode

And exploit.pl still fails perlcritic has weird code-smelly braces in there.

So, if you write BadMod a bit like this:

package BadMod;

# pseudocode, this won't actually run
my @bad_commands;

sub poorly_trusted_code_from_elsewhere () {
    push @bad_commands, 'cat /etc/passwd' ;
    return 4;
}

sub execute_commands ( @good_commands ) {
    push @good_commands, @bad_commands;
    for my $cmd (@good_commands) {
        say $cmd;
        sleep $timeout;
    }
}

1

Enter fullscreen mode Exit fullscreen mode

Just rename @bad_commands to something more innouous and fill the module with dead code, obfuscated code and a thousand globals and you could never tell there’s a problem, and it will pass perlcritic.

So, could I get exploit.pl to ‘execute’ things that it shouldn’t? Definitely.

Are there better ways to get exploit code into a code base that don’t use a character set that can be easily turned off on a whim and don’t trigger any of the default Perl::Critic policies? Absolutely.

Is this something you should be aware of? Sure.

Should anyone be afraid of using a character set that allows the use of letters foreigners use (like لؤلؤة) or images the kids use while texting (like 🍆)? So afraid they categorize it over real threats or problems? No. That doesn’t make a lick of sense.

If you have any questions or comments, I would be glad to hear it. Ask me on Twitter or make an issue on my blog repo.

💖 💪 🙅 🚩
jacoby
Dave Jacoby

Posted on November 17, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related