diff --git a/src/App.tsx b/src/App.tsx index e83aa81..d33cabe 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,9 +2,81 @@ import React, { useState } from 'react'; // import Day1Desktop from './Day1Desktop'; import MainApp from './MainApp'; import ChatterboxTTS from './components/deepfake/ChatterboxTTS'; +import Case1Desktop from './cases/Case1Desktop'; +import Case2Desktop from './cases/Case2Desktop'; +import Case3Desktop from './cases/Case3Desktop'; +import Case4Desktop from './cases/Case4Desktop'; +import MyQuize from './components/MyQuize'; +import CyberSecurityArticle from './components/CyberSecurityArticle'; export type WallpaperType = 'xp' | 'win7' | 'win10'; + + +const App: React.FC = () => { +// const [day1Complete, setDay1Complete] = useState(false); +// if (!day1Complete) { +// return setDay1Complete(true)} />; +// } + // return + + const [showCase1, setShowCase1] = useState(false); + const [showCase2, setShowCase2] = useState(false); + const [showCase3, setShowCase3] = useState(false); + const [showCase4, setShowCase4] = useState(false); + const [showWiki, setShowWiki] = useState(false); + const [showQuiz, setShowQuiz] = useState(false); + const [showDeepfake, setDeepfake] = useState(false); + + return (
+

Хайо

+ + + + + + + + {showWiki && ( +
+ ; +
+ )} + {showCase1 && ( +
+ setShowCase1(false)} />; +
+ )} + {showCase2 && ( +
+ setShowCase2(false)} />; +
+ )} + {showCase3 && ( +
+ setShowCase3(false)} />; +
+ )} + {showCase4 && ( +
+ setShowCase4(false)} />; +
+ )} + + {showQuiz && ( +
+ ; +
+ )} + {showDeepfake && ( +
+ ; +
+ )} +
); +}; + +/* const App: React.FC = () => { // const [day1Complete, setDay1Complete] = useState(false); @@ -14,6 +86,6 @@ const App: React.FC = () => { // return // return ; return ; -}; +};*/ export default App; diff --git a/src/MainApp.tsx b/src/MainApp.tsx index 9b0851b..ae0b1cc 100644 --- a/src/MainApp.tsx +++ b/src/MainApp.tsx @@ -6,6 +6,7 @@ import './App.css'; // import VScodeApp from './apps/vscode/vscode'; import YandexApp from './apps/yandex/Yandex'; import TerminalApp from './apps/terminal/Terminal'; +import AmneziaVPN from './apps/amneziavpn/AmneziaVPN'; export interface WindowType { id: string; @@ -16,7 +17,12 @@ export interface WindowType { url: string; } -const MainApp: React.FC = () => { +interface IMainApp { + showGosuslugi: boolean; + showAmnezia: boolean; +} + +const MainApp: React.FC = ({showGosuslugi, showAmnezia}) => { const [windows, setWindows] = useState([]); const [nextId, setNextId] = useState(1); @@ -67,8 +73,79 @@ const MainApp: React.FC = () => { ); + const AmneziaIcon = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) const openYandex = () => { - openWindow('Yandex', , 'Я', 'https://yandex.ru'); + openWindow('Yandex', , 'Я', 'https://yandex.ru'); + }; + + const openAmnezia = () => { + openWindow('Amnezia VPN', , 'Я', 'https://yandex.ru'); }; const openTerminal = () => { @@ -91,6 +168,10 @@ const MainApp: React.FC = () => { {/* */} {/* */} + {showAmnezia && ( + + )} + {windows.map(window => !window.isMinimized && ( diff --git a/src/apps/amneziavpn/AmneziaVPN.css b/src/apps/amneziavpn/AmneziaVPN.css new file mode 100644 index 0000000..ff56a05 --- /dev/null +++ b/src/apps/amneziavpn/AmneziaVPN.css @@ -0,0 +1,151 @@ +.amnezia-app { + display: flex; + flex-direction: column; + height: 100%; + background: #1a1a2e; + color: #fff; + font-family: 'Segoe UI', sans-serif; + user-select: none; +} + +.amnezia-header { + display: flex; + align-items: center; + gap: 10px; + padding: 16px 20px; + background: #16213e; + border-bottom: 1px solid #0f3460; +} + +.amnezia-title { + font-size: 16px; + font-weight: 600; + letter-spacing: 0.5px; +} + +.amnezia-body { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex: 1; + gap: 24px; + padding: 20px; +} + +.status-indicator { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; +} + +.status-dot { + width: 10px; + height: 10px; + border-radius: 50%; +} + +.status-indicator.connected .status-dot { + background: #6c63ff; + box-shadow: 0 0 8px #6c63ff; +} + +.status-indicator.connecting .status-dot { + background: #f5a623; + animation: pulse 1s infinite; +} + +.status-indicator.disconnected .status-dot { + background: #555; +} + +.shield { + transition: filter 0.3s; +} + +.shield.connected { + filter: drop-shadow(0 0 16px #6c63ff88); +} + +.shield.connecting { + filter: drop-shadow(0 0 12px #f5a62388); +} + +.connect-btn { + padding: 12px 48px; + border-radius: 24px; + border: none; + font-size: 15px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; + letter-spacing: 0.5px; +} + +.connect-btn.disconnected { + background: #6c63ff; + color: #fff; +} + +.connect-btn.disconnected:hover { + background: #7c73ff; + transform: scale(1.03); +} + +.connect-btn.connected { + background: transparent; + color: #ff6b6b; + border: 2px solid #ff6b6b; +} + +.connect-btn.connected:hover { + background: #ff6b6b22; +} + +.connect-btn.connecting { + background: #333; + color: #aaa; + cursor: not-allowed; +} + +.connection-info { + width: 100%; + max-width: 260px; + background: #16213e; + border-radius: 12px; + padding: 14px 18px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.info-row { + display: flex; + justify-content: space-between; + font-size: 13px; +} + +.info-label { + color: #888; +} + +.info-value { + color: #fff; + font-weight: 500; +} + +.spin { + animation: spin 1s linear infinite; + transform-origin: 60px 70px; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} diff --git a/src/apps/amneziavpn/AmneziaVPN.tsx b/src/apps/amneziavpn/AmneziaVPN.tsx new file mode 100644 index 0000000..11250e4 --- /dev/null +++ b/src/apps/amneziavpn/AmneziaVPN.tsx @@ -0,0 +1,96 @@ +import React, { useState } from 'react'; +import './AmneziaVPN.css'; + +const AmneziaVPN: React.FC = () => { + const [connected, setConnected] = useState(() => localStorage.getItem('amnezia_connected') === 'true'); + const [connecting, setConnecting] = useState(false); + + const handleToggle = () => { + if (connected) { + setConnected(false); + localStorage.setItem('amnezia_connected', 'false'); + } else { + setConnecting(true); + setTimeout(() => { + setConnecting(false); + setConnected(true); + localStorage.setItem('amnezia_connected', 'true'); + }, 1500); + } + }; + + const statusText = connecting ? 'Подключение...' : connected ? 'Подключено' : 'Отключено'; + const statusClass = connecting ? 'connecting' : connected ? 'connected' : 'disconnected'; + + return ( +
+
+
+ + + + + + +
+ AmneziaVPN +
+ +
+
+
+ {statusText} +
+ +
+ + + {connected && ( + + )} + {!connected && !connecting && ( + + )} + {connecting && ( + + )} + +
+ + + + {connected && ( +
+
+ Сервер + Netherlands #3 +
+
+ Протокол + WireGuard +
+
+ )} +
+
+ ); +}; + +export default AmneziaVPN; diff --git a/src/apps/vscode/vscode.css b/src/apps/vscode/vscode.css index 21f0f7b..e69de29 100644 --- a/src/apps/vscode/vscode.css +++ b/src/apps/vscode/vscode.css @@ -1,3 +0,0 @@ -.vscode_main_window { - background: blue; -} \ No newline at end of file diff --git a/src/apps/yandex/Yandex.tsx b/src/apps/yandex/Yandex.tsx index 143676b..16d6b7f 100644 --- a/src/apps/yandex/Yandex.tsx +++ b/src/apps/yandex/Yandex.tsx @@ -1,7 +1,11 @@ import React, { useState } from 'react'; import './Yandex.css'; -const YandexApp: React.FC = () => { +interface IYandexApp { + showGosuslugi: boolean; +} + +const YandexApp: React.FC = ({showGosuslugi}) => { const [currentPage, setCurrentPage] = useState<'yandex' | 'email' | 'gosuslugi' | 'fakeSite' | 'telelitra' | 'searchResults'>('yandex'); const [selectedEmail, setSelectedEmail] = useState(null); const [login, setLogin] = useState(''); @@ -107,21 +111,22 @@ const YandexApp: React.FC = () => { />
- -
-
setCurrentPage('email')}> -
- -
- Почта -
-
setCurrentPage('gosuslugi')}> -
- -
- Госуслуги -
-
+ {showGosuslugi && ( +
+
setCurrentPage('email')}> +
+ +
+ Почта +
+
setCurrentPage('gosuslugi')}> +
+ +
+ Госуслуги +
+
+ )}

Найдется всё. Например, ТелеЛитр

diff --git a/src/Day1Desktop.css b/src/cases/Case1Desktop.css similarity index 99% rename from src/Day1Desktop.css rename to src/cases/Case1Desktop.css index 2818adf..34b27d7 100644 --- a/src/Day1Desktop.css +++ b/src/cases/Case1Desktop.css @@ -123,4 +123,4 @@ .day1-modal-btn:hover { background: #45a049; -} \ No newline at end of file +} diff --git a/src/Day1Desktop.tsx b/src/cases/Case1Desktop.tsx similarity index 93% rename from src/Day1Desktop.tsx rename to src/cases/Case1Desktop.tsx index 40c65c8..8632a35 100644 --- a/src/Day1Desktop.tsx +++ b/src/cases/Case1Desktop.tsx @@ -1,12 +1,12 @@ import React, { useState } from 'react'; -import type { WallpaperType } from './App'; -import './Day1Desktop.css'; +import type { WallpaperType } from '../App'; +import './Case1Desktop.css'; -interface Day1DesktopProps { +interface Case1DesktopProps { onComplete: (type: WallpaperType) => void; } -const Day1Desktop: React.FC = ({ onComplete }) => { +const Case1Desktop: React.FC = ({ onComplete }) => { const [version, setVersion] = useState(1); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); @@ -97,4 +97,4 @@ const Day1Desktop: React.FC = ({ onComplete }) => { ); }; -export default Day1Desktop; +export default Case1Desktop; diff --git a/src/cases/Case2Desktop.css b/src/cases/Case2Desktop.css new file mode 100644 index 0000000..e69de29 diff --git a/src/cases/Case2Desktop.tsx b/src/cases/Case2Desktop.tsx new file mode 100644 index 0000000..6112734 --- /dev/null +++ b/src/cases/Case2Desktop.tsx @@ -0,0 +1,19 @@ +import React, { useState } from 'react'; +import type { WallpaperType } from '../App'; +import MainApp from '../MainApp'; +// import './Case2Desktop.css'; + +interface Case2DesktopProps { + onComplete: (type: WallpaperType) => void; +} + +const Case2Desktop: React.FC = ({ onComplete }) => { + + return ( +
+ +
+ ); +}; + +export default Case2Desktop; diff --git a/src/cases/Case3Desktop.tsx b/src/cases/Case3Desktop.tsx new file mode 100644 index 0000000..e2c50ac --- /dev/null +++ b/src/cases/Case3Desktop.tsx @@ -0,0 +1,19 @@ +import React, { useState } from 'react'; +import type { WallpaperType } from '../App'; +import MainApp from '../MainApp'; +// import './Case3Desktop.css'; + +interface Case3DesktopProps { + onComplete: (type: WallpaperType) => void; +} + +const Case3Desktop: React.FC = ({ onComplete }) => { + + return ( +
+ +
+ ); +}; + +export default Case3Desktop; diff --git a/src/cases/Case4Desktop.tsx b/src/cases/Case4Desktop.tsx new file mode 100644 index 0000000..c3c02a9 --- /dev/null +++ b/src/cases/Case4Desktop.tsx @@ -0,0 +1,18 @@ +import React, { useState } from 'react'; +import type { WallpaperType } from '../App'; +import MainApp from '../MainApp'; + +interface Case3DesktopProps { + onComplete: (type: WallpaperType) => void; +} + +const Case4Desktop: React.FC = ({ onComplete }) => { + + return ( +
+ +
+ ); +}; + +export default Case4Desktop; diff --git a/src/components/CyberSecurityArticle.tsx b/src/components/CyberSecurityArticle.tsx new file mode 100644 index 0000000..d266411 --- /dev/null +++ b/src/components/CyberSecurityArticle.tsx @@ -0,0 +1,327 @@ +import React from 'react'; + +const CyberSecurityArticle: React.FC = () => { + return ( + <> + + +
+
+ {/* Инфобокс */} + + +

Кибербезопасность

+ +

+ В современном мире кибербезопасность стала одной из ключевых областей для защиты данных и инфраструктуры в условиях быстрого развития технологий. Из-за перехода на цифровые технологии и автоматизации процессов во всем мире кибербезопасность создает новые возможности и риски, связанные с киберугрозами. Киберугрозы принимают различные формы и требуют от стран не только технологических решений, но и комплексного подхода к обеспечению безопасности. +

+ + {/* Оглавление */} + + +
+

Социальная инженерия, или Как «взломать» человека

+ + {/* Миниатюра */} +
+ Кредитные карты +
Защита финансовых данных
+
+ +

+ Социальная инженерия — понятие совсем не новое, оно появилось давным-давно. Известными специалистами-практиками в этой науке стали, например, Кевин Митник и Фрэнк Абаньяле, которые на сегодняшний день являются ведущими консультантами по безопасности. Они живая иллюстрация того, что преступники могут превращаться в уважаемых экспертов. К примеру, тот же Фрэнк Абаньяле был одним из самых знаменитых и виртуозных мошенников: он умел создавать множество личностей, подделывать чеки и обманывать людей, вытягивая из них конфиденциальную информацию, необходимую для работы мошеннических схем. Если вы смотрели фильм «Поймай меня, если сможешь», вы имеете представление о том, на что способен специалист по социнженерии, если он имеет перед собой ясную цель. Вам просто следует помнить, что для получения от вас нужной информации социнженер может использовать различные мошеннические схемы, не ограничивающиеся приемами, связанными с технологиями или компьютерами, так что лучше пользователям с осторожностью относиться к подозрительным действиям, даже если они кажутся обычными. Классическим приемом, например, является выманивание пароля в телефонном звонке. Кажется, что никто в здравом уме не сообщит свой пароль постороннему, но звонок «с работы» в 9 утра в воскресенье, требующий приехать для какой-то мелочевой технической операции над вашим компьютером, несколько меняет дело. Когда «ваш администратор» предложит просто сказать ему пароль, чтобы он все сделал за вас, вы не только сообщите пароль, но и поблагодарите его за заботу! Ну, может, не лично вы, но примерно половина ваших коллег поступит так гарантированно. +

+ +

+ Как мы уже сказали, сегодня установка комплексного решения для обеспечения безопасности — это необходимость, особенно если вы пользуетесь Интернетом (весьма вероятно, что так оно и есть). Более того, ознакомление с новостями и тенденциями в мире онлайн-угроз и социальной инженерии поможет вам избежать атак такого типа (как онлайн, так и в реальной жизни). Помните, что все гаджеты и технологии защиты ничего не стоят, если вы не знаете, как их правильно использовать, и не осведомлены о том, на что способны злоумышленники. Технологии, которыми пользуются преступники, развиваются, и вам не следует отставать, поэтому немного параноидальности в наше время не повредит. +

+
+ +
+

Куда попадают украденные данные после фишинговой атаки

+ +

+ Вспомните, какие данные вы ввели на фишинговом сайте. Если вы передали мошенникам реквизиты карты, позвоните в банк и заблокируйте ее. Если вы ввели логин и пароль, которые вы используете для других учетных записей, обязательно смените их. Надежный и уникальный пароль поможет создать и сохранить менеджер паролей. +

+ +

+ Включите двухфакторную аутентификацию (2FA) везде, где только возможно. Подробнее о том, что такое 2FA и как ей пользоваться, мы писали здесь. При выборе способа двухфакторной аутентификации лучше не использовать SMS — сообщение с одноразовым кодом могут перехватить. Оптимальный способ — генерировать одноразовые коды в приложении-аутентификаторе, например в Kaspersky Password Manager. +

+ +

+ Проверьте активные сеансы (список устройств, с которых был совершен вход) в важных аккаунтах. Если видите там неизвестное устройство или IP-адрес, смело завершайте незнакомый сеанс. После этого нужно сменить пароль и установить двухфакторную аутентификацию. +

+ + {/* Панорамное фото */} +
+ Обновления продуктов +
+ Регулярные обновления — ключ к безопасности +
+
+ +

+ Помните: использовать один и тот же пароль в нескольких сервисах — фатальная ошибка. Именно этим и пользуются злоумышленники: даже если вы никогда не сталкивались с фишингом, ваши пароли и данные все равно могут оказаться в утечках, ведь жертвами кибератак становятся не только отдельные люди, но и целые компании: за еще не оконченный 2025 год исследовательский центр Identity Theft Resource Center зафиксировал более двух тысяч утечек. Чтобы снизить риски, необходимо создавать для каждого аккаунта уникальный и сложный пароль. Запоминать их необязательно, да это и невозможно — лучше воспользоваться менеджером паролей, который генерирует и безопасно хранит сложные пароли, синхронизирует их между всеми вашими устройствами и автоматически подставляет на сайтах и в приложениях, а также уведомляет, если какой-то из них оказался в базе утечек. +

+
+ +
+

Как распознать дипфейк: атака клонов

+ +

+ Не давайте свою карту никому. Хотя данное правило весьма очевидно, следовать ему в повседневной жизни не так-то просто. Вы можете, например, отдать карту официанту для проведения платежа где-то на кассе или же предоставить ее во временное пользование близкому человеку или ребенку-подростку. Чтобы избежать возможных проблем, настаивайте на том, чтобы присутствовать при проведении платежа, то есть попросите принести терминал к столику или проследуйте за официантом к кассе. Это особенно важно при совершении покупок за рубежом. Чтобы обеспечить надлежащее обращение с кредиткой дома, выпустите дополнительные карты для членов семьи. +

+ +

+ Не используйте карту в подозрительных местах. Угрозой номер один являются уличные банкоматы или банкоматы в менее контролируемых общественных местах. В данном случае существует вероятность использования скимминговых схем, то есть списывания данных кредитной карты и видеосъемки набора PIN-кода для ее клонирования. Избегайте использования карт в очень маленьких магазинах или в других торговых точках с устаревшим оборудованием. +

+ +

+ Никогда не раскрывайте свой PIN-код. Никто не имеет права спрашивать ваш PIN-код — здесь нет никаких исключений. Не записывайте свой PIN. Если вы боитесь забыть его, попробуйте использовать специальный менеджер паролей (о таких можно прочитать в нашем обзоре приложений для iOS и Android). Набирая PIN в банкомате или на терминале, прикрывайте клавиатуру рукой. Не позволяйте никому стоять рядом и подглядывать. Если вы подозреваете, что кто-то узнал ваш PIN, сразу сообщите об этом в банк. +

+ +

+ Сообщайте о любых проблемах. При возникновении любых ситуаций вроде потери карты или непонятного списания средств сразу сообщайте об этом в банк. Своевременность в данном случае играет огромную роль, так как мошенники попытаются воспользоваться вашей картой как можно скорее. К тому же по новому российскому закону банк обязан оперативно компенсировать суммы мошеннических списаний, только если вы заявили о них в течение суток. +

+
+ + {/* Источники */} + +
+
+ + ); +}; + +export default CyberSecurityArticle; \ No newline at end of file diff --git a/src/components/MyQuize.tsx b/src/components/MyQuize.tsx new file mode 100644 index 0000000..4b437d6 --- /dev/null +++ b/src/components/MyQuize.tsx @@ -0,0 +1,957 @@ +import React, { useState, useEffect } from 'react'; + +// ========================================== +// ТИПЫ +// ========================================== +interface Option { + id: string; + text: string; + correct: boolean; +} + +interface Question { + id: number; + title: string; + scenario: string; + type: 'single' | 'multiple'; + options: Option[]; + explanation: string; + cwe: string; + owasp: string; + analysis: Record; +} + +interface QuizProps { + questions: Question[]; + title: string; + testId: string; + onBack: () => void; + onComplete?: (testId: string) => void; +} + +interface MainMenuProps { + onSelect: (id: 'scenarios' | 'basics') => void; + completedTests: Record; +} + +type ViewType = 'menu' | 'scenarios' | 'basics'; + +// ========================================== +// ДАННЫЕ: КВИЗ 1 (Сценарии) +// ========================================== +const QUESTIONS_SCENARIOS: Question[] = [ + { + id: 1, + title: "🎁 Фишинг через 'подарок'", + scenario: "Ты получаешь сообщение в Telegram:\n\"🎁 Тебе подарили Telegram Premium — активируй по ссылке\"", + type: "multiple", + options: [ + { id: 'A', text: "Перейти и ввести логин — это же от друга", correct: false }, + { id: 'B', text: "Проверить ссылку и домен", correct: true }, + { id: 'C', text: "Перейти, но не вводить данные", correct: false }, + { id: 'D', text: "Игнорировать сообщение", correct: true } + ], + explanation: "Такие атаки — массовые. Часто приходят с взломанных аккаунтов друзей.", + cwe: "CWE-601 + CWE-522", + owasp: "A07 Auth Failures", + analysis: { A: "❌ Доверие к источнику = ошибка", C: "❌ Переход уже риск" } + }, + { + id: 2, + title: "🔔 Подозрительный вход", + scenario: "Email: \"Обнаружен вход в ваш аккаунт. Срочно подтвердите данные.\"", + type: "single", + options: [ + { id: 'A', text: "Перейти по ссылке и проверить", correct: false }, + { id: 'B', text: "Зайти через официальный сайт вручную", correct: true }, + { id: 'C', text: "Ответить на письмо", correct: false }, + { id: 'D', text: "Позвонить по номеру из письма", correct: false } + ], + explanation: "Классическая приманка. Всегда заходите через официальный сайт.", + cwe: "CWE-640", + owasp: "A02 / A07", + analysis: { A: "❌ Фишинг", C: "❌ Раскрытие данных", D: "❌ Vishing" } + }, + { + id: 3, + title: "💉 SQL-инъекция", + scenario: "Форма логина:\nТы вводишь: ' OR 1=1 --", + type: "single", + options: [ + { id: 'A', text: "Ничего", correct: false }, + { id: 'B', text: "Ошибка", correct: false }, + { id: 'C', text: "Вход без пароля", correct: true }, + { id: 'D', text: "Блокировка", correct: false } + ], + explanation: "SQL Injection — если input не валидируется, код выполняется.", + cwe: "CWE-89", + owasp: "A03 Injection", + analysis: {} + }, + { + id: 4, + title: "🌐 Фейковый сайт", + scenario: "Ты видишь сайт: paypaI.com (буква i вместо l)", + type: "single", + options: [ + { id: 'A', text: "Это безопасно", correct: false }, + { id: 'B', text: "Это фишинг", correct: true }, + { id: 'C', text: "Это редирект", correct: false }, + { id: 'D', text: "Это CDN", correct: false } + ], + explanation: "Подмена домена — стандартная техника.", + cwe: "CWE-290", + owasp: "A10 / A07", + analysis: {} + }, + { + id: 5, + title: "🎫 Бесплатные билеты", + scenario: "Сайт предлагает бесплатные билеты, нужно оплатить \"доставку\"", + type: "single", + options: [ + { id: 'A', text: "Это маркетинг", correct: false }, + { id: 'B', text: "Это фишинг", correct: true }, + { id: 'C', text: "Это ошибка", correct: false }, + { id: 'D', text: "Легальный сервис", correct: false } + ], + explanation: "Слишком хорошо, чтобы быть правдой — это фишинг.", + cwe: "CWE-840", + owasp: "A04 Insecure Design", + analysis: {} + }, + { + id: 6, + title: "📱 Ввод кода из SMS", + scenario: "Тебя просят ввести код из SMS \"для подтверждения\"", + type: "single", + options: [ + { id: 'A', text: "Это нормально", correct: false }, + { id: 'B', text: "Попытка захвата аккаунта", correct: true }, + { id: 'C', text: "Это CAPTCHA", correct: false }, + { id: 'D', text: "Проверка сети", correct: false } + ], + explanation: "Перехват 2FA. Никогда не сообщайте коды третьим лицам.", + cwe: "CWE-287", + owasp: "A07", + analysis: {} + }, + { + id: 7, + title: "📝 Форма с лишними полями", + scenario: "Сайт просит: логин, пароль, email, телефон, карту", + type: "single", + options: [ + { id: 'A', text: "Нормально", correct: false }, + { id: 'B', text: "Избыточный сбор данных", correct: true }, + { id: 'C', text: "Требование безопасности", correct: false }, + { id: 'D', text: "Баг", correct: false } + ], + explanation: "Data harvesting — сбор больше данных, чем нужно.", + cwe: "CWE-200", + owasp: "A01", + analysis: {} + }, + { + id: 8, + title: "📧 HTML вложение", + scenario: "Письмо с вложением .html вместо ссылки", + type: "single", + options: [ + { id: 'A', text: "Безопасно", correct: false }, + { id: 'B', text: "Это фишинг", correct: true }, + { id: 'C', text: "Это архив", correct: false }, + { id: 'D', text: "Это ошибка", correct: false } + ], + explanation: "Фишинг через HTML вложения обходит фильтры.", + cwe: "CWE-494", + owasp: "A08", + analysis: {} + } +]; + +// ========================================== +// ДАННЫЕ: КВИЗ 2 (Базовый) +// ========================================== +const QUESTIONS_BASICS: Question[] = [ + { + id: 1, + title: "🎣 Определение", + scenario: "Вид интернет-мошенничества, цель которого — украсть конфиденциальные данные пользователя через поддельные сайты, письма или сообщения, маскирующиеся под надежные источники.", + type: "single", + options: [ + { id: 'A', text: "Скиминг", correct: false }, + { id: 'B', text: "Фишинг", correct: true }, + { id: 'C', text: "Буллинг", correct: false }, + { id: 'D', text: "Тимбилдинг", correct: false } + ], + explanation: "Фишинг (phishing) — это мошенничество с использованием социальной инженерии.", + cwe: "CWE- deception", + owasp: "Social Engineering", + analysis: { A: "Скиминг — кража данных с карты", C: "Буллинг — травля", D: "Тимбилдинг — командообразование" } + }, + { + id: 2, + title: "🔗 Поддельная ссылка", + scenario: "Какая из ссылок является фишинговой (ложной)?", + type: "single", + options: [ + { id: 'A', text: "https://www.google.com/", correct: false }, + { id: 'B', text: "https://www.gosuslugi.ru/", correct: false }, + { id: 'C', text: "https://chat.deepseek.com/", correct: false }, + { id: 'D', text: "http://g0v.war", correct: true } + ], + explanation: "g0v.war — подозрительный домен, HTTP вместо HTTPS, опечатка в gov.", + cwe: "CWE-602", + owasp: "A07", + analysis: { D: "❌ HTTP, подмена букв (0 вместо o), подозрительная зона .war" } + }, + { + id: 3, + title: "🖥️ Безопасная ОС", + scenario: "Какая из версий Windows является самой безопасной?", + type: "single", + options: [ + { id: 'A', text: "Windows 11", correct: true }, + { id: 'B', text: "Windows XP", correct: false }, + { id: 'C', text: "Windows 7", correct: false }, + { id: 'D', text: "Windows 10", correct: false } + ], + explanation: "Windows 11 имеет TPM 2.0, Secure Boot и последние патчи безопасности.", + cwe: "CWE-1104", + owasp: "A06", + analysis: { B: "XP не поддерживается с 2014", C: "Windows 7 не поддерживается с 2020", D: "Windows 10 устаревает" } + }, + { + id: 4, + title: "🐴 Вредоносное ПО", + scenario: "Вредоносное ПО, маскирующееся под легитимное приложение для обмана пользователя и проникновения в систему?", + type: "single", + options: [ + { id: 'A', text: "Вирус", correct: false }, + { id: 'B', text: "Троян", correct: true }, + { id: 'C', text: "Червь", correct: false }, + { id: 'D', text: "Кейлоггер", correct: false } + ], + explanation: "Троянский конь маскируется под полезную программу.", + cwe: "CWE-507", + owasp: "A08", + analysis: { A: "Вирус прикрепляется к файлам", C: "Червь распространяется по сети", D: "Кейлоггер — тип трояна" } + }, + { + id: 5, + title: "🔑 Парольная безопасность", + scenario: "Выберите наименее надёжный пароль из предложенных:", + type: "single", + options: [ + { id: 'A', text: "fR97#7HFf", correct: false }, + { id: 'B', text: "123456!", correct: true }, + { id: 'C', text: "abcdefg", correct: false }, + { id: 'D', text: "4YUI&Y4", correct: false } + ], + explanation: "123456! — самый популярный пароль в мире, легко брутфорсится.", + cwe: "CWE-521", + owasp: "A07", + analysis: { B: "❌ В топе самых популярных паролей" } + }, + { + id: 6, + title: "📋 Персональные данные", + scenario: "Любая информация, относящаяся прямо или косвенно к определенному физическому лицу, позволяющая его идентифицировать:", + type: "single", + options: [ + { id: 'A', text: "Новости", correct: false }, + { id: 'B', text: "Персональные данные", correct: true }, + { id: 'C', text: "Военная тайна", correct: false }, + { id: 'D', text: "Паспортные данные", correct: false } + ], + explanation: "Персональные данные — широкое понятие (имя, адрес, телефон, IP).", + cwe: "CWE-359", + owasp: "A01", + analysis: { D: "Паспортные данные — подмножество ПДн" } + }, + { + id: 7, + title: "📶 Wi-Fi безопасность", + scenario: "Безопасно ли пользоваться публичными точками доступа Wi-Fi?", + type: "single", + options: [ + { id: 'A', text: "Да", correct: false }, + { id: 'B', text: "Нет", correct: true }, + { id: 'C', text: "Да, если включено шифрование", correct: false }, + { id: 'D', text: "Нет, если включено шифрование", correct: false } + ], + explanation: "Публичный Wi-Fi опасен (MITM-атаки). Лучше использовать VPN.", + cwe: "CWE-319", + owasp: "A02", + analysis: { C: "Шифрование не защищает от фальшивых точек (Evil Twin)" } + }, + { + id: 8, + title: "🔒 Протоколы безопасности", + scenario: "Какое из перечисленных соединений НЕ является безопасным?", + type: "single", + options: [ + { id: 'A', text: "VPN", correct: false }, + { id: 'B', text: "TLS", correct: false }, + { id: 'C', text: "HTTP", correct: true }, + { id: 'D', text: "HTTPS", correct: false } + ], + explanation: "HTTP передаёт данные в открытом виде. HTTPS = HTTP + TLS/SSL.", + cwe: "CWE-319", + owasp: "A02", + analysis: { C: "❌ Нет шифрования" } + } +]; + +// ========================================== +// СТИЛИ +// ========================================== +const styles: Record = { + container: { + minHeight: '100vh', + backgroundColor: '#0f172a', + padding: '20px', + fontFamily: 'system-ui, -apple-system, sans-serif', + display: 'flex', + justifyContent: 'center', + alignItems: 'center' + }, + card: { + backgroundColor: '#1e293b', + borderRadius: '16px', + border: '1px solid #334155', + maxWidth: '600px', + width: '100%', + overflow: 'hidden', + boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.5)' + }, + menu: { + textAlign: 'center', + padding: '40px' + }, + menuTitle: { + color: '#60a5fa', + fontSize: '32px', + fontWeight: 'bold', + marginBottom: '16px' + }, + menuSubtitle: { + color: '#94a3b8', + marginBottom: '40px', + fontSize: '16px' + }, + menuButton: { + display: 'block', + width: '100%', + padding: '20px', + marginBottom: '16px', + borderRadius: '12px', + border: '2px solid #475569', + backgroundColor: '#334155', + color: '#f8fafc', + fontSize: '18px', + fontWeight: '600', + cursor: 'pointer', + transition: 'all 0.2s', + textAlign: 'left', + position: 'relative' + }, + menuButtonCompleted: { + borderColor: '#22c55e', + backgroundColor: 'rgba(34, 197, 94, 0.1)' + }, + completedBadge: { + position: 'absolute', + right: '20px', + top: '50%', + transform: 'translateY(-50%)', + backgroundColor: '#22c55e', + color: 'white', + width: '32px', + height: '32px', + borderRadius: '50%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontSize: '18px', + boxShadow: '0 2px 8px rgba(34, 197, 94, 0.4)' + }, + header: { + padding: '24px', + borderBottom: '1px solid #334155', + backgroundColor: '#1e293b' + }, + progressBar: { + height: '4px', + backgroundColor: '#334155', + borderRadius: '2px', + marginBottom: '16px', + overflow: 'hidden' + }, + progressFill: { + height: '100%', + backgroundColor: '#3b82f6', + transition: 'width 0.3s ease' + }, + meta: { + display: 'flex', + justifyContent: 'space-between', + color: '#94a3b8', + fontSize: '14px', + marginBottom: '8px' + }, + title: { + color: '#60a5fa', + fontSize: '20px', + fontWeight: 'bold', + marginBottom: '8px' + }, + scenario: { + color: '#e2e8f0', + fontSize: '16px', + lineHeight: '1.5', + whiteSpace: 'pre-line' + }, + hint: { + color: '#94a3b8', + fontSize: '13px', + marginBottom: '12px', + fontStyle: 'italic' + }, + options: { + padding: '24px', + display: 'flex', + flexDirection: 'column', + gap: '12px' + }, + option: { + padding: '16px', + borderRadius: '12px', + border: '2px solid #475569', + backgroundColor: '#334155', + color: '#f1f5f9', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + gap: '12px', + transition: 'all 0.2s', + textAlign: 'left', + fontSize: '15px' + }, + optionSelected: { + borderColor: '#3b82f6', + backgroundColor: '#2563eb' + }, + optionCorrect: { + borderColor: '#22c55e', + backgroundColor: '#16a34a' + }, + optionWrong: { + borderColor: '#ef4444', + backgroundColor: '#dc2626' + }, + optionDisabled: { + opacity: 0.6, + cursor: 'default' + }, + badge: { + width: '28px', + height: '28px', + borderRadius: '50%', + backgroundColor: 'rgba(0,0,0,0.3)', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontWeight: 'bold', + fontSize: '13px', + flexShrink: 0 + }, + explanation: { + padding: '24px', + borderTop: '1px solid #334155', + backgroundColor: 'rgba(0,0,0,0.2)' + }, + explanationCorrect: { + backgroundColor: 'rgba(22, 163, 74, 0.1)' + }, + explanationWrong: { + backgroundColor: 'rgba(220, 38, 38, 0.1)' + }, + resultTitle: { + fontWeight: 'bold', + fontSize: '18px', + marginBottom: '8px', + display: 'flex', + alignItems: 'center', + gap: '8px' + }, + explanationText: { + color: '#cbd5e1', + lineHeight: '1.6', + marginBottom: '12px' + }, + tags: { + display: 'flex', + gap: '8px', + flexWrap: 'wrap', + marginBottom: '16px' + }, + tag: { + backgroundColor: '#475569', + color: '#cbd5e1', + padding: '4px 10px', + borderRadius: '6px', + fontSize: '12px', + fontFamily: 'monospace' + }, + analysis: { + backgroundColor: 'rgba(0,0,0,0.3)', + padding: '12px', + borderRadius: '8px', + fontSize: '13px', + color: '#94a3b8', + marginBottom: '16px' + }, + button: { + width: '100%', + padding: '14px', + borderRadius: '10px', + border: 'none', + backgroundColor: '#3b82f6', + color: 'white', + fontWeight: 'bold', + fontSize: '16px', + cursor: 'pointer', + transition: 'background 0.2s' + }, + buttonDisabled: { + backgroundColor: '#475569', + cursor: 'not-allowed' + }, + backButton: { + marginTop: '20px', + padding: '10px 20px', + backgroundColor: 'transparent', + border: '1px solid #475569', + color: '#94a3b8', + borderRadius: '8px', + cursor: 'pointer', + fontSize: '14px' + }, + completionStatus: { + marginTop: '8px', + fontSize: '14px', + color: '#22c55e', + fontWeight: 'bold' + } +}; + +// ========================================== +// КОМПОНЕНТ: КВИЗ +// ========================================== +const Quiz: React.FC = ({ questions, title, testId, onBack, onComplete }) => { + const [current, setCurrent] = useState(0); + const [selected, setSelected] = useState([]); + const [showResult, setShowResult] = useState(false); + const [score, setScore] = useState(0); + const [answers, setAnswers] = useState<{ correct: boolean }[]>([]); + + // === ФИНАЛЬНЫЙ ЭКРАН === + if (current >= questions.length) { + const percent: number = Math.round((score / questions.length) * 100); + const isPerfect: boolean = percent === 100; + + return ( +
+
+
+
+ {isPerfect ? '🏆' : percent >= 50 ? '🛡️' : '⚠️'} +
+ +

+ {title} — завершён! +

+ + {isPerfect && ( +
+ ✓ Все ответы правильные! +
+ )} + +
+ {percent}% +
+ +
+ Правильно: {score} из {questions.length} +
+ +
+ {answers.map((ans, i) => ( +
+ ))} +
+ +
+ + + +
+
+
+
+ ); + } + + const question: Question = questions[current]; + const isMultiple: boolean = question.type === 'multiple'; + const progress: number = ((current) / questions.length) * 100; + + const handleSelect = (id: string): void => { + if (showResult) return; + + if (isMultiple) { + setSelected(prev => + prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id] + ); + } else { + setSelected([id]); + } + }; + + const checkAnswer = (): void => { + const correctIds: string[] = question.options.filter(o => o.correct).map(o => o.id); + const isCorrect: boolean = + selected.length === correctIds.length && + selected.every(id => correctIds.includes(id)); + + if (isCorrect) setScore(s => s + 1); + setAnswers(prev => [...prev, { correct: isCorrect }]); + setShowResult(true); + }; + + const finishQuiz = (): void => { + const correctIds: string[] = question.options.filter(o => o.correct).map(o => o.id); + const lastCorrect: boolean = selected.length === correctIds.length && + selected.every(id => correctIds.includes(id)); + + const finalAnswers: { correct: boolean }[] = [...answers, { correct: lastCorrect }]; + const finalScore: number = score + (lastCorrect ? 1 : 0); + + setAnswers(finalAnswers); + setScore(finalScore); + + const allCorrect: boolean = finalAnswers.every(a => a.correct); + if (allCorrect && onComplete) { + onComplete(testId); + } + + setCurrent(questions.length); + }; + + const nextQuestion = (): void => { + if (current < questions.length - 1) { + setCurrent(c => c + 1); + setSelected([]); + setShowResult(false); + } + }; + + const getOptionStyle = (option: Option): React.CSSProperties => { + const base: React.CSSProperties = { ...styles.option }; + + if (!showResult) { + if (selected.includes(option.id)) { + return { ...base, ...styles.optionSelected }; + } + return base; + } + + if (option.correct) { + return { ...base, ...styles.optionCorrect }; + } + if (selected.includes(option.id) && !option.correct) { + return { ...base, ...styles.optionWrong }; + } + return { ...base, ...styles.optionDisabled }; + }; + + const isCorrect: boolean = showResult && answers.length > 0 && answers[answers.length - 1]?.correct; + + return ( +
+
+
+
+ {title} + +
+
+
+
+
+ Вопрос {current + 1} из {questions.length} + Правильно: {score} +
+

{question.title}

+

{question.scenario}

+
+ +
+ {isMultiple && ( +
⚡ Можно выбрать несколько вариантов
+ )} + + {question.options.map(option => ( + + ))} +
+ + {showResult ? ( +
+
+ {isCorrect ? '✅' : '❌'} + {isCorrect ? 'Правильно!' : 'Ошибка'} +
+ +

{question.explanation}

+ + {Object.keys(question.analysis).length > 0 && ( +
+ {Object.entries(question.analysis).map(([k, v]) => ( +
{v}
+ ))} +
+ )} + +
+ {question.cwe} + {question.owasp} +
+ + +
+ ) : ( +
+ +
+ )} +
+
+ ); +}; + +// ========================================== +// КОМПОНЕНТ: ГЛАВНОЕ МЕНЮ +// ========================================== +const MainMenu: React.FC = ({ onSelect, completedTests }) => { + const [hovered, setHovered] = useState(null); + + const tests = [ + { + id: 'scenarios' as const, + icon: '🎯', + title: 'Сценарии атак', + desc: '8 реальных кейсов с разбором CWE/OWASP', + color: '#3b82f6' + }, + { + id: 'basics' as const, + icon: '📝', + title: 'Базовый тест', + desc: '8 вопросов на знание терминов и протоколов', + color: '#8b5cf6' + } + ]; + + return ( +
+
+
+
🛡️
+

Кибербезопасность

+

Выберите режим прохождения

+ + {tests.map((test, index) => { + const isCompleted = completedTests[test.id]; + const isHovered = hovered === index; + + return ( + + ); + })} + +
+ {Object.values(completedTests).filter(Boolean).length} из 2 тестов пройдено идеально +
+
+
+
+ ); +}; + +// ========================================== +// ГЛАВНЫЙ КОМПОНЕНТ +// ========================================== +const MyQuize: React.FC = () => { + const [currentView, setCurrentView] = useState('menu'); + const [completedTests, setCompletedTests] = useState>({ + scenarios: false, + basics: false + }); + + useEffect(() => { + const saved = localStorage.getItem('cyberquiz_completed'); + if (saved) { + setCompletedTests(JSON.parse(saved)); + } + }, []); + + const handleComplete = (testId: string): void => { + setCompletedTests(prev => { + const updated = { ...prev, [testId]: true }; + localStorage.setItem('cyberquiz_completed', JSON.stringify(updated)); + return updated; + }); + }; + + if (currentView === 'scenarios') { + return setCurrentView('menu')} + onComplete={handleComplete} + />; + } + + if (currentView === 'basics') { + return setCurrentView('menu')} + onComplete={handleComplete} + />; + } + + return ; +}; + +export default MyQuize;