26

Apparently, running:

perl -n -e 'some perl code' *

Or

find . ... -exec perl -n -e '...' {} +

(same with -p instead of -n)

Or

perl -e 'some code using <>' *

often found in one-liners posted on this site, has security implications. What's the deal? How to avoid it?

32

What's the problem

First, like for many utilities, you'll have an issue with file names starting with -. While in:

sh -c 'inline sh script here' other args

The other args are passed to the inline sh script; with the perl equivalent,

perl -e 'inline perl script here' other args

The other args are scanned for more options to perl first, not to the inline script. So, for instance, if there's a file called -eBEGIN{do something evil} in the current directory,

perl -ne 'inline perl script here;' *

(with or without -n) will do something evil.

Like for other utilities, the work around for that is to use the end-of-options marker (--):

perl -ne 'inline perl script here;' -- *

But even then, it's still dangerous and that's down to the <> operator used by -n/-p.

The issue is explained in perldoc perlop documentation.

That special operator is used to read one line (one record, records being lines by default) of input, where that input is coming from each of the arguments in turn passed in @ARGV.

In:

perl -pe '' a b

-p implies a while (<>) loop around the code (here empty).

<> will first open a, read records one line at a time until the file is exhausted and then open b...

The problem is that, to open the file, it uses the first, unsafe form of open:

open ARGV, "the file as provided"

With that form, if the argument is

  • "> afile", it opens afile in writing mode,
  • "cmd|", it runs cmd and reads it's output.
  • "|cmd", you've a stream open for writing to the input of cmd.

So for instance:

perl -pe '' 'uname|'

Doesn't output the content of the file called uname| (a perfectly valid file name btw), but the output of the uname command.

If you're running:

perl -ne 'something' -- *

And someone has created a file called rm -rf "$HOME"| (again a perfectly valid file name) in the current directory (for instance because that directory was once writeable by others, or you've extracted a dodgy archive, or you've run some dodgy command, or another vulnerability in some other software was exploited), then you're in big trouble. Areas where it's important to be aware of that problem is tools processing files automatically in public areas like /tmp (or tools that may be called by such tools).

Files called > foo, foo|, |foo are a problem. But to a lesser extent < foo and foo with leading or trailing ASCII spacing characters (including space, tab, newline, cr...) as well as that means those files won't be processed or the wrong one will be.

Also beware that some characters in some multi-byte character sets (like ǖ in BIG5-HKSCS) end in byte 0x7c, the encoding of |.

$ printf ǖ | iconv -t BIG5-HKSCS | od -tx1 -tc
0000000  88  7c
        210   |
0000002

So in locales using that charset,

 perl -pe '' ./nǖ

Would try to run the ./n\x88 command as perl would not try to interpret that file name in the user's locale!

How to fix/work around

AFAIK, there is nothing you can do to change that unsafe default behaviour of perl once and for all system-wide.

First, the problem occurs only with characters at the start and end of the file name. So, while perl -ne '' * or perl -ne '' *.txt are a problem,

perl -ne 'some code' ./*.txt

is not because all the arguments now start with ./ and end in .txt (so not -, <, >, |, space...). More generally, it's a good idea to prefix globs with ./. That also avoids problems with files called - or starting with - with many other utilities (and here, that means you don't need the end-of-options (--) marker any more).

Using -T to turn on taint mode helps to some extent. It will abort the command if such malicious file is encountered (only for the > and | cases, not < or whitespace though).

That's useful when using such commands interactively as that alerts you that there's something dodgy going on. That may not be desirable when doing some automatic processing though, as that means someone can make that processing fail just by creating a file.

If you do want to process every file, regardless of their name, you can use the ARGV::readonly perl module on CPAN (unfortunately usually not installed by default). That's a very short module that does:

sub import{
   # Tom Christiansen in Message-ID: <24692.1217339882@chthon>
   # reccomends essentially the following:
   for (@ARGV){
       s/^(\s+)/.\/$1/;   # leading whitespace preserved
       s/^/< /;       # force open for input
       $_.=qq/\0/;    # trailing whitespace preserved & pipes forbidden
   };
};

Basically, it sanitises @ARGV by turning " foo|" for instance into "< ./ foo|\0".

You can do the same in a BEGIN statement in your perl -n/-p command:

perl -pe 'BEGIN{$_.="\0" for @ARGV} your code here' ./*

Here we simplify it on the assumption that ./ is being used.

A side effect of that (and ARGV::readonly) though is that $ARGV in your code here shows that trailing NUL character.

Update 2015-06-03

perl v5.21.5 and above have a new <<>> operator that behaves like <> except that it will not do that special processing. Arguments will only be considered as file names. So with those versions, you can now write:

perl -e 'while(<<>>){ ...;}' -- *

(don't forget the -- or use ./* though) without fear of it overwriting files or running unexpected commands.

-n/-p still use the dangerous <> form though. And beware symlinks are still being followed, so that does not necessarily mean it's safe to use in untrusted directories.

  • 2
    you've been working on that all day, I'll wager. well done. – mikeserv Nov 26 '14 at 8:19
  • 2
    nice update to perl, but it's odd that the perl devs didn't add -P and -N options to make use of it (can't change the existing -p and -n because some scripts may rely on the insecure behaviour) – cas Jul 21 '16 at 3:55
9

In addition to @Stéphane Chazelas's answer, we don't have to worry about this issue if we use -i command line option:

$ perl -pe '' 'uname|'
Linux

$ perl -i -pe '' 'uname|'
Can't open uname|: No such file or directory.

Because when using -i option, perl used stat to check the file status before process it:

$ strace -fe trace=stat perl -pe '' 'uname|'
stat("/home/cuonglm/perl5/lib/perl5/5.20.1/x86_64-linux", 0x7fffd44dff90) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/5.20.1", 0x7fffd44dff90) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/x86_64-linux", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
Process 6106 attached
Linux
Process 6105 suspended
Process 6105 resumed
Process 6106 detached
--- SIGCHLD (Child exited) @ 0 (0) ---

$ strace -fe trace=stat perl -i -pe '' 'uname|'
stat("/home/cuonglm/perl5/lib/perl5/5.20.1/x86_64-linux", 0x7fffdbaf2e50) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/5.20.1", 0x7fffdbaf2e50) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/x86_64-linux", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
stat("uname|", 0x785f40)                = -1 ENOENT (No such file or directory)
Can't open uname|: No such file or directory.
  • 1
    Isn't there a possible race condition between the stat check and the effective perl processing going on just after? – Totor Mar 19 '15 at 9:59
  • @Totor: I think no. – cuonglm Mar 19 '15 at 11:15
  • It's not about stat. It's just that -i is to edit files in place, so it doesn't make sense to accept arguments other than actual file paths, so with -i, that special processing is not done. – Stéphane Chazelas Apr 23 at 14:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.