Rating:

### 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:

php
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 172.23.0.2 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:

php
function waf($data='') { if (strlen($data) > 35) {
return true;
}
if (preg_match("/[!#%&'*+,-.: \[email protected]^_|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.

bash
$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. bash$ binwalk file