
CTF 챌린지 상세 해설: React SPA 리버스 엔지니어링, SVG 이미지 파싱, 그리디 매칭 기반 자동화 솔버 구축 및 만점 달성.
결과: 200/200 (100%) — 토큰: WKX-9245FA70-200 날짜: 2026년 3월 24일
LinkedIn 게시물을 통해 주니어 개발자 프로그램인 Wix KickstartX를 위한 특별 챌린지가 공지되었습니다. 규칙:
wix-kickstartx-challenge-2026.base44.app/ 및 wixkickstart.com메인 사이트 wixkickstart.com은 Wix에서 호스팅하는 사이트입니다(Wix의 Thunderbolt 엔진으로 서버 사이드 렌더링됨). 일반적인 curl은 JavaScript 셸만 반환할 뿐 실제 콘텐츠는 포함하지 않습니다. 이를 렌더링하기 위해 헤드리스 Chromium을 사용했습니다:
chromium --headless --disable-gpu --no-sandbox --virtual-time-budget=10000 \
--dump-dom "https://wixkickstart.com" > /tmp/wix_rendered.html
렌더링된 DOM에서 임베디드 iframe을 추출했습니다:
이것은 챌린지 자체가 아니라 p5.js 파티클 애니메이션(히어로 섹션의 시각적 장식)이었습니다. 실제 챌린지는 Base44 앱에 있습니다.
챌린지는 Base44 — 로우코드 앱 플랫폼에서 실행됩니다. 이 앱은 모든 로직이 하나의 JS 번들에 포함된 단일 페이지 React 애플리케이션입니다:
https://wix-kickstartx-challenge-2026.base44.app/assets/index-oTG160r9.js
크기: 410,809바이트(압축된 React + 앱 로직 + Base44 SDK)
JS 번들을 grep하여 전체 API를 매핑했습니다:
| 함수 | 용도 |
|---|---|
startGame | 게임 세션을 생성하고 200개의 이미지 + 200개의 설명을 반환 |
scoreGame | {matches: {}, sessionId: ""}를 받아 {correctCount: N}을 반환 |
generateToken | sessionId를 받아 완료 토큰을 반환 |
getLeaderboard | 상위 점수를 반환 |
saveNickname | 리더보드용 표시 이름을 저장 |
엔티티: GameSession, Participant
API URL 패턴:
POST /api/apps/{appId}/functions/{functionName}
앱 ID: 69aea07cbcb9a3dd1039a58d
const or = 1800; // Time limit: 1800 seconds (30 minutes)
const rm = 200; // Total items: 200 image-description pairs
중요한 발견 — 이 앱에는 JSON 대량 제출 모드가 있습니다. 압축된 소스에서:
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
}
모달의 플레이스홀더 텍스트:
{
"IMG-001": "DESC-042",
"IMG-002": "DESC-017",
...
}
즉, UI에서 200번 클릭할 필요 없이 프로그래밍 방식으로 200개 매칭 전체의 JSON 매핑을 제출할 수 있습니다.
Base44 SDK는 JWT 인증을 사용합니다:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
JWT 페이로드에는 다음이 포함됩니다:
{
"sub": "[email protected]",
"exp": 1782162460,
"iat": 1774386460
}
모든 API 호출에 필요한 헤더:
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/
POST /api/apps/69aea07cbcb9a3dd1039a58d/functions/startGame
Status: 200
Transferred: 62.87 kB compressed (2.20 MB decompressed)
Content-Encoding: br (Brotli)
응답 구조:
{
"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
}
각 이미지는 다음을 포함하는 200x200 SVG입니다:
디코딩된 SVG 예시 (IMG-001):
<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>
각 설명은 엄격한 패턴을 따릅니다:
{background} field[, overlaid with {overlay}]. {N} elements total:
{size} {opacity} {color} {shape} ({rotation}, at {position}) ·
{size} {opacity} {color} {shape} ({rotation}, at {position}) · ...
예시:
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) · ...
SVG 속성을 설명 어휘에 매핑해야 했습니다. 이를 위해 정확한 대응 관계를 찾기 위해 200개 모든 이미지와 200개 모든 설명을 분석해야 했습니다.
양쪽의 출현 횟수를 세어 1:1 매핑을 확립했습니다:
| SVG 그라데이션 시작 색상 | 개수 | 설명 단어 | 개수 |
|---|---|---|---|
#0a1628 | 28 | pitch | 28 |
#eef2f7 | 20 | frosted | 20 |
#0d0d0d | 19 | tenebrous | 19 |
#fef9f0 | 18 | pearlescent | 18 |
#1e0a2e | 18 | nocturnal | 18 |
#0a1a0a | 17 | midnight | 17 |
#1a0a0a | 15 | inky | 15 |
#f5f0eb | 14 | milky | 14 |
#f7f3ee | 14 | ethereal | 14 |
#f0f0f0 | 14 | radiant | 14 |
#f0f7f4 | 13 | glowing | 13 |
#1a1a2e | 10 | somber | 10 |
방법: 200개 모든 SVG에서 고유한 그라데이션 시작 색상을 세고, 200개 모든 설명에서 고유한 첫 단어를 센 다음 개수로 매칭합니다.
SVG 오버레이는 유형에 따라 다르게 구현됩니다:
| SVG 패턴 | 탐지 방법 | 개수 | 설명 이름 | 개수 |
|---|---|---|---|---|
<line> 요소, 수평 (dy=0) | x1,y1,x2,y2 확인 | 35 | striped overlay | 35 |
<line> 요소, 수직 (dx=0) | x1,y1,x2,y2 확인 | 34 | lattice pattern | 34 |
opacity="0.06"인 <circle> (점) | 낮은 불투명도 원 개수 세기 | 30 | stippled layer | 30 |
| 오버레이 요소가 전혀 없음 | 선 없음, 배경 도형 없음 | 28 | (없음) | 28 |
<line> 요소, 대각선 | 기울기 방향 확인 | 27 | tilted cross-lines | 27 |
opacity="0.06"인 작은 <polygon>/<path> | 낮은 불투명도 속성 도형 | 25 | arrow-band texture | 25 |
fill="none" stroke="#888"인 <circle> | stroke만 있는 원 | 21 | ringed pattern | 21 |
핵심 통찰: 배경 패턴 요소는 opacity를 XML 속성으로 사용하는 반면(예: opacity="0.06"), 전경 도형은 style 속성 내부에서 opacity를 사용합니다(예: style="opacity:0.7"). 이 구분은 배경 패턴과 실제 도형을 분리하는 데 결정적이었습니다.
200개 모든 SVG는 정확히 28개의 고유한 hex 채우기 색상을 사용하며, 200개 모든 설명은 정확히 28개의 고유한 색상 이름을 사용합니다.
| Hex | 설명 이름 | Hex | 설명 이름 | |
|---|---|---|---|---|
#708090 | 청회색 | #cd7f32 | 놋쇠색 | |
#f5f5f5 | 거의 흰색 | #dc143c | 불타는 빨강 | |
#00bcd4 | 전기 시안 | #2196f3 | 하늘색 | |
#ff6b6b | 연어 핑크 | #228b22 | 짙은 초록 | |
#98ff98 | 옅은 초록 | #b0b0b0 | 플래티넘 | |
#0f52ba | 진한 사파이어 | #800020 | 옥스블러드 | |
#ff8c00 | 진한 주황 | #4b0082 | 진한 보라 | |
#e2725b | 테라 로사 | #ffbf00 | 메리골드 | |
#b7410e | 적갈색 | #ff69b4 | 캔디 핑크 | |
#ffd700 | 밝은 금색 | #c0c0c0 | 주석색 | |
#6b8e23 | 이끼색 | #008080 | 진한 틸 | |
#40e0d0 | 옅은 틸 | #0047ab | 로열 블루 | |
#e34234 | 번트 시에나 | #a0522d | 진한 빨강 | |
#4a4a4a | 어두운 회색 | #36454f | 짙은 회색 |
| SVG 요소 | 탐지 로직 | 설명 이름 |
|---|---|---|
<polygon> 꼭짓점 3개 | 공백으로 구분된 점 쌍 개수 세기 | pike |
<polygon> 꼭짓점 4개 | tilted square | |
<polygon> 꼭짓점 5개 | quint form | |
<polygon> 꼭짓점 6개 | bee cell | |
<polygon> 꼭짓점 10개 | asterisk | |
<rect> (배경 아님) | width/height가 있고 200x200이 아님 | tilted square |
<circle> | 태그 이름 | disc |
fill-rule="evenodd"인 <path> | 두 개의 동심원 호 경로 | donut |
단일 호 + Z를 가진 <path> | 반원 경로 | half-disc |
L 명령 6개 이상인 <path> | 십자/플러스 모양 | crosshair |
L 명령 3-5개인 <path> | 화살표 모양 | pointer |
원점에서 도형 꼭짓점까지의 최대 반경 기준:
| 반경 범위 | 설명 이름 |
|---|---|
| 0–12 | minuscule |
| 13–16 | modest |
| 17–20 | mid-sized |
| 21–25 | substantial |
| 26+ | massive |
| SVG 불투명도 값 | 설명 이름 |
|---|---|
| 1.0 | solid |
| 0.85 | nearly solid |
| 0.7 | semi-transparent |
| 0.5 | faint |
| SVG rotate() 값 | 설명 이름 |
|---|---|
| 0° | upright |
| 1–20° | slightly tilted |
| 21–55° | diagonal |
| 56–75° | steeply angled |
| 76–105° | sideways |