### Cloud Computing v1

*A misc challenge that has nothing to do with the cloud, nor with computing.*

> Welcome to our new cloud function computing platform, enjoy here. http://pwnable.org:47780/

With those quoted words as the only description, a look at the website kindly gives us some of its source code:

So we upload a php file, it gets filtered by a WAF which we can't see, and then we can execute it?

We figured out what it does not like pretty quickly:

* dots
So no domains
* spaces
* underlines
So no `$_GET`
* capital letters
* Any input above 35 characters

> `"$()/;<=>?[\]{}~` all allowed, other ascii symbols banned
> \- hyperreality [cr0wn]

We also noticed that some functions are disabled.

> I don't think scandir works. neither does exec, system or passthru.
> \- Sansero [flagbot]

#### Digression

Let's make those things work. That was not a fruitful approach, but we did nonetheless consider some options.

In order to work around the character count limit, it would be nice if we could include a file from our own domain. For that, we'd need a short enough domain. But wait! domains contain dots!

To work around that problem, we can specify the IP address [without dots, as a decimal number](https://superuser.com/questions/857603/are-ip-addresses-with-and-without-leading-zeroes-the-same#comment1136605_857609).

Or we could also store the actual payload in a different GET parameter and eval that. But we don't have underscores. Since we can use eval though, we can use the character code.

> ` \- bazumo [flagbot]

#### Back On Track

At this point, bazumo made an important discovery: if we specify in the GET request `data` as an array - i.e. `?data[]=print("HELLO");` instead of `?data=print("HELLO");` - waf does not complain!

> that's sick, we can now get arbitrary length payloads
> \- hyperreality [cr0wn]

> wrong.
> > Request-URI Too Long
> > The requested URL's length exceeds the capacity limit for this server.
> > *Apache/2.4.38 (Debian) Server at Port 8000*
> pasting a complete urlencoded c99 shell is too much.
> \- LucidBrot [flagbot]

With `get_defined_functions(true)` we get a list of only the enabled functions.

I soon came to believe that `readfile(index.php)` did not work despite being on that list whereas `highlight_file(index.php)` worked. In retrospect, that issue was probably just my browser which decided to put the output of `readfile` into a comment instead of printing it on the page, because I hadn't wrapped the call in `print()`.

First of all, we tried to activate all [error_reporting](https://www.php.net/manual/en/function.error-reporting.php).

> ````
> error_reporting ([ int$level ] ) : int
> ````
> The **error_reporting()** function sets the [error_reporting](https://www.php.net/manual/en/errorfunc.configuration.php#ini.error-reporting) directive at runtime. PHP has many levels of errors, using this function sets that level for the duration (runtime) of your script. If the optional `level` is not set, **error_reporting()** will just return the current error reporting level.

The documentation sounds straightforward enough. But it is not, unless one reads closely. `$level` is a bitmask, so we actually needed to call `error_reporting(-1);`, which I find really counterintuitive.

Anyway, with error reporting enabled, we have something to work with:

> Warning: readfile(): **open_basedir restriction in effect.** File(/var/www/html/sandbox/index.php) is not within the allowed path(s): (/var/www/html/sandbox/1c8606cc4b48bc4fc247f31cdd94ceced948d144/) in /var/www/html/sandbox/1c8606cc4b48bc4fc247f31cdd94ceced948d144/index.php on line 1

At that point, tamas_dxw had a clue.

#### Phuck3

> as opposed to `easyphp`, we have a writable subdirectory and we can call `ini_set` / `ini_alter` so we could probably break out of open_basedir jail with `phuck3` (or something from [here](https://github.com/w181496/Web-CTF-Cheatsheet#open_basedir繞過))
> \- tamas_dxw [emwtf]

And finally, we were able to read the file `function.php`!

The point of `phuck3` is to trick `open_basedir` using chdir, as advised by the docs:

> When a script tries to access the filesystem, for example using [include](https://www.php.net/manual/en/function.include.php), or [fopen()](https://www.php.net/manual/en/function.fopen.php), 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**.
> The special value `.` indicates that the working directory of the script will be used as the base-directory. This is, however, a little dangerous as the working directory of the script can easily be changed with [chdir()](https://www.php.net/manual/en/function.chdir.php).
> Source: [php.net](https://www.php.net/manual/en/ini.core.php#ini.open-basedir)

We are actually allowed to modify our `open_basedir` settings - as long as we are setting them to a directory we are allowed to access.

So let us do exactly that, in a totally innocent way.

1. Create a directory within our personal sandbox directory, because we have write access there.
2. `chdir` into that new directory.
3. `ini_set('open_basedir','..');`
That is totally legal, because we are allowed to access the parent directory anyway.
4. Move into the parent directory. That's allowed by `open_basedir`.
5. Do that again. And again. Until we're at the `/` directory.
6. `ini_set('open_basedir','/')`;
7. We now can access any directory we want - as long as the permissions of the directory allow us, of course. We only circumvented the `open_basedir` restriction.

#### Waf Sucks

Of course we can now read `function.php`:

function waf($data='')
if (strlen($data) > 35) {
return true;
if (preg_match("/[!#%&'*+,-.: \t@^_`|A-Z]/m", $data, $match) >= 1) {
return true;
return false;

function initShellEnv($dir)
ini_set("open_basedir", "/var/www/html/$dir");

Now we can understand why that array bypass worked.

Firstly, `strlen` is applied to the data. If the data is an array, it will be implicitly converted to string. And `"Array"` is shorter than 35 characters.

Secondly, `preg_match` checks for violating data with a regex on a target *string*.
> `preg_match()` returns `1` if the pattern matches given subject, `0` if it does not, or `FALSE` if an error occurred.
> **Warning**
This function may return Boolean `FALSE`, but may also return a non-Boolean value which evaluates to `FALSE`.
> Source: [php.net](https://www.php.net/manual/en/function.preg-match.php)

So if an error happens within `preg_match`, it will return something that can be interpreted as an integer of value `0`. Which is not `>= 1`.

#### Get The Flag

One `file_get_contents('/flag')` later and we had... *something*.

> wtf, there's a file at `/flag` but it's garbage
> \- hyperreality [cr0wn]

> send it anyway
> \- LucidBrot [flagbot]

Downloading it in a [different way](https://stackoverflow.com/a/7263944/2550406) as an octet-stream gives us a gzip archive. And within that there's a `flag.img`.

$ file flag.img
flag.img: Linux rev 1.0 ext2 filesystem data (mounted or unclean), UUID=d4d08581-e309-4c51-990b-6472ba249420 (large files)

Obviously a mountable filesystem. But when we mounted it, all we got was an empty drive with only a `lost+found` dir which was empty as well.

There must be something hidden.

$ binwalk file

0 0x0 Linux EXT filesystem, rev 1.0, ext2 filesystem data (mounted or unclean), UUID=d4d08581-e309-4c51-990b-6472ba24ba24
46080 0xB400 PNG image, 728 x 100, 8-bit/color RGB, non-interlaced
46121 0xB429 Zlib compressed data, default compression

Right! There's some additional PNG image as well as some compressed data. Those files can be extracted using `binwalk --dd=".*" file`.

And in the PNG resides the flag!

Original writeup (https://eric.mink.li/publications/0ctf_cloud_computing_v1.html).