Délinéarisation et injection d’objet en PHP

Divulgâchage : Un jour, on a inventé l’idée un peu folle de transférer des objets d’une application à l’autre. Par la suite, on s’est rendu compte que cette possibilité ouvrait de grandes possibilités d’attaque pour qui peut injecter son propre contenu. Finalement, on s’est rendu compte qu’on ne devrait jamais utiliser la délinéarisation.

Il fut un temps, révolu, ou développer un site web consistait à proposer une interface graphique sympa pour faciliter la vie des utilisateurs. Il suffisait d’agrémenter du HTML avec des instructions PHP pour donner à son site un je ne sais quoi qui dynamise l’ensemble et rende le monde meilleur.

Puis on s’est rendu compte qu’on pourrait carrément faire communiquer des applications entre elles. S’ouvrait alors tout un univers de possibles… Enrichir un service tiers, déléguer des fonctionnalités (comme l’authentification), unifier plusieurs plateformes,… Et pour permettre à tout ce petit monde de se comprendre, on a inventé plein de formats de données.

Dans notre frénésie créative, on a inventé la (dé)linéarisation, un moyen de transférer nativement des structures complexes, dont des objets, d’une application à l’autre. C’était sûrement une idée géniale sur le coup (parce que super pratique pour les développeurs), mais comme on va le voir aujourd’hui, c’est sûrement la dernière chose que vous devriez utiliser dans une application.

Ça avait l’air bien parti. Steve Jurvetson @ flickr

Bon, en vrai, je préférerais encore délinéariser que de gérer du XML. Heureusement, d’autres technologies on été inventées et nous évitent de devoir recourir à de telle extrémités.

Le problème de la (dé)linéarisation, c’est qu’elle permet à vos utilisateurs (humains ou pas), de forger leurs propres objets dans votre application. Biens choisis, certains objets auront des comportements pas vraiment prévu et permettront à ces petits malins de détourner votre application à leur avantage.

Cette fois, la solution est simple, si vous voyez un appel à unserialize(), ou un truc qui y ressemble, même de loin : fuyez ; le terrain est miné. Si vous pensez pouvoir vous en sortir en faisant attention, demandez une assistance psychologique, votre vie est peut être en danger.

Les objets en PHP

Cette vulnérabilité étant typique du monde des objets, quelques rappels préliminaires peuvent être utiles si vous n’avez pas l’habitude de développer de cette manière. J’utilise ici du PHP, mais le fonctionnement serait sensiblement le même dans un autre langage objets.

Si vous ne savez pas ce qu’est un objet, dites-vous que c’est un truc qui contient à la fois des données (des variables qu’on appelle attributs ou membre) et des fonctionnalités (des fonctions qu’on appelle méthodes). Votre programme est plein de ces trucs qui interagissent les uns avec les autres pour, globalement, résoudre votre problème et vous fournir les résultats que vous attendez.

Des entités qui interagissent. Free-Photos @ pixabay

Personnellement, je vois vraiment les programmes objets comme des écosystèmes plein de petites bêtes (mes objets) qui vivent leur vie et interagissent les uns avec les autres. Lorsque je programme, je définit les espèces des bestioles avec leurs caractéristiques et leurs comportements.

Ça a un côté démiurge, mais depuis que je consulte, ça va mieux.

La plupart du temps, ces objets sont décrit dans ce qu’on appelle des classes, sortes de patrons, modèles, moules ou toute autre métaphore qui signifie qu’un objet est créé en mémoire et sera manipulé d’après ce qui est écrit dans la classe. La programmation orientée objet consiste donc d’une part à définir des classes puis à créer des objets (on dit instancier) et enfin à lancer les interactions.

« La plupart du temps » parce que certains langages, comme javascript, boudent ce formalisme et préfèrent ajouter les attributs et les méthodes à la volée sans aucune notion de type, les objets étant créés par clonage, c’est un joyeux bordel créatif. Ça ne les rend pas invulnérables (que du contraire), l’approche est juste différente.

Pour la bonne cause, voici une petite classe, décrivant des utilisateurs très simples dont le seul but est de se répondre poliment lorsqu’ils se saluent… Je documente ici avec doxygen plus pour l’exemple que la nécessité.

/**
 * Classe décrivant les utilisateurs
 */
class User {
    
    /**
     * Attribut stockant le nom de l'objet
     */
    public $name ;
    
    /**
     * Constructeur, pour initialiser un nouvel objet
     *
     * @param $name le nom de l'objet
     */
    public function __construct(string $name) {
        $this->name = $name ;
    }
    
    /**
     * Méthode pour connaître le nom de l'objet
     *
     * @return le nom de l'objet
     */
    public function whoAreYou() {
        return $this->name ;
    }
    
    /**
     * Méthode permettant de saluer l'objet
     *
     * @param $other l'objet qui salue
     * @return la réponse de l'objet
     */
    public function hello(User $other) {
        return "Nice to meet you " . $other->name
            . ", I am " . $this->name ;
    }
}

Une fois cette classe définie et son code chargé par vos scripts, vous pouvez l’utiliser pour créer des objets et les faire interagir.

// Création de deux objets
$foo = new User("Foo") ;
$bar = new User("Bar") ;
// Appel de méthode
echo $foo->hello($bar) ;
// > Nice to meet you Bar, I am Foo

La linéarisation

Aussi appelée sérialisation (américanisme de serialization), ça consiste à transformer des objets (parfois complexes) en une chaîne de caractère (ou une suite de 00 et de 11). Notez que le but, ici, est d’effectuer plus tard la transformation inverse, on parle alors de délinéarisation, ou désérialisation. La documentation PHP résume bien la chose en parlant de générer une représentation stockable.

Linéariser pour délinéariser ensuite. 123090 @ pixabay

Le truc, c’est que copier simplement la mémoire ne marchera pas. D’abord parce que les objets nécessaires peuvent être réparti un peu partout. Ensuite, parce que l’ensemble de la mémoire nécessaire peut contenir des données inutiles (voir sensibles, ce qui serait dommage). Enfin, les objets peuvent contenir des ressources non stockables, comme des descripteurs de fichiers, des connexions à des bases de données,…

On a donc du mettre au point des techniques spécifiques pour sauvegarder ces données complexes.

Nativement

En PHP, la linéarisation est effectuée nativement avec les deux fonctions suivantes :

Pour compléter, PHP fourni d’autres moyens pour transformer vos objets en chaînes (et vice versa). var_export() génère un code source PHP qu’il faut interpréter pour retrouver vos données (très dangereux donc). Ou encore json_encode() et sa réciproque json_decode() mais dans ce cas on perd complètement le typage.

Pour être vraiment complet, des fous ont même inventé une méthode à base de XML… Après avoir installé l’extension PEAR XML Serializer, vous pourrez exporter et importer vos objets depuis le format XML (au pris de 3 points de santé mentale).

Pour revenir à la linéarisation native, voyons ce que ça donnerait avec nos utilisateurs :

$foo    = new User("Foo") ;
echo serialize($foo) ;
// O:4:"User":1:{s:4:"name";s:3:"Foo";}

Si vous voulez comprendre le sens profond de cette linéarisation, le voici :

  • O:4:User: signifie qu’on est en présence d’un objet linéarisé (O), le nom de la classe fait 4 caractères et vaut User,
  • 1:{...} nous donne le nombre d’attributs de l’objet (ici, 1), ceux-ci sont linéarisés les uns après les autre entre les accolades,
  • s:4:"name"; signifie que le nom du premier attribut fait 4 caractère et vaut name,
  • s:3:"Foo"; signifie que la valeur du premier attribut est une chaîne, de 3 caractères, valant Foo.

Et il est tout aussi facile de délinéariser les données précédentes :

$string = 'O:4:"User":1:{s:4:"name";s:3:"Foo";}' ;
$foobis = unserialize($string) ;

var_dump($foo ==  $foobis) ; // bool(true)
var_dump($foo === $foobis) ; // bool(false)

Après délinéarisation, on obtient un deuxième objet, équivalent au premier mais distinct. L’opérateur d’égalité == portant sur la classe et le contenu des attributs nous dit donc qu’ils sont égaux. L’opérateur identique === portant sur la référence des objets nous dit, par contre, qu’ils sont distincts.

Spécialisation

Lorsque vos objets sont trop complexes pour être linéarisés automatiquement, PHP vous fourni deux moyens de surcharger ses mécanismes afin d’utiliser les vôtres.

Ça n’est pas qu’une voiture. Retour vers le futur
  1. Via les méthodes magiques : __sleep() et __wakeup() (cf. doc officielle). La première est appelée avant la linéarisation et est sensée retourner la liste des noms des attributs à linéariser. La deuxième est appelée juste après avoir délinéarisé l’objet (sans paramètre puisque les attributs ont été restaurés).
  2. En implémentant une interface : Serializable qui vous demande d’implémenter deux méthodes. serialize() qui retourne la représentation de votre objet, et unserialize() qui fait l’inverse.

Par exemple, avec nos utilisateurs, si on voulait enregistrer le nombre de générations qui séparent une copie de l’objet original, voici le genre de code qu’on pourrait produire.

class User implements Serializable {
    // Previous code here
    
    public $generation = 0 ;
    
    public function serialize() {
        $data = [$this->me, $this->generation + 1] ;
        return serialize($data) ;
    }
    
    public function unserialize($string) {
        $data = unserialize($string) ;
        list($this->me, $this->generation) = $data ;
    }
}

Bon à savoir, si vous implémentez l’interface Serializable, les méthodes magiques __sleep() et __wakeup() seront ignorées.

Pour ceux qui font du RAII, sachez que la linéarisation vous demandera quelques précautions…

  • Une fois linéarisé, l’objet existe encore, son destructeur sera donc appelé normalement,
  • Lors de la délinéarisation, le constructeur n’est pas appelé (puisqu’on considère qu’on remet un objet « en l’état »).

Pourquoi faire ? La sauvegarde

Le premier intérêt de linéariser des objets est qu’il permet leur stockage puis leur restauration. Ainsi, on peut à tout moment sauvegarder l’état des objets dans des fichiers puis les restaurer en cas de besoin depuis ces fichiers de sauvegarde.

On pourrait imaginer que dans notre exemple, on commence d’abord par sauvegarder nos objets dans des fichiers.

file_put_contents("foo.txt", serialize(new User("Foo"))) ;
file_put_contents("bar.txt", serialize(new User("Bar"))) ;

Puis, plus tard dans le script (ou dans un autre scripts d’ailleurs), de les récupérer et poursuivre les calculs.

$foo = unserialize(file_get_contents("foo.txt")) ;
$bar = unserialize(file_get_contents("bar.txt")) ;
echo $foo->hello($bar) ;
// Nice to meet you Bar, I am Foo

Cet exemple utilise des fichiers mais la linéarisation peut aussi être utilisée pour sauvegarder des informations dans une base de donnée. Par contre, comme on va le voir plus loin, les cookies, sont une très mauvaise idée.

Pourquoi faire ? Le transfert

En plus de restaurer une sauvegarde locale, la linéarisation permet aussi de transférer des objets d’une application à l’autre. Les calculs pouvant alors être répartis en fonction des ressources disponibles ou de la logique de l’algorithme.

On pourrait imaginer que dans notre exemple, l’appel à hello() se fasse sur un serveur spécifique. Le script correspondant restaurerait les objets qui lui sont passés en paramètre (getvars ici mais tout est envisageable) avant d’appeler la méthode idoine.

$foo = unserialize($_GET["foo"]) ;
$bar = unserialize($_GET["bar"]) ;
echo $foo->hello($bar) ;

Dans ce cas, la création des objets se ferait dans un autre scripts, peut être sur un autre serveur à l’autre bout du monde. La linéarisation permettant de passer l’état des objets d’un serveur à l’autre.

echo file_get_contents(
    "http://example.com/hello.php"
    . "?foo=" . urlencode(serialize(new User("Foo")))
    . "&bar=" . urlencode(serialize(new User("Bar")))
) ;
// Nice to meet you Bar, I am Foo

Je vous l’accorde, c’est un peu exagéré dans notre cas mais si vous disposez de ressources très particulières sur un serveur et ne voulez pas le surcharger avec des calculs accessoires (i.e. un HSM pour hacher des informations), l’idée n’est plus si incongrue que ça.

Comme on va le voir dans la suite, utiliser la linéarisation est une mauvaise idée si vous ne pouvez établir de confiance entre les deux serveurs. Dans une optique de défense en profondeur, la linéarisation pour transférer des objets est aussi une mauvaise idée.

Exploitation

Maintenant qu’on a vu que la (dé)linéarisation, c’est super, on va voir à quel point c’est une mauvaise idée : chaque fois que vous délinéariserez une chaîne venant d’un attaquant, vous lui permettrez d’injecter ses propres objets. Comme on va le voir, ça peut lui ouvrir des accès ou exécuter du code…

Exemple

Commençons par une application très simplifiée qui réutilise notre classe User. Admettons que lors de l’authentification, l’Identity Provider stocke l’utilisateur dans un cookie. Quelque chose dans ce genre :

$_COOKIE["user"] = serialize(new User($username)) ;

On pourrait alors imaginer qu’une autre partie de l’application, le Service Provider fasse un contrôle d’accès et ouvre des fonctionnalités restreintes s’il s’agit de l’administrateur :

$user = unserialize($_COOKIE["user"]) ;
if ($user->name == "admin") {
    // God mode !
    // ...
}

Pour un utilisateur qui ne triche pas, le cookie est généré par l’application et le nom ne vaut admin que s’il s’agit d’un administrateur.

Modifier le comportement

Un attaquant, par contre, peut tricher et envoyer ce qu’il veut. Soit en changeant des caractères dans la linéarisation, soit en créant son propre objet qu’il linéarise ensuite. Dans les deux cas, il pourrait créer cette chaîne :

'O:4:"User":1:{s:4:"name";s:5:"admin";}'

Avec cette chaîne, l’objet délinéarisé aura le bon nom, nous ouvrant les portes des fonctionnalités restreintes !

Si vous pensez que le problème est dans la classe User, et qu’on devrait faire être plus prudents lors de la (dé)linéarisation, je vais vous montrer que non.

Si vous pensez que le problème vient que le cookie soit en clair, il y a du vrai, mais on verra à la fin pourquoi même ça, je le déconseille.

Admettons qu’on interdise carrément la valeur admin avec quelque chose de ce genre :

class User {
    // Previous code here
    
    public function __wakeup() {
        if ($this->name == "admin") {
            throw new Exception("Admin can not be unserialize") ;
        }
    }
}

Un attaquant peut très bien vous envoyer autre chose qu’un User. N’importe quel autre type disposant d’un attribut name valant admin fera l’affaire. Au pire, on peut même se rabattre sur le type stdClass (la classe native de PHP pour tous les objets sans classe spécifique) et lui ajouter manuellement l’attribut :

$o = new stdClass();
$o->name = "admin" ;

echo serialize($o) ;
// O:8:"stdClass":1:{s:4:"name";s:5:"admin";}

Avec une valeur comme celle-ci, le script ne délinéarise plus un User mais un stdClass. Comme le PHP n’est pas très typé, ça ne lui posera pas de soucis, la condition pourra être évaluée et nous ouvrir les portes…

Le problème ne vient pas de la classe User mais du fait d’avoir délinéarisé un objet venant d’un utilisateur. S’il est hostile, il peut va injecter les objets qu’il veut pour son propre bénéfice.

Exécuter du code

À ce stade, vous pourriez vous dire qu’on devrait être encore plus prudent et, par exemple, utiliser des méthodes plutôt que des attributs puisqu’un attaquant ne peut pas en ajouter.

Dans ce cas, admettons qu’aucune classe, nulle part dans votre application, n’ait de méthode whoAreYou(). Vous pourriez vous dire que le code suivant est sûr :

$user = unserialize($_COOKIE["user"]) ;
if ($user->whoAreYou() == "admin") {
    // God mode !
    // ...
}

L’appel à la méthode va échouer si l’attaquant utilise autre chose qu’un User. La situation est donc sous contrôle !?

non

L’idée, cette fois, n’est plus de contourner la condition pour obtenir un accès privilégié, mais d’injecter des objets qui ont des méthodes utiles et s’arranger pour qu’elles soient appelées. Et le truc, c’est que même si votre code n’appelle pas beaucoup de méthodes, certaines méthodes magiques sont en fait systématiquement appelées :

Restons donc sur les méthodes magiques classiques et admettons qu’on dispose, quelque part dans l’application, d’une classe qui s’occupe de journaliser des événements dans des fichiers. Quelque chose de ce genre :

class Logger {
    private $filename ;
    private $buffer ;
    
    public function __construct($filename) {
        $this->filename = $filename ;
        $this->buffer   = "" ;
    }
    
    public function log($message) {
        $this->buffer .= "$message\n" ;
    }
    
    public function __destruct() {
        file_put_contents(
            $this->filename,
            $this->buffer, FILE_APPEND
        ) ;
    }
}

Cette classe définit des objets qui ont pour but de temporiser les messages d’événements pour les ajouter d’un seul coup dans un fichier journal spécifique. Rien de compliqué, c’est très classique comme genre de chose.

Si vous voulez jouer avec cette vulnérabilité, je vous conseille le challenge 26 de Natas qui vous laisse mettre en œuvre une injection d’objet via une classe très proche de celle-ci (vous devrez adapter).

Pour l’attaquant, cette classe est un cadeau. Il lui suffit d’injecter un objet Logger dans votre application. Comme il maîtrise les attributs filename et buffer, il pourra écrire ce qu’il veut et où il veut lorsque le destructeur sera appelé…

// Code chez l'attaquant
class Logger {
    public $filename ;
    public $buffer ;
}

$payload = new Logger() ;
$payload->filename = '/var/www/index.php' ;
$payload->buffer   = '<?php echo "Hello world" ;' ;

echo serialize($payload) ;
// O:6:"Logger":2:{s:8:"filename";s:18:"/var/www/index.php";s:6:"buffer";s:26:"<?php echo "Hello world" ;";}

En injectant cette charge utile dans le cookie, l’application va la délinéariser et obtenir un objet de type Logger. Bien sûr, l’appel à whoAreYou() va échouer puisque l’objet n’a pas cette méthode. Mais lorsque le script terminera, l’objet sera détruit, appelant sa méthode __destruct(). Celle-ci poussera alors son tampon dans le fichier sur le serveur, écrivant ce que vous vouliez là où vous le vouliez.

Cette fois encore, le problème n’est pas dans la classe Logger qui ne ferait pas assez attention. Le problème, c’est que le script a délinéarisé des données provenant d’un attaquant qui peut donc injecter ce qu’il veut.

Protections et mauvaises idées

C’est maintenant le plus difficile. Je vous ai montré un truc super et alors que vous tombiez amoureux, je vous ai montré qu’en fait, c’est dangereux. Le deuil est difficile et après le choc, le déni puis la colère, vous voulez peut être négocier : « Peut être que si… ».

non, même avec des si, ça reste dangereux. Ça va faire mal, c’est déprimant mais vous finirez par l’accepter et reprendrez le cours normal de votre vie.

« Un jour, nous rirons de tout cela » GLaDOS, portal 2

Chargement des classes

Pour que l’injection d’objet fonctionne, il faut que les classes des objets que l’attaquant injecte soient chargées par votre application. Vous pourriez donc vous dire :

Mon script charge très peu de classes, je les connais, je ne risque donc rien.

Pour que votre argument soit valable, vous chargez donc manuellement tous les fichiers nécessaires dans chaque script. Conséquence, vous vous interdisez scrupuleusement deux fonctionnalités bien pratiques du PHP :

  1. Le chargement automatique des classes et en particulier la norme PSR-4 pourtant très pratiques pour organiser votre code source.
  2. Les librairies sur packagist ou plus généralement toute notion de dépendance via composer, pourtant bien pratique pour ne pas réinventer la roue.

Notez que pour certains langages, vous ne pouvez pas le désactiver et toute classe dans votre application sera disponible : Java et python, entre autres.

Mais admettons que vous appliquiez ces restrictions.

C’est quand même une mauvaise idée en termes de défense en profondeur car vous avez du définir un périmètre bien trop gros et flou pour être garanti sans problème. Car un logiciel, ça vit : de nouveaux morceaux sont ajoutés régulièrement, d’anciens meurent sans forcément disparaître.

Au fil des maintenances, correctives ou évolutives, le code modifié constitue un risque permanent qu’une classe utile pour un attaquant soit ajoutée. Plus le code évoluera, plus il sera difficile de garantir qu’aucune classe ni méthode ne peut être utilisée par un attaquant. Et admettons que vous trouviez qu’une classe fourni un tel moyen, que feriez vous si elle est en fait nécessaire pour votre application ?

C’est plus confortable avec des moufles. Free-Photos @ pixabay

Garder la sérialisation en s’interdisant le chargement automatique des classes et en s’imposant des vérifications formelles de plus en plus coûteuses, c’est comme un chirurgien qui opérerait avec des moufles parce que c’est plus confortable et puis tant pis pour la maniabilité des clamps et des bistouris.

Liste blanche

Depuis PHP 7.0, la fonction unserialize() dispose d’un nouveau paramètre avec lequel vous pouvez fournir une liste blanche des classes autorisées. Si le type de l’objet n’est pas dans la liste, la délinéarisation échoue en vous fournissant un objet inutilisable. Et là, je vous vois venir… « Du coup, j’ai juste à mettre la bonne liste et je peux même utiliser composer ! »

Techniquement, un objet de la classe __PHP_Incomplete_Class avec lequel vous ne pouvez presque rien faire. Les accès aux attributs fournissent une valeur NULL et remontent un log NOTICE. Les appels aux méthodes échouent en erreur fatale.

Le problème, c’est que même si ça réduit pas mal le risques, ça ne l’élimine pas pour autant. Lors de la maintenance de votre application, qu’est-ce qui vous garanti que personne ne va se tromper ?

C’est plus facile avec des mitaines. JilWellington @ pixabay

Cette fois, c’est comme si votre chirurgien vous disait qu’il a compris qu’avec des moufles, les outils sont peu maniables, du coup il opère avec des mitaines, ça laisse quelques petites peluches, mais les coupes sont enfin nettes.

Signer les exports

Sur le même genre de principe qu’en SAML, on peut utiliser l’idée de signer les exports. Une fois la donnée linéarisée, on en calcule la signature cryptographique qu’on joint au message. Lors de la réception, on vérifie d’abord la signature et si elle est valide, on délinéarise et on poursuit les calculs.

C’est signé par cryptographie, c’est donc sûr mathématiquement !

Le premier problème, c’est qu’il n’existe aucun standard ni aucune fonctions natives ou intégrées proposant ce mécanisme de linéarisation signée. Il faudrait alors développer sa propre tambouille et je vous rappelle qu’on parle ici de cryptographie. Le domaine pour lequel on conseille justement d’éviter les trucs faits maisons.

Le deuxième, non des moindres, c’est que la signature ne sert pas à ça. Elle permet bien sûr de certifier l’origine des données mais en aucun cas de l’innocuité du contenu. C’est en quelque sorte revenir à une vision périmétrique de la sécurité.

C’est moins risqué si c’est signé. WolfBlur @ pixabay

Après un contrôle sanitaire, le chirurgien s’est enfin décidé à appliquer des mesures strictes. Il se fourni maintenant exclusivement chez un fabricant de mitaines d’origine contrôlée avec certificat d’authenticité sur chaque lot.

Petits projets et preuves de concept

Enfin, j’aimerais terminer sur les petits projets. Ceux qui sont fait très rapidement juste pour tester un truc ou prouver un concept.

C’est juste pour tester vite fait, ça sera jamais en ligne.

En fait, même dans ce cas, je vous déconseille d’utiliser la linéarisation. Je vois que vous êtes déçu et je vais vous dire pourquoi, même pour ces petits codes, c’est une mauvaise idée…

  1. Si votre projet est si petit, pourquoi avoir besoin d’un truc aussi avancé que la linéarisation ?
  2. Si vous prenez de mauvaises habitudes pour les petits projets, vous les garderez pour les plus grands.
  3. Si vous saviez le nombre de preuves de concept qui passent en prod parce que c’est plus rapide et moins cher…
C’est qu’un exercice, spa grave. kerttu @ pixabay

La direction de l’hôpital ayant finalement interdit l’utilisation des moufles et mitaines lors des opérations, le chirurgien les as mis de côté pour l’enseignement et l’entraînement des urgentistes. Une fois au travail, ça leur demande toujours une petite période d’adaptation mais au bout de trois incisions, ça va mieux.

Et maintenant ?

Ne jamais délinéariser

Vous l’aurez donc compris, toute tentative de délinéariser des données, que ce soit via serialize() ou des imitations, n’apportera que ruine et désolation dans votre projet…

Et tout ça parce que vous utilisez une fonction qui, c’est vrai, à l’air sympa, est toute mignonne et viens du temps béni des anciens qui savaient, eux, comment vivre en harmonie avec Le Code.

Franchement, portez des gants adaptés. sasint @ pixabay

La sécurité informatique, c’est un métier à risques. Il faut déjà être très bon à l’entraînement pour avoir une chance de pas faire d’erreur en condition réelle. L’excellence est à ce prix : la vigilance permanente.

Se méfier des entrées

Si vous avez vraiment besoin de transférer des structures complexes entre vos applications, mon conseil sera le même que pour toute donnée extérieure à vos composants :

Ne faites pas confiance aux données extérieures.

Les formats de représentation pour émettre et recevoir ne devrait jamais contenir d’instructions de typage qui soit suivi par votre code. Au mieux, vous pourriez joindre des indications qu’il devra vérifier avant de les utiliser.

Exemple avec JSON

Techniquement, n’importe quel format sans typage répond à ce critère. Vous pourriez vous tourner vers le JSON qui a l’avantage d’être très bien intégré par toutes les plateformes.

Pour revenir à notre exemple initial et nos utilisateurs qui se disent bonjours, on pourrait ajouter une méthode de construction à partir de JSON (on peut parler de fabrique statique) comme suit :

class User implements JsonSerializable {
    // Previous code here ...
    
    // Only if name is private
    public function jsonSerialize() {
        return ["name" => $this->name ] ;
    }
    
    public static function FromJson($json) {
        $table = json_decode($json) ;
        return new User($table["name"])
    }
}

On peut alors reprendre le code d’exemple de sauvegarde des objets dans des fichiers en remplaçant la sérialisation par un encodage en JSON.

file_put_contents("foo.txt", json_encode(new User("Foo"))) ;
file_put_contents("bar.txt", json_encode(new User("Bar"))) ;

La récupération des objets se fait tout aussi simplement mais cette fois, on ne laisse pas PHP choisir le type en fonction du contenu récupéré, on le force manuellement par l’appel à la fabrique User::FromJson().

$foo = User::FromJson(file_get_contents("foo.txt")) ;
$bar = User::FromJson(file_get_contents("bar.txt")) ;
echo $foo->hello($bar) ;
// Nice to meet you Bar, I am Foo

Les autres exemples se modifient de la même manière et évitent toute injection malveillante.