Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
wix-kickstartx-challenge-writeup — Análise detalhada de um desafio de CTF: engenharia reversa de um SPA em React, parsing de imagens SVG, criação de um solucionador automatizado com correspondência gulosa e obtenção de uma pontuação perfeita. | Kitploit
Ferramentas/GitHubGitHub/d0rb/wix-kickstartx-challenge-writeup
Engenharia ReversaCTFAprendizado e Educação
GitHubd0rb/wix-kickstartx-challenge-writeup

wix-kickstartx-challenge-writeup

Análise detalhada de um desafio de CTF: engenharia reversa de um SPA em React, parsing de imagens SVG, criação de um solucionador automatizado com correspondência gulosa e obtenção de uma pontuação perfeita.

Ver Repositório

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar
5há 3 mesesAinda não revisado

Wix KickstartX Challenge 2026 — Write-up Completo

Resultado: 200/200 (100%) — Token: WKX-9245FA70-200 Data: 24 de março de 2026


1. Descoberta e Reconhecimento

O Anúncio do Desafio

Uma publicação no LinkedIn anunciou um desafio especial para o Wix KickstartX — um programa para desenvolvedores juniores. As regras:

  • 30 minutos, apenas uma tentativa
  • As 10 maiores pontuações avançam diretamente para a fase de exame
  • Dois links fornecidos: wix-kickstartx-challenge-2026.base44.app/ e wixkickstart.com

Encontrando o Ponto de Entrada do Desafio

O site principal wixkickstart.com é um site hospedado na Wix (renderizado no servidor pelo mecanismo Thunderbolt da Wix). Um curl padrão retorna apenas o shell JavaScript — sem conteúdo real. Usamos Chromium headless para renderizá-lo:

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

Do DOM renderizado extraímos um iframe embutido:

root@kitploit:~

Isso acabou sendo uma animação de partículas p5.js (enfeite da seção hero), não o desafio em si. O desafio real está no app Base44.


2. Engenharia Reversa do App do Desafio

Plataforma: Base44

O desafio roda na Base44 — uma plataforma de apps low-code. O app é uma aplicação React de página única com toda a lógica em um único bundle JS:

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

Tamanho: 410.809 bytes (React minificado + lógica do app + SDK Base44)

Extraindo a Superfície da API

Ao buscar no bundle JS, mapeamos toda a API:

Entidades: GameSession, Participant

Padrão de URL da API:

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

App ID: 69aea07cbcb9a3dd1039a58d

Constantes-chave do Bundle

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

Descoberta do Envio em JSON

Uma descoberta crítica — o app tem um modo de envio em massa por JSON. Do código minificado:

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
}

Texto de placeholder no modal:

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

Isso significa que não precisamos clicar 200 vezes na interface — podemos enviar um mapeamento JSON de todas as 200 correspondências programaticamente.

Fluxo de Autenticação

O SDK Base44 usa autenticação JWT:

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

O payload do JWT contém:

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

Cabeçalhos necessários para todas as chamadas de API:

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. Entendendo os Dados do Jogo

Resposta de startGame

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

Estrutura da resposta:

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
}

Formato de Imagem: SVG Inline (codificado em base64)

Cada imagem é um SVG 200x200 contendo:

  1. Um gradiente de fundo (tema claro ou escuro)
  2. Um padrão de overlay opcional (linhas, pontos, anéis, etc.)
  3. Formas geométricas com cores, tamanhos, opacidade, rotação e posição específicos

Exemplo de SVG decodificado (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>

Formato de Descrição: Linguagem Natural Estruturada

Cada descrição segue um padrão estrito:

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

Exemplo:

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. Construindo o Solucionador Automatizado

Passo 1: Mapear Todas as Constantes

Precisávamos mapear propriedades SVG para o vocabulário das descrições. Isso exigiu analisar todas as 200 imagens e todas as 200 descrições para encontrar correspondências exatas.

Mapeamento de Fundo (12 tipos)

Ao contar ocorrências nos dois lados, estabelecemos um mapeamento 1:1:

Método: Contar as cores iniciais únicas do gradiente em todos os 200 SVGs, contar a primeira palavra única em todas as 200 descrições e fazer a correspondência pela contagem.

Mapeamento de Overlay (7 tipos)

Os overlays SVG são implementados de forma diferente conforme o tipo:

Insight-chave: Os elementos de padrão de fundo usam opacity como um atributo XML (por exemplo, opacity="0.06"), enquanto as formas em primeiro plano usam opacity dentro do atributo style (por exemplo, style="opacity:0.7"). Essa distinção foi crucial para separar os padrões de fundo das formas reais.

Mapeamento de Cores (28 cores)

Todos os 200 SVGs usam exatamente 28 cores hexadecimais de preenchimento únicas. Todas as 200 descrições usam exatamente 28 nomes de cores únicos.

Mapeamento de Tipos de Forma (10 tipos)

Mapeamento de Tamanho

Baseado no raio máximo dos vértices da forma a partir da origem:

Mapeamento de Opacidade

Valor de opacidade no SVGNome na Descrição
1.0solid
0.85nearly solid
0.7semi-transparent

Mapeamento de Rotação

Grade de Posição

As formas são posicionadas em uma grade 7x7 nas coordenadas: [14.3, 42.9, 71.4, 100.0, 128.6, 157.1, 185.7] tanto para X quanto para Y.

Elas mapeiam para nomes de colunas: far-left, left, inner-left, center, inner-right, right, far-right E nomes de linhas: top, upper, upper-mid, center, lower-mid, lower, bottom

Passo 2: Analisar Todos os SVGs

Para cada uma das 200 imagens SVG:

  1. Decodificar o base64 para XML SVG bruto
  2. Extrair o fundo — ler a primeira cor de <stop> de <linearGradient id="bg">
  3. Classificar o overlay — verificar <line>, <circle fill="none" stroke>, pontos/formas de baixa opacidade
  4. Extrair as formas — encontrar todos os elementos <polygon>, <circle>, <path>, <rect> que:
    • NÃO são o retângulo de fundo (ignorar width="200")
    • NÃO são o retângulo de vinheta (ignorar fill="url(#vig)")
    • NÃO são elementos do padrão de overlay (ignorar opacity="0.0x" como atributo)
    • TÊM style="opacity:..." (todas as formas em primeiro plano têm isso)

Passo 3: Analisar Todas as Descrições

Para cada uma das 200 descrições de texto:

  1. Fundo: primeira palavra (ex.: "milky", "pitch", "tenebrous")
  2. Overlay: texto após "overlaid with" na primeira frase
  3. Contagem de formas: regex (\d+) elements total
  4. Formas individuais: padrão de regex:
    root@kitploit:~
    (minuscule|modest|mid-sized|substantial|massive)
    (solid|nearly solid|semi-transparent|faint)
    (color name)
    (shape type)
    ((rotation), at (position))
    

Passo 4: Correspondência Baseada em Pontuação

O algoritmo de correspondência usa uma abordagem de pontuação gulosa:

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

Em seguida, atribuir guloso os pares de maior pontuação primeiro:

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. Execução e Envio

Chamadas de API Feitas

1. Iniciar Jogo

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

2. Pontuar o Jogo

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. Gerar Token

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

Linha do Tempo

PassoAção
21:07:45 UTCJogo iniciado (startGame chamado)
21:07–21:15Desenvolvimento do script solucionador (análise + correspondência)
~21:15

Tempo total de resolução: ~8 minutos de uma janela de 30 minutos.


6. Resumo da Stack Técnica


7. Principais Insights

Por Que Isso Funcionou

  1. O endpoint de envio em JSON foi o facilitador crítico — sem ele, precisaríamos de automação de navegador para clicar 400 vezes (selecionar imagem + selecionar descrição x 200).

  2. O mapeamento baseado em contagem foi o avanço para cores, fundos e overlays. Em vez de adivinhar a que hex #cd7f32 corresponde em inglês, contamos que ele aparece 65 vezes nos SVGs e "brass" aparece 65 vezes nas descrições — contagem única = correspondência garantida.

  3. Restrições rígidas eliminam candidatos rapidamente. Cada imagem tem uma combinação única de (tipo_de_fundo, tipo_de_overlay, contagem_de_formas). Com 12 fundos x 7 overlays x contagens de formas variadas, a maioria das imagens tem apenas algumas descrições possíveis, não 200.

  4. SVG é dado estruturado. Ao contrário de imagens raster (PNG/JPG), SVGs são XML — cada forma, cor, posição e rotação está explicitamente codificada como texto. Nenhuma visão computacional é necessária.

O Que Tornou o Desafio Difícil

  1. A detecção de overlay era complicada. Os padrões de fundo usavam 5 técnicas SVG diferentes: elementos <line>, <circle> apenas com stroke, <circle> com atributo de baixa opacidade, <polygon> com atributo de baixa opacidade e a ausência de todos eles. A distinção-chave era opacity como atributo XML (fundo) vs. dentro de style (formas em primeiro plano).

  2. O vocabulário de cores não era óbvio. Nomes como "oxblood" (#800020), "brass" (#cd7f32) e "deep sapphire" (#0f52ba) exigiram a abordagem de contagem — não dá para adivinhar com segurança apenas pelos valores hexadecimais.

  3. A classificação de formas a partir de paths SVG exigiu entender comandos de path SVG:

    • M (moveto), L (lineto), A (arc), (closepath)
Baixar ferramenta
FunçãoFinalidade
startGameCria uma sessão de jogo, retorna 200 imagens + 200 descrições
scoreGameAceita {matches: {}, sessionId: ""}, retorna {correctCount: N}
generateTokenRecebe sessionId, retorna token de conclusão
getLeaderboardRetorna as maiores pontuações
saveNicknameSalva o nome de exibição para o leaderboard
Cor Inicial do Gradiente SVGContagemPalavra na DescriçãoContagem
#0a162828pitch28
#eef2f720frosted20
#0d0d0d19tenebrous19
#fef9f018pearlescent18
#1e0a2e18nocturnal18
#0a1a0a17midnight17
#1a0a0a15inky15
#f5f0eb14milky14
#f7f3ee14ethereal14
#f0f0f014radiant14
#f0f7f413glowing13
#1a1a2e10somber10
Padrão SVGMétodo de DetecçãoContagemNome na DescriçãoContagem
Elementos <line> horizontais (dy=0)Verificar x1,y1,x2,y235striped overlay35
Elementos <line> verticais (dx=0)Verificar x1,y1,x2,y234lattice pattern34
<circle> com opacity="0.06" (pontos)Contar círculos de baixa opacidade30stippled layer30
Nenhum elemento de overlaySem linhas, sem formas de fundo28(nenhum)28
Elementos <line> diagonaisVerificar a direção da inclinação27tilted cross-lines27
<polygon>/<path> pequenos com opacity="0.06"Formas com atributo de baixa opacidade25arrow-band texture25
<circle> com fill="none" stroke="#888"Círculos apenas com stroke21ringed pattern21
HexNome na DescriçãoHexNome na Descrição
#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
Elemento SVGLógica de DetecçãoNome na Descrição
<polygon> com 3 vérticesContar pares de pontos separados por espaçopike
<polygon> com 4 vérticestilted square
<polygon> com 5 vérticesquint form
<polygon> com 6 vérticesbee cell
<polygon> com 10 vérticesasterisk
<rect> (não-fundo)Tem largura/altura, não 200x200tilted square
<circle>Nome da tagdisc
<path> com fill-rule="evenodd"Dois caminhos de arco concêntricosdonut
<path> com arco único + ZCaminho de semicírculohalf-disc
<path> com 6+ comandos LFormato de cruz/maiscrosshair
<path> com 3-5 comandos LFormato semelhante a setapointer
Faixa de RaioNome na Descrição
0–12minuscule
13–16modest
17–20mid-sized
21–25substantial
26+massive
0.5faint
Valor de rotate() no SVGNome na Descrição
0°upright
1–20°slightly tilted
21–55°diagonal
56–75°steeply angled
76–105°sideways
  • Para cada forma, extrair:
    • Cor: do atributo fill, mapeada via COLOR_MAP
    • Tipo: da tag do elemento + contagem de vértices / comandos de path
    • Tamanho: das coordenadas dos vértices ou do raio
    • Opacidade: de style="opacity:X"
    • Rotação: de transform="... rotate(X)"
    • Posição: de transform="translate(X,Y) ..."
  • scoreGame enviado — 200/200 corretas
    ~21:15generateToken chamado — WKX-9245FA70-200
    ComponenteTecnologia
    Plataforma do desafioBase44 (construtor de apps low-code)
    FrontendReact SPA (bundle JS único, ~410KB)
    BackendPython/uvicorn atrás do Cloudflare
    CDN/ProxyCloudflare (HTTP/3, compressão Brotli)
    AutenticaçãoJWT (HS256), armazenado no localStorage
    Formato de dadosSVG (base64 inline), API JSON
    Tempo realWebSocket (socket.io) para atualizações de sessão ao vivo
    SolucionadorPython 3 (análise com regex, sem bibliotecas externas)
    Z
  • Um donut = dois paths de arco concêntricos com fill-rule="evenodd"
  • Um half-disc = um path de arco único
  • Uma crosshair = path com 6+ segmentos de linha (formato de cruz/mais)
  • Um pointer = path com 3-5 segmentos de linha (formato de seta)