php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #69209 Path resolution inconsistency
Submitted: 2015-03-09 22:56 UTC Modified: 2015-05-27 00:50 UTC
From: ak4t0sh at free dot fr Assigned:
Status: Not a bug Package: Streams related
PHP Version: Irrelevant OS: Linux
Private report: No CVE-ID: None
 [2015-03-09 22:56 UTC] ak4t0sh at free dot fr
Description:
------------
Hi,

During a code review i found a strange behaviour in most of php function that deal with path handling.

Indeed I found a code that required a file this way : `require __DIR__."../../include/myfile.php";` building a path like like `/path/to../../include/myfile.php` instead of the right path `/path/to/../include/myfile.php`
But strangely the file was correctly loaded by require...
As you can see when you execute test script in the "normal case" the file is loaded even if file_exists() return false...

The others parts of the test script are here as a POC to demonstrate that such behaviour could lead to a LFI exploitation. To test them remove the exit at line 22.
Indeed as the file `/path/to/../include/myfile.php` is loaded without any error or warning when calling `/path/to../../include/myfile.php` the developer could miss the coding error.
But if an attacker could create a symlink `/path/to.` which target an especially crafted file tree that could allow him to read / write / execute others (private) files. (LFI)
In fact this is not a very easy LFI to exploit but in my case the file was a template file so as an attacker a possible exploitation would be to replace the loaded stream by my own (malicious) code without editing the original source code.

The problem seems to be located to the "../../" part only.
Indeed the following paths logically failed :
    /path/to../../include../myfile.php
    /path/to/../include../myfile.php

After some researches i suppose that the problem is in "zend_resolve_path" function but as i'm not familiar with php internal code i prefer let the experts look and decide if it's a bug or a feature ;)

This has been tested on default installation of PHP 5.6.5, 5.4.36 and PHP 5.3.3 on Debian Jessie, Wheezy and Squeeze


Arnaud Trouvé (ak4t0sh)

Test script:
---------------
wget http://www.arnaudtrouve.fr/php-dotted-path-issue/dotted-path-issue-test.sh
chmod +x dotted-path-issue-test.sh
wget http://www.arnaudtrouve.fr/php-dotted-path-issue/poc.txt
mv poc.txt poc.php
./dotted-path-issue-test.sh

Expected result:
----------------
cf test script comments

Actual result:
--------------
cf test script comments

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-03-10 15:54 UTC] ak4t0sh at free dot fr
All things considered, that vulnerability is not a LFI (indeed there is no injection in code) but it's more like a stream redirection
 [2015-03-10 22:54 UTC] stas@php.net
-Status: Open +Status: Not a bug
 [2015-03-10 22:54 UTC] stas@php.net
There's no error here. Path /path/to../../include/myfile.php is the same as /path/include/myfile.php as .. in the file/directory name is completely valid and .. as always removes previous component.
 [2015-03-12 11:47 UTC] ak4t0sh at free dot fr
-Summary: Path resolution error with trailing dotted directory name +Summary: Path resolution inconsistency
 [2015-03-12 11:47 UTC] ak4t0sh at free dot fr
ok thanks for the explanation i was wrong there is no problem with dot character handling i thought there would a check from the entire path validity before its resolution.

However i think there is a real problem in path resolving consistency between file_exists/stream_resolve_include_path/realpath and fopen/file_get_contents/file_put_contents/require/include...(others)
Indeed the fact is in the test case 2 (aka WTF : single dotted path in test file) file_exists/stream_resolve_include_path/realpath are all returning false but we can still open file using fopen, etc...


Here a smaller script to test it : 
========================================================================
<?php
mkdir(__DIR__."/foo");
file_put_contents(__DIR__."/foo/bar.php", "content");
var_dump(file_exists(__DIR__."/foo/bar.php"), , realpath(__DIR__."/foo/bar.php"), stream_resolve_include_path(__DIR__."/foo/bar.php"), file_get_contents(__DIR__."/foo/bar.php"));
var_dump(file_exists(__DIR__."/inexistant_dir/../foo/bar.php"), realpath(__DIR__."/inexistant_dir/../foo/bar.php"), stream_resolve_include_path(__DIR__."/inexistant_dir/../foo/bar.php"), file_get_contents(__DIR__."/inexistant_dir/../foo/bar.php"));
?>
========================================================================
So on one hand i have file_exists (and others functions) which return me the the file "inexistant_dir/../foo/bar.php" does not exists and on the other hand i can still open it using file_get_contents...
So if i follow your reasoning about ".." that remove the previous component of path we can not really rely on file_exists because in this script it should have remove "inexistant_dir" from path and so return true. Same for realpath and stream_resolve_include_path which should have return __DIR__."/foo/bar.php" instead of false.


So theorically i can write a code like this (i know it's stupid - it's for demo)
========================================================================
<?php
mkdir(__DIR__."/foo");
file_put_contents(__DIR__."/foo/bar.php", "content");
if (file_exists(__DIR__."/inexistant_dir/../foo/bar.php") == false)
    echo file_get_contents(__DIR__."/inexistant_dir/../foo/bar.php");    #does not exists ? i don't care open it anyway
?>
========================================================================

As there is no problem with dot character i renamed this issue.

Is there an explanation to such inconsistency ? If not as i cannot do it i let you reopen the issue and remove the security flag because i think this should be fixed or documented at least.
 [2015-05-26 14:59 UTC] ak4t0sh at free dot fr
Hi,

Any feedback about such inconsistency ?

Sincerely,

Arnaud Trouvé (ak4t0sh)
 [2015-05-27 00:50 UTC] stas@php.net
-Type: Security +Type: Bug
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Fri Oct 18 06:01:27 2019 UTC