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.
Verder lezen in de kennisbank
Deze artikelen in het portaal geven je meer achtergrond en praktische context:
- API's — de onzichtbare lijm van het internet
- SSL/TLS — waarom dat slotje in je browser ertoe doet
- Encryptie — de kunst van het onleesbaar maken
- Wachtwoord-hashing — hoe websites je wachtwoord opslaan
- Penetratietesten vs. vulnerability scans
Je hebt een account nodig om de kennisbank te openen. Inloggen of registreren.
Gerelateerde securitymaatregelen
Deze artikelen bieden aanvullende context en verdieping: