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
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: ak4t0sh at free dot fr
New email:
PHP Version: OS:

 

 [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

Pull Requests

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-2024 The PHP Group
All rights reserved.
Last updated: Sun Dec 22 11:01:30 2024 UTC