jan-karel.nl
Home / Securitymaatregelen / Webbeveiliging / Deserialisatie Preventie

Deserialisatie Preventie

Deserialisatie Preventie

Objecten Zonder Verrassingen

Veilige webontwikkeling draait niet om extra frictie, maar om betere defaults in ontwerp, code en releaseflow.

Bij Deserialisatie Preventie zit de meeste winst in veilige defaults die in elke release automatisch worden afgedwongen.

Dat maakt security minder een losse controle achteraf en meer een standaardkwaliteit van je product.

Directe maatregelen (15 minuten)

Waarom dit telt

De kern van Deserialisatie Preventie is risicoreductie in de praktijk. Technische context ondersteunt de maatregelkeuze, maar implementatie en borging staan centraal.

Verdediging

Algemene principes

1. Deserialiseer nooit onvertrouwde data met type-informatie

Dit is de gouden regel. Als je data van een gebruiker ontvangt en die data bepaalt welk type object er wordt aangemaakt, dan heb je een probleem. Gebruik formaten die geen type-informatie bevatten (JSON zonder TypeNameHandling, protobuf met een vast schema) of valideer strikt welke types zijn toegestaan.

2. Whitelist klassen

Als je absoluut moet deserialiseren met type-informatie, gebruik dan een whitelist van toegestane klassen. Niet een blacklist — die is altijd incompleet.

Java (serialisatie-filter, JEP 290):

// Alleen specifieke klassen toestaan
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
    "com.myapp.model.*;!*"
);
ObjectInputStream ois = new ObjectInputStream(stream);
ois.setObjectInputFilter(filter);

.NET:

// Gebruik System.Text.Json in plaats van Newtonsoft.Json
// Of als je Newtonsoft moet gebruiken:
var settings = new JsonSerializerSettings {
    TypeNameHandling = TypeNameHandling.None  // NOOIT Auto/Objects/All
};

PHP:

<?php
// Gebruik allowed_classes parameter (PHP 7+)
$obj = unserialize($data, ['allowed_classes' => ['User', 'Product']]);

// Of beter: gebruik helemaal geen unserialize() op user input
// Gebruik JSON:
$data = json_decode($input, true);

3. Integriteitscontroles

Voeg een HMAC (Hash-based Message Authentication Code) toe aan geserialiseerde data. Als de data is gewijzigd, detecteer je dat voor deserialisatie:

// Voor serialisatie
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
byte[] signature = mac.doFinal(serializedData);
// Stuur serializedData + signature

// Voor deserialisatie
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
byte[] expectedSignature = mac.doFinal(receivedData);
if (!MessageDigest.isEqual(expectedSignature, receivedSignature)) {
    throw new SecurityException("Data is gewijzigd!");
}

4. Verwijder onnodige gadgets van het classpath

In Java: als je Apache Commons Collections niet actief gebruikt, verwijder het dan van je classpath. Geen gadgets op het classpath = geen gadget chains.

5. Upgrade

Veel van deze kwetsbaarheden zijn gefixt in nieuwere versies: - PHP 8.0 heeft het meeste type juggling gedrag verwijderd - lodash.merge filtert __proto__ sinds versie 4.6.2 - .NET’s BinaryFormatter is deprecated en verwijderd in .NET 9 - Java’s serialisatie-filters (JEP 290) zijn beschikbaar sinds Java 9

Taalspecifieke verdediging

Java:

// NIET:
ObjectInputStream ois = new ObjectInputStream(untrustedStream);
Object obj = ois.readObject();

// WEL: gebruik JSON (Jackson, Gson) met POJOs
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
// Deserialiseer naar een specifiek type, niet naar Object
User user = mapper.readValue(jsonString, User.class);

.NET:

// NIET:
BinaryFormatter bf = new BinaryFormatter();
object obj = bf.Deserialize(stream);

// NIET:
var settings = new JsonSerializerSettings {
    TypeNameHandling = TypeNameHandling.Auto
};

// WEL: System.Text.Json (geen TypeNameHandling)
var user = JsonSerializer.Deserialize<User>(jsonString);

PHP:

<?php
// NIET:
$obj = unserialize($_COOKIE['session']);

// WEL: JSON
$data = json_decode($_COOKIE['session'], true);

// NIET:
if (md5($input) == $stored_hash) { ... }

// WEL: strict comparison en password_verify
if (password_verify($input, $stored_hash)) { ... }

JavaScript:

// NIET: kwetsbare deep merge
function merge(target, source) {
    for (let key in source) {
        target[key] = source[key];  // Polluted!
    }
}

// WEL: filter prototype keys
function safeMerge(target, source) {
    for (let key of Object.keys(source)) {
        if (key === '__proto__' || key === 'constructor') continue;
        if (typeof source[key] === 'object' && source[key] !== null) {
            target[key] = target[key] || {};
            safeMerge(target[key], source[key]);
        } else {
            target[key] = source[key];
        }
    }
}

// OF: gebruik Object.create(null) voor prototype-loze objecten
const safeStore = Object.create(null);

// OF: gebruik Map
const safeMap = new Map();

Referentietabel

Deserialisatie-risico’s per taal

Taal Sink Detectie Gadget tool Verdediging
Java ObjectInputStream.readObject() AC ED 00 05 / rO0AB ysoserial JEP 290 filters, whitelist classes
.NET BinaryFormatter.Deserialize() Base64 in ViewState/cookies ysoserial.net System.Text.Json, geen TypeNameHandling
PHP unserialize() O: prefix in data PHPGGC json_decode(), allowed_classes
Python pickle.loads() \x80\x05 header json.loads(), nooit pickle op user input
Ruby Marshal.load() \x04\x08 header JSON.parse()

Magic bytes voor detectie

Formaat Hex Base64 prefix
Java serialized AC ED 00 05 rO0ABQ
.NET BinaryFormatter Variabel Variabel (check ViewState)
Python pickle (v5) 80 05 gAU
Ruby Marshal (4.8) 04 08 BAg
PHP serialized Leesbaar: O:, a:, s: N/A (tekst)

PHP type juggling cheat sheet

Expressie Resultaat Waarom
"0e123" == "0e456" true Beide zijn 0 in scientific notation
true == "anything" true Bool true == elke niet-lege string
0 == "php" true (< 8.0) php” cast naar int 0
"" == null true Lege string is null-achtig
"0" == false true String “0” is falsy
[] == null false Array is niet null
strcmp([], "str") NULL strcmp crasht op arrays
NULL == 0 true NULL cast naar int 0

Prototype pollution RCE chains

Template engine Polluted property Impact
EJS outputFunctionName RCE via template render
Pug block RCE via template compile
Handlebars pendingContent RCE via compiler
Nunjucks env RCE via environment

Tools

Tool URL Gebruik
ysoserial https://github.com/frohoff/ysoserial Java deserialisatie payloads
ysoserial.net https://github.com/pwntester/ysoserial.net .NET deserialisatie payloads
PHPGGC https://github.com/ambionics/phpggc PHP gadget chain payloads
marshalsec https://github.com/mbechler/marshalsec Diverse marshalling-formaten
JNDI safety checklist Interne richtlijnen Veilige parser- en lookup-configuratie

Samenvatting

Deserialisatie is het probleem dat ontstaat wanneer we vergeten dat niet alle koffers door vrienden worden ingepakt. We nemen binaire blobs, JSON met type-hints, en PHP-strings van het internet, en we bouwen er objecten van in het geheugen van onze server. Objecten die methoden hebben. Objecten die dingen doen.

Java’s gadget chains laten zien hoe bestaande bibliotheken aan elkaar geketend kunnen worden tot een remote code execution machine. .NET’s BinaryFormatter en ViewState bewijzen dat het probleem niet taalgebonden is. PHP’s type juggling demonstreert dat je niet eens deserialisatie nodig hebt — een == in plaats van === is genoeg om authenticatie te omzeilen. En JavaScript’s prototype pollution toont aan dat het verrijken van de prototype-keten van alle objecten in een applicatie slechts een __proto__ property verwijderd is.

De verdediging is conceptueel eenvoudig: vertrouw geen data van de gebruiker om objecten te construeren. Gebruik type-safe formaten zonder class-informatie. Whitelist wat er gedeserialiseerd mag worden. Valideer integriteit met HMACs. En als je PHP schrijft, gebruik dan ===. Altijd. Overal. Geen uitzonderingen.

Op de hoogte blijven?

Ontvang maandelijks cybersecurity-inzichten in je inbox.

← Webbeveiliging ← Home