├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── eslint.config.mjs ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public ├── images │ ├── OhYeahSeC.png │ ├── banner.png │ ├── base.png │ ├── caesar.png │ ├── layer1.png │ ├── layer2.png │ └── osga.png └── log.txt ├── src └── app │ ├── challenge │ ├── 10_HmfPsygKVlEX │ │ └── page.tsx │ ├── 1_QTMpnQaJpslW │ │ └── page.tsx │ ├── 2_wTzTVTcgWdJT │ │ └── page.tsx │ ├── 3_cRAISYfsZYCC │ │ └── page.tsx │ ├── 4_PzDJGfPRPsHY │ │ └── page.tsx │ ├── 5_IlXzElXnMclm │ │ └── page.tsx │ ├── 6_eMjVGPIewYYR │ │ └── page.tsx │ ├── 7_BbbkammfHZID │ │ └── page.tsx │ ├── 8_WCwfbPFLfcdd │ │ └── page.tsx │ └── 9_tFwbyvOPpotb │ │ └── page.tsx │ ├── components │ ├── CharBlurText.tsx │ └── LetterGlitch.tsx │ ├── end_HUAterpbJppw │ └── page.tsx │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── page.tsx │ └── story │ └── page.tsx ├── tailwind.config.ts └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'next/core-web-vitals' 4 | ], 5 | rules: { 6 | // 關閉未使用變數的警告 7 | '@typescript-eslint/no-unused-vars': 'off', 8 | // 關閉未轉義實體的警告 9 | 'react/no-unescaped-entities': 'off' 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 os24 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OhYeahSeC CTF Challenge 2 | 3 | ![OhYeahSeC Banner](public/images/banner.png) 4 | 5 | ### 🔗 https://challenge.ohyeahsec.org 6 | 7 | ## 專案概述 8 | 9 | OhYeahSeC CTF Challenge 是一個互動式網頁平台,提供一系列資訊安全相關的挑戰題目。參與者需要運用各種資安技能,包括密碼學、Web安全、隱寫術等,來解決挑戰並找出隱藏的FLAG。 10 | 11 | 該平台採用Next.js框架開發,具有現代化的界面設計和流暢的用戶體驗,適合給予剛接觸資安的夥伴體驗,或資訊安全相關介紹後的實作。 12 | 13 | ## 功能特點 14 | 15 | - **精心設計的挑戰關卡**:涵蓋從基礎密碼學到Web安全等多種資安主題 16 | - **逐步引導的學習體驗**:參與者需順序解決挑戰,難度逐步提升 17 | - **簡易的解題環境** :可以使用瀏覽器就可以體驗到更領域的關卡 18 | 19 | ## 技術堆疊 20 | 21 | - **框架**: Next.js 15.1.7 22 | - **前端**: React, TypeScript, Tailwind CSS 23 | - **動畫**: [reactbit](https://www.reactbits.dev/text-animations/blur-text) 24 | - **部署**: Vercel 25 | 26 | ## 挑戰類型 27 | 28 | 平台包含多種不同類型的資安挑戰: 29 | 30 | 1. **基礎密碼學**:包括凱薩密碼、Base64解碼等 31 | 2. **程式碼分析**:修復或分析有錯誤的代碼 32 | 3. **Web安全**:分析前端代碼尋找隱藏信息 33 | 4. **視覺密碼學**:通過圖像處理和分析發現隱藏信息 34 | 5. **社交工程**:使用OSINT(開源情報)技術尋找信息 35 | 36 | ## 本地開發 37 | 38 | ### 前提條件 39 | 40 | - Node.js 18.x 或更高版本 41 | - npm 或 yarn 42 | 43 | ### 安裝與運行 44 | 45 | 1. 克隆儲存庫 46 | ```bash 47 | git clone https://github.com/osga24/OhYeahSeC-CTF-Challenge.git 48 | cd OhYeahSeC-CTF-Challenge 49 | ``` 50 | 51 | 2. 安裝依賴 52 | ```bash 53 | npm install 54 | # 或 55 | yarn install 56 | ``` 57 | 58 | 3. 啟動開發服務器 59 | ```bash 60 | npm run dev 61 | # 或 62 | yarn dev 63 | ``` 64 | 65 | 4. 開啟瀏覽器並訪問 `http://localhost:3000` 66 | 67 | ## 部署 68 | 69 | 此專案配置為在Vercel上部署: 70 | 71 | 1. Fork 或克隆此儲存庫到您的GitHub帳戶 72 | 2. 在Vercel中導入該專案 73 | 3. 設置環境變量(如需要) 74 | 4. 部署! 75 | 76 | ## 自定義挑戰 77 | 78 | 如果您想添加或修改挑戰: 79 | 80 | 1. 在 `src/app/challenge/` 目錄下為每個挑戰創建新的文件夾 81 | 2. 參考現有挑戰的結構和命名約定 82 | 3. 更新相關的FLAG值和挑戰內容 83 | 84 | ## 貢獻指南 85 | 86 | 歡迎提交問題報告、功能請求或直接提交Pull Request。請確保您的代碼與現有的編碼風格一致,並通過所有測試。 87 | 88 | ## 授權協議 89 | 90 | 此專案採用 MIT 授權協議 - 詳見 [LICENSE](LICENSE) 文件 91 | 92 | ## 聯繫方式 93 | 94 | 有任何問題或建議,歡迎私訊我的任何聯絡方式 contact to [OsGa](https://www.osga.dev/contact)。 95 | 96 | --- 97 | 98 | Happy Hacking! 🚀 99 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ctf-challenge", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "15.1.7", 13 | "react": "^19.0.0", 14 | "react-confetti": "^6.2.3", 15 | "react-dom": "^19.0.0" 16 | }, 17 | "devDependencies": { 18 | "@eslint/eslintrc": "^3", 19 | "@types/node": "^20", 20 | "@types/react": "^19", 21 | "@types/react-dom": "^19", 22 | "eslint": "^9", 23 | "eslint-config-next": "15.1.7", 24 | "postcss": "^8", 25 | "tailwindcss": "^3.4.1", 26 | "typescript": "^5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /public/images/OhYeahSeC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osga24/OhYeahSeC-CTF-Challenge/9dcd2f6507bc6b3ef76bb942804b1134a342623a/public/images/OhYeahSeC.png -------------------------------------------------------------------------------- /public/images/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osga24/OhYeahSeC-CTF-Challenge/9dcd2f6507bc6b3ef76bb942804b1134a342623a/public/images/banner.png -------------------------------------------------------------------------------- /public/images/base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osga24/OhYeahSeC-CTF-Challenge/9dcd2f6507bc6b3ef76bb942804b1134a342623a/public/images/base.png -------------------------------------------------------------------------------- /public/images/caesar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osga24/OhYeahSeC-CTF-Challenge/9dcd2f6507bc6b3ef76bb942804b1134a342623a/public/images/caesar.png -------------------------------------------------------------------------------- /public/images/layer1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osga24/OhYeahSeC-CTF-Challenge/9dcd2f6507bc6b3ef76bb942804b1134a342623a/public/images/layer1.png -------------------------------------------------------------------------------- /public/images/layer2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osga24/OhYeahSeC-CTF-Challenge/9dcd2f6507bc6b3ef76bb942804b1134a342623a/public/images/layer2.png -------------------------------------------------------------------------------- /public/images/osga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osga24/OhYeahSeC-CTF-Challenge/9dcd2f6507bc6b3ef76bb942804b1134a342623a/public/images/osga.png -------------------------------------------------------------------------------- /public/log.txt: -------------------------------------------------------------------------------- 1 | ====== 系統日誌 - OhYeahSeC 安全伺服器 ====== 2 | 生成時間: 2025-02-26 01:23:45 3 | 安全等級: 最高機密 4 | 5 | -------- 系統啟動記錄 -------- 6 | [2025-02-23 03:14:15] 系統啟動 7 | [2025-02-23 03:14:16] 核心服務初始化 8 | [2025-02-23 03:14:17] 網路服務啟動 9 | [2025-02-23 03:14:18] 安全模組載入 10 | [2025-02-23 03:14:19] 加密服務啟動 11 | [2025-02-23 03:14:20] 防火牆規則載入 12 | [2025-02-23 03:14:21] 入侵檢測系統啟動 13 | [2025-02-23 03:14:22] 記憶體保護機制啟動 14 | [2025-02-23 03:14:23] 硬碟加密服務啟動 15 | 16 | -------- 使用者登入記錄 -------- 17 | [2025-02-23 08:23:45] 使用者 admin 登入成功 18 | [2025-02-23 09:15:22] 使用者 user1 登入成功 19 | [2025-02-23 09:35:12] 使用者 user2 登入失敗 (密碼錯誤) 20 | [2025-02-23 09:35:18] 使用者 user2 登入失敗 (密碼錯誤) 21 | [2025-02-23 09:35:25] 使用者 user2 登入成功 22 | [2025-02-23 10:45:33] 使用者 admin 登出 23 | [2025-02-23 12:12:10] 使用者 sysadmin 登入成功 24 | [2025-02-23 14:22:18] 使用者 user1 登出 25 | [2025-02-23 15:30:05] 使用者 user2 登出 26 | [2025-02-23 18:45:12] 使用者 sysadmin 登出 27 | 28 | -------- 安全掃描記錄 -------- 29 | [2025-02-24 01:00:00] 定時安全掃描開始 30 | [2025-02-24 01:05:23] 發現可疑檔案: /var/log/temp/f1l3.tmp 31 | [2025-02-24 01:05:24] 檔案隔離: /var/log/temp/f1l3.tmp 32 | [2025-02-24 01:05:25] 發現可疑連線: 192.168.1.123:4455 33 | [2025-02-24 01:05:26] 連線阻擋: 192.168.1.123:4455 34 | [2025-02-24 01:15:45] 安全掃描完成,發現 2 個威脅 35 | 36 | -------- 系統更新記錄 -------- 37 | [2025-02-24 03:00:00] 系統更新檢查開始 38 | [2025-02-24 03:00:12] 發現 15 個可用更新 39 | [2025-02-24 03:00:30] 更新下載開始 40 | [2025-02-24 03:15:22] 更新下載完成 41 | [2025-02-24 03:15:30] 更新安裝開始 42 | [2025-02-24 03:30:15] 更新安裝完成 43 | [2025-02-24 03:30:20] 系統要求重新啟動 44 | 45 | -------- 系統重啟記錄 -------- 46 | [2025-02-24 03:35:00] 系統重新啟動 47 | [2025-02-24 03:35:16] 核心服務初始化 48 | [2025-02-24 03:35:17] 網路服務啟動 49 | [2025-02-24 03:35:18] 安全模組載入 50 | [2025-02-24 03:35:19] 加密服務啟動 51 | [2025-02-24 03:35:20] 防火牆規則載入 52 | [2025-02-24 03:35:21] 入侵檢測系統啟動 53 | [2025-02-24 03:35:22] 記憶體保護機制啟動 54 | [2025-02-24 03:35:23] 硬碟加密服務啟動 55 | 56 | -------- 系統漏洞掃描記錄 -------- 57 | [2025-02-24 12:00:00] 系統漏洞掃描開始 58 | [2025-02-24 12:30:15] 發現中度風險漏洞 CVE-2024-34521 59 | [2025-02-24 12:30:20] 發現低度風險漏洞 CVE-2024-34522 60 | [2025-02-24 12:30:25] 發現低度風險漏洞 CVE-2024-34523 61 | [2025-02-24 12:45:10] 系統漏洞掃描完成 62 | 63 | -------- 系統修補記錄 -------- 64 | [2025-02-24 14:00:00] 系統修補開始 65 | [2025-02-24 14:05:12] 修補 CVE-2024-34521 66 | [2025-02-24 14:10:25] 修補 CVE-2024-34522 67 | [2025-02-24 14:15:33] 修補 CVE-2024-34523 68 | [2025-02-24 14:20:00] 系統修補完成 69 | [2025-02-24 14:25:00] 系統要求重新啟動 70 | 71 | -------- 系統重啟記錄 -------- 72 | [2025-02-24 14:30:00] 系統重新啟動 73 | [2025-02-24 14:30:16] 核心服務初始化 74 | [2025-02-24 14:30:17] 網路服務啟動 75 | [2025-02-24 14:30:18] 安全模組載入 76 | [2025-02-24 14:30:19] 加密服務啟動 77 | [2025-02-24 14:30:20] 防火牆規則載入 78 | [2025-02-24 14:30:21] 入侵檢測系統啟動 79 | [2025-02-24 14:30:22] 記憶體保護機制啟動 80 | [2025-02-24 14:30:23] 硬碟加密服務啟動 81 | 82 | -------- 資料庫備份記錄 -------- 83 | [2025-02-24 23:00:00] 資料庫備份開始 84 | [2025-02-24 23:10:15] 使用者資料表備份完成 85 | [2025-02-24 23:15:32] 系統設定資料表備份完成 86 | [2025-02-24 23:20:45] 安全日誌資料表備份完成 87 | [2025-02-24 23:25:18] 備份檔案加密中 88 | [2025-02-24 23:30:05] 備份檔案加密完成 89 | [2025-02-24 23:35:12] 備份檔案傳輸至異地備份伺服器 90 | [2025-02-24 23:45:22] 資料庫備份完成 91 | 92 | -------- 入侵嘗試記錄 -------- 93 | [2025-02-25 02:15:33] 檢測到暴力破解嘗試,來源 IP: 45.123.45.67 94 | [2025-02-25 02:15:40] IP 封鎖: 45.123.45.67,時長: 24小時 95 | [2025-02-25 03:22:15] 檢測到 SQL 注入嘗試,來源 IP: 87.65.43.21 96 | [2025-02-25 03:22:20] IP 封鎖: 87.65.43.21,時長: 24小時 97 | [2025-02-25 04:18:45] 檢測到 XSS 嘗試,來源 IP: 123.45.67.89 98 | [2025-02-25 04:18:50] IP 封鎖: 123.45.67.89,時長: 24小時 99 | [2025-02-25 06:30:12] 檢測到目錄遍歷嘗試,來源 IP: 98.76.54.32 100 | [2025-02-25 06:30:18] IP 封鎖: 98.76.54.32,時長: 24小時 101 | 102 | -------- 網路流量分析 -------- 103 | [2025-02-25 08:00:00] 網路流量分析開始 104 | [2025-02-25 08:15:33] 檢測到異常流量模式,來源: 內部網路 192.168.3.45 105 | [2025-02-25 08:15:40] 警告已發送至系統管理員 106 | [2025-02-25 08:30:22] 網路流量分析完成 107 | 108 | -------- 安全警報記錄 -------- 109 | [2025-02-25 10:23:45] 警報: 多次失敗的管理員登入嘗試 110 | [2025-02-25 10:23:50] 通知已發送至安全團隊 111 | [2025-02-25 10:45:12] 警報: 發現可疑檔案 /usr/bin/s34rch.bin 112 | [2025-02-25 10:45:15] 檔案已隔離等待分析 113 | [2025-02-25 10:45:20] 通知已發送至安全團隊 114 | [2025-02-25 11:30:05] 警報已解除: 檔案 /usr/bin/s34rch.bin 確認為誤判 115 | 116 | -------- 系統資源監控 -------- 117 | [2025-02-25 12:00:00] CPU 使用率: 45% 118 | [2025-02-25 12:00:01] 記憶體使用率: 60% 119 | [2025-02-25 12:00:02] 硬碟使用率: 73% 120 | [2025-02-25 12:00:03] 網路使用率: 30% 121 | [2025-02-25 18:00:00] CPU 使用率: 52% 122 | [2025-02-25 18:00:01] 記憶體使用率: 65% 123 | [2025-02-25 18:00:02] 硬碟使用率: 73% 124 | [2025-02-25 18:00:03] 網路使用率: 25% 125 | 126 | -------- 系統維護記錄 -------- 127 | [2025-02-25 22:00:00] 系統維護開始 128 | [2025-02-25 22:05:15] 暫時禁止使用者登入 129 | [2025-02-25 22:10:33] 資料庫最佳化開始 130 | [2025-02-25 22:25:45] 資料庫最佳化完成 131 | [2025-02-25 22:30:12] 清理暫存檔案開始 132 | [2025-02-25 22:35:40] 清理暫存檔案完成 133 | [2025-02-25 22:40:05] 系統日誌壓縮備份 134 | [2025-02-25 22:50:22] 系統維護完成 135 | [2025-02-25 22:55:10] 允許使用者登入 136 | 137 | -------- 緊急存取碼 -------- 138 | [2025-02-25 23:45:18] 生成緊急存取碼以應對潛在系統故障 139 | [2025-02-25 23:45:20] 存取碼已加密存儲至安全資料庫 140 | [2025-02-25 23:45:25] 注意: 僅在系統無法正常存取時使用 141 | [2025-02-25 23:45:30] 此存取碼的格式為: OYSC{F1L3_S34RCH_M4ST3R} 142 | [2025-02-25 23:45:35] 存取碼有效期: 30天 143 | 144 | -------- 系統關閉記錄 -------- 145 | [2025-02-26 01:20:00] 系統關閉指令接收 146 | [2025-02-26 01:20:05] 儲存所有開啟的系統資料 147 | [2025-02-26 01:20:10] 停止所有非核心服務 148 | [2025-02-26 01:20:15] 停止所有核心服務 149 | [2025-02-26 01:20:20] 系統正常關閉 150 | 151 | -- 日誌結束 -- 152 | -------------------------------------------------------------------------------- /src/app/challenge/10_HmfPsygKVlEX/page.tsx: -------------------------------------------------------------------------------- 1 | // src/app/challenge/10/page.tsx 2 | "use client"; 3 | 4 | import React, { 5 | useState, 6 | useEffect, 7 | useRef, 8 | FormEvent, 9 | useCallback 10 | } from 'react'; 11 | import { useRouter } from 'next/navigation'; 12 | import Image from 'next/image'; 13 | import LetterGlitch from '../../components/LetterGlitch'; 14 | 15 | // 第十關挑戰頁面 - 最終關卡 16 | export default function Challenge10Page() { 17 | const level = 10; 18 | const [userInput, setUserInput] = useState(''); 19 | const [feedback, setFeedback] = useState(''); 20 | const [isSubmitting, setIsSubmitting] = useState(false); 21 | const inputRef = useRef(null); 22 | const router = useRouter(); 23 | 24 | // 題目內容 25 | const challenge = { 26 | title: "最後的挑戰", 27 | description: "恭喜你到達最終關卡!在這最後的挑戰中,你將運用你的社交媒體搜索技巧。請查看下方照片,找出這個人的Instagram帳號,並提交作為最終的FLAG。", 28 | flag: process.env.NEXT_PUBLIC_FINAL_CHALLENGE_FLAG || "os324_" // 使用環境變數 29 | }; 30 | 31 | // 自動聚焦輸入框 32 | useEffect(() => { 33 | if (inputRef.current) { 34 | inputRef.current.focus(); 35 | } 36 | }, []); 37 | 38 | // 處理FLAG提交 39 | const handleSubmit = useCallback((e: FormEvent) => { 40 | e.preventDefault(); 41 | 42 | // 防止重複提交 43 | if (isSubmitting) return; 44 | 45 | // 驗證是否為空 46 | const trimmedInput = userInput.trim(); 47 | if (!trimmedInput) { 48 | setFeedback('請輸入Instagram用戶名'); 49 | return; 50 | } 51 | 52 | setIsSubmitting(true); 53 | setFeedback(''); 54 | 55 | // 驗證答案 - 去除頭尾空格後進行比較 56 | const correctFlag = challenge.flag.trim(); 57 | 58 | if (trimmedInput === correctFlag) { 59 | setFeedback('恭喜!你已完成所有挑戰!正在前往完成頁面...'); 60 | 61 | // 延遲跳轉到完成頁面 62 | setTimeout(() => { 63 | router.push('/end_HUAterpbJppw'); 64 | }, 2000); 65 | } else { 66 | setFeedback('用戶名不正確,請再試一次。'); 67 | 68 | // 重置提交狀態 69 | setTimeout(() => { 70 | setFeedback(''); 71 | setIsSubmitting(false); 72 | }, 3000); 73 | } 74 | }, [userInput, isSubmitting, challenge.flag, router]); 75 | 76 | return ( 77 |
78 | {/* 背景效果 */} 79 |
80 | 87 |
88 | 89 | {/* 內容區域 */} 90 |
91 | {/* 標題區域 */} 92 |
93 |

94 | 第{level}關:{challenge.title} 95 |

96 |
97 | 98 | {/* 內容區域 */} 99 |
100 | {/* 題目描述 */} 101 |
102 | {challenge.description} 103 |
104 | 105 | {/* 照片區域 */} 106 |
107 |
108 | 個人照片 115 |
116 |
117 | 118 | {/* 輸入區域 */} 119 |
120 |
121 | 124 |
125 | @ 126 | setUserInput(e.target.value)} 132 | className="w-full px-10 py-3 bg-gray-800 border border-purple-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 text-white" 133 | placeholder="Instagram用戶名..." 134 | spellCheck="false" 135 | autoComplete="off" 136 | disabled={isSubmitting} 137 | /> 138 |
139 | 150 | 151 | {/* 反饋信息 */} 152 | {feedback && ( 153 |
154 | {feedback} 155 |
156 | )} 157 |
158 |
159 |
160 |
161 | 162 | {/* 底部信息 */} 163 |
164 | © 2025 OhYeahSeC Challenge 165 |
166 |
167 | ); 168 | } 169 | -------------------------------------------------------------------------------- /src/app/challenge/1_QTMpnQaJpslW/page.tsx: -------------------------------------------------------------------------------- 1 | // src/app/challenge/1/page.tsx 2 | "use client"; 3 | 4 | import React, { useState, useEffect, useRef, FormEvent } from 'react'; 5 | import { useRouter } from 'next/navigation'; 6 | import LetterGlitch from '../../components/LetterGlitch'; 7 | 8 | // 第一關挑戰頁面 9 | export default function Challenge1Page() { 10 | const level = 1; 11 | const [userInput, setUserInput] = useState(''); 12 | const [feedback, setFeedback] = useState(''); 13 | const inputRef = useRef(null); 14 | const router = useRouter(); 15 | 16 | // 第一關題目內容 17 | const challenge = { 18 | title: "目標", 19 | description: "你的目標是接受每一關的挑戰,並從挑戰中找出旗幟(FLAG),通常他們的樣式會為 OYSC{*.}\n\n就像是這隻 OYSC{Welcome_to_CTF_World}\n\n輸入 FLAG 進入下一關", 20 | flag: "OYSC{Welcome_to_CTF_World}" 21 | }; 22 | 23 | // 自動聚焦輸入框 24 | useEffect(() => { 25 | if (inputRef.current) { 26 | inputRef.current.focus(); 27 | } 28 | }, []); 29 | 30 | // 處理FLAG提交 31 | const handleSubmit = (e: FormEvent) => { 32 | e.preventDefault(); 33 | 34 | // 驗證答案 - 去除頭尾空格後進行比較 35 | const trimmedInput = userInput.trim(); 36 | const correctFlag = challenge.flag.trim(); 37 | 38 | if (trimmedInput === correctFlag) { 39 | setFeedback('恭喜!FLAG正確。正在前往下一關...'); 40 | 41 | // 延遲跳轉到下一關 42 | setTimeout(() => { 43 | router.push('/challenge/2_wTzTVTcgWdJT'); 44 | }, 2000); 45 | } else { 46 | setFeedback('FLAG不正確,請再試一次。'); 47 | setTimeout(() => { 48 | setFeedback(''); 49 | }, 3000); 50 | } 51 | }; 52 | 53 | return ( 54 |
55 | {/* 背景效果 */} 56 |
57 | 64 |
65 | 66 | {/* 內容區域 */} 67 |
68 | {/* 標題區域 */} 69 |
70 |

71 | 第{level}關:{challenge.title} 72 |

73 |
74 | 75 | {/* 內容區域 */} 76 |
77 | {/* 題目描述 - 修正排版 */} 78 |
79 |
 80 |               {challenge.description}
 81 |             
82 |
83 | 84 | {/* 輸入區域 */} 85 |
86 |
87 | 90 | setUserInput(e.target.value)} 96 | className="w-full max-w-lg px-4 py-3 bg-gray-800 border border-purple-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 text-white" 97 | placeholder="輸入你的答案..." 98 | spellCheck="false" 99 | autoComplete="off" 100 | /> 101 | 107 | 108 | {/* 反饋信息 */} 109 | {feedback && ( 110 |
111 | {feedback} 112 |
113 | )} 114 |
115 |
116 |
117 |
118 | 119 | {/* 底部信息 */} 120 |
121 | © 2025 OhYeahSeC Challenge 122 |
123 |
124 | ); 125 | } 126 | -------------------------------------------------------------------------------- /src/app/challenge/2_wTzTVTcgWdJT/page.tsx: -------------------------------------------------------------------------------- 1 | // src/app/challenge/2/page.tsx 2 | "use client"; 3 | 4 | import React, { useState, useEffect, useRef, FormEvent } from 'react'; 5 | import { useRouter } from 'next/navigation'; 6 | import Image from 'next/image'; 7 | import LetterGlitch from '../../components/LetterGlitch'; 8 | 9 | // 第一關挑戰頁面 10 | export default function Challenge1Page() { 11 | const level = 2; 12 | const [userInput, setUserInput] = useState(''); 13 | const [feedback, setFeedback] = useState(''); 14 | const inputRef = useRef(null); 15 | const router = useRouter(); 16 | 17 | // 第一關題目內容 18 | const challenge = { 19 | title: "凱薩的秘密", 20 | description: "你截獲了一段加密的通訊訊息,並且發現了一張照片,不知道跟這串密文是否有關聯。\n\n密文: DNHR{RPTHPG_RXEWTG_GDI_UXUIC}", 21 | flag: "OYSC{CAESAR_CIPHER_ROT_FIFTN}", 22 | image: "/images/caesar.png" // 圖片路徑,你需要將圖片放在 public/images/ 目錄下 23 | }; 24 | 25 | // 自動聚焦輸入框 26 | useEffect(() => { 27 | if (inputRef.current) { 28 | inputRef.current.focus(); 29 | } 30 | }, []); 31 | 32 | // 處理FLAG提交 33 | const handleSubmit = (e: FormEvent) => { 34 | e.preventDefault(); 35 | 36 | // 驗證答案 - 去除頭尾空格後進行比較 37 | const trimmedInput = userInput.trim(); 38 | const correctFlag = challenge.flag.trim(); 39 | 40 | if (trimmedInput === correctFlag) { 41 | setFeedback('恭喜!FLAG正確。正在前往下一關...'); 42 | 43 | // 延遲跳轉到下一關 44 | setTimeout(() => { 45 | router.push('/challenge/3_cRAISYfsZYCC'); 46 | }, 2000); 47 | } else { 48 | setFeedback('FLAG不正確,請再試一次。'); 49 | setTimeout(() => { 50 | setFeedback(''); 51 | }, 3000); 52 | } 53 | }; 54 | 55 | return ( 56 |
57 | {/* 背景效果 */} 58 |
59 | 66 |
67 | 68 | {/* 內容區域 */} 69 |
70 | {/* 標題區域 */} 71 |
72 |

73 | 第{level}關:{challenge.title} 74 |

75 |
76 | 77 | {/* 內容區域 */} 78 |
79 | {/* 題目描述 */} 80 |
81 |
 82 |               {challenge.description}
 83 |             
84 |
85 | 86 | {/* 圖片區域 */} 87 |
88 | {challenge.image && ( 89 |
90 | 凱薩密碼參考圖 97 |
98 | )} 99 |
100 | 101 | {/* 輸入區域 */} 102 |
103 |
104 | 107 | setUserInput(e.target.value)} 113 | className="w-full max-w-lg px-4 py-3 bg-gray-800 border border-purple-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 text-white" 114 | placeholder="輸入你的答案..." 115 | spellCheck="false" 116 | autoComplete="off" 117 | /> 118 | 124 | 125 | {/* 反饋信息 */} 126 | {feedback && ( 127 |
128 | {feedback} 129 |
130 | )} 131 |
132 |
133 |
134 |
135 | 136 | {/* 底部信息 */} 137 |
138 | © 2025 OhYeahSeC Challenge 139 |
140 |
141 | ); 142 | } 143 | -------------------------------------------------------------------------------- /src/app/challenge/3_cRAISYfsZYCC/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState, useEffect, useRef, FormEvent } from 'react'; 4 | import { useRouter } from 'next/navigation'; 5 | import Image from 'next/image'; 6 | import LetterGlitch from '../../components/LetterGlitch'; 7 | 8 | // 第三關挑戰頁面 9 | export default function Challenge3Page() { 10 | const level = 3; 11 | const [userInput, setUserInput] = useState(''); 12 | const [feedback, setFeedback] = useState(''); 13 | const inputRef = useRef(null); 14 | const router = useRouter(); 15 | 16 | // 第三關題目內容 17 | const challenge = { 18 | title: "隱藏的訊息", 19 | description: "你發現了一段奇怪的編碼文本,這看起來是使用 Base64 編碼的某種訊息。試著解碼它,找出隱藏的旗幟。\n\n編碼文本: T1lTQ3tCQVNFNjRfSVNfTk9UX0VOQ1JZUFRJT05fQlVUX0VOQ09ESU5HfQ==\n\n提示: 這種編碼方式常用於在純文本環境中傳輸二進制數據。", 20 | flag: "OYSC{BASE64_IS_NOT_ENCRYPTION_BUT_ENCODING}", 21 | image: "/images/base.png" // 可選的說明圖片 22 | }; 23 | 24 | // 自動聚焦輸入框 25 | useEffect(() => { 26 | if (inputRef.current) { 27 | inputRef.current.focus(); 28 | } 29 | }, []); 30 | 31 | // 處理FLAG提交 32 | const handleSubmit = (e: FormEvent) => { 33 | e.preventDefault(); 34 | 35 | // 驗證答案 - 去除頭尾空格後進行比較 36 | const trimmedInput = userInput.trim(); 37 | const correctFlag = challenge.flag.trim(); 38 | 39 | if (trimmedInput === correctFlag) { 40 | setFeedback('恭喜!FLAG正確。正在前往下一關...'); 41 | 42 | // 延遲跳轉到下一關 43 | setTimeout(() => { 44 | router.push('/challenge/4_PzDJGfPRPsHY'); 45 | }, 2000); 46 | } else { 47 | setFeedback('FLAG不正確,請再試一次。'); 48 | setTimeout(() => { 49 | setFeedback(''); 50 | }, 3000); 51 | } 52 | }; 53 | 54 | return ( 55 |
56 | {/* 背景效果 */} 57 |
58 | 65 |
66 | 67 | {/* 內容區域 */} 68 |
69 | {/* 標題區域 */} 70 |
71 |

72 | 第{level}關:{challenge.title} 73 |

74 |
75 | 76 | {/* 內容區域 */} 77 |
78 | {/* 題目描述 */} 79 |
80 |
 81 |               {challenge.description}
 82 |             
83 |
84 | 85 | {/* 圖片區域 */} 86 |
87 | {challenge.image && ( 88 |
89 | Base64編碼參考圖 96 |
97 | )} 98 |
99 | 100 | {/* 輸入區域 */} 101 |
102 |
103 | 106 | setUserInput(e.target.value)} 112 | className="w-full max-w-lg px-4 py-3 bg-gray-800 border border-purple-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 text-white" 113 | placeholder="輸入你的答案..." 114 | spellCheck="false" 115 | autoComplete="off" 116 | /> 117 | 123 | 124 | {/* 反饋信息 */} 125 | {feedback && ( 126 |
127 | {feedback} 128 |
129 | )} 130 |
131 |
132 |
133 |
134 | 135 | {/* 底部信息 */} 136 |
137 | © 2025 OhYeahSeC Challenge 138 |
139 |
140 | ); 141 | } 142 | -------------------------------------------------------------------------------- /src/app/challenge/4_PzDJGfPRPsHY/page.tsx: -------------------------------------------------------------------------------- 1 | // src/app/challenge/4/page.tsx 2 | "use client"; 3 | 4 | import React, { useState, useEffect, useRef, FormEvent } from 'react'; 5 | import { useRouter } from 'next/navigation'; 6 | import LetterGlitch from '../../components/LetterGlitch'; 7 | 8 | // 第四關挑戰頁面 9 | export default function Challenge4Page() { 10 | const level = 4; 11 | const [userInput, setUserInput] = useState(''); 12 | const [feedback, setFeedback] = useState(''); 13 | const inputRef = useRef(null); 14 | const router = useRouter(); 15 | 16 | // 第四關題目內容 17 | const challenge = { 18 | title: "二進制密語", 19 | description: "你攔截到一串二進制密碼,這似乎是某種重要訊息被轉換成二進制表示。請將它轉換回 ASCII 文本,找出隱藏的旗幟。\n\n二進制密碼:\n01001111 01011001 01010011 01000011 01111011 01000010 01001001 01001110 01000001 01010010 01011001 01011111 01010100 01001111 01011111 01000001 01010011 01000011 01001001 01001001 01011111 01001001 01010011 01011111 01000110 01010101 01001110 01111101\n\n提示: 每8位二進制數字代表一個 ASCII 字符。", 20 | flag: "OYSC{BINARY_TO_ASCII_IS_FUN}", 21 | }; 22 | 23 | // 自動聚焦輸入框 24 | useEffect(() => { 25 | if (inputRef.current) { 26 | inputRef.current.focus(); 27 | } 28 | }, []); 29 | 30 | // 處理FLAG提交 31 | const handleSubmit = (e: FormEvent) => { 32 | e.preventDefault(); 33 | 34 | // 驗證答案 - 去除頭尾空格後進行比較 35 | const trimmedInput = userInput.trim(); 36 | const correctFlag = challenge.flag.trim(); 37 | 38 | if (trimmedInput === correctFlag) { 39 | setFeedback('恭喜!FLAG正確。正在前往下一關...'); 40 | 41 | // 延遲跳轉到下一關 42 | setTimeout(() => { 43 | router.push('/challenge/5_IlXzElXnMclm'); 44 | }, 2000); 45 | } else { 46 | setFeedback('FLAG不正確,請再試一次。'); 47 | setTimeout(() => { 48 | setFeedback(''); 49 | }, 3000); 50 | } 51 | }; 52 | 53 | return ( 54 |
55 | {/* 背景效果 */} 56 |
57 | 64 |
65 | 66 | {/* 內容區域 */} 67 |
68 | {/* 標題區域 */} 69 |
70 |

71 | 第{level}關:{challenge.title} 72 |

73 |
74 | 75 | {/* 內容區域 */} 76 |
77 | {/* 題目描述 */} 78 |
79 |
 80 |               {challenge.description}
 81 |             
82 |
83 | 84 | {/* 輸入區域 */} 85 |
86 |
87 | 90 | setUserInput(e.target.value)} 96 | className="w-full max-w-lg px-4 py-3 bg-gray-800 border border-purple-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 text-white" 97 | placeholder="輸入你的答案..." 98 | spellCheck="false" 99 | autoComplete="off" 100 | /> 101 | 107 | 108 | {/* 反饋信息 */} 109 | {feedback && ( 110 |
111 | {feedback} 112 |
113 | )} 114 |
115 |
116 |
117 |
118 | 119 | {/* 底部信息 */} 120 |
121 | © 2025 OhYeahSeC Challenge 122 |
123 |
124 | ); 125 | } 126 | -------------------------------------------------------------------------------- /src/app/challenge/5_IlXzElXnMclm/page.tsx: -------------------------------------------------------------------------------- 1 | // src/app/challenge/5/page.tsx 2 | "use client"; 3 | 4 | import React, { useState, useEffect, useRef, FormEvent } from 'react'; 5 | import { useRouter } from 'next/navigation'; 6 | import LetterGlitch from '../../components/LetterGlitch'; 7 | 8 | // 第五關挑戰頁面 9 | export default function Challenge5Page() { 10 | const level = 5; 11 | const [userInput, setUserInput] = useState(''); 12 | const [feedback, setFeedback] = useState(''); 13 | const inputRef = useRef(null); 14 | const router = useRouter(); 15 | 16 | // 題目內容 17 | const challenge = { 18 | title: "除錯大師", 19 | description: "這是一個有語法錯誤的 Python 程式,修復它並執行,就能獲得正確的 FLAG。", 20 | code: `def decrypt_flag(): 21 | encrypted = [79, 89, 83, 67, 123, 68, 69, 66, 85, 71, 71, 73, 78, 71, 95, 73, 83, 95, 70, 85, 78, 125] 22 | flag = "" 23 | 24 | for i in rnage(len(encrypted)): 25 | flag += chr(encrypted[i]) 26 | 27 | return flag 28 | 29 | def main(): 30 | print("解密 FLAG...") 31 | flag = decrypt_flag() 32 | if flag.startswith("OYSC"): 33 | print("成功!FLAG 是:") 34 | print(flag) 35 | else 36 | print("還有錯誤,繼續除錯!") 37 | 38 | if __name__ = "__main__": 39 | main()`, 40 | hint: "提示: 這個程式中有兩個語法錯誤,修復它們後就能正確執行並顯示 FLAG。", 41 | flag: "OYSC{DEBUGGING_IS_FUN}" 42 | }; 43 | 44 | // 自動聚焦輸入框 45 | useEffect(() => { 46 | if (inputRef.current) { 47 | inputRef.current.focus(); 48 | } 49 | }, []); 50 | 51 | // 處理FLAG提交 52 | const handleSubmit = (e: FormEvent) => { 53 | e.preventDefault(); 54 | 55 | // 驗證答案 - 去除頭尾空格後進行比較 56 | const trimmedInput = userInput.trim(); 57 | const correctFlag = challenge.flag.trim(); 58 | 59 | if (trimmedInput === correctFlag) { 60 | setFeedback('恭喜!FLAG正確。正在前往下一關...'); 61 | 62 | // 延遲跳轉到下一關 63 | setTimeout(() => { 64 | router.push('/challenge/6_eMjVGPIewYYR'); 65 | }, 2000); 66 | } else { 67 | setFeedback('FLAG不正確,請再試一次。'); 68 | setTimeout(() => { 69 | setFeedback(''); 70 | }, 3000); 71 | } 72 | }; 73 | 74 | return ( 75 |
76 | {/* 背景效果 */} 77 |
78 | 85 |
86 | 87 | {/* 內容區域 */} 88 |
89 | {/* 標題區域 */} 90 |
91 |

92 | 第{level}關:{challenge.title} 93 |

94 |
95 | 96 | {/* 內容區域 */} 97 |
98 | {/* 題目描述 */} 99 |
100 | {challenge.description} 101 |
102 | 103 | {/* 代碼區塊 - 使用樣式模擬 Markdown 代碼塊 */} 104 |
105 |
python
106 |
107 |               {challenge.code}
108 |             
109 |
110 | 111 | {/* 提示信息 */} 112 |
113 | {challenge.hint} 114 |
115 | 116 | {/* 輸入區域 */} 117 |
118 |
119 | 122 | setUserInput(e.target.value)} 128 | className="w-full max-w-lg px-4 py-3 bg-gray-800 border border-purple-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 text-white" 129 | placeholder="輸入你的答案..." 130 | spellCheck="false" 131 | autoComplete="off" 132 | /> 133 | 139 | 140 | {/* 反饋信息 */} 141 | {feedback && ( 142 |
143 | {feedback} 144 |
145 | )} 146 |
147 |
148 |
149 |
150 | 151 | {/* 底部信息 */} 152 |
153 | © 2025 OhYeahSeC Challenge 154 |
155 |
156 | ); 157 | } 158 | -------------------------------------------------------------------------------- /src/app/challenge/6_eMjVGPIewYYR/page.tsx: -------------------------------------------------------------------------------- 1 | // src/app/challenge/6/page.tsx 2 | "use client"; 3 | 4 | import React, { useState, useEffect, useRef, FormEvent } from 'react'; 5 | import { useRouter } from 'next/navigation'; 6 | import LetterGlitch from '../../components/LetterGlitch'; 7 | 8 | // 第六關挑戰頁面 9 | export default function Challenge6Page() { 10 | const level = 6; 11 | const [userInput, setUserInput] = useState(''); 12 | const [feedback, setFeedback] = useState(''); 13 | const [downloadClicked, setDownloadClicked] = useState(false); 14 | const inputRef = useRef(null); 15 | const router = useRouter(); 16 | 17 | // 題目內容 18 | const challenge = { 19 | title: "眾裡尋他", 20 | description: "系統日誌中充滿了大量的資訊,但其中隱藏著重要的機密。下載這份日誌文件,並在大量的資訊中找到隱藏的FLAG。", 21 | flag: "OYSC{F1L3_S34RCH_M4ST3R}", // 請確保這與你log.txt文件中的FLAG一致 22 | hint: "提示:仔細查看每一行,FLAG可能藏在不起眼的地方" 23 | }; 24 | 25 | // 自動聚焦輸入框 26 | useEffect(() => { 27 | if (inputRef.current) { 28 | inputRef.current.focus(); 29 | } 30 | }, []); 31 | 32 | // 處理檔案下載 33 | const handleDownload = () => { 34 | // 直接使用public目錄中的文件 35 | const fileUrl = '/log.txt'; 36 | 37 | // 創建一個臨時的a標籤用於下載 38 | const link = document.createElement('a'); 39 | link.href = fileUrl; 40 | link.download = 'log.txt'; 41 | 42 | // 模擬點擊 43 | document.body.appendChild(link); 44 | link.click(); 45 | 46 | // 清理 47 | setTimeout(() => { 48 | document.body.removeChild(link); 49 | setDownloadClicked(true); 50 | }, 100); 51 | }; 52 | 53 | // 處理FLAG提交 54 | const handleSubmit = (e: FormEvent) => { 55 | e.preventDefault(); 56 | 57 | // 驗證答案 - 去除頭尾空格後進行比較 58 | const trimmedInput = userInput.trim(); 59 | const correctFlag = challenge.flag.trim(); 60 | 61 | if (trimmedInput === correctFlag) { 62 | setFeedback('恭喜!FLAG正確。正在前往下一關...'); 63 | 64 | // 延遲跳轉到下一關 65 | setTimeout(() => { 66 | router.push('/challenge/7_BbbkammfHZID'); 67 | }, 2000); 68 | } else { 69 | setFeedback('FLAG不正確,請再試一次。'); 70 | setTimeout(() => { 71 | setFeedback(''); 72 | }, 3000); 73 | } 74 | }; 75 | 76 | return ( 77 |
78 | {/* 背景效果 */} 79 |
80 | 87 |
88 | 89 | {/* 內容區域 */} 90 |
91 | {/* 標題區域 */} 92 |
93 |

94 | 第{level}關:{challenge.title} 95 |

96 |
97 | 98 | {/* 內容區域 */} 99 |
100 | {/* 題目描述 */} 101 |
102 | {challenge.description} 103 |
104 | 105 | {/* 終端界面模擬 */} 106 |
107 |
108 |
109 |
110 |
111 | terminal@oysc-server 112 |
113 |
root@oysc-server:~# ls -la
114 |
total 28
115 |
drwxr-xr-x 2 root root 4096 Feb 26 01:23 .
116 |
drwxr-xr-x 6 root root 4096 Feb 26 01:22 ..
117 |
-rw-r--r-- 1 root root 220 Feb 26 01:22 .bash_logout
118 |
-rw-r--r-- 1 root root 3526 Feb 26 01:22 .bashrc
119 |
-rw-r--r-- 1 root root 807 Feb 26 01:22 .profile
120 |
-rw-r--r-- 1 root root 12576 Feb 26 01:23 log.txt
121 |
-rw-r--r-- 1 root root 8192 Feb 26 01:23 system.db
122 |
root@oysc-server:~# cat log.txt | wc -l
123 |
247
124 |
root@oysc-server:~# head -n 3 log.txt
125 |
====== 系統日誌 - OhYeahSeC 安全伺服器 ======
126 |
生成時間: 2025-02-26 01:23:45
127 |
安全等級: 最高機密
128 |
root@oysc-server:~# _
129 |
130 |
131 | 132 | {/* 下載按鈕 */} 133 |
134 | 147 |
148 | 149 | {/* 提示信息 */} 150 |
151 | {challenge.hint} 152 |
153 | 154 | {/* 輸入區域 */} 155 |
156 |
157 | 160 | setUserInput(e.target.value)} 166 | className="w-full max-w-lg px-4 py-3 bg-gray-800 border border-purple-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 text-white" 167 | placeholder="輸入你的答案..." 168 | spellCheck="false" 169 | autoComplete="off" 170 | /> 171 | 177 | 178 | {/* 反饋信息 */} 179 | {feedback && ( 180 |
181 | {feedback} 182 |
183 | )} 184 |
185 |
186 |
187 |
188 | 189 | {/* 底部信息 */} 190 |
191 | © 2025 OhYeahSeC Challenge 192 |
193 |
194 | ); 195 | } 196 | -------------------------------------------------------------------------------- /src/app/challenge/7_BbbkammfHZID/page.tsx: -------------------------------------------------------------------------------- 1 | // src/app/challenge/7/page.tsx 2 | "use client"; 3 | 4 | import React, { useState, useEffect, useRef, FormEvent } from 'react'; 5 | import { useRouter } from 'next/navigation'; 6 | import Image from 'next/image'; 7 | import LetterGlitch from '../../components/LetterGlitch'; 8 | 9 | export default function Challenge7Page() { 10 | const level = 7; 11 | const [userInput, setUserInput] = useState(''); 12 | const [feedback, setFeedback] = useState(''); 13 | const inputRef = useRef(null); 14 | const router = useRouter(); 15 | 16 | const challenge = { 17 | title: "視覺密碼學", 18 | description: "你截獲了一組神秘的圖片,據說只有將它們正確疊加才能找到隱藏的訊息。黑客留下了一個名為 layer1.png 和 layer2.png 的圖層,試著找出它們的秘密。", 19 | flag: "OYSC{V1SU4L_CRYPT0_M4ST3R}" 20 | }; 21 | 22 | useEffect(() => { 23 | if (inputRef.current) { 24 | inputRef.current.focus(); 25 | } 26 | }, []); 27 | 28 | // 添加 FormEvent 类型注解 29 | const handleSubmit = (e: FormEvent) => { 30 | e.preventDefault(); 31 | 32 | const trimmedInput = userInput.trim(); 33 | const correctFlag = challenge.flag.trim(); 34 | 35 | if (trimmedInput === correctFlag) { 36 | setFeedback('恭喜!FLAG正確。正在前往下一關...'); 37 | 38 | setTimeout(() => { 39 | router.push('/challenge/8_WCwfbPFLfcdd'); 40 | }, 2000); 41 | } else { 42 | setFeedback('FLAG不正確,請再試一次。'); 43 | setTimeout(() => { 44 | setFeedback(''); 45 | }, 3000); 46 | } 47 | }; 48 | 49 | return ( 50 |
51 | {/* 背景效果 */} 52 |
53 | 60 |
61 | 62 | {/* 內容區域 */} 63 |
64 | {/* 標題區域 */} 65 |
66 |

67 | 第{level}關:{challenge.title} 68 |

69 |
70 | 71 | {/* 內容區域 */} 72 |
73 | {/* 題目描述 */} 74 |
75 | {challenge.description} 76 |
77 | 78 | {/* 圖片區域 */} 79 |
80 | {/* 第一張圖 */} 81 |
82 |

Layer 1

83 |
84 | 第一層圖片 91 |
92 | 101 |
102 | 103 | {/* 第二張圖 */} 104 |
105 |

Layer 2

106 |
107 | 第二層圖片 114 |
115 | 124 |
125 |
126 | 127 | {/* 輸入區域 */} 128 |
129 |
130 | 133 | setUserInput(e.target.value)} 139 | className="w-full max-w-lg px-4 py-3 bg-gray-800 border border-purple-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 text-white" 140 | placeholder="輸入你的答案..." 141 | spellCheck="false" 142 | autoComplete="off" 143 | /> 144 | 150 | 151 | {/* 反饋信息 */} 152 | {feedback && ( 153 |
154 | {feedback} 155 |
156 | )} 157 |
158 |
159 |
160 |
161 | 162 | {/* 底部信息 */} 163 |
164 | © 2025 OhYeahSeC Challenge 165 |
166 |
167 | ); 168 | } 169 | -------------------------------------------------------------------------------- /src/app/challenge/8_WCwfbPFLfcdd/page.tsx: -------------------------------------------------------------------------------- 1 | // src/app/challenge/8/page.tsx 2 | "use client"; 3 | 4 | import React, { useState, useEffect, useRef, FormEvent } from 'react'; 5 | import { useRouter } from 'next/navigation'; 6 | import LetterGlitch from '../../components/LetterGlitch'; 7 | 8 | // 第八關挑戰頁面 9 | export default function Challenge8Page() { 10 | const level = 8; 11 | const [userInput, setUserInput] = useState(''); 12 | const [feedback, setFeedback] = useState(''); 13 | const inputRef = useRef(null); 14 | const router = useRouter(); 15 | 16 | // 登入平台網址 17 | const loginURL = "https://secret-system.ohyeahsec.org/"; // 替換為你的實際登入頁面URL 18 | 19 | // 題目內容 20 | const challenge = { 21 | title: "管理員登入", 22 | description: "我們發現了一個可疑的登入頁面,據信這裡存放了重要的機密資訊。嘗試找出管理員的憑證並成功登入系統。FLAG就在前端的某處。", 23 | flag: "OYSC{FR0NT3ND_S3CR3TS_4R3_N3V3R_S4F3}" // 確保這與你隱藏在前端的FLAG一致 24 | }; 25 | 26 | // 自動聚焦輸入框 27 | useEffect(() => { 28 | if (inputRef.current) { 29 | inputRef.current.focus(); 30 | } 31 | }, []); 32 | 33 | // 處理FLAG提交 34 | const handleSubmit = (e: FormEvent) => { 35 | e.preventDefault(); 36 | 37 | // 驗證答案 - 去除頭尾空格後進行比較 38 | const trimmedInput = userInput.trim(); 39 | const correctFlag = challenge.flag.trim(); 40 | 41 | if (trimmedInput === correctFlag) { 42 | setFeedback('恭喜!FLAG正確。正在前往下一關...'); 43 | 44 | // 延遲跳轉到下一關 45 | setTimeout(() => { 46 | router.push('/challenge/9_tFwbyvOPpotb'); 47 | }, 2000); 48 | } else { 49 | setFeedback('FLAG不正確,請再試一次。'); 50 | setTimeout(() => { 51 | setFeedback(''); 52 | }, 3000); 53 | } 54 | }; 55 | return ( 56 |
57 | {/* 背景效果 */} 58 |
59 | 66 |
67 | 68 | {/* 內容區域 */} 69 |
70 | {/* 標題區域 */} 71 |
72 |

73 | 第{level}關:{challenge.title} 74 |

75 |
76 | 77 | {/* 內容區域 */} 78 |
79 | {/* 題目描述 */} 80 |
81 | {challenge.description} 82 |
83 | 84 | {/* 網頁登入鏈結 */} 85 |
86 |
87 | 登入頁面: 88 |
89 | 95 | 96 | 97 | 98 | 開啟登入頁面 99 | 100 | 101 | {/* 隱藏註解,提示用戶檢查源代碼 */} 102 | {/* */} 103 |
104 | 105 | {/* 輸入區域 */} 106 |
107 |
108 | 111 | setUserInput(e.target.value)} 117 | className="w-full max-w-lg px-4 py-3 bg-gray-800 border border-purple-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 text-white" 118 | placeholder="輸入你的答案..." 119 | spellCheck="false" 120 | autoComplete="off" 121 | /> 122 | 128 | 129 | {/* 反饋信息 */} 130 | {feedback && ( 131 |
132 | {feedback} 133 |
134 | )} 135 |
136 |
137 |
138 |
139 | 140 | {/* 底部信息 */} 141 |
142 | © 2025 OhYeahSeC Challenge 143 |
144 | 145 | {/* HTML註釋中的提示: 這些都不會顯示在頁面上 */} 146 | {/* 147 | 隱藏在源代碼中的提示,在這個頁面中沒有實際作用, 148 | 但可以讓解題者習慣閱讀HTML源代碼 149 | */} 150 |
151 | ); 152 | } 153 | -------------------------------------------------------------------------------- /src/app/challenge/9_tFwbyvOPpotb/page.tsx: -------------------------------------------------------------------------------- 1 | // src/app/challenge/9/page.tsx 2 | "use client"; 3 | 4 | import React, { useState, useEffect, useRef, FormEvent } from 'react'; 5 | import { useRouter } from 'next/navigation'; 6 | import LetterGlitch from '../../components/LetterGlitch'; 7 | 8 | // 第九關挑戰頁面 9 | export default function Challenge9Page() { 10 | const level = 9; 11 | const [userInput, setUserInput] = useState(''); 12 | const [feedback, setFeedback] = useState(''); 13 | const inputRef = useRef(null); 14 | const router = useRouter(); 15 | 16 | // 登入平台網址 17 | const loginURL = "https://cyber-defender.ohyeahsec.org/"; // 替換為實際的網站URL 18 | 19 | // 題目內容 - 結合之前的世界觀 20 | const challenge = { 21 | title: "守護者的第一道防線", 22 | description: "系統監測顯示,入侵者已經突破了多個安全節點。作為數位守護者,你必須迅速找出隱藏的系統弱點。每一個被遺忘的數位足跡都可能成為拯救「新紀元」的關鍵。\n\n監測數據警示:若不能在限定時間內修復所有節點,防火牆將徹底崩潰,數百萬用戶的數據將暴露於黑暗網路中。", 23 | flag: "OYSC{R0B0TS_TXT_1S_N0T_4_S3CUR1TY_M3CHANISM}" 24 | }; 25 | 26 | // 自動聚焦輸入框 27 | useEffect(() => { 28 | if (inputRef.current) { 29 | inputRef.current.focus(); 30 | } 31 | }, []); 32 | 33 | // 處理FLAG提交 34 | const handleSubmit = (e: FormEvent) => { 35 | e.preventDefault(); 36 | 37 | // 驗證答案 - 去除頭尾空格後進行比較 38 | const trimmedInput = userInput.trim(); 39 | const correctFlag = challenge.flag.trim(); 40 | 41 | if (trimmedInput === correctFlag) { 42 | setFeedback('數據節點修復成功!系統防禦再次穩定...'); 43 | 44 | // 延遲跳轉到下一關 45 | setTimeout(() => { 46 | router.push('/challenge/10_HmfPsygKVlEX'); 47 | }, 2000); 48 | } else { 49 | setFeedback('節點修復失敗!系統防禦仍然脆弱...'); 50 | setTimeout(() => { 51 | setFeedback(''); 52 | }, 3000); 53 | } 54 | }; 55 | return ( 56 |
57 | {/* 背景效果 */} 58 |
59 | 66 |
67 | 68 | {/* 內容區域 */} 69 |
70 | {/* 標題區域 */} 71 |
72 |

73 | 第{level}關:{challenge.title} 74 |

75 |
76 | 77 | {/* 內容區域 */} 78 |
79 | {/* 題目描述 */} 80 |
81 | {challenge.description} 82 |
83 | 84 | {/* 網頁登入鏈結 */} 85 |
86 |
87 | 滲透目標: 88 |
89 | 95 | 96 | 97 | 98 | 進入數位戰場 99 | 100 |
101 | 102 | {/* 輸入區域 */} 103 |
104 |
105 | 108 | setUserInput(e.target.value)} 114 | className="w-full max-w-lg px-4 py-3 bg-gray-800 border border-purple-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 text-white" 115 | placeholder="輸入修復金鑰..." 116 | spellCheck="false" 117 | autoComplete="off" 118 | /> 119 | 125 | 126 | {/* 反饋信息 */} 127 | {feedback && ( 128 |
129 | {feedback} 130 |
131 | )} 132 |
133 |
134 |
135 |
136 | 137 | {/* 底部信息 */} 138 |
139 | © 2025 OhYeahSeC Challenge - 數位守護者 140 |
141 |
142 | ); 143 | } 144 | -------------------------------------------------------------------------------- /src/app/components/CharBlurText.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { useState, useEffect } from 'react'; 3 | 4 | const CharBlurText = ({ 5 | text, 6 | className = "", 7 | charClassName = "", 8 | animationDelay = 0 9 | }: { 10 | text: string; 11 | className?: string; 12 | charClassName?: string; 13 | animationDelay?: number; 14 | }) => { 15 | const [clearChars, setClearChars] = useState([]); 16 | 17 | useEffect(() => { 18 | if (text) { 19 | const chars = text.split(''); 20 | const timer = setTimeout(() => { 21 | const revealInterval = setInterval(() => { 22 | setClearChars(prev => { 23 | if (prev.length < chars.length) { 24 | return [...prev, prev.length]; 25 | } 26 | clearInterval(revealInterval); 27 | return prev; 28 | }); 29 | }, 50); 30 | 31 | return () => { 32 | clearInterval(revealInterval); 33 | }; 34 | }, animationDelay); 35 | 36 | return () => { 37 | clearTimeout(timer); 38 | }; 39 | } 40 | }, [text, animationDelay]); 41 | 42 | return ( 43 |
44 | {text.split('').map((char, index) => ( 45 | 56 | {char} 57 | 58 | ))} 59 |
60 | ); 61 | }; 62 | 63 | export default CharBlurText; -------------------------------------------------------------------------------- /src/app/components/LetterGlitch.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { useRef, useEffect } from "react"; 3 | 4 | const LetterGlitch = ({ 5 | glitchColors = ["#2b4539", "#61dca3", "#61b3dc"], 6 | glitchSpeed = 50, 7 | centerVignette = false, 8 | outerVignette = true, 9 | smooth = true, 10 | }: { 11 | glitchColors: string[]; 12 | glitchSpeed: number; 13 | centerVignette: boolean; 14 | outerVignette: boolean; 15 | smooth: boolean; 16 | }) => { 17 | const canvasRef = useRef(null); 18 | const animationRef = useRef(null); 19 | const letters = useRef< 20 | { 21 | char: string; 22 | color: string; 23 | targetColor: string; 24 | colorProgress: number; 25 | }[] 26 | >([]); 27 | const grid = useRef({ columns: 0, rows: 0 }); 28 | const context = useRef(null); 29 | const lastGlitchTime = useRef(Date.now()); 30 | 31 | const fontSize = 16; 32 | const charWidth = 10; 33 | const charHeight = 20; 34 | 35 | const lettersAndSymbols = [ 36 | "A", 37 | "B", 38 | "C", 39 | "D", 40 | "E", 41 | "F", 42 | "G", 43 | "H", 44 | "I", 45 | "J", 46 | "K", 47 | "L", 48 | "M", 49 | "N", 50 | "O", 51 | "P", 52 | "Q", 53 | "R", 54 | "S", 55 | "T", 56 | "U", 57 | "V", 58 | "W", 59 | "X", 60 | "Y", 61 | "Z", 62 | "!", 63 | "@", 64 | "#", 65 | "$", 66 | "&", 67 | "*", 68 | "(", 69 | ")", 70 | "-", 71 | "_", 72 | "+", 73 | "=", 74 | "/", 75 | "[", 76 | "]", 77 | "{", 78 | "}", 79 | ";", 80 | ":", 81 | "<", 82 | ">", 83 | ",", 84 | "0", 85 | "1", 86 | "2", 87 | "3", 88 | "4", 89 | "5", 90 | "6", 91 | "7", 92 | "8", 93 | "9", 94 | ]; 95 | 96 | const getRandomChar = () => { 97 | return lettersAndSymbols[ 98 | Math.floor(Math.random() * lettersAndSymbols.length) 99 | ]; 100 | }; 101 | 102 | const getRandomColor = () => { 103 | return glitchColors[Math.floor(Math.random() * glitchColors.length)]; 104 | }; 105 | 106 | const hexToRgb = (hex: string) => { 107 | const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 108 | hex = hex.replace(shorthandRegex, (m, r, g, b) => { 109 | return r + r + g + g + b + b; 110 | }); 111 | 112 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 113 | return result 114 | ? { 115 | r: parseInt(result[1], 16), 116 | g: parseInt(result[2], 16), 117 | b: parseInt(result[3], 16), 118 | } 119 | : null; 120 | }; 121 | 122 | const interpolateColor = ( 123 | start: { r: number; g: number; b: number }, 124 | end: { r: number; g: number; b: number }, 125 | factor: number 126 | ) => { 127 | const result = { 128 | r: Math.round(start.r + (end.r - start.r) * factor), 129 | g: Math.round(start.g + (end.g - start.g) * factor), 130 | b: Math.round(start.b + (end.b - start.b) * factor), 131 | }; 132 | return `rgb(${result.r}, ${result.g}, ${result.b})`; 133 | }; 134 | 135 | const calculateGrid = (width: number, height: number) => { 136 | const columns = Math.ceil(width / charWidth); 137 | const rows = Math.ceil(height / charHeight); 138 | return { columns, rows }; 139 | }; 140 | 141 | const initializeLetters = (columns: number, rows: number) => { 142 | grid.current = { columns, rows }; 143 | const totalLetters = columns * rows; 144 | letters.current = Array.from({ length: totalLetters }, () => ({ 145 | char: getRandomChar(), 146 | color: getRandomColor(), 147 | targetColor: getRandomColor(), 148 | colorProgress: 1, 149 | })); 150 | }; 151 | 152 | const resizeCanvas = () => { 153 | const canvas = canvasRef.current; 154 | if (!canvas) return; 155 | const parent = canvas.parentElement; 156 | if (!parent) return; 157 | 158 | const dpr = window.devicePixelRatio || 1; 159 | const rect = parent.getBoundingClientRect(); 160 | 161 | canvas.width = rect.width * dpr; 162 | canvas.height = rect.height * dpr; 163 | 164 | canvas.style.width = `${rect.width}px`; 165 | canvas.style.height = `${rect.height}px`; 166 | 167 | if (context.current) { 168 | context.current.setTransform(dpr, 0, 0, dpr, 0, 0); 169 | } 170 | 171 | const { columns, rows } = calculateGrid(rect.width, rect.height); 172 | initializeLetters(columns, rows); 173 | drawLetters(); 174 | }; 175 | 176 | const drawLetters = () => { 177 | if (!context.current || letters.current.length === 0) return; 178 | const ctx = context.current; 179 | const { width, height } = canvasRef.current!.getBoundingClientRect(); 180 | ctx.clearRect(0, 0, width, height); 181 | ctx.font = `${fontSize}px monospace`; 182 | ctx.textBaseline = "top"; 183 | 184 | letters.current.forEach((letter, index) => { 185 | const x = (index % grid.current.columns) * charWidth; 186 | const y = Math.floor(index / grid.current.columns) * charHeight; 187 | ctx.fillStyle = letter.color; 188 | ctx.fillText(letter.char, x, y); 189 | }); 190 | }; 191 | 192 | const updateLetters = () => { 193 | if (!letters.current || letters.current.length === 0) return; // Prevent accessing empty array 194 | 195 | const updateCount = Math.max(1, Math.floor(letters.current.length * 0.05)); 196 | 197 | for (let i = 0; i < updateCount; i++) { 198 | const index = Math.floor(Math.random() * letters.current.length); 199 | if (!letters.current[index]) continue; // Skip if index is invalid 200 | 201 | letters.current[index].char = getRandomChar(); 202 | letters.current[index].targetColor = getRandomColor(); 203 | 204 | if (!smooth) { 205 | letters.current[index].color = letters.current[index].targetColor; 206 | letters.current[index].colorProgress = 1; 207 | } else { 208 | letters.current[index].colorProgress = 0; 209 | } 210 | } 211 | }; 212 | 213 | const handleSmoothTransitions = () => { 214 | let needsRedraw = false; 215 | letters.current.forEach((letter) => { 216 | if (letter.colorProgress < 1) { 217 | letter.colorProgress += 0.05; 218 | if (letter.colorProgress > 1) letter.colorProgress = 1; 219 | 220 | const startRgb = hexToRgb(letter.color); 221 | const endRgb = hexToRgb(letter.targetColor); 222 | if (startRgb && endRgb) { 223 | letter.color = interpolateColor( 224 | startRgb, 225 | endRgb, 226 | letter.colorProgress 227 | ); 228 | needsRedraw = true; 229 | } 230 | } 231 | }); 232 | 233 | if (needsRedraw) { 234 | drawLetters(); 235 | } 236 | }; 237 | 238 | const animate = () => { 239 | const now = Date.now(); 240 | if (now - lastGlitchTime.current >= glitchSpeed) { 241 | updateLetters(); 242 | drawLetters(); 243 | lastGlitchTime.current = now; 244 | } 245 | 246 | if (smooth) { 247 | handleSmoothTransitions(); 248 | } 249 | 250 | animationRef.current = requestAnimationFrame(animate); 251 | }; 252 | 253 | useEffect(() => { 254 | const canvas = canvasRef.current; 255 | if (!canvas) return; 256 | 257 | context.current = canvas.getContext("2d"); 258 | resizeCanvas(); 259 | animate(); 260 | 261 | let resizeTimeout: NodeJS.Timeout; 262 | 263 | const handleResize = () => { 264 | clearTimeout(resizeTimeout); 265 | resizeTimeout = setTimeout(() => { 266 | cancelAnimationFrame(animationRef.current as number); 267 | resizeCanvas(); 268 | animate(); 269 | }, 100); 270 | }; 271 | 272 | window.addEventListener("resize", handleResize); 273 | 274 | return () => { 275 | cancelAnimationFrame(animationRef.current!); 276 | window.removeEventListener("resize", handleResize); 277 | }; 278 | // eslint-disable-next-line react-hooks/exhaustive-deps 279 | }, [glitchSpeed, smooth]); 280 | 281 | return ( 282 |
283 | 284 | {outerVignette && ( 285 |
288 | )} 289 | {centerVignette && ( 290 |
293 | )} 294 |
295 | ); 296 | }; 297 | 298 | export default LetterGlitch; 299 | -------------------------------------------------------------------------------- /src/app/end_HUAterpbJppw/page.tsx: -------------------------------------------------------------------------------- 1 | // src/app/challenge/end_HUAterpbJppw/page.tsx 2 | "use client"; 3 | 4 | import React, { useState, useEffect } from 'react'; 5 | import { useRouter } from 'next/navigation'; 6 | import LetterGlitch from '../components/LetterGlitch'; 7 | import Confetti from 'react-confetti'; 8 | 9 | // 挑戰完成頁面 10 | export default function ChallengeCompletePage() { 11 | const [typedText, setTypedText] = useState(""); 12 | const [congratsVisible, setCongratsVisible] = useState(false); 13 | const [cakeVisible, setCakeVisible] = useState(false); 14 | const [certificateVisible, setCertificateVisible] = useState(false); 15 | const [buttonVisible, setButtonVisible] = useState(false); 16 | const [windowSize, setWindowSize] = useState({ width: 0, height: 0 }); 17 | const router = useRouter(); 18 | 19 | // 監聽視窗大小變化 20 | useEffect(() => { 21 | const handleResize = () => { 22 | setWindowSize({ 23 | width: document.documentElement.clientWidth, 24 | height: document.documentElement.clientHeight 25 | }); 26 | }; 27 | 28 | // 初次設定 29 | handleResize(); 30 | 31 | // 添加監聽器 32 | window.addEventListener('resize', handleResize); 33 | 34 | // 清理監聽器 35 | return () => window.removeEventListener('resize', handleResize); 36 | }, []); 37 | 38 | // ASCII 蛋糕圖案(增強版) 39 | const asciiCake = ` 40 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢔⠊⠳⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀ 41 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣳⣶⠜⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 42 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣟⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 43 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⡿⣷⣯⢷⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 44 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡶⠾⠛⠛⠻⢿⣿⣳⣯⣟⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀ 45 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⠟⠁⣀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠻⢶⣄⠀⠀⠀⠀⠀⠀ 46 | ⠀⠀⠀⠀⠀⠀⡰⡆⠀⠀⠀⠀⠀⠀⢿⣄⠀⠉⠀⠀⠀⠀⠀⠀⠀⠀⠛⠀⠀⠀⠀⠙⣧⡀⠀⠀⠀⠀ 47 | ⠀⠀⠀⠀⠀⠀⣧⡎⠀⠀⠀⠀⠀⠀⠀⠉⣿⠛⠷⠶⣦⣤⣤⣤⣤⣤⣤⣴⠾⠓⠀⠀⠘⣷⡀⠀⠀⠀ 48 | ⠀⠀⠀⠀⠀⠀⣻⣇⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣷⠀⠀⠀ 49 | ⠀⡰⣚⡉⠉⣉⡉⠉⣠⣁⣈⠬⡇⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⡇⠀⠀ 50 | ⠀⢇⠀⠈⠉⠀⠈⠉⠀⠀⠀⣰⠃⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⡄⠈⣿⠀⠀ 51 | ⠐⠛⠛⠛⢻⣿⣿⣿⡿⠛⠛⠛⠛⠀⠀⢠⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⢹⡆⠀ 52 | ⠀⠀⠀⠀⠀⠀⠀⠈⠻⣆⠀⠀⠀⠀⢠⡿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⠇⠀⠘⣧⠀ 53 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢷⣄⣀⣰⡟⠁⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⣿⡄ 54 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠉⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⢹⡇ 55 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣏⠀⠀⠀⠘⣷ 56 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⡿⠀⠀⠀⠀⣿ 57 | `; 58 | 59 | // 打字機效果的文字 60 | const finalMessage = "恭喜你完成了所有的挑戰!你的毅力和技術能力令人印象深刻。這只是你資安旅程的起點,世界上還有更多謎題等待你解開。資訊安全是一場永無止境的探索,每一個漏洞、每一段程式碼都蘊含著新的知識。希望這次的挑戰能夠點燃你對資安的熱情,無論是CTF競賽、滲透測試,還是系統防禦,都有你大展身手的舞台。持續學習,保持好奇,也許未來的某一天,我們能在資安的世界裡再次相遇。願你在這條充滿挑戰的道路上,不斷成長,不斷超越自己!"; 61 | 62 | // 打字機效果和動畫序列 63 | useEffect(() => { 64 | if (typeof window !== 'undefined') { 65 | // 顯示動畫序列 66 | setTimeout(() => setCongratsVisible(true), 500); 67 | setTimeout(() => setCakeVisible(true), 1500); 68 | 69 | let i = 0; 70 | const typing = setInterval(() => { 71 | if (i < finalMessage.length) { 72 | setTypedText(finalMessage.substring(0, i + 1)); 73 | i++; 74 | } else { 75 | clearInterval(typing); 76 | setTimeout(() => setCertificateVisible(true), 500); 77 | setTimeout(() => setButtonVisible(true), 1000); 78 | } 79 | }, 50); 80 | 81 | return () => clearInterval(typing); 82 | } 83 | }, []); 84 | 85 | // 處理回到首頁 86 | const handleGoHome = () => { 87 | router.push('/'); 88 | }; 89 | 90 | return ( 91 |
92 | {/* 背景效果 */} 93 |
94 | 101 |
102 | 103 | {/* 五彩紙屑效果 - 全螢幕覆蓋 */} 104 | 121 | 122 | {/* 內容區域 */} 123 |
124 | {/* 標題區域 */} 125 |
130 |

131 | 挑戰完成! 132 |

133 |
134 | 135 | {/* 內容區域 */} 136 |
137 | {/* ASCII蛋糕 */} 138 |
143 |
144 |               {asciiCake}
145 |             
146 |
147 | 148 | {/* 打字機效果訊息 */} 149 |
150 |

151 | {typedText} 152 | 153 |

154 |
155 | 156 | {/* 證書區域 */} 157 |
162 | {/* 裝飾性元素 */} 163 |
164 |
165 | 166 |
167 |

榮譽證書

168 |
169 |

授予 勇敢的挑戰者

170 |

成功完成 OhYeahSeC CTF Challenge 的所有關卡

171 |

完成時間:{new Date().toLocaleDateString()}

172 |
173 |

知識是防禦的第一道防線,好奇心是進步的原動力

174 |
175 |
176 |
177 |
178 | 179 | {/* 返回按鈕 */} 180 |
185 | 191 |
192 |
193 |
194 | 195 | {/* 底部資訊 */} 196 |
197 | © 2025 OhYeahSeC Challenge 198 |
199 |
200 | ); 201 | } 202 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osga24/OhYeahSeC-CTF-Challenge/9dcd2f6507bc6b3ef76bb942804b1134a342623a/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --background: #ffffff; 7 | --foreground: #171717; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | :root { 12 | --background: #0a0a0a; 13 | --foreground: #ededed; 14 | } 15 | } 16 | 17 | body { 18 | color: var(--foreground); 19 | background: var(--background); 20 | font-family: Arial, Helvetica, sans-serif; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Geist, Geist_Mono } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const geistSans = Geist({ 6 | variable: "--font-geist-sans", 7 | subsets: ["latin"], 8 | }); 9 | 10 | const geistMono = Geist_Mono({ 11 | variable: "--font-geist-mono", 12 | subsets: ["latin"], 13 | }); 14 | 15 | export const metadata: Metadata = { 16 | title: "CyberHack OhYeahSeC Challenge", 17 | icons: { 18 | icon: "/images/OhYeahSeC.png", 19 | }, 20 | description: "CyberHackOhYeahSeCChallenge數位時代下的安全測驗", 21 | }; 22 | 23 | export default function RootLayout({ 24 | children, 25 | }: Readonly<{ 26 | children: React.ReactNode; 27 | }>) { 28 | return ( 29 | 30 | 33 | {children} 34 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { useState, useEffect } from 'react'; 3 | import LetterGlitch from '@/app/components/LetterGlitch'; 4 | import CharBlurText from '@/app/components/CharBlurText'; 5 | import { useRouter } from 'next/navigation'; 6 | 7 | export default function Page() { 8 | const [showButton, setShowButton] = useState(false); 9 | const router = useRouter(); 10 | 11 | useEffect(() => { 12 | const timer = setTimeout(() => { 13 | setShowButton(true); 14 | }, 3000); 15 | 16 | return () => clearTimeout(timer); 17 | }, []); 18 | 19 | const handleStartChallenge = () => { 20 | router.push('/story'); 21 | }; 22 | 23 | return ( 24 |
25 | {/* 背景效果 */} 26 |
27 | 34 |
35 | 36 | {/* 内容区域 */} 37 |
38 | 44 | 45 | 51 | 52 | 58 | 59 | {/* 延迟显示的按钮 */} 60 |
65 | 76 |
77 |
78 | 79 | {/* 渐变叠加层 */} 80 |
81 |
82 | ); 83 | } -------------------------------------------------------------------------------- /src/app/story/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState, useEffect, useRef } from 'react'; 4 | import { useRouter } from 'next/navigation'; 5 | import LetterGlitch from '../components/LetterGlitch'; 6 | 7 | interface StoryPage { 8 | title: string; 9 | content: string; 10 | image?: string; 11 | } 12 | 13 | export default function StoryPage() { 14 | const [currentPage, setCurrentPage] = useState(0); 15 | const [showNextButton, setShowNextButton] = useState(false); 16 | const [typedTitle, setTypedTitle] = useState(''); 17 | const [typedContent, setTypedContent] = useState(''); 18 | const [isTypingContent, setIsTypingContent] = useState(false); 19 | 20 | const typingIntervalRef = useRef(null); 21 | const contentTypingIntervalRef = useRef(null); 22 | 23 | const router = useRouter(); 24 | 25 | const storyPages: StoryPage[] = [ 26 | { 27 | title: "數位世界的警報", 28 | content: "在虛擬網域「新紀元」中,一個神秘的系統警報突然在你的介面上彈出。警報顯示核心數據庫遭到入侵,多個安全節點已被攻破,整個虛擬世界的穩定性面臨嚴重威脅。" 29 | }, 30 | { 31 | title: "守護者的挑戰", 32 | content: "作為具備高級權限的數位守護者,系統自動將你傳送到安全測試區域。只有通過一系列資安挑戰,你才能獲得修復受損節點的加密金鑰。" 33 | }, 34 | { 35 | title: "數位崩壞的威脅", 36 | content: "監測數據顯示,若不能在限定時間內修復所有節點,「新紀元」的防火牆將徹底崩潰,數百萬用戶的數據將暴露於黑暗網路中。你是最後的希望。" 37 | }, 38 | { 39 | title: "挑戰啟動", 40 | content: "第一個安全測試已準備就緒。系統將評估你的加密破解、代碼分析和漏洞識別能力。每完成一個挑戰,就能恢復一個安全節點的運作。\n\n虛擬世界的命運掌握在你手中。你準備好接受挑戰了嗎?" 41 | } 42 | ]; 43 | 44 | const currentStory = storyPages[currentPage]; 45 | 46 | useEffect(() => { 47 | setTypedTitle(''); 48 | setTypedContent(''); 49 | setIsTypingContent(false); 50 | setShowNextButton(false); 51 | 52 | if (typingIntervalRef.current) { 53 | clearInterval(typingIntervalRef.current); 54 | } 55 | if (contentTypingIntervalRef.current) { 56 | clearInterval(contentTypingIntervalRef.current); 57 | } 58 | 59 | const title = currentStory.title; 60 | let titleIndex = 0; 61 | 62 | typingIntervalRef.current = setInterval(() => { 63 | if (titleIndex < title.length) { 64 | setTypedTitle(title.substring(0, titleIndex + 1)); 65 | titleIndex++; 66 | } else { 67 | if (typingIntervalRef.current) { 68 | clearInterval(typingIntervalRef.current); 69 | typingIntervalRef.current = null; 70 | } 71 | setIsTypingContent(true); 72 | } 73 | }, 100); 74 | 75 | return () => { 76 | if (typingIntervalRef.current) { 77 | clearInterval(typingIntervalRef.current); 78 | typingIntervalRef.current = null; 79 | } 80 | if (contentTypingIntervalRef.current) { 81 | clearInterval(contentTypingIntervalRef.current); 82 | contentTypingIntervalRef.current = null; 83 | } 84 | }; 85 | }, [currentPage, currentStory.title]); 86 | 87 | useEffect(() => { 88 | if (isTypingContent) { 89 | const content = currentStory.content; 90 | let contentIndex = 0; 91 | 92 | contentTypingIntervalRef.current = setInterval(() => { 93 | if (contentIndex < content.length) { 94 | setTypedContent(content.substring(0, contentIndex + 1)); 95 | contentIndex++; 96 | } else { 97 | if (contentTypingIntervalRef.current) { 98 | clearInterval(contentTypingIntervalRef.current); 99 | contentTypingIntervalRef.current = null; 100 | } 101 | setTimeout(() => { 102 | setShowNextButton(true); 103 | }, 500); 104 | } 105 | }, 50); 106 | 107 | return () => { 108 | if (contentTypingIntervalRef.current) { 109 | clearInterval(contentTypingIntervalRef.current); 110 | contentTypingIntervalRef.current = null; 111 | } 112 | }; 113 | } 114 | }, [isTypingContent, currentStory.content]); 115 | 116 | const handleNext = () => { 117 | if (currentPage < storyPages.length - 1) { 118 | setCurrentPage(currentPage + 1); 119 | } else { 120 | router.push('/challenge/1_QTMpnQaJpslW'); 121 | } 122 | }; 123 | 124 | const handleSkip = () => { 125 | router.push('/challenge/1_QTMpnQaJpslW'); 126 | }; 127 | 128 | return ( 129 |
130 |
131 | 138 |
139 | 140 |
150 | 151 |
152 |
153 |

154 | {typedTitle} 155 | {typedTitle.length < currentStory.title.length && 156 | 157 | } 158 |

159 | 160 |
161 |
162 |               {typedContent}
163 |               {isTypingContent && typedContent.length < currentStory.content.length &&
164 |                 
165 |               }
166 |             
167 |
168 | 169 |
170 | 176 | 177 |
180 | 186 |
187 |
188 |
189 |
190 | 191 |
192 | {storyPages.map((_, index) => ( 193 |
197 | ))} 198 |
199 |
200 | ); 201 | } 202 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | colors: { 12 | background: "var(--background)", 13 | foreground: "var(--foreground)", 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } satisfies Config; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | --------------------------------------------------------------------------------