Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
wix-kickstartx-challenge-writeup — CTFチャレンジの詳細なwrite-up:React SPAのリバースエンジニアリング、SVG画像のパース、貪欲マッチングによる自動ソルバーの構築、そして完璧なスコア達成。 | Kitploit
ツール/GitHubGitHub/d0rb/wix-kickstartx-challenge-writeup
リバースエンジニアリングCTF学習と教育
GitHubd0rb/wix-kickstartx-challenge-writeup

wix-kickstartx-challenge-writeup

CTFチャレンジの詳細なwrite-up:React SPAのリバースエンジニアリング、SVG画像のパース、貪欲マッチングによる自動ソルバーの構築、そして完璧なスコア達成。

リポジトリを見る

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有
3ヶ月前未レビュー

Wix KickstartX Challenge 2026 — 完全版Writeup

結果:200/200(100%) — トークン:WKX-9245FA70-200 日付:2026年3月24日


1. 発見と偵察

チャレンジの発表

LinkedInの投稿で、Wix KickstartX(ジュニア開発者プログラム)向けの特別なチャレンジが発表されました。 ルール:

  • 30分、挑戦は1回のみ
  • 上位10名の最高スコアが試験ステージに直接進出
  • 提供されたリンクは2つ:wix-kickstartx-challenge-2026.base44.app/ と wixkickstart.com

チャレンジのエントリーポイントを探る

メインサイトwixkickstart.comはWixがホストするサイトです(WixのThunderboltエンジンによるサーバーサイドレンダリング)。標準のcurlはJavaScriptのシェルのみを返し、実際のコンテンツは含まれません。レンダリングにはヘッドレスChromiumを使用しました:

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

レンダリングされたDOMから、埋め込まれたiframeを抽出しました:

root@kitploit:~

これはp5.jsによるパーティクルアニメーション(ヒーローセクションの見た目を飾るもの)であり、チャレンジ本体ではありませんでした。実際のチャレンジはBase44アプリ上にあります。


2. チャレンジアプリのリバースエンジニアリング

プラットフォーム:Base44

チャレンジはBase44(ローコードアプリプラットフォーム)上で動作します。このアプリはシングルページのReactアプリケーションで、すべてのロジックが1つのJSバンドルにまとめられています:

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

サイズ: 410,809バイト(minified React + アプリロジック + Base44 SDK)

APIサーフェスの抽出

JSバンドルをgrepすることで、API全体をマッピングしました:

エンティティ: GameSession、Participant

API URLパターン:

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

アプリID: 69aea07cbcb9a3dd1039a58d

バンドル内の主要定数

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

JSON一括送信の発見

重要な発見:このアプリにはJSON一括送信モードがあります。minifyされたソースより:

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
}

モーダル内のプレースホルダーテキスト:

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

つまり、UI上で200回クリックする必要はなく、200件すべてのマッチのJSONマッピングをプログラム的に送信できます。

認証フロー

Base44 SDKはJWT認証を使用します:

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

JWTペイロードには以下が含まれます:

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

すべての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. ゲームデータの理解

startGame レスポンス

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

レスポンス構造:

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
}

画像形式:インラインSVG(base64エンコード)

各画像は200x200のSVGで、以下を含みます:

  1. 背景グラデーション(ライトまたはダークテーマ)
  2. オプションのオーバーレイパターン(線、点、リングなど)
  3. 特定の色、サイズ、不透明度、回転、位置を持つ幾何学シェイプ

デコードされた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>

説明文の形式:構造化された自然言語

各説明文は厳密なパターンに従います:

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

例:

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. 自動ソルバーの構築

ステップ1:すべての定数をマッピングする

SVGのプロパティを説明文の語彙に対応付ける必要がありました。正確な対応を見つけるには、200件すべての画像と200件すべての説明文を分析する必要がありました。

背景のマッピング(12タイプ)

両側の出現回数を数えることで、1:1のマッピングを確立しました:

方法: 200件すべてのSVGでユニークなグラデーション開始色を数え、200件すべての説明文でユニークな最初の単語を数え、件数で照合します。

オーバーレイのマッピング(7タイプ)

SVGのオーバーレイはタイプによって実装方法が異なります:

重要な洞察: 背景パターンの要素はopacityをXMLの属性として使用します(例:opacity="0.06")。一方、前景シェイプはopacityをstyle属性内で使用します(例:style="opacity:0.7")。この区別は、背景パターンと実際のシェイプを分離するうえで決定的に重要でした。

色のマッピング(28色)

200件すべてのSVGは、正確に28種類のユニークなhexフィル色を使用しています。200件すべての説明文は、正確に28種類のユニークな色名を使用しています。

シェイプタイプのマッピング(10タイプ)

サイズのマッピング

原点からのシェイプ頂点の最大半径に基づく:

不透明度のマッピング

SVGのopacity値説明名
1.0solid
0.85nearly solid
0.7semi-transparent
0.5

回転のマッピング

位置グリッド

シェイプは7x7グリッドの座標に配置されます: [14.3, 42.9, 71.4, 100.0, 128.6, 157.1, 185.7](XとYの両方)。

これらは次のカラム名に対応します:far-left, left, inner-left, center, inner-right, right, far-right 行名は次のとおりです:top, upper, upper-mid, center, lower-mid, lower, bottom

ステップ2:すべてのSVGを解析する

200件のSVG画像のそれぞれについて:

  1. デコード — base64を生のSVG XMLにデコードする
  2. 背景を抽出 — <linearGradient id="bg">の最初の<stop>色を読み取る
  3. オーバーレイを分類 — <line>、<circle fill="none" stroke>、低不透明度のドット/シェイプを確認する
  4. シェイプを抽出 — 以下の条件を満たすすべての<polygon>、<circle>、<path>、<rect>要素を見つける:
    • 背景のrectではない(width="200"をスキップ)
    • ビネットのrectではない(fill="url(#vig)"をスキップ)
    • オーバーレイパターンの要素ではない(属性としてのopacity="0.0x"をスキップ)
    • style="opacity:..."を持っている(すべての前景シェイプはこれを持つ)
  5. 各シェイプについて、以下を抽出:
    • 色:fill属性から取得し、COLOR_MAPでマッピング

ステップ3:すべての説明文を解析する

200件のテキスト説明文のそれぞれについて:

  1. 背景:最初の単語(例:"milky"、"pitch"、"tenebrous")
  2. オーバーレイ:最初の文の"overlaid with"の後のテキスト
  3. シェイプ数:正規表現 (\d+) elements total
  4. 個々のシェイプ:正規表現パターン:
    root@kitploit:~
    (minuscule|modest|mid-sized|substantial|massive)
    (solid|nearly solid|semi-transparent|faint)
    (color name)
    (shape type)
    ((rotation), at (position))
    

ステップ4:スコアベースのマッチング

マッチングアルゴリズムは貪欲なスコアリング手法を使用します:

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

その後、スコアが最も高いペアから順に貪欲に割り当てます:

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. 実行と送信

実行したAPI呼び出し

1. ゲーム開始

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

2. スコア送信

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. トークン生成

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

タイムライン

ステップアクション
21:07:45 UTCゲーム開始(startGame呼び出し)
21:07–21:15ソルバースクリプトの開発(解析+マッチング)
~21:15scoreGame送信 — 200/200正解
~21:15generateToken呼び出し —

合計解答時間:30分間のうち約8分。


6. 技術スタックのまとめ


7. 重要な洞察

成功した理由

  1. JSON送信エンドポイントが決定的な鍵でした。これがなければ、ブラウザ自動化で400回クリックする必要があったでしょう(画像の選択+説明文の選択×200)。

  2. カウントによるマッピングが、色・背景・オーバーレイの突破口になりました。hex #cd7f32が英語で何に対応するかを推測する代わりに、SVG内に65回出現し、説明文内に"brass"が65回出現することを数えました。出現回数がユニーク = 確実に一致します。

  3. ハード制約は候補を素早く排除します。 各画像は(background_type、overlay_type、shape_count)のユニークな組み合わせを持ちます。12種類の背景×7種類のオーバーレイ×さまざまなシェイプ数により、ほとんどの画像では200件ではなく、ほんの一握りの説明マッチ候補しかありません。

  4. SVGは構造化データです。 ラスター画像(PNG/JPG)とは異なり、SVGはXMLであり、すべてのシェイプ、色、位置、回転がテキストとして明示的にエンコードされています。コンピュータビジョンは不要です。

難しかった点

  1. オーバーレイ検出は厄介でした。 背景パターンは5つの異なるSVGテクニックを使用していました:<line>要素、ストロークのみの<circle>、低不透明度属性の<circle>、低不透明度属性の<polygon>、そしてこれらがすべて存在しないパターンです。重要な区別は、XML属性としてのopacity(背景)とstyle内のopacity(前景シェイプ)でした。

  2. 色の語彙は自明ではありませんでした。 "oxblood"(#800020)、"brass"(#cd7f32)、"deep sapphire"(#0f52ba)のような名前にはカウント手法が必要でした。16進数の値だけからこれらを確実に推測することはできません。

  3. SVGパスからのシェイプ分類には、SVGパスコマンドの理解が必要でした:

    • M(moveto)、L(lineto)、A(arc)、Z(closepath)
ツールをダウンロード
関数目的
startGameゲームセッションを作成し、200枚の画像と200件の説明文を返す
scoreGame{matches: {}, sessionId: ""}を受け取り、{correctCount: N}を返す
generateTokensessionIdを受け取り、完了トークンを返す
getLeaderboard上位スコアを返す
saveNicknameリーダーボード用の表示名を保存する
SVGグラデーション開始色件数説明語件数
#0a162828pitch28
#eef2f720frosted20
#0d0d0d19tenebrous19
#fef9f018pearlescent18
#1e0a2e18nocturnal18
#0a1a0a17midnight17
#1a0a0a15inky15
#f5f0eb14milky14
#f7f3ee14ethereal14
#f0f0f014radiant14
#f0f7f413glowing13
#1a1a2e10somber10
SVGパターン検出方法件数説明名件数
<line> elements, horizontal (dy=0)x1,y1,x2,y2をチェック35striped overlay35
<line> elements, vertical (dx=0)x1,y1,x2,y2をチェック34lattice pattern34
<circle> with opacity="0.06" (dots)低不透明度の円を数える30stippled layer30
No overlay elements at all線も背景シェイプもない28(none)28
<line> elements, diagonal傾きの方向をチェック27tilted cross-lines27
Small <polygon>/<path> with opacity="0.06"低不透明度属性のシェイプ25arrow-band texture25
<circle> with fill="none" stroke="#888"ストロークのみの円21ringed pattern21
Hex説明名Hex説明名
#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要素検出ロジック説明名
<polygon> 3 verticesスペース区切りの座標ペアを数えるpike
<polygon> 4 verticestilted square
<polygon> 5 verticesquint form
<polygon> 6 verticesbee cell
<polygon> 10 verticesasterisk
<rect> (non-background)width/heightを持ち、200x200ではないtilted square
<circle>タグ名disc
<path> with fill-rule="evenodd"2つの同心円弧パスdonut
<path> with single arc + Z半円のパスhalf-disc
<path> with 6+ L commands十字/プラス形状crosshair
<path> with 3-5 L commands矢印状のシェイプpointer
半径の範囲説明名
0–12minuscule
13–16modest
17–20mid-sized
21–25substantial
26+massive
faint
SVGのrotate()値説明名
0°upright
1–20°slightly tilted
21–55°diagonal
56–75°steeply angled
76–105°sideways
  • タイプ:要素タグ+頂点数/パスコマンドから取得
  • サイズ:頂点座標または半径から取得
  • 不透明度:style="opacity:X"から取得
  • 回転:transform="... rotate(X)"から取得
  • 位置:transform="translate(X,Y) ..."から取得
  • WKX-9245FA70-200
    コンポーネント技術
    チャレンジプラットフォームBase44(ローコードアプリビルダー)
    フロントエンドReact SPA(単一JSバンドル、約410KB)
    バックエンドCloudflareの背後にあるPython/uvicorn
    CDN/プロキシCloudflare(HTTP/3、Brotli圧縮)
    認証JWT(HS256)、localStorageに保存
    データ形式SVG(base64インライン)、JSON API
    リアルタイムライブセッション更新用のWebSocket(socket.io)
    ソルバーPython 3(正規表現パース、外部ライブラリなし)
  • donut(ドーナツ型) = fill-rule="evenodd"を持つ2つの同心円弧パス
  • half-disc(半円盤) = 単一の円弧パス
  • crosshair(十字型) = 6以上の線分を持つパス(十字/プラス形状)
  • pointer(ポインター型) = 3〜5の線分を持つパス(矢印形状)