JEP 290, Filter Incoming Serialization Data
Contexte
Depuis quelques semaines, je déroule les nouveautés du JDK 17. Cette semaine, nous allons revenir en arrière et nous pencher sur une précédente JEP contenue dans le JDK 9. Elle tente de s’attaquer au problème de la désérialisation. Cela n’est pas une spécificité de la plateforme Java. Mais ce problème n’est pas anodin car la désérialisation non sécurisée fait partie du Top 10 OWASP des failles de sécurité des applications web.
Rappel sur la sérialisation
L’objectif est pouvoir transformer uns instance en flux d’octets. Ce dernier sera stocké dans un fichier, passera par le réseau ou autres. Puis, le flux d’octects sera retransformer en instance.
Sérialisation
Voici le code pour sérialiser une chaine de caractère et une date. Nous utilisons la classe ObjectOutputStream
et la méthode writeObject
OutputStream o = ...
ObjectOutputStream s = new ObjectOutputStream(o);
s.writeObject("Today's date");
s.writeObject(new Date());
Désérialisation
La désérialisation est l’opération inverse. Elle permet de transformer le flux d’octets en instance. C’est la classe compagnon ObjectInputStream
et la méthode readObject
.
InputStream o = ...
ObjectInputStream s = new ObjectInputStream(o);
String str = (String) s.readObject();
Date d = (Date) s.readObject();
Vous aurez remarqué que la sérialisation et la désérialisation sont réalisées dans le même ordre : String
, puis Date
Activation / Autorisation
La classe doit implémenter tout simplement l’interface java.lang.Serializable
.
class MaClasse implements Serializable {
}
Et c’est tout !
Personnalisation
Les opérations sont réalisées par défaut par la JVM au niveau de la classe Object
.
class Object {
void readObject(ObjectInputStream ois);
void writeObject(ObjectOutputStream oos)
}
De ce fait, le développeur peut surcharger les méthodes readObject
et writeObject
afin de réaliser sa propre implémentation.
C’est malheureusement par ce biais que les attaques peuvent se produire. Car la JVM va ainsi charger du code. Et ce dernier peut être malveillant.
JEP 290 Filter Incoming Serialization Data
Cette fonctionnalité va permettre l’utilisation de filtre lors de la lecture de flux entrants de la désérialization afin d’améliorer la robustesse et la sécurité.
Tout l’intérêt est que le filtrage est réalisé avant le chargement de la classe. (donc avant le chargement du code potentiellement malveillant).
Les objectifs sont les suivants :
-
Fournir un mécasnime de filtre flexible pour autoriser la désérialisation des classes,
-
Fournir des métriques pour le filtre concernant la taille et la complexité du graphe des instances désérialisés,
-
Fournir un mécanisme compatible avec RMI,
-
Ne pas utiliser de sous-classes ou de modification de la classe
ObjectInputStream
, -
Pouvoir être configuré de manière globale avec des propriétés.
Définition de l’API
Le filtre est décrit via l’interface ObjectInputFilter
:
interface ObjectInputFilter {
Status checkInput(FilterInput filterInfo);
enum Status {
UNDECIDED,
ALLOWED,
REJECTED;
}
interface FilterInfo {
Class<?> serialClass();
long arrayLength();
long depth();
long references();
long streamBytes();
}
public static class Config {
public static void setSerialFilter(ObjectInputFilter filter);
public static ObjectInputFilter getSerialFilter(ObjectInputFilter filter) ;
public static ObjectInputFilter createFilter(String patterns);
}
}
C’est la méthode checkInput
qui va être appeler pour vérifier le flux d’entrée.
L’interface FiltreInfo
correspond bien au deuxième objectif : avoir des métriques pendant la phase de désérialization.
Configuration du filtre de manière globale
Cela se passe par la propriété jdk.serialFilter
. Il suffit de positionner la propriété sur la ligne de commande.
java -Djdk.serialFilter="fr.lbenoit.exemple*;java.base/*;!*" ...
Dans cet exemple, les classes du package fr.lbenoit.exemple et les classes du module java.base
sont autorisés. Toutes les autres classes ne le sont pas.
La propriété peut être positionnée dans le fichier conf/security/java.properties
Configuration du filtre par programmation
Pour cela, il suffit d’invoquer la méthode statique createFilter
:
var filtre = ObjectInputFilter.Config.createFilter("fr.lbenoit.exemple.*;java.base/*;!*")
Le filtre peut être positionnée de deux manières :
-
de manière globale
ObjectInputFilter.Config.setSerialFilter(filtre);
-
par flux
Chaque flux peut avoir son filtre dédié. En effet, la classe ObjectInputStream
contient des méthodes pour manipuler le filtre.
public class ObjectInputStream ... {
public final void setObjectInputFilter(ObjectInputFilter filter);
public final ObjectInputFilter getObjectInputFilter(ObjectInputFilter filter);
}
Donc, pour positionner le filtre, il suffit d’appeler la méthode setObjectInputFilter
sur le flux :
ObjectInputStream ois = new ...
ois.setObjectInputFilter(filtre);
Motifs possibles pour la configuration
Comme nous pouvons le voir dans les exemples, nous ne sommes pas obliger de créer une classe implémentant l’interface ObjectInputFilter
. Nous pouvons parfaitement utiliser l’implémentation par défaut qui utilise des motifs pour construire les filtres.
Les différents motifs sont :
-
Si le motif correpond au nom de la classe, seule cette classe est autorisé : fr.lbenoit.exemple.Moto (Seule la classe
fr.lbenoit.exemple.Moto
est autorisée) -
Si le motif est précédé par un point d’intérrogation : !fr.lbenoit.exemple.Voiture (Seule la classe
fr.lbenoit.Voiture
n’est pas autorisée, tous les autres le sont) -
Si le motif se termine par
.*
, toutes les classes du packages sont autorisées : fr.lbenoit.exemple.* (fr.lbenoit.exemple.Moto
etfr.lbenoit.exemple.Voiture
sont autorisées) -
Si le motif se termine par
.**
, toutes les classes et sous-classes du packages sont autorisées : fr.lbenoit.* * (fr.lbenoit.Moto
,fr.lbenoit.exemple.Voiture
etfr.lbenoit.composants.Moteur
sont autorisées) -
Si le motif contient le caractère
/
, cela concerne le module -
Sinon la décision est
UNDECIDED
Moteur de recherche
"Eduquer, ce n'est pas remplir des vases mais c'est d'allumer des feux." - Michel Montaigne
Billets récents
- Eclipse plante systématiquement sous Debian (et autres distribution Linux)
- JEP 463, Implicitly Declared Classes and Instance Main Methods (Second Preview)
- Debian - Montée de version de Debian 11 (Bullseye) à Debian 12 (Bookworm)
- JEP 451, Prepare to Disallow the Dynamic Loading of Agents
- JEP 444, Virtual Threads