Sprint 24: AWS WAF Challenge lösen — CSS/Layout-Fakes + Async-Drain (Option A) #90
Labels
No labels
bug
docs
feature
housekeeping
html-spec
performance
react-compat
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
glow-all/true-headless-browser#90
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problembeschreibung
amazon.de (und weitere AWS WAF-geschützte Seiten) erhalten keinen Content — der WAF-JavaScript-Challenge-Token wird nicht korrekt gelöst.
Aktuelle Messung:
Die 1.9KB sind ausschließlich die WAF-Challenge-Seite (kein Amazon-Content).
Debug-Ergebnisse (Sprint 19, Issue #86):
new Function(challengeJS)(Bun-Blank)document is not definedexecuteRaw(challengeJS)mitwith(_win)AwsWafIntegrationbleibt undefinedAwsWafIntegrationimmer noch undefinedWarum der Code nicht durchläuft
Die Challenge-JS ist eine generator-basierte async State-Machine:
Struktur:
Die
window.AwsWafIntegration-Zuweisung ist innerhalb des async generators, nicht am Ende des IIFE. Der Code:_0xe31008(generator)()(OK ✅ — startet)AwsWafIntegrationwird nie gesetztRoot-Cause-Analyse
Die Challenge-JS ist identisch mit AWS WAF SDK (token.awswaf.com). Sie implementiert einen vollständigen Challenge-Token-Flow:
Warum es bei uns scheitert (Hypothesen):
Hypothese A: Fehlende CSS/Layout-APIs (wahrscheinlich)
Die Challenge prüft Browser-Umgebung via:
document.scripts.length→ haben wir ✅document.querySelector('script[src*="challenge"]')→ haben wir ✅element.offsetWidth / offsetHeight→ Happy DOM gibt immer 0 ❌element.getBoundingClientRect()→ alle Werte = 0 ❌window.matchMedia(...)→ vorhanden aber keine Media-Query-Auswertung ❌In einem echten Browser hat sogar ein leeres
<body>non-zerooffsetHeight. Viele Anti-Bot-Systeme nutzen Layout-basierte Checks als Browser-Fingerabdruck.Hypothese B: fetch() ans WAF-Backend scheitert (sehr wahrscheinlich)
Nach dem Proof-of-Work macht challenge.js
fetch()an:Wenn dieser Request fehlschlägt (falsche Headers, fehlende CORS-Credentials, falscher Body), hängt der async generator im Retry-Loop.
Hypothese C: Promise-Microtask-Timing (möglich)
Unsere
with(_win)+eval()-Realm könnte Promise-Microtask-Queue anders behandeln als ein Browser. Wenn Generator-Yields aufgelöst werden müssen aber Microtasks nicht korrekt flushen, bleibt der Generator stehen.Hypothese D: window.setTimeout / setInterval (möglich)
Die Challenge hat 7
setTimeoutund 1setIntervalAufrufe. Wenn unsere Timer-Implementierung (VirtualFrameClock) nicht korrekt mit der Challenge interagiert, können Timeout-basierte Retry-Loops niemals feuern.Lösungsansätze
Option A: Minimal-CSS/Layout-Engine + WAF-Adaption (empfohlen)
Konzept: Wir müssen nicht das volle CSS-Engine (Layout-Baum, Box-Modell, Painting) implementieren. Aber wir brauchen plausible Default-Werte für die APIs, die Anti-Bot-Systeme checken.
Phase 1: Browser-Fingerprint verbessern (~2h)
Implementiere realistische Defaults für CSS/Layout-APIs:
Details zu matchMedia:
Phase 2: Hybrid Fetch mit korrekten WAF-Headers (~2h)
Der challenge.js
fetch()ans WAF-Backend muss als "same-origin" behandelt werden (es ist eine CloudFront-Eigenschaft der gleichen Site):Alternative: AWS WAF Challenge-Request direkt ohne CORS-Prüfung erlauben.
Phase 3: Async-Generator-Drain (~1h)
Nachdem challenge.js per
executeRaw()geladen wurde, müssen wir den async-Teil abwarten:Phase 4: Verifikation und Tests (~1h)
Option B: Reines fetch()-Intercept + Token-Injection
Statt die ganze Challenge-JS auszuführen, intercepte den fetch()-Call ans WAF-Backend und antworte mit einem synthetischen Token:
Nachteil: Crypto-Proof-of-Work kann nicht simuliert werden — signierter Token nicht synthetisierbar. Der WAF-Backend verifiziert die Signatur. Ohne echten Proof-of-Work wird der Request abgewiesen.
👉 Option B scheitert, weil wir den Crypto-Teil nicht falschen können.
Option C: Hybrider Ansatz — Playwright für initiale WAF-Seite
Nachteil: Playwright-Abhängigkeit, Performance (braucht echten Browser), Komplexität.
Option D: Systematische Browser-API-Kompatibilität (Ultimativ)
Erstelle eine Browser-API-Gap-Analyse und implementiere alle fehlenden APIs systematisch (nicht nur für WAF):
Aufwand: ~2-3 Wochen für vollständige Abdeckung. Zu viel für Issue #86.
Empfohlener Ansatz: Option A (Phasen 1+2+3)
Warum Option A:
Phasen-Zeitplan:
Akzeptanzkriterien
AwsWafIntegrationwird innerhalb von 10s nachexecuteRaw()gesetztgetToken()erfolgreich abgerufenaws-waf-tokenwird korrekt gesetztmatchMedia("(max-width: 768px)")evaluiert korrektelement.getBoundingClientRect()liefert non-zero für body/htmlBetroffene Dateien
src/fakes/browser-environment.tssrc/fakes/navigator.tssrc/pages/request-manager.tssrc/dom/parser.tssrc/pages/page.tstests/integration/amazon-waf.test.tsTechnische Risiken
crypto.subtleexistiert in Happy DOM bereitsDependencies
Querverweise
Generischer Fix in Commit
cc42ee2✅Problem: Async generator runners (WAF Challenge-JS, Cloudflare Turnstile) hingen nach
executeRaw()im Microtask-Queue. Der Parser ging direkt zum nächsten Script — der Generator bekam nie einen Tick.Lösung (generisch — nicht WAF-spezifisch):
drainMicrotasks()nach jedem blocking Script executeRaw()Promise.resolve().then(...)flushen Microtask-QueuesBereits vorhanden (Sprint 23b):
location.reload()patched → funktioniert nach Token-ErhaltFakeLayoutmit offsetWidth/Height/getBoundingClientRect/matchMediaSubtleCryptoChrome-kompatibelfetch()mit CookieJar + Chrome-HeadernNächster Schritt: Amazon.de-Crawl testen ob >50KB DOM erreicht wird.