React RCE‑incident: waarom HMAC‑ondertekening, sleutelrotatie en Zero Trust nu essentieel zijn

Wat de RCE‑kwetsbaarheid ons leert: “Het moment dat je data vertrouwt, is het einde”



De recente RCE‑kwetsbaarheid (CVE‑2025‑55182) in React Server Components/Next.js is niet zomaar een nieuwsfeit dat “React is gebroken” betekent. Het belangrijkste bericht is veel algemener.

“Als je ooit data vertrouwt die door de client is verzonden, zal het uiteindelijk altijd doorbreken.”

De kern van de kwetsbaarheid is dat de server de Flight‑protocoldata (metadata) van de client zonder voldoende verificatie direct gebruikte voor module‑laden en object‑toegang.

  • “Deze waarde wordt door React veilig gemaakt.”
  • “Omdat het geen API is die we zelf gebruiken, is het ok.”

Deze impliciete vertrouwensketen leidde uiteindelijk tot een punt waar RCE mogelijk was zonder voorafgaande authenticatie.

We moeten de vraag veranderen:

  • “Waarom is React gebroken?”
  • “Welke data vertrouwen we nu zonder ondertekening of verificatie?”

Het antwoord op die vraag brengt de concepten HMAC‑ondertekening en Zero Trust naar voren.


Waarom HMAC‑ondertekening belangrijk is: “Is deze data echt door onze server gemaakt?”

In de praktijk vertrouwen systemen vaak op “vertrouwen tussen servers”.

  • Front‑end ↔ Back‑end
  • Microservice A ↔ B
  • Back‑end ↔ Back‑end (asynchrone taken, queue, webhook, interne API, etc.)

De data die tussen deze systemen stroomt, wordt vaak op de volgende manier verondersteld:

“Deze URL is niet publiek, dus alleen interne systemen zullen het aanroepen.”

“Dit tokenformaat is alleen bekend bij onze service, dus het is veilig.”

Aanvallers proberen echter altijd deze veronderstellingen te breken.

Hier fungeert HMAC (bijv. HMAC‑SHA256) als antwoord op een fundamentele vraag:

“Heeft iemand die onze geheime sleutel kent, deze aanvraag/bericht echt gemaakt?”

Kortom:

  • Alleen de partij die de HMAC‑sleutel kent, kan een geldige ondertekening genereren.
  • De server ontvangt payload + signature en kan controleren of
  • de ondertekening klopt
  • het bericht niet is gewijzigd.

Een wereld zonder HMAC‑ondertekening

Hoe zou een REST/Webhook/Interne API eruit zien zonder HMAC‑ondertekening?

  • Als een aanvaller de URL‑structuur en parameters redelijk kan achterhalen,
  • kan hij willekeurige verzoeken maken en de server laten denken dat het een “natuurlijk intern verzoek” is.

Bijvoorbeeld:

POST /internal/run-action
Content-Type: application/json

{
  "action": "promoteUser",
  "userId": 123
}

Zelfs als dit een interne endpoint is, kan een netwerk‑breuk, een kwetsbare interne proxy, of blootstelling van CI/CD‑logs, voorbeeldcode of documentatie de aanvaller in staat stellen om het endpoint te benaderen alsof het intern is.

Met HMAC‑ondertekening zou de situatie anders zijn:

  • De volledige verzoekbody wordt ondertekend.
  • De server valideert elke signature.

“Zelfs als je de URL en parameters kent, kun je geen geldige aanvraag maken zonder de sleutel.”


Voorbeeld: interne acties beschermen met HMAC‑ondertekening



Een eenvoudig voorbeeld (TypeScript/Node.js‑achtige pseudocode).

1. Server heeft een gedeelde geheime sleutel

const HMAC_SECRET = process.env.HMAC_SECRET!; // uit .env of Secret Manager

2. Client (of interne service) maakt een verzoek

import crypto from 'crypto';

function signPayload(payload: object): string {
  const json = JSON.stringify(payload);
  return crypto
    .createHmac('sha256', HMAC_SECRET)
    .update(json)
    .digest('hex');
}

const payload = {
  action: 'promoteUser',
  userId: 123,
};

const signature = signPayload(payload);

// verzenden
fetch('/internal/run-action', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Signature': signature,
  },
  body: JSON.stringify(payload),
});

3. Server valideert

function verifySignature(payload: any, signature: string): boolean {
  const json = JSON.stringify(payload);
  const expected = crypto
    .createHmac('sha256', HMAC_SECRET)
    .update(json)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex'),
  );
}

app.post('/internal/run-action', (req, res) => {
  const signature = req.headers['x-signature'];

  if (!signature || !verifySignature(req.body, String(signature))) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // tot hier gekomen betekent:
  // 1) payload is niet gewijzigd
  // 2) de aanvraag is gemaakt door iemand die HMAC_SECRET kent
  handleInternalAction(req.body);
});

Dit patroon bewijst dat de data echt van onze kant komt.


Als er meerdere beheerders (operators / developers) zijn: HMAC‑sleutelrotatie is geen keuze, maar noodzaak

Een andere praktische uitdaging met HMAC‑sleutels is de mens.

  • Operators, SRE, backend‑ontwikkelaars, freelancers, stagiairs…
  • Niet iedereen heeft toegang tot .env, maar
  • GitOps, CI/CD, documentatie, Slack, Notion, etc. kunnen de sleutel blootstellen.

Het cruciale punt is:

“We gaan ervan uit dat de sleutel kan lekken.”

We moeten ontwerpen alsof “één dag zal het lekken”.

Daarom is HMAC‑sleutelrotatie essentieel.

Waarom rotatie nodig is

  1. Mensen veranderen * Vertrek, teamwissel, beëindiging van freelancers * Iemand die de sleutel kende, mag die niet meer kennen.
  2. We kunnen een lek niet volledig traceren * Slack‑DM, lokale notities, screenshots, memo… * “We hebben nooit een lek gehad” is praktisch onmogelijk.
  3. Na een incident moet herstel mogelijk zijn * Als een sleutel vóór een bepaald moment is gelekt, * Kunnen we zeggen: “Vanaf nu accepteren we alleen verzoeken ondertekend met de nieuwe sleutel.”

Praktische rotatie‑patronen

In organisaties met meerdere beheerders wordt vaak het volgende aanbevolen:

  1. Versiebeheer van sleutels * HMAC_SECRET_V1, HMAC_SECRET_V2 enz. * Verstuur kid (key id) in header of payload.
  2. Server houdt meerdere sleutels * Verifieer met V1 en V2, maar na een periode verwijder V1.
  3. Documenteer het rotatieproces * “Nieuwe sleutel genereren → distribueren → beide partijen wisselen naar nieuwe sleutel → oude sleutel verwijderen” * Maak een checklist + runbook zodat iedereen het kan volgen.
  4. Scheiding van bevoegdheden * De persoon die sleutels beheert, is niet noodzakelijkerwijs degene die code schrijft.

Een HMAC‑sleutel die eenmaal is vastgesteld, is een kans op volledige systeem‑crash als hij gelekt wordt.

Rotatie beperkt dit risico in de tijd.


Zero Trust: “Vertrouwen is geen structuur, het moet per verzoek worden opgebouwd”

Een kernprincipe dat we opnieuw moeten herinneren na het React RCE‑incident:

De basis van beveiliging is Zero Trust.

Zero Trust is simpel:

  • “Omdat het intern is, is het ok.”
  • “Omdat het alleen in de front‑end wordt gebruikt, is het veilig.”
  • “Omdat we het zelf hebben gebouwd, is het beschermd.”

Deze aannames moeten uitdrukkelijk worden verboden.

In plaats daarvan stellen we de volgende vragen:

  • “Is dit ontwerp gebaseerd op de veronderstelling dat de invoer kwaadwillend kan zijn?”
  • “Hebben we bewijs dat het verzoek afkomstig is van de bedoelde entiteit?”
  • “Hoe verifiëren we dat het bericht niet is gewijzigd?”
  • “Wat is de impact als de sleutel gelekt wordt, en hoe herstellen we?”

Het React RCE‑incident illustreert dit:

  • De Flight‑data werd vertrouwd omdat “React levert het”.
  • Hierdoor konden client‑aanpassingen direct invloed hebben op de server‑module‑laadroute.

Zero Trust zou dit meteen als een risico signaleren:

“Client‑aanpasbare geserialiseerde data die invloed heeft op de server‑code = altijd een risico.”

En de eerste verdediging is:

  • HMAC‑ondertekening (integriteit & herkomst)
  • Sleutelrotatie (beperken van schade bij lek)

Samenvatting: “Vertrouw niet, laat het bewijzen”

De kernboodschap van dit artikel kan in drie punten worden samengevat:

  1. Vertrouw data nooit standaard * Zelfs “interne” data die over het netwerk gaat, kan een aanvalspunt zijn.
  2. HMAC‑ondertekening is het basishulpmiddel * REST‑API, webhook, interne RPC, asynchrone berichten, etc. * In omgevingen met meerdere beheerders, maak sleutelrotatie een verplichte procedure.
  3. Zero Trust is de basis * “Intern = veilig” is een fout. * Vertrouwen moet per verzoek, per data, via verificatie worden opgebouwd.

Het React RCE‑incident herinnert ons aan de noodzaak van een Zero Trust‑mentaliteit in een tijd waarin de grens tussen front‑end en back‑end vervaagt.

“Vertrouw niet, verifieer. Verifieer via code, processen en sleutel‑beheer.”

De eerste stap is HMAC‑ondertekening en sleutelrotatie, bovenop de filosofie van Zero Trust.

image