vakus
vakus

Reputation: 740

Comparing beginning of two paths doesn't work properly

I am creating simple website on which I allow my users to upload and download the files. I have created simple check to avoid the users 'wondering' outside the /srv/public/ directory, however the check only prevents people from exiting /srv/ directory (it ignores when the user navigates to /srv/ or /srv/private/ etc).

The code which is checking whatever the path user specified is correct is:

$dir = "/srv/public/";

if(!isset($_POST['path'])){
    $_POST['path'] = '/';
}
$path = realpath($dir . $_POST['path']);
if(!substr($path, 0, 12) === $dir){ //This seems to never get executed
    $path = realpath($dir);
}
if(!file_exists($path)){
    $path = realpath($dir);
}
$path = rtrim($path, '/') . '/';

In case the user wonders outside the allowed /srv/public/ I want to set their path back to /srv/public/.

Here is the entire code of the website:

<?php

include("../functions.php");

if(!isLoggedIn($db)){
    header("location: /custom/403.php", true, 403);
    die();
}

$stmt = $db->prepare("SELECT permission FROM permissions WHERE userid=? AND permission='file.public.download'");
$stmt->bind_param("i", $_SESSION['userid']);
$stmt->execute();
$stmt->store_result();
if($stmt->num_rows == 0){
    header("location: /custom/403.php", true, 403);
    die();
}
$stmt->close();
$dir = "/srv/public/";

if(isset($_GET['action'])){
    if($_GET['action'] == 'download' && isset($_GET['path'])){
        $filepath = realpath($dir . $_GET['path']);
        if(substr($filepath, 0, 12) === $dir){
            if(file_exists($filepath)){
                ini_set("auto_detect_line_endings", true);
                header('Content-Description: File Transfer');
                header('Content-Type: application/octet-stream');
                header('Content-Disposition: attachment; filename="'.basename($filepath).'"');
                header('Expires: 0');
                header('Cache-Control: must-revalidate');
                header('Pragma: public');
                header('Content-Length: ' . filesize($filepath));
                readfile($filepath);
                die();
            }else{
                header("location /custom/404.php", true, 404);
            }
        }else{
            //the location is outside the allowed path.
            header("location /custom/403.php", true, 403);
            die();
        }
    }
}

include("../include/header.php");


if(!isset($_POST['path'])){
    $_POST['path'] = '/';
}
$path = realpath($dir . $_POST['path']);
if(!substr($path, 0, 12) === $dir){
    $path = realpath($dir);
}
if(!file_exists($path)){
    $path = realpath($dir);
}
$path = rtrim($path, '/') . '/';
?>


<link href="/styles/wintools-firefox.css" type="text/css" rel="stylesheet">
<script src="/scripts/wintools.js"></script>

<div id="header">
    <form action="public.php" method="post" style="display: inline-block">
        <input type="hidden" value="<?php echo(substr($path, 11)."../"); ?>" name="path">
        <input type="submit" value="back">
    </form>
    <form action="public.php" method="post" style="display: inline-block">
        <input type="text" value="<?php echo(substr($path, 11)); ?>" name="path">
        <input type="submit" value="Go">
    </form>
</div>
<div id="content">
    <?php


    $files = scandir($path);
    unset($files[array_search(".", $files, true)]);
    unset($files[array_search("..", $files, true)]);
    foreach($files as $file){
        if(is_dir($path.$file."/")){
            echo("<form style='display: inline-block;' name='".$file."' action='public.php' method='post'><input type='hidden' name='path' value='".$_POST['path'].$file."'><div class='folder' onclick='document.forms[\"".$file."\"].submit();'><div class='title'>".$file."</div></div></form>");
        }else{
            echo("<div class='folder' style='background-image: url(\"../images/file.png\")' onclick='window.location=\"public.php?action=download&path=".$file."\"'><div class='title'>".$file."</div></div>");
        }
    }
    ?>
</div>
<?php

include("../include/footer.php");

?>

I have spent few hours trying to fix this problem and rewriting this few lines of code, but nothing seems to be working.

Upvotes: 1

Views: 34

Answers (1)

AbraCadaver
AbraCadaver

Reputation: 78994

You need to extract the sub-string and compare it to the $dir:

if(substr($path, 0, 12) != $dir){

Your current code is this:

if(
!substr($path, 0, 12) /* returns a string and then negates it ! which results in false */
 === $dir){           /* false does NOT === $dir */

To see, do:

var_dump(substr($path, 0, 12));
var_dump(!substr($path, 0, 12));
var_dump(!substr($path, 0, 12) === $dir);

But it may make more sense to just check if $dir starts with $path (contains and starts at position 0):

if(strpos($dir, $path) !== 0) {

Upvotes: 2

Related Questions