Reputation: 740
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
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