ReedyBear's Blog

Docs: Remove path traversal in PHP

Also see realpath docs

As Posted to StackOverflow:

Use this or other solutions at your own risk. You should check the expectations in the test code & then modify the path traversal code to fit your needs. This may also miss some edge cases I didn't think to test.

See edits for previous version. My prior version was way over-engineered.

Remove path traversal

<?php
/**
 * Remove path traversal from a string path. Path expected to be from a url, so it is urldecoded
 */
function remove_path_traversal(string $path): string {
    $path = str_replace("\\","/",$path);
    $has_lead = substr($path,0,1)=='/';
    $has_trail = substr($path,-1)=='/';
    $path = '/'.$path.'/';
    while (strpos($path, '/../')!==false || strpos($path, '/./')!==false){
        $path = '/'.str_replace(['/../','/./'],'/',$path);
    }
    $path = str_replace(['////','///','//'],'/',$path);
    if (!$has_trail&&substr($path,-1)=='/')$path = substr($path, 0,-1);
    if (!$has_lead)$path = substr($path,1);
    return $path;
}

Test the function

Create a .php file and run it with php traversaltest.php

<?php
require(__DIR__.'/../src/functions.php'); // or wherever you defined the function

$urls = [
    "/" => "/",
    "/test" => "/test",
    "/test.html" => "/test.html",
    "/test/./" => "/test/",
    "/test/./some-file.txt" => "/test/some-file.txt",
    "/test/../some-file.txt" => "/test/some-file.txt",
    "//../test/../some-file.txt" => "/test/some-file.txt",
    "/dir/../../../../test.html" => "/dir/test.html",
    "/dir/./../.../..../...../....../test.html" => "/dir/.../..../...../....../test.html",
    "../abc/def/" => "abc/def/",
    "/abc/def/.." => "/abc/def",
    "abc/def/.." => "abc/def",
];

echo "\n\n";
foreach ($urls as $url => $target_file){
    $safe_path = \Bear\remove_path_traversal($url);

    if ($safe_path == $target_file)$status = "pass";
    else $status = "fail";
    echo "\n($status) Input($url), output($safe_path), expected($target_file)";
}

echo "\n\n";

#docs