6

I have a legacy PHP app, which has code similar to this:

<?php

$doc = $_GET['doc'];

if (preg_match('#\.\./#', $doc)) {
  header("HTTP/1.1 403 Forbidden");
  die();
}

$content = file_get_contents("/home/example/src/docs/" . $doc);

echo $content;

The filter looks a little bit too simplistic to me. I have tried throwing all sorts of attacks at it and it seems to stop them. Can anyone think of a way to bypass this filter?

  • From what I can see, your simplistic check via that regular expression is indeed safe on Linux/POSIX. It's vulnerable "only" on Windows, which you or someone else might realistically move your app to at some point in the future. But using '#\.\.(?:/|\\\\)#' instead of your regex string should make this safe on Windows as well. The only downside that I can see is that the solution is not context-aware. A new operating system might be used, a new directory separator might be introduced, PHP might start accepting Unicode look-alikes of the forward slash as directory separators, etc. – caw Mar 13 '17 at 2:06
2

It is indeed way too simplistic. You can bypass it using a backslash character. Example of a way to bypass it on my IIS server: http://example.com/index.php?doc=..\/index.php

It checks for "../" while, in order to make it "safer", it should check for ".." or don't allow any dots at all and make sure it's only included in the extension of the file requested.

  • 1
    I tried using ..\/, but on a linux system it didn't access the file correctly. I'd rather not blacklist patterns if possible. I have seen a few people talk about using realpath() in PHP to validate a path before using it, but I can find any useful documentation on doing this. – Noodles May 9 '16 at 0:49
  • @Noodles That's because Linux accepts the forward slash only while Windows accepts both the forward slash and the backslash, and even combinations of them, as directory separators. – caw Mar 13 '17 at 2:08
2

While I can't think of anything on a Linux/POSIX system other than the escape already described by Technidev, that's not to say there isn't one. If it were me, I'd be putting my trust in realpath() being able to handle the semantics of path strings correctly regardless of the underlying OS....

$basedir="/home/example/src/docs/";
$target=realpath("/home/example/src/docs/" . $_GET['doc']);
if (substr($target, 0, strlen($basedir)) != $basedir || !file_exists($target)) {
   header("HTTP/1.1 403 Forbidden");
   exit -1;
}
1

Input Filtering & Bypassing

Filters are always great as an additional layer of security, but they shouldn't be your only line of defense, because generally, filters are missing context, thus making 100% secure filtering impossible.

For example, your filter doesn't know anything about the underlying OS. Does it accept \ as directory separator? If so, ..\ may work. @Technidev mentioned another issue with ..\/. Depending on the OS, other attacks may work as well (for example, there may be a character that is ignored when treating paths, but not when filtering; I tried %00 as an example, but it doesn't work). There shouldn't be a general bypass that will work independently of OS though.

A better - but possibly too restrictive - filter would filter for ...

Proper Defenses

Your implied question seems to be if this code is secure, or how to make it more secure.

If you don't need a path, just use basename, which will return a file name.

If you do need a path, but it should be inside the docs directory, resolve the path and compare it to the base path.

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.