Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
wix-kickstartx-challenge-writeup — Ausführlicher Write-up einer CTF-Challenge: Reverse Engineering einer React-SPA, Parsen von SVG-Bildern, Erstellen eines automatisierten Solvers mit Greedy-Matching und Erzielen einer perfekten Punktzahl. | Kitploit
Tools/GitHubGitHub/d0rb/wix-kickstartx-challenge-writeup
Reverse EngineeringCTFLernen & Bildung
GitHubd0rb/wix-kickstartx-challenge-writeup

wix-kickstartx-challenge-writeup

Ausführlicher Write-up einer CTF-Challenge: Reverse Engineering einer React-SPA, Parsen von SVG-Bildern, Erstellen eines automatisierten Solvers mit Greedy-Matching und Erzielen einer perfekten Punktzahl.

Repository anzeigen

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen
vor 3 MonatenNoch nicht geprüft

Wix KickstartX Challenge 2026 — Vollständiger Writeup

Ergebnis: 200/200 (100 %) — Token: WKX-9245FA70-200 Datum: 24. März 2026


1. Entdeckung & Aufklärung

Die Challenge-Ankündigung

Ein LinkedIn-Post kündigte eine spezielle Challenge für Wix KickstartX an — ein Junior-Entwicklerprogramm. Die Regeln:

  • 30 Minuten, nur ein Versuch
  • Die 10 höchsten Punktzahlen rücken direkt in die Prüfungsphase vor
  • Zwei Links wurden bereitgestellt: wix-kickstartx-challenge-2026.base44.app/ und wixkickstart.com

Den Challenge-Einstiegspunkt finden

Die Hauptseite wixkickstart.com ist eine von Wix gehostete Seite (serverseitig gerendert von Wix' Thunderbolt-Engine). Ein Standard-curl-Aufruf gibt nur die JavaScript-Hülle zurück — keine tatsächlichen Inhalte. Wir verwendeten headless Chromium, um sie zu rendern:

root@kitploit:~
chromium --headless --disable-gpu --no-sandbox --virtual-time-budget=10000 \
  --dump-dom "https://wixkickstart.com" > /tmp/wix_rendered.html

Aus dem gerenderten DOM extrahierten wir einen eingebetteten iframe:

root@kitploit:~

Das entpuppte sich als p5.js-Partikelanimation (Augenschmaus im Hero-Bereich), nicht als die Challenge selbst. Die eigentliche Challenge befindet sich in der Base44-App.


2. Reverse Engineering der Challenge-App

Plattform: Base44

Die Challenge läuft auf Base44 — einer Low-Code-App-Plattform. Die App ist eine Single-Page-React-Anwendung, deren gesamte Logik in einem einzigen JS-Bundle steckt:

root@kitploit:~
https://wix-kickstartx-challenge-2026.base44.app/assets/index-oTG160r9.js

Größe: 410.809 Bytes (minifiziertes React + App-Logik + Base44-SDK)

Die API-Oberfläche extrahieren

Indem wir das JS-Bundle per grep durchsuchten, bildeten wir die gesamte API ab:

Entitäten: GameSession, Participant

API-URL-Muster:

root@kitploit:~
POST /api/apps/{appId}/functions/{functionName}

App-ID: 69aea07cbcb9a3dd1039a58d

Wichtige Konstanten aus dem Bundle

root@kitploit:~
const or = 1800;   // Time limit: 1800 seconds (30 minutes)
const rm = 200;    // Total items: 200 image-description pairs

Entdeckung der JSON-Übermittlung

Eine entscheidende Entdeckung — die App verfügt über einen JSON-Stapelübermittlungsmodus. Aus dem minifizierten Quellcode:

root@kitploit:~
function Ik({onSubmit:r, onClose:n}) {
    // ...
    h = JSON.parse(s)  // Parse JSON input
    // Validation: must be object like { "IMG-001": "DESC-042", ... }
    r(h)  // Submit all matches at once
}

Platzhaltertext im Modal:

root@kitploit:~
{
  "IMG-001": "DESC-042",
  "IMG-002": "DESC-017",
  ...
}

Das bedeutet, dass wir nicht 200 Mal in der UI klicken müssen — wir können ein JSON-Mapping aller 200 Zuordnungen programmatisch übermitteln.

Authentifizierungsablauf

Das Base44-SDK nutzt JWT-Authentifizierung:

root@kitploit:~
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Das JWT-Payload enthält:

root@kitploit:~
{
  "sub": "[email protected]",
  "exp": 1782162460,
  "iat": 1774386460
}

Erforderliche Header für alle API-Aufrufe:

root@kitploit:~
Content-Type: application/json
Authorization: Bearer {jwt_token}
X-App-Id: 69aea07cbcb9a3dd1039a58d
Base44-Functions-Version: prod
X-Origin-URL: https://wix-kickstartx-challenge-2026.base44.app/

3. Die Spieldaten verstehen

startGame-Antwort

root@kitploit:~
POST /api/apps/69aea07cbcb9a3dd1039a58d/functions/startGame
Status: 200
Transferred: 62.87 kB compressed (2.20 MB decompressed)
Content-Encoding: br (Brotli)

Antwortstruktur:

root@kitploit:~
{
  "status": "active",
  "sessionId": "69c2fd218e5b26f307c941c9",
  "startedAt": "2026-03-24T21:07:45.101Z",
  "imageOrder": ["IMG-154", "IMG-102", ...],   // 200 items (display order)
  "descOrder": ["DESC-125", "DESC-109", ...],  // 200 items (display order)
  "imagesData": { "IMG-001": "data:image/svg+xml;base64,...", ... },  // 200 SVGs
  "descriptionsData": { "DESC-105": "milky field, overlaid with...", ... }  // 200 texts
}

Bildformat: Inline-SVG (base64-kodiert)

Jedes Bild ist ein 200x200-SVG mit:

  1. Einem Hintergrundverlauf (helles oder dunkles Design)
  2. Einem optionalen Überlagerungsmuster (Linien, Punkte, Ringe usw.)
  3. Geometrischen Formen mit bestimmten Farben, Größen, Deckkraft, Rotation und Position

Beispiel für ein dekodiertes SVG (IMG-001):

root@kitploit:~
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200">
  <defs>
    <linearGradient id="bg" ...>
      <stop offset="0%" stop-color="#f5f0eb"/>
      <stop offset="100%" stop-color="#e8e0d5"/>
    </linearGradient>
  </defs>
  <rect width="200" height="200" fill="url(#bg)"/>
  <!-- Optional overlay lines/dots/rings here -->
  <polygon points="..." fill="#E2725B" transform="translate(128.6,185.7) rotate(90)"
           style="opacity:0.5;filter:drop-shadow(...)"/>
  <!-- More shapes... -->
</svg>

Beschreibungsformat: Strukturierte natürliche Sprache

Jede Beschreibung folgt einem strengen Muster:

root@kitploit:~
{background} field[, overlaid with {overlay}]. {N} elements total:
{size} {opacity} {color} {shape} ({rotation}, at {position}) ·
{size} {opacity} {color} {shape} ({rotation}, at {position}) · ...

Beispiel:

root@kitploit:~
milky field, overlaid with tilted cross-lines. 10 elements total:
minuscule nearly solid dim gray pike (steeply angled, at center inner-left) ·
substantial solid azure pike (diagonal, at top center) · ...

4. Aufbau des automatisierten Solvers

Schritt 1: Alle Konstanten abbilden

Wir mussten SVG-Eigenschaften auf das Beschreibungsvokabular abbilden. Dafür analysierten wir alle 200 Bilder und alle 200 Beschreibungen, um exakte Entsprechungen zu finden.

Hintergrund-Zuordnung (12 Typen)

Durch Zählen der Vorkommen auf beiden Seiten stellten wir eine 1:1-Zuordnung her:

Methode: Eindeutige Verlaufs-Startfarben in allen 200 SVGs zählen, eindeutige erste Wörter in allen 200 Beschreibungen zählen, anhand der Anzahl zuordnen.

Überlagerungs-Zuordnung (7 Typen)

SVG-Überlagerungen werden je nach Typ unterschiedlich implementiert:

Wichtige Erkenntnis: Hintergrundmusterelemente verwenden opacity als XML-Attribut (z. B. opacity="0.06"), während Vordergrundformen opacity innerhalb des style-Attributs verwenden (z. B. style="opacity:0.7"). Diese Unterscheidung war entscheidend, um Hintergrundmuster von den eigentlichen Formen zu trennen.

Farb-Zuordnung (28 Farben)

Alle 200 SVGs verwenden genau 28 eindeutige Hex-Füllfarben. Alle 200 Beschreibungen verwenden genau 28 eindeutige Farbnamen.

Formtyp-Zuordnung (10 Typen)

Größen-Zuordnung

Basierend auf dem maximalen Radius der Formeckpunkte vom Ursprung:

Deckkraft-Zuordnung

SVG-Opacity-WertBeschreibungsname
1.0solid
0.85nearly solid
0.7semi-transparent

Rotations-Zuordnung

Positionsraster

Formen werden auf einem 7x7-Raster an den Koordinaten platziert:

root@kitploit:~
[14.3, 42.9, 71.4, 100.0, 128.6, 157.1, 185.7] for both X and Y.

Diese bilden die Spaltennamen ab: far-left, left, inner-left, center, inner-right, right, far-right Und die Zeilennamen: top, upper, upper-mid, center, lower-mid, lower, bottom

Schritt 2: Alle SVGs parsen

Für jedes der 200 SVG-Bilder:

  1. Base64 zu rohem SVG-XML dekodieren
  2. Hintergrund extrahieren — erste <stop>-Farbe aus <linearGradient id="bg"> auslesen
  3. Überlagerung klassifizieren — nach <line>, <circle fill="none" stroke>, Punkten/Formen mit niedriger Deckkraft prüfen
  4. Formen extrahieren — alle <polygon>, <circle>, <path>, <rect>-Elemente finden, die:
    • NICHT das Hintergrund-Rechteck sind (width="200" überspringen)
    • NICHT das Vignetten-Rechteck sind (fill="url(#vig)" überspringen)
    • KEINE Überlagerungsmusterelemente sind (opacity="0.0x" als Attribut überspringen)
    • DOCH ein style="opacity:..." haben (alle Vordergrundformen haben das)

Schritt 3: Alle Beschreibungen parsen

Für jede der 200 Textbeschreibungen:

  1. Hintergrund: erstes Wort (z. B. „milky", „pitch", „tenebrous")
  2. Überlagerung: Text nach „overlaid with" im ersten Satz
  3. Formanzahl: Regex (\d+) elements total
  4. Einzelne Formen: Regex-Muster:
    root@kitploit:~
    (minuscule|modest|mid-sized|substantial|massive)
    (solid|nearly solid|semi-transparent|faint)
    (color name)
    (shape type)
    ((rotation), at (position))
    

Schritt 4: Score-basiertes Abgleichen

Der Abgleichsalgorithmus verwendet einen Greedy-Scoring-Ansatz:

root@kitploit:~
def score_match(img, desc):
    # Hard constraints — must match exactly
    if img['num'] != desc['num']:      return -10000  # Shape count
    if img['bg'] != desc['bg']:        return -10000  # Background type
    if img['overlay'] != desc['overlay']: return -10000  # Overlay type

    score = 100  # Base score for matching hard constraints

    # Soft scoring — color overlap
    for color in img_colors:
        if color in desc_colors:
            score += 10

    # Soft scoring — type overlap
    for type in img_types:
        if type in desc_types:
            score += 8

    # Per-shape detail matching (Hungarian-style greedy)
    for each image_shape:
        find best matching desc_shape by:
            +20 if color matches
            +15 if type matches
            +10 if size matches
            +8  if opacity matches
            +5  if rotation matches
        score += best_match_score

    return score

Danach werden die Paare mit der höchsten Punktzahl zuerst gierig zugewiesen:

root@kitploit:~
all_scores.sort(reverse=True)
for score, img_id, desc_id in all_scores:
    if img_id not in matched and desc_id not in used:
        matches[img_id] = desc_id

5. Ausführung & Übermittlung

Durchgeführte API-Aufrufe

1. Spiel starten

root@kitploit:~
POST /api/apps/69aea07cbcb9a3dd1039a58d/functions/startGame
Body: {"email": "[email protected]"}
Response: 200 OK (2.2 MB — all game data)

2. Spiel bewerten

root@kitploit:~
POST /api/apps/69aea07cbcb9a3dd1039a58d/functions/scoreGame
Body: {
  "matches": {"IMG-032": "DESC-182", "IMG-198": "DESC-134", ...},
  "sessionId": "69c2fd218e5b26f307c941c9"
}
Response: 200 OK
{"correctCount": 200, "scoreSeal": "3d1ac8a7e7be4856"}

3. Token generieren

root@kitploit:~
POST /api/apps/69aea07cbcb9a3dd1039a58d/functions/generateToken
Body: {"sessionId": "69c2fd218e5b26f307c941c9"}
Response: 200 OK
{"token": "WKX-9245FA70-200", "score": 200}

Zeitverlauf

SchrittAktion
21:07:45 UTCSpiel gestartet (startGame aufgerufen)
21:07–21:15Entwicklung des Solver-Skripts (Parsing + Matching)
~21:15scoreGame übermittelt —

Gesamtlösungszeit: ~8 Minuten eines 30-Minuten-Fensters.


6. Zusammenfassung des technischen Stacks


7. Wichtige Erkenntnisse

Warum das funktionierte

  1. Der JSON-Übermittlungs-Endpoint war der entscheidende Wegbereiter — ohne ihn hätten wir Browser-Automatisierung gebraucht, um 400 Mal zu klicken (Bild auswählen + Beschreibung auswählen x 200).

  2. Zählbasiertes Zuordnen war der Durchbruch bei Farben, Hintergründen und Überlagerungen. Statt zu raten, welchem englischen Begriff das Hex #cd7f32 entspricht, zählten wir, dass es 65 Mal in den SVGs vorkommt und „brass" 65 Mal in den Beschreibungen — eindeutige Anzahl = garantierte Übereinstimmung.

  3. Harte Randbedingungen eliminieren Kandidaten schnell. Jedes Bild hat eine eindeutige Kombination aus (background_type, overlay_type, shape_count). Bei 12 Hintergründen x 7 Überlagerungen x unterschiedlichen Formanzahlen haben die meisten Bilder nur eine Handvoll möglicher Beschreibungsübereinstimmungen, nicht 200.

  4. SVG-Dateien sind strukturierte Daten. Anders als Rasterbilder (PNG/JPG) sind SVGs XML — jede Form, Farbe, Position und Rotation ist explizit als Text kodiert. Keine Computer Vision nötig.

Was es herausfordernd machte

  1. Die Überlagerungserkennung war knifflig. Hintergrundmuster nutzten 5 verschiedene SVG-Techniken: <line>-Elemente, <circle> nur mit Stroke, <circle> mit niedrigem Opacity-Attribut, <polygon> mit niedrigem Opacity-Attribut sowie die Abwesenheit all dieser Elemente. Die entscheidende Unterscheidung war opacity als XML-Attribut (Hintergrund) gegenüber innerhalb von style (Vordergrundformen).

  2. Das Farbvokabular war nicht offensichtlich. Namen wie „oxblood" (#800020), „brass" (#cd7f32) und „deep sapphire" (#0f52ba) erforderten den Zählansatz — aus reinen Hex-Werten kann man diese nicht zuverlässig erraten.

  3. Die Formklassifizierung aus SVG-Pfaden erforderte Verständnis der SVG-Pfadbefehle:

    • M (moveto), L (lineto), A (arc), (closepath)
Tool herunterladen
FunktionZweck
startGameErstellt eine Spielsitzung, gibt 200 Bilder + 200 Beschreibungen zurück
scoreGameAkzeptiert {matches: {}, sessionId: ""}, gibt {correctCount: N} zurück
generateTokenNimmt sessionId entgegen, gibt Abschluss-Token zurück
getLeaderboardGibt die besten Punktzahlen zurück
saveNicknameSpeichert den Anzeigenamen für die Bestenliste
SVG-Verlauf-StartfarbeAnzahlBeschreibungswortAnzahl
#0a162828pitch28
#eef2f720frosted20
#0d0d0d19tenebrous19
#fef9f018pearlescent18
#1e0a2e18nocturnal18
#0a1a0a17midnight17
#1a0a0a15inky15
#f5f0eb14milky14
#f7f3ee14ethereal14
#f0f0f014radiant14
#f0f7f413glowing13
#1a1a2e10somber10
SVG-MusterErkennungsmethodeAnzahlBeschreibungsnameAnzahl
<line>-Elemente, horizontal (dy=0)x1,y1,x2,y2 prüfen35striped overlay35
<line>-Elemente, vertikal (dx=0)x1,y1,x2,y2 prüfen34lattice pattern34
<circle> mit opacity="0.06" (Punkte)Kreise mit niedriger Deckkraft zählen30stippled layer30
Überhaupt keine ÜberlagerungselementeKeine Linien, keine Hintergrundformen28(keine)28
<line>-Elemente, diagonalSteigungsrichtung prüfen27tilted cross-lines27
Kleine <polygon>/<path>-Elemente mit opacity="0.06"Formen mit niedrigem Opacity-Attribut25arrow-band texture25
<circle> mit fill="none" stroke="#888"Kreise nur mit Stroke21ringed pattern21
HexBeschreibungsnameHexBeschreibungsname
#708090blue-gray#cd7f32brass
#f5f5f5near white#dc143cfiery red
#00bcd4electric cyan#2196f3azure
#ff6b6bsalmon pink#228b22rich green
#98ff98pale green#b0b0b0platinum
#0f52badeep sapphire#800020oxblood
#ff8c00deep orange#4b0082dark purple
#e2725bterra rosa#ffbf00marigold
#b7410erusset#ff69b4candy pink
#ffd700bright gold#c0c0c0tin
#6b8e23moss#008080deep teal
#40e0d0pale teal#0047abroyal blue
#e34234burnt sienna#a0522ddeep red
#4a4a4adim gray#36454fdark gray
SVG-ElementErkennungslogikBeschreibungsname
<polygon> 3 EckenLeerzeichengetrennte Punktpaare zählenpike
<polygon> 4 Eckentilted square
<polygon> 5 Eckenquint form
<polygon> 6 Eckenbee cell
<polygon> 10 Eckenasterisk
<rect> (kein Hintergrund)Hat width/height, nicht 200x200tilted square
<circle>Tag-Namedisc
<path> mit fill-rule="evenodd"Zwei konzentrische Bogenpfadedonut
<path> mit einem einzelnen Bogen + ZHalbkreis-Pfadhalf-disc
<path> mit 6+ L-BefehlenKreuz/Plus-Formcrosshair
<path> mit 3–5 L-BefehlenPfeilartige Formpointer
RadiusbereichBeschreibungsname
0–12minuscule
13–16modest
17–20mid-sized
21–25substantial
26+massive
0.5
faint
SVG-rotate()-WertBeschreibungsname
0°upright
1–20°slightly tilted
21–55°diagonal
56–75°steeply angled
76–105°sideways
  • Für jede Form extrahieren:
    • Farbe: aus dem fill-Attribut, über COLOR_MAP zugeordnet
    • Typ: aus Element-Tag + Eckpunktanzahl / Pfadbefehlen
    • Größe: aus Eckpunktkoordinaten oder Radius
    • Deckkraft: aus style="opacity:X"
    • Rotation: aus transform="... rotate(X)"
    • Position: aus transform="translate(X,Y) ..."
  • 200/200 korrekt
    ~21:15generateToken aufgerufen — WKX-9245FA70-200
    KomponenteTechnologie
    Challenge-PlattformBase44 (Low-Code-App-Builder)
    FrontendReact-SPA (ein einzelnes JS-Bundle, ~410 KB)
    BackendPython/uvicorn hinter Cloudflare
    CDN/ProxyCloudflare (HTTP/3, Brotli-Kompression)
    AuthJWT (HS256), in localStorage gespeichert
    DatenformatSVG (Base64 inline), JSON-API
    EchtzeitWebSocket (socket.io) für Live-Sitzungsupdates
    SolverPython 3 (Regex-Parsing, keine externen Bibliotheken)
    Z
  • Ein Donut = zwei konzentrische Bogenpfade mit fill-rule="evenodd"
  • Ein Halbkreis = ein einzelner Bogenpfad
  • Ein Fadenkreuz = Pfad mit 6+ Liniensegmenten (Kreuz/Plus-Form)
  • Ein Zeiger = Pfad mit 3–5 Liniensegmenten (Pfeilform)