3

I am working on a lab for a class I am taking and I have a question about checking Perl strings for specific input.

Essentially what I want to be able to do is make sure that the input I am receiving from a user is something like:

/home/[anything is valid]/memo

The whole point of the lab is to prevent a pathname attack in a simple program that the instructor provided us with. So I want to check to make sure that the pathname provided by the user is within this format before doing anything with it.

I am currently using the abs_path() method in Perl to make get the absolute path of the string being passed in, but now I need to make sure that the absolute path contains what I have above.

Here is what I am trying to achieve:

my $input = "localhost:8080/cgi-bin/memo.cgi?memo=/home/megaboz/memo/new_CEO";
my $memo = '/home/megaboz/memo/new_CEO';
my $pathName = abs_path($memo);
if($pathName ne "/home/[anything works here]/memo/[anything works here]") {
       #throw an error
}
else {
       #process input
}

Any pointers?

brian d foy
  • 129,424
  • 31
  • 207
  • 592
Nathan Anderson
  • 155
  • 1
  • 2
  • 7
  • 2
    See https://perldoc.perl.org/perlretut for an introduction to perl regular expressions. – clamp Mar 16 '21 at 21:06
  • You sould do some reading on [regex](https://perldoc.perl.org/perlre) `die 'Wrong path input' unless $memo =~ m!^/home/[^/]*/memo/!;` – Polar Bear Mar 16 '21 at 21:13
  • Well, the first thing I'd question is why you are letting URLs pass around actual paths in the query. – brian d foy Mar 17 '21 at 15:52
  • And, what do you mean by "anything works here"? If you are trying to keep them from doing something with paths, you have to exclude at least some things in those positions. The directory separator and virtual directories `.` and `..` at least. – brian d foy Mar 17 '21 at 15:53

2 Answers2

3

Welcome to the wonderful world of regular expressions, which is something Perl is quite good with.

Let's walk through how to construct one of these. First, we usually use forward slashes to denote a regex, i.e.

/some-expression/

but since your paths have forward slashes in them, doing so would involve a messy bit of string escaping, so we'll use an alternate delimiter with m instead.

m(some-expression)

Now, we want to start with /home/ and end with /memo. You can read all about the different syntax in the link above, but in regular expressions we use ^ and $ (called anchors) to represent, respectively, the start and end of the string. So our regex is going to look like

m(^/home/SOMETHING/memo$)

Now for the piece in the middle. We want anything to pass. Your general purpose "anything" regex is a dot ., which matches any single character. And we can apply the Kleene star *, which says "zero or more of whatever comes before". So .* together says "zero or more of anything at all".

m(^/home/.*/memo$)

There's our regex. To apply it, we use =~ to ask "does it match", or !~ to ask "does it fail". The way your code is structured, we want to check for failure.

if ($pathName !~ m(^/home/.*/memo$)) {
    ...
} else {
    ...
}

Regular expressions are fairly ubiquitous and can be used in basically any programming language, so it's definitely a skill worth having (although Perl is particularly well-known for having strong regex support, so you're in the right tool for string matching capabilities).

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • This isn't going to work for the problem. By allowing anything in `.*`, you end up accepting strings such as `/home/../etc/memo/..`. You don't want to allow them to move around the filesystem at their convenience like that. – brian d foy Mar 17 '21 at 15:52
3

There's a lot that's missing from your question, so I'll have to make some guesses. And, since Stackoverflow is mostly about other people having similar problems reading these answers, some of this may not apply to you. Futhermore, most of this is about web security and not particular to Perl. You'd want to go through the same things in any language.

First, you say "anything works here". Don't let that be true. Consider that .., the virtual parent directory, specifies movement around the directory structure:

/home/../../memo/../../../target.pl

You end up with a file that you didn't want to expose. Not only that, if they were, through other means, able to make a memo symlink in the right spots, they are able to use that to move around too. That is, you can't really tell what file you'll get just by looking at the path because symlink (or hard links, too, I guess) can completely change things. What if memo was a symlink to /?

Second, don't ever let remote CGI users tell you where a file is. That's just too much for them to decide for you. Instead, it looks like there are two things that you will allow them to supply. A directory in the second position and something at the end. Make them specify those two things in isolation:

https://localhost:8080/cgi-bin/memo.cgi?user=megaboz&thing=NewCEO

You still have to validate these two things, but it's much easier to do them separately than in the middle of a bunch of other things. And, since you are taking input from the user and mapping it onto the file system, you should use taint checking (perlsec), which helps you catch user input being used outside your program. To untaint a value, use a match and capture what you will allow. I suggest that you do not try to salvage any bad data here. If it doesn't match what you expect, return an error. Also, it's better to specify what you allow rather than come up with everything you will disallow:

#!perl -T

my( $user  ) = however_you_get_CGI_params( 'user' ) =~ m/\A([a-z0-9]+)\z/i;
my( $thing ) = however_you_get_CGI_params( 'thing' ) =~ m/\A([a-z0-9]+)\z/i;

unless( defined $user and defined $thing ) { ... return some error ... }

Now, this doesn't mean that the values you now have in $user and $thing are true. They are merely valid values. Map those to whatever you need to fetch. Since you've constructed a path, checking that the path exists might be enough:

use File::Spec::Functions;
my $path = catfile( '/home', $user, 'memo', $thing );

unless( -e $path ) {  ... return some error ... }
brian d foy
  • 129,424
  • 31
  • 207
  • 592