From 64c1e21e564b45cb7fb9918fbf88fe822fd7fb36 Mon Sep 17 00:00:00 2001 From: Sherry Long Date: Wed, 18 Feb 2026 20:08:09 -0600 Subject: [PATCH 01/22] Chopped ctf --- app/ctf/miniapi/route.ts | 26 +++ app/ctf/miniapi/store.ts | 1 + app/ctf/miniapi/unlock/route.ts | 34 ++++ app/ctf/page.tsx | 289 ++++++++++++++++++++++++++++++++ 4 files changed, 350 insertions(+) create mode 100644 app/ctf/miniapi/route.ts create mode 100644 app/ctf/miniapi/store.ts create mode 100644 app/ctf/miniapi/unlock/route.ts create mode 100644 app/ctf/page.tsx diff --git a/app/ctf/miniapi/route.ts b/app/ctf/miniapi/route.ts new file mode 100644 index 00000000..0a129610 --- /dev/null +++ b/app/ctf/miniapi/route.ts @@ -0,0 +1,26 @@ +import { NextResponse } from "next/server"; + +async function sha256(text: string) { + const encoder = new TextEncoder(); + const data = encoder.encode(text); + + const hashBuffer = await crypto.subtle.digest("SHA-256", data); + + return Array.from(new Uint8Array(hashBuffer)) + .map(b => b.toString(16).padStart(2, "0")) + .join(""); +} + +const SERVER_SECRET = "top-secret-key"; + +export async function GET() { + const flag = "flag{flag-5}"; + const hiddenFlag = "flag{flag-6}"; + + const secret = await sha256(hiddenFlag + SERVER_SECRET); + + return NextResponse.json({ + flag, + secret + }); +} diff --git a/app/ctf/miniapi/store.ts b/app/ctf/miniapi/store.ts new file mode 100644 index 00000000..0efe13f2 --- /dev/null +++ b/app/ctf/miniapi/store.ts @@ -0,0 +1 @@ +export const tokenStore = new Map(); diff --git a/app/ctf/miniapi/unlock/route.ts b/app/ctf/miniapi/unlock/route.ts new file mode 100644 index 00000000..3944c827 --- /dev/null +++ b/app/ctf/miniapi/unlock/route.ts @@ -0,0 +1,34 @@ +import { NextResponse } from "next/server"; + +async function sha256(text: string) { + const encoder = new TextEncoder(); + const data = encoder.encode(text); + + const hashBuffer = await crypto.subtle.digest("SHA-256", data); + + return Array.from(new Uint8Array(hashBuffer)) + .map(b => b.toString(16).padStart(2, "0")) + .join(""); +} + +const SERVER_SECRET = "top-secret-key"; + +export async function GET(req: Request) { + const url = new URL(req.url); + const secret = url.searchParams.get("secret"); + + if (!secret) { + return NextResponse.json({ error: "Missing secret" }, { status: 400 }); + } + + const hiddenFlag = "flag{flag-6}"; + const expected = await sha256(hiddenFlag + SERVER_SECRET); + + if (secret !== expected) { + return NextResponse.json({ error: "Invalid secret" }, { status: 400 }); + } + + return NextResponse.json({ + decoded: hiddenFlag + }); +} diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx new file mode 100644 index 00000000..e4401361 --- /dev/null +++ b/app/ctf/page.tsx @@ -0,0 +1,289 @@ +"use client"; + +import ErrorSnackbar from "@/components/ErrorSnackbar/ErrorSnackbar"; +import Loading from "@/components/Loading/Loading"; +import { Box, Button, Container, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; + +export default function CTF() { + const [loading, setLoading] = useState(true); + const [showErrorAlert, setShowErrorAlert] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + + useEffect(() => { + const t = setTimeout(() => setLoading(false), 250); + + (window as any).$FETCH_FLAG$ = { + reward: "flag{flag-3}", + reveal: () => { + console.log("flag{flag-4}"); + } + }; + + (window as any).$HINT_1$ = { + hint: "Elements" + }; + + (window as any).$HINT_2$ = { + hint: "Elements -> id=UNLOCK-ME -> Uncheck display: none" + }; + + (window as any).$HINT_3$ = { + hint: "Console -> window -> $FETCH_FLAG$" + }; + + (window as any).$HINT_4$ = { + hint: "Console -> window -> $FETCH_FLAG$ -> $FETCH_FLAG$.reveal()" + }; + + (window as any).$HINT_5$ = { + hint: 'Console -> fetch("/ctf/miniapi/") -> Network -> Response' + }; + + (window as any).$HINT_6$ = { + hint: 'Console -> fetch("/ctf/miniapi/unlock?secret=xxx") -> Network -> Response' + }; + + return () => clearTimeout(t); + }, []); + + const pingMiniApi = async () => { + try { + await fetch("/ctf/miniapi"); + } catch (e: any) { + setErrorMessage(e?.message || "Failed to ping MiniAPI."); + setShowErrorAlert(true); + } + }; + + const pingMiniApiAgain = async () => { + try { + await fetch("/ctf/miniapi/unlock"); + } catch (e: any) { + setErrorMessage(e?.message || "Failed to ping MiniAPI."); + setShowErrorAlert(true); + } + }; + + if (loading) return ; + + return ( + + setShowErrorAlert(false)} + message={errorMessage} + anchorOrigin={{ vertical: "bottom", horizontal: "center" }} + /> + + {/* FLAG 1 — Elements */} + + YOUR FIRST FLAG HERE! + + + {/* Background */} + + + + + + + HackAstra CTF + + + + Instructions here! + + + + + Objective + + + Find flags in increasing difficulty. + + + + {/* FLAG 2 — “unhide me” via DevTools */} + + + {/* MiniAPI button (Network discovery) */} + + + + + {/* MiniAPI button (Network discovery) */} + + + + + + + + ); +} From 2915ba17ff11026b20e475114d071f86626e4056 Mon Sep 17 00:00:00 2001 From: Sherry Long Date: Wed, 18 Feb 2026 21:51:11 -0600 Subject: [PATCH 02/22] Remove store --- app/ctf/miniapi/store.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 app/ctf/miniapi/store.ts diff --git a/app/ctf/miniapi/store.ts b/app/ctf/miniapi/store.ts deleted file mode 100644 index 0efe13f2..00000000 --- a/app/ctf/miniapi/store.ts +++ /dev/null @@ -1 +0,0 @@ -export const tokenStore = new Map(); From cf80918ca6514f47b52b123c7025f50829069a0d Mon Sep 17 00:00:00 2001 From: Richard Xu Date: Thu, 19 Feb 2026 21:37:30 -0600 Subject: [PATCH 03/22] more flags --- app/ctf/page.tsx | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx index e4401361..ea948e90 100644 --- a/app/ctf/page.tsx +++ b/app/ctf/page.tsx @@ -20,6 +20,15 @@ export default function CTF() { } }; + (window as any).$SECRET_FLAG$ = { + get: () => atob("ZmxhZ3tmbGFnLTl9") + }; + (window as any).$HIDDEN_FLAG$ = { + unlock: () => { + console.log("flag{flag-10}"); + } + }; + (window as any).$HINT_1$ = { hint: "Elements" }; @@ -44,6 +53,22 @@ export default function CTF() { hint: 'Console -> fetch("/ctf/miniapi/unlock?secret=xxx") -> Network -> Response' }; + (window as any).$HINT_7$ = { + hint: "Elements -> id=VISIBILITY-FLAG -> Change visibility: hidden to visible" + }; + + (window as any).$HINT_8$ = { + hint: "Elements -> id=OPACITY-FLAG -> Change opacity: 0 to 1" + }; + + (window as any).$HINT_9$ = { + hint: "Console -> window -> $SECRET_FLAG$.get()" + }; + + (window as any).$HINT_10$ = { + hint: "Console -> window -> $HIDDEN_FLAG$ -> $HIDDEN_FLAG$.unlock()" + }; + return () => clearTimeout(t); }, []); @@ -188,6 +213,28 @@ export default function CTF() { > Find flags in increasing difficulty. + {/* FLAG 7 — Elements (visibility hidden) inside objective box */} + {/* FLAG 2 — “unhide me” via DevTools */} @@ -259,6 +306,28 @@ export default function CTF() { > Pong? + {/* FLAG 8 — Elements (opacity 0) under Pong button */} + + + + PRO TIPS + + + + • Use Developer Tools (F12 or right-click → + Inspect) + + + • Check Console tab for special window + objects + + + • Monitor Network tab for API responses + + + • Look for hidden elements with display: + none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"flag{flag-1}"} + ); } diff --git a/app/ctf/submit/page.tsx b/app/ctf/submit/page.tsx new file mode 100644 index 00000000..4fad6206 --- /dev/null +++ b/app/ctf/submit/page.tsx @@ -0,0 +1,685 @@ +"use client"; + +import { Box, Container, Typography, TextField, Alert } from "@mui/material"; +import { useState, useEffect } from "react"; +import { motion, Variants } from "framer-motion"; +import { GradientButton } from "@/components/GradientButton/GradientButton"; +import Link from "next/link"; +import styles from "./submit.module.scss"; + +const CORRECT_FLAGS = [ + "flag{flag-1}", + "flag{flag-2}", + "flag{flag-3}", + "flag{flag-4}", + "flag{flag-5}", + "flag{flag-6}" +]; + +const TwinklingStar = ({ + size, + top, + left, + delay, + duration +}: { + size: number; + top: string; + left: string; + delay: number; + duration: number; +}) => ( + +); + +const ShootingStar = ({ + delay, + duration +}: { + delay: number; + duration: number; +}) => ( + +); + +const FloatingPlanet = ({ + src, + top, + left, + right, + size, + delay, + duration +}: { + src: string; + top: string; + left?: string; + right?: string; + size: string; + delay: number; + duration: number; +}) => ( + +); + +const containerVariants: Variants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + delayChildren: 0.3, + staggerChildren: 0.1 + } + } +}; + +const itemVariants: Variants = { + hidden: { opacity: 0, y: 30 }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.6, + ease: [0.25, 0.46, 0.45, 0.94] + } + } +}; + +export default function CTFSubmit() { + const [flagInputs, setFlagInputs] = useState(["", "", "", "", "", ""]); + const [flagStatus, setFlagStatus] = useState<(boolean | null)[]>([ + null, + null, + null, + null, + null, + null + ]); + const [showSuccess, setShowSuccess] = useState(false); + const [stars, setStars] = useState< + { + id: number; + size: number; + top: string; + left: string; + delay: number; + duration: number; + }[] + >([]); + + useEffect(() => { + const savedFlags = localStorage.getItem("ctf_flags"); + if (savedFlags) { + const parsedFlags = JSON.parse(savedFlags); + setFlagInputs(parsedFlags); + validateFlags(parsedFlags); + } + + const generatedStars = Array.from({ length: 100 }).map((_, i) => ({ + id: i, + size: Math.random() * 4 + 1, + top: `${Math.random() * 100}%`, + left: `${Math.random() * 100}%`, + delay: Math.random() * 8, + duration: 2 + Math.random() * 3 + })); + setStars(generatedStars); + }, []); + + const validateFlags = (flags: string[]) => { + const newStatus = flags.map(flag => { + if (!flag) return null; + const isCorrect = CORRECT_FLAGS.includes(flag.trim()); + return isCorrect; + }); + setFlagStatus(newStatus); + }; + + const handleFlagChange = (index: number, value: string) => { + const newInputs = [...flagInputs]; + newInputs[index] = value; + setFlagInputs(newInputs); + + localStorage.setItem("ctf_flags", JSON.stringify(newInputs)); + validateFlags(newInputs); + + const allCorrect = + newInputs.filter(f => f && CORRECT_FLAGS.includes(f.trim())) + .length === CORRECT_FLAGS.length; + setShowSuccess(allCorrect); + }; + + const correctCount = flagStatus.filter(s => s === true).length; + const progress = (correctCount / CORRECT_FLAGS.length) * 100; + + return ( + + {stars.map(star => ( + + ))} + + + + + + + + + + + + + + + + + + + + + + + + + + + + SUBMIT YOUR FLAGS + + + + + + Enter the flags you've discovered. Your progress + will be automatically saved. Good luck! + + + + {showSuccess && ( + + + Congratulations! You've found all the flags! + + + )} + + + + + + + Progress: {correctCount} / {CORRECT_FLAGS.length}{" "} + flags + + + + + + {flagInputs.map((flag, index) => ( + + + + FLAG {index + 1} + {flagStatus[index] === true && " ✓"} + {flagStatus[index] === false && + " ✗"} + + + handleFlagChange( + index, + e.target.value + ) + } + placeholder="Enter flag..." + sx={{ + fontFamily: "Montserrat", + "& .MuiOutlinedInput-root": { + fontFamily: "Montserrat", + color: "white", + backgroundColor: + "rgba(0, 0, 0, 0.3)", + borderRadius: "8px", + "& fieldset": { + borderColor: + flagStatus[ + index + ] === true + ? "#4CAF50" + : flagStatus[ + index + ] === false + ? "#F44336" + : "rgba(255, 255, 255, 0.3)" + }, + "&:hover fieldset": { + borderColor: + flagStatus[ + index + ] === true + ? "#4CAF50" + : flagStatus[ + index + ] === false + ? "#F44336" + : "rgba(163, 21, 214, 0.5)" + }, + "&.Mui-focused fieldset": { + borderColor: + flagStatus[ + index + ] === true + ? "#4CAF50" + : flagStatus[ + index + ] === false + ? "#F44336" + : "#A315D6" + } + }, + "& .MuiInputBase-input": { + color: "white", + fontFamily: "Montserrat" + } + }} + /> + + + ))} + + + + + + + + ← Back to CTF + + + + + + + + ); +} diff --git a/app/ctf/submit/submit.module.scss b/app/ctf/submit/submit.module.scss new file mode 100644 index 00000000..b09804eb --- /dev/null +++ b/app/ctf/submit/submit.module.scss @@ -0,0 +1,55 @@ +.ctfSubmitSection { + position: relative; + width: 100%; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + background: linear-gradient(to bottom, #020316, #16133e); + z-index: 1; +} + +@keyframes shimmer { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} + +.flagCard { + background: rgba(255, 255, 255, 0.08); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 16px; + padding: 20px; + transition: all 0.3s ease; + + &:hover { + transform: translateY(-4px); + box-shadow: 0 12px 35px rgba(163, 21, 214, 0.25); + border-color: rgba(163, 21, 214, 0.4); + } +} + +.progressBar { + background: rgba(255, 255, 255, 0.1); + border-radius: 10px; + overflow: hidden; + height: 12px; + + .progressFill { + height: 100%; + background: linear-gradient(90deg, #a315d6, #fdab60, #a315d6); + background-size: 200% 100%; + animation: shimmer 2s linear infinite; + transition: width 0.5s ease; + } +} + +@media (max-width: 768px) { + .flagCard { + padding: 16px; + } +} From a0576b11e47f42a3564d25b23291a9a061f7c1c4 Mon Sep 17 00:00:00 2001 From: PrachodK Date: Sat, 21 Feb 2026 18:01:43 -0600 Subject: [PATCH 05/22] fix ctf issues and improve mobile responsiveness --- app/ctf/page.tsx | 60 +++++++++++++++++--- app/ctf/submit/page.tsx | 58 ++++++++++++++----- components/GradientButton/GradientButton.tsx | 10 +++- 3 files changed, 103 insertions(+), 25 deletions(-) diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx index 6e5df005..fc5ebd70 100644 --- a/app/ctf/page.tsx +++ b/app/ctf/page.tsx @@ -3,6 +3,7 @@ import ErrorSnackbar from "@/components/ErrorSnackbar/ErrorSnackbar"; import Loading from "@/components/Loading/Loading"; import { Box, Button, Container, Typography, TextField } from "@mui/material"; +import Link from "next/link"; import { useEffect, useState } from "react"; import { motion, Variants } from "framer-motion"; import { GradientButton } from "@/components/GradientButton/GradientButton"; @@ -123,7 +124,13 @@ const FloatingDebris = ({ maxWidth: "150px", zIndex: 5, pointerEvents: "none", - filter: "drop-shadow(0 0 10px rgba(255, 255, 255, 0.3))" + opacity: { xs: 0.4, sm: 0.6, md: 0.8, lg: 1 }, + filter: { + xs: "brightness(0.5) drop-shadow(0 0 10px rgba(255, 255, 255, 0.3))", + sm: "brightness(0.7) drop-shadow(0 0 10px rgba(255, 255, 255, 0.3))", + md: "brightness(0.8) drop-shadow(0 0 10px rgba(255, 255, 255, 0.3))", + lg: "drop-shadow(0 0 10px rgba(255, 255, 255, 0.3))" + } }} /> ); @@ -311,7 +318,13 @@ export default function CTF() { zIndex: 11, pointerEvents: "none", objectFit: "contain", - filter: "drop-shadow(0px 0px 30px rgba(238,130,205,0.8))" + opacity: { xs: 0.5, sm: 0.7, md: 0.8, lg: 1 }, + filter: { + xs: "brightness(0.5) drop-shadow(0px 0px 30px rgba(238,130,205,0.8))", + sm: "brightness(0.7) drop-shadow(0px 0px 30px rgba(238,130,205,0.8))", + md: "brightness(0.8) drop-shadow(0px 0px 30px rgba(238,130,205,0.8))", + lg: "drop-shadow(0px 0px 30px rgba(238,130,205,0.8))" + } }} /> @@ -342,7 +355,13 @@ export default function CTF() { zIndex: 11, pointerEvents: "none", objectFit: "contain", - filter: "drop-shadow(0px 0px 30px rgba(255,165,89,0.8))" + opacity: { xs: 0.5, sm: 0.7, md: 0.8, lg: 1 }, + filter: { + xs: "brightness(0.5) drop-shadow(0px 0px 30px rgba(255,165,89,0.8))", + sm: "brightness(0.7) drop-shadow(0px 0px 30px rgba(255,165,89,0.8))", + md: "brightness(0.8) drop-shadow(0px 0px 30px rgba(255,165,89,0.8))", + lg: "drop-shadow(0px 0px 30px rgba(255,165,89,0.8))" + } }} /> @@ -392,7 +411,13 @@ export default function CTF() { maxWidth: "none", zIndex: 9, pointerEvents: "none", - opacity: 0.8 + opacity: { xs: 0.3, sm: 0.5, md: 0.7, lg: 0.8 }, + filter: { + xs: "brightness(0.4)", + sm: "brightness(0.6)", + md: "brightness(0.8)", + lg: "none" + } }} /> @@ -421,7 +446,13 @@ export default function CTF() { maxWidth: "none", zIndex: 7, pointerEvents: "none", - opacity: 0.5 + opacity: { xs: 0.2, sm: 0.35, md: 0.5, lg: 0.6 }, + filter: { + xs: "brightness(0.4)", + sm: "brightness(0.6)", + md: "brightness(0.8)", + lg: "none" + } }} /> @@ -546,7 +577,13 @@ export default function CTF() { maxWidth: "none", zIndex: 6, pointerEvents: "none", - opacity: 0.4 + opacity: { xs: 0.2, sm: 0.3, md: 0.4, lg: 0.5 }, + filter: { + xs: "brightness(0.4)", + sm: "brightness(0.6)", + md: "brightness(0.8)", + lg: "none" + } }} /> @@ -575,7 +612,13 @@ export default function CTF() { maxWidth: "none", zIndex: 7, pointerEvents: "none", - opacity: 0.5 + opacity: { xs: 0.2, sm: 0.35, md: 0.5, lg: 0.6 }, + filter: { + xs: "brightness(0.4)", + sm: "brightness(0.6)", + md: "brightness(0.8)", + lg: "none" + } }} /> @@ -926,8 +969,9 @@ export default function CTF() { diff --git a/app/ctf/submit/page.tsx b/app/ctf/submit/page.tsx index 4fad6206..800244fc 100644 --- a/app/ctf/submit/page.tsx +++ b/app/ctf/submit/page.tsx @@ -101,7 +101,7 @@ const FloatingPlanet = ({ top: string; left?: string; right?: string; - size: string; + size: string | { xs: string; md: string }; delay: number; duration: number; }) => ( @@ -124,11 +124,19 @@ const FloatingPlanet = ({ top, left, right, - width: size, + width: typeof size === "string" ? size : undefined, maxWidth: "none", zIndex: 5, pointerEvents: "none", - objectFit: "contain" + objectFit: "contain", + opacity: { xs: 0.4, sm: 0.6, md: 0.8, lg: 1 }, + filter: { + xs: "brightness(0.5)", + sm: "brightness(0.7)", + md: "brightness(0.8)", + lg: "none" + }, + ...(typeof size !== "string" && { width: size }) }} /> ); @@ -198,9 +206,9 @@ export default function CTFSubmit() { }, []); const validateFlags = (flags: string[]) => { - const newStatus = flags.map(flag => { + const newStatus = flags.map((flag, index) => { if (!flag) return null; - const isCorrect = CORRECT_FLAGS.includes(flag.trim()); + const isCorrect = flag.trim() === CORRECT_FLAGS[index]; return isCorrect; }); setFlagStatus(newStatus); @@ -214,9 +222,9 @@ export default function CTFSubmit() { localStorage.setItem("ctf_flags", JSON.stringify(newInputs)); validateFlags(newInputs); - const allCorrect = - newInputs.filter(f => f && CORRECT_FLAGS.includes(f.trim())) - .length === CORRECT_FLAGS.length; + const allCorrect = newInputs.every((flag, index) => { + return flag && flag.trim() === CORRECT_FLAGS[index]; + }); setShowSuccess(allCorrect); }; @@ -246,7 +254,7 @@ export default function CTFSubmit() { src="/schedule/pink_planet.svg" top="5%" left="-5%" - size="28vw" + size={{ xs: "40vw", md: "28vw" }} delay={0} duration={7} /> @@ -255,7 +263,7 @@ export default function CTFSubmit() { src="/schedule/orange_planet.svg" top="12%" right="-8%" - size="24vw" + size={{ xs: "38vw", md: "24vw" }} delay={0.5} duration={6} /> @@ -285,7 +293,13 @@ export default function CTFSubmit() { maxWidth: "none", zIndex: 6, pointerEvents: "none", - opacity: 0.6 + opacity: { xs: 0.3, sm: 0.4, md: 0.5, lg: 0.6 }, + filter: { + xs: "brightness(0.4)", + sm: "brightness(0.6)", + md: "brightness(0.8)", + lg: "none" + } }} /> @@ -314,7 +328,13 @@ export default function CTFSubmit() { maxWidth: "none", zIndex: 7, pointerEvents: "none", - opacity: 0.7 + opacity: { xs: 0.3, sm: 0.5, md: 0.6, lg: 0.7 }, + filter: { + xs: "brightness(0.4)", + sm: "brightness(0.6)", + md: "brightness(0.8)", + lg: "none" + } }} /> @@ -343,7 +363,13 @@ export default function CTFSubmit() { maxWidth: "none", zIndex: 5, pointerEvents: "none", - opacity: 0.5 + opacity: { xs: 0.2, sm: 0.35, md: 0.4, lg: 0.5 }, + filter: { + xs: "brightness(0.4)", + sm: "brightness(0.6)", + md: "brightness(0.8)", + lg: "none" + } }} /> @@ -479,6 +505,7 @@ export default function CTFSubmit() { }} > Congratulations! You've found all the flags! + Show a staff member to redeem your points. )} @@ -564,9 +591,10 @@ export default function CTFSubmit() { }} > FLAG {index + 1} - {flagStatus[index] === true && " ✓"} + {flagStatus[index] === true && + " (correct)"} {flagStatus[index] === false && - " ✗"} + " (incorrect)"} { +export const GradientButton = ({ text, link, target }: GradientButtonProps) => { return ( - + Date: Sun, 22 Feb 2026 18:29:01 -0600 Subject: [PATCH 06/22] Fix planet/text overlap issues on CTF pages --- app/ctf/page.tsx | 36 +++++++++++--------------- app/ctf/submit/page.tsx | 56 ++++++++++------------------------------- 2 files changed, 28 insertions(+), 64 deletions(-) diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx index fc5ebd70..198672e4 100644 --- a/app/ctf/page.tsx +++ b/app/ctf/page.tsx @@ -2,13 +2,10 @@ import ErrorSnackbar from "@/components/ErrorSnackbar/ErrorSnackbar"; import Loading from "@/components/Loading/Loading"; -import { Box, Button, Container, Typography, TextField } from "@mui/material"; -import Link from "next/link"; +import { Box, Button, Container, Typography } from "@mui/material"; import { useEffect, useState } from "react"; import { motion, Variants } from "framer-motion"; import { GradientButton } from "@/components/GradientButton/GradientButton"; -import Image from "next/image"; -import styles from "./page.module.scss"; const TwinklingStar = ({ size, @@ -310,9 +307,10 @@ export default function CTF() { top: { xs: "5%", md: "3%" }, left: { xs: "-5%", md: "-8%" }, width: { - xs: "35vw", - sm: "28vw", - md: "25vw" + xs: "26vw", + sm: "22vw", + md: "20vw", + lg: "22vw" }, maxWidth: "none", zIndex: 11, @@ -365,16 +363,6 @@ export default function CTF() { }} /> - - Explore, discover, and uncover hidden secrets - embedded in this page. Use your browser's developer - tools and keen observation skills to find all flags. - Every flag you find brings you closer to victory! + embedded in this page. Use your browser's + developer tools and keen observation skills to find + all flags. Every flag you find brings you closer to + victory! diff --git a/app/ctf/submit/page.tsx b/app/ctf/submit/page.tsx index 800244fc..5a0fca07 100644 --- a/app/ctf/submit/page.tsx +++ b/app/ctf/submit/page.tsx @@ -3,7 +3,6 @@ import { Box, Container, Typography, TextField, Alert } from "@mui/material"; import { useState, useEffect } from "react"; import { motion, Variants } from "framer-motion"; -import { GradientButton } from "@/components/GradientButton/GradientButton"; import Link from "next/link"; import styles from "./submit.module.scss"; @@ -101,7 +100,7 @@ const FloatingPlanet = ({ top: string; left?: string; right?: string; - size: string | { xs: string; md: string }; + size: string | { xs?: string; sm?: string; md?: string; lg?: string }; delay: number; duration: number; }) => ( @@ -254,7 +253,7 @@ export default function CTFSubmit() { src="/schedule/pink_planet.svg" top="5%" left="-5%" - size={{ xs: "40vw", md: "28vw" }} + size={{ xs: "24vw", sm: "20vw", md: "18vw", lg: "20vw" }} delay={0} duration={7} /> @@ -268,41 +267,6 @@ export default function CTFSubmit() { duration={6} /> - - - Enter the flags you've discovered. Your progress - will be automatically saved. Good luck! + Enter the flags you've discovered. Your + progress will be automatically saved. Good luck! @@ -504,8 +473,9 @@ export default function CTFSubmit() { fontFamily: "Montserrat" }} > - Congratulations! You've found all the flags! - Show a staff member to redeem your points. + Congratulations! You've found all the + flags! Show a staff member to redeem your + points. )} From 17a00528296ecaec07250c9e0f3266f2bcd885c0 Mon Sep 17 00:00:00 2001 From: Jasmine Date: Mon, 23 Feb 2026 22:30:24 -0600 Subject: [PATCH 07/22] fixed conflicts --- app/ctf/miniapi/beam/route.ts | 17 ++++++ app/ctf/miniapi/decrypt/stepA/route.ts | 13 +++++ app/ctf/miniapi/decrypt/stepB/route.ts | 13 +++++ app/ctf/miniapi/decrypt/stepC/route.ts | 13 +++++ app/ctf/miniapi/decrypt/stepD/route.ts | 13 +++++ app/ctf/miniapi/decrypt/verify/route.ts | 40 +++++++++++++ app/ctf/page.tsx | 77 +++++++++++++++++++++++++ 7 files changed, 186 insertions(+) create mode 100644 app/ctf/miniapi/beam/route.ts create mode 100644 app/ctf/miniapi/decrypt/stepA/route.ts create mode 100644 app/ctf/miniapi/decrypt/stepB/route.ts create mode 100644 app/ctf/miniapi/decrypt/stepC/route.ts create mode 100644 app/ctf/miniapi/decrypt/stepD/route.ts create mode 100644 app/ctf/miniapi/decrypt/verify/route.ts diff --git a/app/ctf/miniapi/beam/route.ts b/app/ctf/miniapi/beam/route.ts new file mode 100644 index 00000000..ec67e908 --- /dev/null +++ b/app/ctf/miniapi/beam/route.ts @@ -0,0 +1,17 @@ +import { NextResponse } from "next/server"; + +export async function GET(req: Request) { + const access = req.headers.get("X-CTF-Access"); + + if (access !== "granted") { + return NextResponse.json( + { + error: "Access denied", + hint: "Try again with header X-CTF-Access: granted" + }, + { status: 401 } + ); + } + + return NextResponse.json({ flag: "flag{flag-11}" }); +} diff --git a/app/ctf/miniapi/decrypt/stepA/route.ts b/app/ctf/miniapi/decrypt/stepA/route.ts new file mode 100644 index 00000000..aac8e5d8 --- /dev/null +++ b/app/ctf/miniapi/decrypt/stepA/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from "next/server"; + +export async function GET() { + return NextResponse.json({ + fragment: "33d8fb44", + meta: { + processedAt: 1700000300, + serverId: "node-7f3a", + region: "us-east-2", + latency: 42 + } + }); +} diff --git a/app/ctf/miniapi/decrypt/stepB/route.ts b/app/ctf/miniapi/decrypt/stepB/route.ts new file mode 100644 index 00000000..6fddf5a2 --- /dev/null +++ b/app/ctf/miniapi/decrypt/stepB/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from "next/server"; + +export async function GET() { + return NextResponse.json({ + fragment: "38", + meta: { + processedAt: 1700000100, + serverId: "node-2b9c", + region: "us-west-1", + latency: 38 + } + }); +} diff --git a/app/ctf/miniapi/decrypt/stepC/route.ts b/app/ctf/miniapi/decrypt/stepC/route.ts new file mode 100644 index 00000000..e432c193 --- /dev/null +++ b/app/ctf/miniapi/decrypt/stepC/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from "next/server"; + +export async function GET() { + return NextResponse.json({ + fragment: "25613627b90500", + meta: { + processedAt: 1700000400, + serverId: "node-1a4d", + region: "eu-central-1", + latency: 91 + } + }); +} diff --git a/app/ctf/miniapi/decrypt/stepD/route.ts b/app/ctf/miniapi/decrypt/stepD/route.ts new file mode 100644 index 00000000..ec46ae33 --- /dev/null +++ b/app/ctf/miniapi/decrypt/stepD/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from "next/server"; + +export async function GET() { + return NextResponse.json({ + fragment: "28aa3a60", + meta: { + processedAt: 1700000200, + serverId: "node-9e1f", + region: "ap-southeast-1", + latency: 67 + } + }); +} diff --git a/app/ctf/miniapi/decrypt/verify/route.ts b/app/ctf/miniapi/decrypt/verify/route.ts new file mode 100644 index 00000000..e57ab299 --- /dev/null +++ b/app/ctf/miniapi/decrypt/verify/route.ts @@ -0,0 +1,40 @@ +import { NextResponse } from "next/server"; + +async function sha256(text: string) { + const encoder = new TextEncoder(); + const data = encoder.encode(text); + const hashBuffer = await crypto.subtle.digest("SHA-256", data); + return Array.from(new Uint8Array(hashBuffer)) + .map(b => b.toString(16).padStart(2, "0")) + .join(""); +} + +const SERVER_SECRET = "top-secret-key"; +const RECONSTRUCTED_SECRET = "3828aa3a6033d8fb4425613627b90500"; + +export async function GET(req: Request) { + const url = new URL(req.url); + const reconstructed = url.searchParams.get("secret"); + + if (!reconstructed) { + return NextResponse.json( + { + error: "Missing secret param", + hint: "Submit reconstructed fragments as ?secret=..." + }, + { status: 400 } + ); + } + + if (reconstructed !== RECONSTRUCTED_SECRET) { + return NextResponse.json( + { error: "Incorrect secret" }, + { status: 400 } + ); + } + + const hiddenFlag = "flag{3828aa3a6033d8fb4425613627b90500}"; + const flag = await sha256(hiddenFlag + SERVER_SECRET); + + return NextResponse.json({ flag }); +} diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx index 198672e4..83cd35af 100644 --- a/app/ctf/page.tsx +++ b/app/ctf/page.tsx @@ -260,6 +260,27 @@ export default function CTF() { } }; + const beamMiniApi = async () => { + try { + await fetch("/ctf/miniapi/beam"); + } catch (e: any) { + setErrorMessage(e?.message || "Failed to beam."); + setShowErrorAlert(true); + } + }; + + const decryptSignal = async () => { + try { + fetch("/ctf/miniapi/decrypt/stepA"); + fetch("/ctf/miniapi/decrypt/stepB"); + fetch("/ctf/miniapi/decrypt/stepC"); + fetch("/ctf/miniapi/decrypt/stepD"); + } catch (e: any) { + setErrorMessage(e?.message || "Failed to decrypt."); + setShowErrorAlert(true); + } + }; + if (loading) return ; return ( @@ -936,6 +957,62 @@ export default function CTF() { Pong? + + + + + + Date: Tue, 24 Feb 2026 03:41:54 -0600 Subject: [PATCH 08/22] Finalize CTF --- app/ctf/miniapi/beam/route.ts | 17 - app/ctf/miniapi/decrypt/verify/route.ts | 40 -- .../{decrypt/stepA => packet1}/route.ts | 3 +- .../{decrypt/stepB => packet2}/route.ts | 3 +- .../{decrypt/stepC => packet3}/route.ts | 3 +- .../{decrypt/stepD => packet4}/route.ts | 3 +- app/ctf/miniapi/route.ts | 4 +- app/ctf/miniapi/unlock/route.ts | 28 +- app/ctf/page.tsx | 504 +++++++++--------- 9 files changed, 271 insertions(+), 334 deletions(-) delete mode 100644 app/ctf/miniapi/beam/route.ts delete mode 100644 app/ctf/miniapi/decrypt/verify/route.ts rename app/ctf/miniapi/{decrypt/stepA => packet1}/route.ts (80%) rename app/ctf/miniapi/{decrypt/stepB => packet2}/route.ts (81%) rename app/ctf/miniapi/{decrypt/stepC => packet3}/route.ts (82%) rename app/ctf/miniapi/{decrypt/stepD => packet4}/route.ts (80%) diff --git a/app/ctf/miniapi/beam/route.ts b/app/ctf/miniapi/beam/route.ts deleted file mode 100644 index ec67e908..00000000 --- a/app/ctf/miniapi/beam/route.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { NextResponse } from "next/server"; - -export async function GET(req: Request) { - const access = req.headers.get("X-CTF-Access"); - - if (access !== "granted") { - return NextResponse.json( - { - error: "Access denied", - hint: "Try again with header X-CTF-Access: granted" - }, - { status: 401 } - ); - } - - return NextResponse.json({ flag: "flag{flag-11}" }); -} diff --git a/app/ctf/miniapi/decrypt/verify/route.ts b/app/ctf/miniapi/decrypt/verify/route.ts deleted file mode 100644 index e57ab299..00000000 --- a/app/ctf/miniapi/decrypt/verify/route.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { NextResponse } from "next/server"; - -async function sha256(text: string) { - const encoder = new TextEncoder(); - const data = encoder.encode(text); - const hashBuffer = await crypto.subtle.digest("SHA-256", data); - return Array.from(new Uint8Array(hashBuffer)) - .map(b => b.toString(16).padStart(2, "0")) - .join(""); -} - -const SERVER_SECRET = "top-secret-key"; -const RECONSTRUCTED_SECRET = "3828aa3a6033d8fb4425613627b90500"; - -export async function GET(req: Request) { - const url = new URL(req.url); - const reconstructed = url.searchParams.get("secret"); - - if (!reconstructed) { - return NextResponse.json( - { - error: "Missing secret param", - hint: "Submit reconstructed fragments as ?secret=..." - }, - { status: 400 } - ); - } - - if (reconstructed !== RECONSTRUCTED_SECRET) { - return NextResponse.json( - { error: "Incorrect secret" }, - { status: 400 } - ); - } - - const hiddenFlag = "flag{3828aa3a6033d8fb4425613627b90500}"; - const flag = await sha256(hiddenFlag + SERVER_SECRET); - - return NextResponse.json({ flag }); -} diff --git a/app/ctf/miniapi/decrypt/stepA/route.ts b/app/ctf/miniapi/packet1/route.ts similarity index 80% rename from app/ctf/miniapi/decrypt/stepA/route.ts rename to app/ctf/miniapi/packet1/route.ts index aac8e5d8..37b3c670 100644 --- a/app/ctf/miniapi/decrypt/stepA/route.ts +++ b/app/ctf/miniapi/packet1/route.ts @@ -2,7 +2,8 @@ import { NextResponse } from "next/server"; export async function GET() { return NextResponse.json({ - fragment: "33d8fb44", + fragment: "c5068e0aab8cc42f123", + param: "signal", meta: { processedAt: 1700000300, serverId: "node-7f3a", diff --git a/app/ctf/miniapi/decrypt/stepB/route.ts b/app/ctf/miniapi/packet2/route.ts similarity index 81% rename from app/ctf/miniapi/decrypt/stepB/route.ts rename to app/ctf/miniapi/packet2/route.ts index 6fddf5a2..1b1d78e8 100644 --- a/app/ctf/miniapi/decrypt/stepB/route.ts +++ b/app/ctf/miniapi/packet2/route.ts @@ -2,7 +2,8 @@ import { NextResponse } from "next/server"; export async function GET() { return NextResponse.json({ - fragment: "38", + fragment: "7dd09094d1380", + param: "signal", meta: { processedAt: 1700000100, serverId: "node-2b9c", diff --git a/app/ctf/miniapi/decrypt/stepC/route.ts b/app/ctf/miniapi/packet3/route.ts similarity index 82% rename from app/ctf/miniapi/decrypt/stepC/route.ts rename to app/ctf/miniapi/packet3/route.ts index e432c193..549c0cd5 100644 --- a/app/ctf/miniapi/decrypt/stepC/route.ts +++ b/app/ctf/miniapi/packet3/route.ts @@ -2,7 +2,8 @@ import { NextResponse } from "next/server"; export async function GET() { return NextResponse.json({ - fragment: "25613627b90500", + fragment: "acb2136c08b", + param: "signal", meta: { processedAt: 1700000400, serverId: "node-1a4d", diff --git a/app/ctf/miniapi/decrypt/stepD/route.ts b/app/ctf/miniapi/packet4/route.ts similarity index 80% rename from app/ctf/miniapi/decrypt/stepD/route.ts rename to app/ctf/miniapi/packet4/route.ts index ec46ae33..576921db 100644 --- a/app/ctf/miniapi/decrypt/stepD/route.ts +++ b/app/ctf/miniapi/packet4/route.ts @@ -2,7 +2,8 @@ import { NextResponse } from "next/server"; export async function GET() { return NextResponse.json({ - fragment: "28aa3a60", + fragment: "b0b4e856121957afd0eeb", + param: "signal", meta: { processedAt: 1700000200, serverId: "node-9e1f", diff --git a/app/ctf/miniapi/route.ts b/app/ctf/miniapi/route.ts index 0a129610..9b1b768f 100644 --- a/app/ctf/miniapi/route.ts +++ b/app/ctf/miniapi/route.ts @@ -14,8 +14,8 @@ async function sha256(text: string) { const SERVER_SECRET = "top-secret-key"; export async function GET() { - const flag = "flag{flag-5}"; - const hiddenFlag = "flag{flag-6}"; + const flag = "hackctf{flag7-m1n14p1}"; + const hiddenFlag = "hackctf{flag8-4p1m4573r}"; const secret = await sha256(hiddenFlag + SERVER_SECRET); diff --git a/app/ctf/miniapi/unlock/route.ts b/app/ctf/miniapi/unlock/route.ts index 3944c827..f5f93908 100644 --- a/app/ctf/miniapi/unlock/route.ts +++ b/app/ctf/miniapi/unlock/route.ts @@ -16,16 +16,34 @@ const SERVER_SECRET = "top-secret-key"; export async function GET(req: Request) { const url = new URL(req.url); const secret = url.searchParams.get("secret"); + const signal = url.searchParams.get("signal"); - if (!secret) { - return NextResponse.json({ error: "Missing secret" }, { status: 400 }); + if (!secret && !signal) { + return NextResponse.json( + { error: "Missing query parameter" }, + { status: 400 } + ); } - const hiddenFlag = "flag{flag-6}"; + if (secret && signal) { + return NextResponse.json( + { error: "Only one query parameter allowed" }, + { status: 400 } + ); + } + + let param = secret ? secret : signal; + let hiddenFlag = secret + ? "hackctf{flag8-4p1m4573r}" + : "hackctf{c0ngr475y0ub3477h3c7f}"; + const expected = await sha256(hiddenFlag + SERVER_SECRET); - if (secret !== expected) { - return NextResponse.json({ error: "Invalid secret" }, { status: 400 }); + if (param !== expected) { + return NextResponse.json( + { error: "Parameter value is incorrect" }, + { status: 400 } + ); } return NextResponse.json({ diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx index 83cd35af..af035de8 100644 --- a/app/ctf/page.tsx +++ b/app/ctf/page.tsx @@ -2,7 +2,15 @@ import ErrorSnackbar from "@/components/ErrorSnackbar/ErrorSnackbar"; import Loading from "@/components/Loading/Loading"; -import { Box, Button, Container, Typography } from "@mui/material"; +import { + Box, + Button, + Container, + Typography, + Accordion, + AccordionDetails, + AccordionSummary +} from "@mui/material"; import { useEffect, useState } from "react"; import { motion, Variants } from "framer-motion"; import { GradientButton } from "@/components/GradientButton/GradientButton"; @@ -183,60 +191,60 @@ export default function CTF() { })); setStars(generatedStars); - (window as any).$FETCH_FLAG$ = { - reward: "flag{flag-3}", + (window as any).$SECRET$ = { + flag: "hackctf{flag4-c0n501353cr37}", reveal: () => { - console.log("flag{flag-4}"); + console.log("hackctf{flag5-c41170pr1n7}"); + }, + password: (password: number) => { + if (password === 64) { + console.log("aGFja2N0ZntmbGFnNi1kM2MwZDNtM30="); + } else { + console.log( + "Authentication failed. What's the magic number?" + ); + } } }; - (window as any).$SECRET_FLAG$ = { - get: () => atob("ZmxhZ3tmbGFnLTl9") - }; - (window as any).$HIDDEN_FLAG$ = { - unlock: () => { - console.log("flag{flag-10}"); - } + (window as any).$MAGIC_NUMBER$ = { + number: 64 }; (window as any).$HINT_1$ = { - hint: "Elements" + hint: "I'm in Elements!" }; (window as any).$HINT_2$ = { - hint: "Elements -> id=UNLOCK-ME -> Uncheck display: none" + hint: "You might need to REVEAL-ME by checking out the CSS" }; (window as any).$HINT_3$ = { - hint: "Console -> window -> $FETCH_FLAG$" + hint: "Check your comms, you received a message! You might need to FIND-ME in the DOM" }; (window as any).$HINT_4$ = { - hint: "Console -> window -> $FETCH_FLAG$ -> $FETCH_FLAG$.reveal()" + hint: "There might be more than hints stored in the console..." }; (window as any).$HINT_5$ = { - hint: 'Console -> fetch("/ctf/miniapi/") -> Network -> Response' + hint: "f is for function!" }; (window as any).$HINT_6$ = { - hint: 'Console -> fetch("/ctf/miniapi/unlock?secret=xxx") -> Network -> Response' + hint: "Your browser knows how to decode me" }; (window as any).$HINT_7$ = { - hint: "Elements -> id=VISIBILITY-FLAG -> Change visibility: hidden to visible" + hint: "Psst.. I heard there’s a secret API. Maybe you can ping it from your comms" }; (window as any).$HINT_8$ = { - hint: "Elements -> id=OPACITY-FLAG -> Change opacity: 0 to 1" + hint: "A new endpoint! This one requires a secret..." }; (window as any).$HINT_9$ = { - hint: "Console -> window -> $SECRET_FLAG$.get()" - }; - - (window as any).$HINT_10$ = { - hint: "Console -> window -> $HIDDEN_FLAG$ -> $HIDDEN_FLAG$.unlock()" + hint: "A signal has been scrambled across multiple transmissions. The truth is in the timing" }; return () => clearTimeout(t); @@ -260,21 +268,16 @@ export default function CTF() { } }; - const beamMiniApi = async () => { - try { - await fetch("/ctf/miniapi/beam"); - } catch (e: any) { - setErrorMessage(e?.message || "Failed to beam."); - setShowErrorAlert(true); - } - }; - const decryptSignal = async () => { try { - fetch("/ctf/miniapi/decrypt/stepA"); - fetch("/ctf/miniapi/decrypt/stepB"); - fetch("/ctf/miniapi/decrypt/stepC"); - fetch("/ctf/miniapi/decrypt/stepD"); + const steps = ["packet1", "packet2", "packet3", "packet4"]; + for (let i = steps.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [steps[i], steps[j]] = [steps[j], steps[i]]; + } + steps.map(step => { + fetch(`/ctf/miniapi/${step}`); + }); } catch (e: any) { setErrorMessage(e?.message || "Failed to decrypt."); setShowErrorAlert(true); @@ -641,7 +644,7 @@ export default function CTF() { display: "flex", flexDirection: "column", alignItems: "center", - justifyContent: "center", + justifyContent: "flex-start", textAlign: "center", py: 6 }} @@ -749,35 +752,50 @@ export default function CTF() { fontFamily: "Montserrat", fontSize: { xs: "14px", md: "16px" }, color: "rgba(255, 255, 255, 0.85)", - lineHeight: 1.7 + lineHeight: 1.7, + mb: 2 }} > - Find all 10 hidden flags scattered throughout - this page. Each flag is hidden using different - techniques — from inspecting elements to - exploring network requests and console commands. + Find all 9 hidden flags scattered throughout + this page. To get started, right-click anywhere + on the page and select Inspect. - - + > + + Need hints? + + + + Luckily, your mission commander has left you + some clues. In your browser's Developer + Tools, navigate to the Console tab. + Type $HINT_1$ and press Enter to + reveal the hint. This works for all 9 hints! + + @@ -813,230 +831,184 @@ export default function CTF() { mb: 2 }} > - PRO TIPS + COMMS - - • Use Developer Tools (F12 or right-click → - Inspect) - - - • Check Console tab for special window - objects - - - • Monitor Network tab for API responses - - - • Look for hidden elements with display: - none - + YOU HAVE 1 MESSAGE - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - + + + + + {"YOUR FIRST FLAG HERE: hackctf{flag1-p141n51gh7}"} + - - - {"flag{flag-1}"} - ); } From db1589ab9e1e2bc1e2e5df59fedb9f7ac711aee0 Mon Sep 17 00:00:00 2001 From: Sherry Long Date: Tue, 24 Feb 2026 03:54:06 -0600 Subject: [PATCH 09/22] Fix flag --- app/ctf/miniapi/packet1/route.ts | 2 +- app/ctf/miniapi/packet2/route.ts | 2 +- app/ctf/miniapi/packet3/route.ts | 2 +- app/ctf/miniapi/packet4/route.ts | 2 +- app/ctf/miniapi/unlock/route.ts | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/ctf/miniapi/packet1/route.ts b/app/ctf/miniapi/packet1/route.ts index 37b3c670..72f7821c 100644 --- a/app/ctf/miniapi/packet1/route.ts +++ b/app/ctf/miniapi/packet1/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; export async function GET() { return NextResponse.json({ - fragment: "c5068e0aab8cc42f123", + fragment: "8720414d7ad2bdb65cd9", param: "signal", meta: { processedAt: 1700000300, diff --git a/app/ctf/miniapi/packet2/route.ts b/app/ctf/miniapi/packet2/route.ts index 1b1d78e8..76574869 100644 --- a/app/ctf/miniapi/packet2/route.ts +++ b/app/ctf/miniapi/packet2/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; export async function GET() { return NextResponse.json({ - fragment: "7dd09094d1380", + fragment: "5b0125706439da", param: "signal", meta: { processedAt: 1700000100, diff --git a/app/ctf/miniapi/packet3/route.ts b/app/ctf/miniapi/packet3/route.ts index 549c0cd5..a5430530 100644 --- a/app/ctf/miniapi/packet3/route.ts +++ b/app/ctf/miniapi/packet3/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; export async function GET() { return NextResponse.json({ - fragment: "acb2136c08b", + fragment: "ee3111b1dd03a7", param: "signal", meta: { processedAt: 1700000400, diff --git a/app/ctf/miniapi/packet4/route.ts b/app/ctf/miniapi/packet4/route.ts index 576921db..8efde622 100644 --- a/app/ctf/miniapi/packet4/route.ts +++ b/app/ctf/miniapi/packet4/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; export async function GET() { return NextResponse.json({ - fragment: "b0b4e856121957afd0eeb", + fragment: "1420c4108b01de39", param: "signal", meta: { processedAt: 1700000200, diff --git a/app/ctf/miniapi/unlock/route.ts b/app/ctf/miniapi/unlock/route.ts index f5f93908..4eb66dda 100644 --- a/app/ctf/miniapi/unlock/route.ts +++ b/app/ctf/miniapi/unlock/route.ts @@ -35,9 +35,10 @@ export async function GET(req: Request) { let param = secret ? secret : signal; let hiddenFlag = secret ? "hackctf{flag8-4p1m4573r}" - : "hackctf{c0ngr475y0ub3477h3c7f}"; + : "hackctf{flag9-c0ngr475y0ub3477h3c7f}"; const expected = await sha256(hiddenFlag + SERVER_SECRET); + console.log(expected); if (param !== expected) { return NextResponse.json( From 7680352c740be36ce260379a1a3caf4b7c8cdb30 Mon Sep 17 00:00:00 2001 From: PrachodK Date: Tue, 24 Feb 2026 14:47:18 -0600 Subject: [PATCH 10/22] Add accordion dropdown arrow to CTF hints section --- app/ctf/page.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx index af035de8..d58aa27c 100644 --- a/app/ctf/page.tsx +++ b/app/ctf/page.tsx @@ -11,6 +11,7 @@ import { AccordionDetails, AccordionSummary } from "@mui/material"; +import { ExpandMore } from "@mui/icons-material"; import { useEffect, useState } from "react"; import { motion, Variants } from "framer-motion"; import { GradientButton } from "@/components/GradientButton/GradientButton"; @@ -772,6 +773,9 @@ export default function CTF() { }} > + } sx={{ fontFamily: "Tsukimi Rounded", fontWeight: 600, From a11069bfa61e7150344056e027ff8ebc63ca8a88 Mon Sep 17 00:00:00 2001 From: Sherry Long Date: Tue, 24 Feb 2026 17:47:35 -0600 Subject: [PATCH 11/22] Add better hint --- app/ctf/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx index d58aa27c..5a1e3cb3 100644 --- a/app/ctf/page.tsx +++ b/app/ctf/page.tsx @@ -225,7 +225,7 @@ export default function CTF() { }; (window as any).$HINT_4$ = { - hint: "There might be more than hints stored in the console..." + hint: "There might be more than hints stored in the console.. Try entering \'window\'" }; (window as any).$HINT_5$ = { From 378e0d837bef790eba3185aec8422609602a789d Mon Sep 17 00:00:00 2001 From: Sherry Long Date: Tue, 24 Feb 2026 18:59:40 -0600 Subject: [PATCH 12/22] Fix ping button alignment and update objective --- app/ctf/page.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx index 5a1e3cb3..90fc763d 100644 --- a/app/ctf/page.tsx +++ b/app/ctf/page.tsx @@ -759,7 +759,9 @@ export default function CTF() { > Find all 9 hidden flags scattered throughout this page. To get started, right-click anywhere - on the page and select Inspect. + on the page and select Inspect. Keep an + eye on the Elements, Console, and{" "} + Network tabs. - YOU HAVE 1 MESSAGE + YOU HAVE 1 MESSAGE: Date: Tue, 24 Feb 2026 20:44:05 -0600 Subject: [PATCH 13/22] Add point distribution and flag format info to CTF submit page --- app/ctf/submit/page.tsx | 233 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 212 insertions(+), 21 deletions(-) diff --git a/app/ctf/submit/page.tsx b/app/ctf/submit/page.tsx index 5a0fca07..f166c92f 100644 --- a/app/ctf/submit/page.tsx +++ b/app/ctf/submit/page.tsx @@ -12,9 +12,33 @@ const CORRECT_FLAGS = [ "flag{flag-3}", "flag{flag-4}", "flag{flag-5}", - "flag{flag-6}" + "flag{flag-6}", + "flag{flag-7}", + "flag{flag-8}", + "flag{flag-9}" ]; +const FLAG_POINTS = [ + { range: "1-3", points: 2, color: "#4CAF50" }, + { range: "4-6", points: 6, color: "#2196F3" }, + { range: "7-8", points: 10, color: "#FF9800" }, + { range: "9", points: 16, color: "#E91E63" } +]; + +const getFlagPoints = (index: number): number => { + if (index < 3) return 2; + if (index < 6) return 6; + if (index < 8) return 10; + return 16; +}; + +const getFlagColor = (index: number): string => { + if (index < 3) return "#4CAF50"; + if (index < 6) return "#2196F3"; + if (index < 8) return "#FF9800"; + return "#E91E63"; +}; + const TwinklingStar = ({ size, top, @@ -164,13 +188,26 @@ const itemVariants: Variants = { }; export default function CTFSubmit() { - const [flagInputs, setFlagInputs] = useState(["", "", "", "", "", ""]); + const [flagInputs, setFlagInputs] = useState([ + "", + "", + "", + "", + "", + "", + "", + "", + "" + ]); const [flagStatus, setFlagStatus] = useState<(boolean | null)[]>([ null, null, null, null, null, + null, + null, + null, null ]); const [showSuccess, setShowSuccess] = useState(false); @@ -445,7 +482,7 @@ export default function CTFSubmit() { fontSize: { xs: "16px", md: "18px" }, color: "rgba(255, 255, 255, 0.85)", textAlign: "center", - mb: 4, + mb: 2, maxWidth: "600px", mx: "auto" }} @@ -455,6 +492,137 @@ export default function CTFSubmit() { + + + + Flag Format + + + hackctf{"{flag-name-here}"} + + + You can submit flags in any order! + + + + + + + + Point Distribution + + + {FLAG_POINTS.map((item, index) => ( + + + Flag + {item.range.includes("-") + ? "s" + : ""}{" "} + {item.range} + + + {item.points}pts + + + ))} + + + + {showSuccess && ( - - FLAG {index + 1} - {flagStatus[index] === true && - " (correct)"} - {flagStatus[index] === false && - " (incorrect)"} - + + FLAG {index + 1} + {flagStatus[index] === true && + " (correct)"} + {flagStatus[index] === false && + " (incorrect)"} + + + {getFlagPoints(index)}pts + + Date: Tue, 24 Feb 2026 22:27:26 -0600 Subject: [PATCH 14/22] Fix correct flags and show all 9 flags --- app/ctf/submit/page.tsx | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/app/ctf/submit/page.tsx b/app/ctf/submit/page.tsx index f166c92f..1cd797de 100644 --- a/app/ctf/submit/page.tsx +++ b/app/ctf/submit/page.tsx @@ -7,15 +7,15 @@ import Link from "next/link"; import styles from "./submit.module.scss"; const CORRECT_FLAGS = [ - "flag{flag-1}", - "flag{flag-2}", - "flag{flag-3}", - "flag{flag-4}", - "flag{flag-5}", - "flag{flag-6}", - "flag{flag-7}", - "flag{flag-8}", - "flag{flag-9}" + "hackctf{flag1-p141n51gh7}", + "hackctf{flag2-1nv151b13}", + "hackctf{flag3-53cr37m3554g3}", + "hackctf{flag4-c0n501353cr37}", + "hackctf{flag5-c41170pr1n7}", + "hackctf{flag6-d3c0d3m3}", + "hackctf{flag7-m1n14p1}", + "hackctf{flag8-4p1m4573r}", + "hackctf{flag9-c0ngr475y0ub3477h3c7f}" ]; const FLAG_POINTS = [ @@ -226,8 +226,11 @@ export default function CTFSubmit() { const savedFlags = localStorage.getItem("ctf_flags"); if (savedFlags) { const parsedFlags = JSON.parse(savedFlags); - setFlagInputs(parsedFlags); - validateFlags(parsedFlags); + const allFlags = Array(9) + .fill("") + .map((_, i) => parsedFlags[i] || ""); + setFlagInputs(allFlags); + validateFlags(allFlags); } const generatedStars = Array.from({ length: 100 }).map((_, i) => ({ @@ -242,11 +245,14 @@ export default function CTFSubmit() { }, []); const validateFlags = (flags: string[]) => { - const newStatus = flags.map((flag, index) => { - if (!flag) return null; - const isCorrect = flag.trim() === CORRECT_FLAGS[index]; - return isCorrect; - }); + const newStatus = Array(9) + .fill(null) + .map((_, index) => { + const flag = flags[index]; + if (!flag) return null; + const isCorrect = flag.trim() === CORRECT_FLAGS[index]; + return isCorrect; + }); setFlagStatus(newStatus); }; From ddaefb9f0b924e734ffec80448f19473e90cfe8d Mon Sep 17 00:00:00 2001 From: PrachodK Date: Wed, 25 Feb 2026 12:23:22 -0600 Subject: [PATCH 15/22] Add copy button for flag 2, move submission button to top, add level headers to submit page --- app/ctf/page.tsx | 65 +++++-- app/ctf/submit/page.tsx | 365 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 410 insertions(+), 20 deletions(-) diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx index 90fc763d..59a31f90 100644 --- a/app/ctf/page.tsx +++ b/app/ctf/page.tsx @@ -698,7 +698,7 @@ export default function CTF() { color: "rgba(255, 255, 255, 0.9)", maxWidth: "700px", mx: "auto", - mb: 4, + mb: 2, lineHeight: 1.8 }} > @@ -710,6 +710,16 @@ export default function CTF() { + + + + + + + + You found a flag: hackctf{"{flag2-1nv151b13}"} + + + {"YOUR FIRST FLAG HERE: hackctf{flag1-p141n51gh7}"} diff --git a/app/ctf/submit/page.tsx b/app/ctf/submit/page.tsx index 1cd797de..89cbfc8e 100644 --- a/app/ctf/submit/page.tsx +++ b/app/ctf/submit/page.tsx @@ -682,6 +682,18 @@ export default function CTFSubmit() { variants={itemVariants} style={{ width: "100%" }} > + + Level 1 + - {flagInputs.map((flag, index) => ( + {flagInputs.slice(0, 3).map((flag, index) => ( ))} + + + Level 2 + + + {flagInputs.slice(3, 6).map((flag, idx) => { + const index = idx + 3; + return ( + + + + + FLAG {index + 1} + {flagStatus[index] === + true && " (correct)"} + {flagStatus[index] === + false && " (incorrect)"} + + + {getFlagPoints(index)}pts + + + + handleFlagChange( + index, + e.target.value + ) + } + placeholder="Enter flag..." + sx={{ + fontFamily: "Montserrat", + "& .MuiOutlinedInput-root": + { + fontFamily: + "Montserrat", + color: "white", + backgroundColor: + "rgba(0, 0, 0, 0.3)", + borderRadius: "8px", + "& fieldset": { + borderColor: + flagStatus[ + index + ] === true + ? "#4CAF50" + : flagStatus[ + index + ] === + false + ? "#F44336" + : "rgba(255, 255, 255, 0.3)" + }, + "&:hover fieldset": + { + borderColor: + flagStatus[ + index + ] === + true + ? "#4CAF50" + : flagStatus[ + index + ] === + false + ? "#F44336" + : "rgba(163, 21, 214, 0.5)" + }, + "&.Mui-focused fieldset": + { + borderColor: + flagStatus[ + index + ] === + true + ? "#4CAF50" + : flagStatus[ + index + ] === + false + ? "#F44336" + : "#A315D6" + } + }, + "& .MuiInputBase-input": { + color: "white", + fontFamily: "Montserrat" + } + }} + /> + + + ); + })} + + + + Level 3 + + + {flagInputs.slice(6, 9).map((flag, idx) => { + const index = idx + 6; + return ( + + + + + FLAG {index + 1} + {flagStatus[index] === + true && " (correct)"} + {flagStatus[index] === + false && " (incorrect)"} + + + {getFlagPoints(index)}pts + + + + handleFlagChange( + index, + e.target.value + ) + } + placeholder="Enter flag..." + sx={{ + fontFamily: "Montserrat", + "& .MuiOutlinedInput-root": + { + fontFamily: + "Montserrat", + color: "white", + backgroundColor: + "rgba(0, 0, 0, 0.3)", + borderRadius: "8px", + "& fieldset": { + borderColor: + flagStatus[ + index + ] === true + ? "#4CAF50" + : flagStatus[ + index + ] === + false + ? "#F44336" + : "rgba(255, 255, 255, 0.3)" + }, + "&:hover fieldset": + { + borderColor: + flagStatus[ + index + ] === + true + ? "#4CAF50" + : flagStatus[ + index + ] === + false + ? "#F44336" + : "rgba(163, 21, 214, 0.5)" + }, + "&.Mui-focused fieldset": + { + borderColor: + flagStatus[ + index + ] === + true + ? "#4CAF50" + : flagStatus[ + index + ] === + false + ? "#F44336" + : "#A315D6" + } + }, + "& .MuiInputBase-input": { + color: "white", + fontFamily: "Montserrat" + } + }} + /> + + + ); + })} + From f71f086a917e884f77e15e113c84cf2ac81899f0 Mon Sep 17 00:00:00 2001 From: Sherry Long Date: Wed, 25 Feb 2026 13:07:49 -0600 Subject: [PATCH 16/22] Update hints and small fixes --- app/ctf/page.tsx | 38 +++++++++++++++++--------------------- app/ctf/submit/page.tsx | 5 +++-- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx index 59a31f90..ed998117 100644 --- a/app/ctf/page.tsx +++ b/app/ctf/page.tsx @@ -197,8 +197,8 @@ export default function CTF() { reveal: () => { console.log("hackctf{flag5-c41170pr1n7}"); }, - password: (password: number) => { - if (password === 64) { + password: (key: number) => { + if (key === 64) { console.log("aGFja2N0ZntmbGFnNi1kM2MwZDNtM30="); } else { console.log( @@ -217,11 +217,11 @@ export default function CTF() { }; (window as any).$HINT_2$ = { - hint: "You might need to REVEAL-ME by checking out the CSS" + hint: "I might be NOT-DISPLAYED until you check the CSS" }; (window as any).$HINT_3$ = { - hint: "Check your comms, you received a message! You might need to FIND-ME in the DOM" + hint: "Check your comms, you received a message! I wonder where you can FIND-ME in the DOM" }; (window as any).$HINT_4$ = { @@ -229,11 +229,11 @@ export default function CTF() { }; (window as any).$HINT_5$ = { - hint: "f is for function!" + hint: "There's a lot to this secret. Hint: f is for function" }; (window as any).$HINT_6$ = { - hint: "Your browser knows how to decode me" + hint: "The password is encoded in magic!" }; (window as any).$HINT_7$ = { @@ -871,7 +871,7 @@ export default function CTF() { }} > - - You found a flag: hackctf{"{flag2-1nv151b13}"} - + display: "block" + } + }} + > - + {"YOUR FIRST FLAG HERE: hackctf{flag1-p141n51gh7}"} diff --git a/app/ctf/submit/page.tsx b/app/ctf/submit/page.tsx index 89cbfc8e..189f795c 100644 --- a/app/ctf/submit/page.tsx +++ b/app/ctf/submit/page.tsx @@ -533,7 +533,7 @@ export default function CTFSubmit() { fontWeight: 600 }} > - hackctf{"{flag-name-here}"} + hackctf{"{flag#-flagcontent}"} - You can submit flags in any order! + Submit flags in the exact format. You can submit + in any order! From 45152a9bb183913b07f106dc89cef1b44bd9d9cc Mon Sep 17 00:00:00 2001 From: Jacob Edley Date: Wed, 25 Feb 2026 19:43:21 -0600 Subject: [PATCH 17/22] Rotate pink planet --- app/ctf/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx index ed998117..74546016 100644 --- a/app/ctf/page.tsx +++ b/app/ctf/page.tsx @@ -320,7 +320,7 @@ export default function CTF() { animate={{ y: [0, 20, 0], x: [0, 10, 0], - rotate: [0, 8, 0] + rotate: [90, 98, 90] }} transition={{ duration: 7, From 795995907126fca4481ed047d08a5c99fd17fdf7 Mon Sep 17 00:00:00 2001 From: PrachodK Date: Wed, 25 Feb 2026 20:55:39 -0600 Subject: [PATCH 18/22] Move submission button to top, remove duplicate copy button --- app/ctf/page.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx index 74546016..01657436 100644 --- a/app/ctf/page.tsx +++ b/app/ctf/page.tsx @@ -690,6 +690,16 @@ export default function CTF() { + + + + + + - - - - - - Date: Wed, 25 Feb 2026 21:31:58 -0600 Subject: [PATCH 19/22] Replace hardcoded flags with API verification --- app/ctf/submit/page.tsx | 390 +++++++++++++++++++++++++++++----------- 1 file changed, 282 insertions(+), 108 deletions(-) diff --git a/app/ctf/submit/page.tsx b/app/ctf/submit/page.tsx index 189f795c..f5c3376d 100644 --- a/app/ctf/submit/page.tsx +++ b/app/ctf/submit/page.tsx @@ -1,22 +1,20 @@ "use client"; -import { Box, Container, Typography, TextField, Alert } from "@mui/material"; -import { useState, useEffect } from "react"; +import { + Box, + Container, + Typography, + TextField, + Alert, + Button, + CircularProgress +} from "@mui/material"; +import { useState, useEffect, useRef, useCallback } from "react"; import { motion, Variants } from "framer-motion"; import Link from "next/link"; import styles from "./submit.module.scss"; -const CORRECT_FLAGS = [ - "hackctf{flag1-p141n51gh7}", - "hackctf{flag2-1nv151b13}", - "hackctf{flag3-53cr37m3554g3}", - "hackctf{flag4-c0n501353cr37}", - "hackctf{flag5-c41170pr1n7}", - "hackctf{flag6-d3c0d3m3}", - "hackctf{flag7-m1n14p1}", - "hackctf{flag8-4p1m4573r}", - "hackctf{flag9-c0ngr475y0ub3477h3c7f}" -]; +const API_URL = "https://adonix.hackillinois.org"; const FLAG_POINTS = [ { range: "1-3", points: 2, color: "#4CAF50" }, @@ -39,6 +37,12 @@ const getFlagColor = (index: number): string => { return "#E91E63"; }; +interface FlagStatusType { + correct: boolean | null; + claimed: boolean; + loading: boolean; +} + const TwinklingStar = ({ size, top, @@ -199,18 +203,16 @@ export default function CTFSubmit() { "", "" ]); - const [flagStatus, setFlagStatus] = useState<(boolean | null)[]>([ - null, - null, - null, - null, - null, - null, - null, - null, - null - ]); + const [flagStatus, setFlagStatus] = useState( + Array(9) + .fill(null) + .map(() => ({ correct: null, claimed: false, loading: false })) + ); const [showSuccess, setShowSuccess] = useState(false); + const [isAuthenticated, setIsAuthenticated] = useState( + null + ); + const debounceRefs = useRef<(NodeJS.Timeout | null)[]>(Array(9).fill(null)); const [stars, setStars] = useState< { id: number; @@ -223,6 +225,19 @@ export default function CTFSubmit() { >([]); useEffect(() => { + const checkAuth = async () => { + try { + const response = await fetch(`${API_URL}/auth/token/`, { + mode: "cors", + credentials: "include" + }); + setIsAuthenticated(response.ok); + } catch { + setIsAuthenticated(false); + } + }; + checkAuth(); + const savedFlags = localStorage.getItem("ctf_flags"); if (savedFlags) { const parsedFlags = JSON.parse(savedFlags); @@ -230,7 +245,26 @@ export default function CTFSubmit() { .fill("") .map((_, i) => parsedFlags[i] || ""); setFlagInputs(allFlags); - validateFlags(allFlags); + } + + const savedStatus = localStorage.getItem("ctf_status"); + if (savedStatus) { + try { + const parsedStatus = JSON.parse(savedStatus); + const allStatus = Array(9) + .fill(null) + .map( + (_, i) => + parsedStatus[i] || { + correct: null, + claimed: false, + loading: false + } + ); + setFlagStatus(allStatus); + } catch { + /* ignore */ + } } const generatedStars = Array.from({ length: 100 }).map((_, i) => ({ @@ -244,34 +278,137 @@ export default function CTFSubmit() { setStars(generatedStars); }, []); - const validateFlags = (flags: string[]) => { - const newStatus = Array(9) - .fill(null) - .map((_, index) => { - const flag = flags[index]; - if (!flag) return null; - const isCorrect = flag.trim() === CORRECT_FLAGS[index]; - return isCorrect; + const submitFlag = useCallback(async (index: number, answer: string) => { + const flagId = `flag${index + 1}`; + + setFlagStatus(prev => { + const newStatus = [...prev]; + newStatus[index] = { ...newStatus[index], loading: true }; + return newStatus; + }); + + try { + const response = await fetch(`${API_URL}/ctf/submit/${flagId}/`, { + method: "POST", + mode: "cors", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ answer: answer.trim() }) }); - setFlagStatus(newStatus); - }; + + const data = await response.json(); + + if (response.ok) { + return { correct: true, claimed: false }; + } + if (data.error === "AlreadyClaimed") { + return { correct: true, claimed: true }; + } + return { correct: false, claimed: false }; + } catch { + return { correct: false, claimed: false }; + } + }, []); const handleFlagChange = (index: number, value: string) => { const newInputs = [...flagInputs]; newInputs[index] = value; setFlagInputs(newInputs); - localStorage.setItem("ctf_flags", JSON.stringify(newInputs)); - validateFlags(newInputs); - const allCorrect = newInputs.every((flag, index) => { - return flag && flag.trim() === CORRECT_FLAGS[index]; - }); - setShowSuccess(allCorrect); + if (debounceRefs.current[index]) { + clearTimeout(debounceRefs.current[index]!); + } + + if (!value.trim()) return; + + debounceRefs.current[index] = setTimeout(async () => { + const result = await submitFlag(index, value); + setFlagStatus(prev => { + const newStatus = [...prev]; + newStatus[index] = { + correct: result.correct, + claimed: result.claimed, + loading: false + }; + localStorage.setItem("ctf_status", JSON.stringify(newStatus)); + return newStatus; + }); + }, 800); }; - const correctCount = flagStatus.filter(s => s === true).length; - const progress = (correctCount / CORRECT_FLAGS.length) * 100; + const correctCount = flagStatus.filter(s => s.correct === true).length; + const progress = (correctCount / 9) * 100; + + useEffect(() => { + setShowSuccess(correctCount === 9); + }, [correctCount]); + + if (isAuthenticated === null) { + return ( + + + + ); + } + + if (!isAuthenticated) { + return ( + + + Please log in to submit flags + + + + ); + } return ( - Progress: {correctCount} / {CORRECT_FLAGS.length}{" "} - flags + Progress: {correctCount} / {9} flags @@ -717,17 +853,19 @@ export default function CTFSubmit() { className={styles.flagCard} sx={{ background: - flagStatus[index] === true + flagStatus[index].correct === + true ? "rgba(76, 175, 80, 0.15)" - : flagStatus[index] === - false + : flagStatus[index] + .correct === false ? "rgba(244, 67, 54, 0.1)" : "rgba(255, 255, 255, 0.08)", border: - flagStatus[index] === true + flagStatus[index].correct === + true ? "2px solid #4CAF50" - : flagStatus[index] === - false + : flagStatus[index] + .correct === false ? "2px solid #F44336" : "1px solid rgba(255, 255, 255, 0.15)" }} @@ -746,21 +884,21 @@ export default function CTFSubmit() { fontSize: "14px", fontWeight: 600, color: - flagStatus[index] === - true + flagStatus[index] + .correct === true ? "#4CAF50" - : flagStatus[ - index - ] === false + : flagStatus[index] + .correct === + false ? "#F44336" : "rgba(255, 255, 255, 0.7)" }} > FLAG {index + 1} - {flagStatus[index] === true && - " (correct)"} - {flagStatus[index] === false && - " (incorrect)"} + {flagStatus[index].correct === + true && " (correct)"} + {flagStatus[index].correct === + false && " (incorrect)"} FLAG {index + 1} - {flagStatus[index] === - true && " (correct)"} - {flagStatus[index] === - false && " (incorrect)"} + {flagStatus[index] + .correct === true && + " (correct)"} + {flagStatus[index] + .correct === false && + " (incorrect)"} FLAG {index + 1} - {flagStatus[index] === - true && " (correct)"} - {flagStatus[index] === - false && " (incorrect)"} + {flagStatus[index] + .correct === true && + " (correct)"} + {flagStatus[index] + .correct === false && + " (incorrect)"} Date: Thu, 26 Feb 2026 11:12:37 -0600 Subject: [PATCH 20/22] Make password less obvious and fix congratulations message --- app/ctf/page.tsx | 4 ++-- app/ctf/submit/page.tsx | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx index 01657436..2607e3d0 100644 --- a/app/ctf/page.tsx +++ b/app/ctf/page.tsx @@ -198,8 +198,8 @@ export default function CTF() { console.log("hackctf{flag5-c41170pr1n7}"); }, password: (key: number) => { - if (key === 64) { - console.log("aGFja2N0ZntmbGFnNi1kM2MwZDNtM30="); + if (key === (window as any).$MAGIC_NUMBER$.number) { + console.log(btoa("hackctf{flag6-d3c0d3m3}")); } else { console.log( "Authentication failed. What's the magic number?" diff --git a/app/ctf/submit/page.tsx b/app/ctf/submit/page.tsx index f5c3376d..7ac23244 100644 --- a/app/ctf/submit/page.tsx +++ b/app/ctf/submit/page.tsx @@ -786,8 +786,7 @@ export default function CTFSubmit() { }} > Congratulations! You've found all the - flags! Show a staff member to redeem your - points. + flags! )} From d16568b3de90c097caacb582843d0390078a5d64 Mon Sep 17 00:00:00 2001 From: Sherry Long Date: Thu, 26 Feb 2026 12:36:29 -0600 Subject: [PATCH 21/22] Encode flags in source code --- app/ctf/miniapi/route.ts | 19 ++++++++-- app/ctf/miniapi/unlock/route.ts | 16 +++++++-- app/ctf/page.tsx | 62 +++++++++++++++++++++++++++++---- app/ctf/utils.ts | 4 +++ 4 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 app/ctf/utils.ts diff --git a/app/ctf/miniapi/route.ts b/app/ctf/miniapi/route.ts index 9b1b768f..70dd6ae3 100644 --- a/app/ctf/miniapi/route.ts +++ b/app/ctf/miniapi/route.ts @@ -1,4 +1,5 @@ import { NextResponse } from "next/server"; +import { derive } from "../utils"; async function sha256(text: string) { const encoder = new TextEncoder(); @@ -11,11 +12,23 @@ async function sha256(text: string) { .join(""); } -const SERVER_SECRET = "top-secret-key"; +const SERVER_SECRET = derive(["LWtleQ==", "ZWNyZXQ=", "dG9wLXM="]); export async function GET() { - const flag = "hackctf{flag7-m1n14p1}"; - const hiddenFlag = "hackctf{flag8-4p1m4573r}"; + const flag = derive([ + "MX0=", + "MW4xNHA=", + "YWc3LW0=", + "dGZ7Zmw=", + "aGFja2M=" + ]); + const hiddenFlag = derive([ + "NzNyfQ==", + "cDFtNDU=", + "YWc4LTQ=", + "dGZ7Zmw=", + "aGFja2M=" + ]); const secret = await sha256(hiddenFlag + SERVER_SECRET); diff --git a/app/ctf/miniapi/unlock/route.ts b/app/ctf/miniapi/unlock/route.ts index 4eb66dda..0b1d5d34 100644 --- a/app/ctf/miniapi/unlock/route.ts +++ b/app/ctf/miniapi/unlock/route.ts @@ -1,4 +1,5 @@ import { NextResponse } from "next/server"; +import { derive } from "../../utils"; async function sha256(text: string) { const encoder = new TextEncoder(); @@ -11,7 +12,7 @@ async function sha256(text: string) { .join(""); } -const SERVER_SECRET = "top-secret-key"; +const SERVER_SECRET = derive(["LWtleQ==", "ZWNyZXQ=", "dG9wLXM="]); export async function GET(req: Request) { const url = new URL(req.url); @@ -34,8 +35,17 @@ export async function GET(req: Request) { let param = secret ? secret : signal; let hiddenFlag = secret - ? "hackctf{flag8-4p1m4573r}" - : "hackctf{flag9-c0ngr475y0ub3477h3c7f}"; + ? derive(["NzNyfQ==", "cDFtNDU=", "YWc4LTQ=", "dGZ7Zmw=", "aGFja2M="]) + : derive([ + "fQ==", + "aDNjN2Y=", + "YjM0Nzc=", + "NzV5MHU=", + "MG5ncjQ=", + "YWc5LWM=", + "dGZ7Zmw=", + "aGFja2M=" + ]); const expected = await sha256(hiddenFlag + SERVER_SECRET); console.log(expected); diff --git a/app/ctf/page.tsx b/app/ctf/page.tsx index 2607e3d0..1ad7543f 100644 --- a/app/ctf/page.tsx +++ b/app/ctf/page.tsx @@ -15,6 +15,7 @@ import { ExpandMore } from "@mui/icons-material"; import { useEffect, useState } from "react"; import { motion, Variants } from "framer-motion"; import { GradientButton } from "@/components/GradientButton/GradientButton"; +import { derive } from "./utils"; const TwinklingStar = ({ size, @@ -193,13 +194,41 @@ export default function CTF() { setStars(generatedStars); (window as any).$SECRET$ = { - flag: "hackctf{flag4-c0n501353cr37}", + flag: (() => { + return derive([ + "Mzd9", + "MzUzY3I=", + "MG41MDE=", + "YWc0LWM=", + "dGZ7Zmw=", + "aGFja2M=" + ]); + })(), reveal: () => { - console.log("hackctf{flag5-c41170pr1n7}"); + console.log( + derive([ + "fQ==", + "cHIxbjc=", + "NDExNzA=", + "YWc1LWM=", + "dGZ7Zmw=", + "aGFja2M=" + ]) + ); }, password: (key: number) => { if (key === (window as any).$MAGIC_NUMBER$.number) { - console.log(btoa("hackctf{flag6-d3c0d3m3}")); + console.log( + btoa( + derive([ + "bTN9", + "M2MwZDM=", + "YWc2LWQ=", + "dGZ7Zmw=", + "aGFja2M=" + ]) + ) + ); } else { console.log( "Authentication failed. What's the magic number?" @@ -880,7 +909,14 @@ export default function CTF() { color: "#FDAB60" }} > - {"hackctf{flag3-53cr37m3554g3}"} + {derive([ + "ZzN9", + "bTM1NTQ=", + "M2NyMzc=", + "YWczLTU=", + "dGZ7Zmw=", + "aGFja2M=" + ])} @@ -1011,7 +1047,13 @@ export default function CTF() { mx: "auto", display: "none", "&::after": { - content: '"hackctf{flag2-1nv151b13}"', + content: `"${derive([ + "YjEzfQ==", + "bnYxNTE=", + "YWcyLTE=", + "dGZ7Zmw=", + "aGFja2M=" + ])}"`, color: "#FDAB60", fontFamily: "Montserrat", fontSize: "16px", @@ -1023,7 +1065,13 @@ export default function CTF() {