|
|
@@ -0,0 +1,599 @@
|
|
|
+<!DOCTYPE html>
|
|
|
+<html lang="ru">
|
|
|
+<head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
|
|
+ <title>Linux Hardening Challenge — Аудит безопасности</title>
|
|
|
+ <style>
|
|
|
+ * {
|
|
|
+ box-sizing: border-box;
|
|
|
+ user-select: none; /* better terminal feel, but text can be selected */
|
|
|
+ }
|
|
|
+
|
|
|
+ body {
|
|
|
+ background: linear-gradient(145deg, #0a0f1a 0%, #0c111c 100%);
|
|
|
+ font-family: 'Courier New', 'Fira Code', 'JetBrains Mono', monospace;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ min-height: 100vh;
|
|
|
+ margin: 0;
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* game container */
|
|
|
+ .game-container {
|
|
|
+ max-width: 1300px;
|
|
|
+ width: 100%;
|
|
|
+ background: #0b0e14;
|
|
|
+ border-radius: 28px;
|
|
|
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255,255,255,0.05);
|
|
|
+ overflow: hidden;
|
|
|
+ border: 1px solid #2a2f3f;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* header: scene & progress */
|
|
|
+ .game-header {
|
|
|
+ background: #070a10;
|
|
|
+ padding: 16px 24px;
|
|
|
+ border-bottom: 1px solid #252a36;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .scene-badge {
|
|
|
+ background: #1e2433;
|
|
|
+ padding: 6px 14px;
|
|
|
+ border-radius: 60px;
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 0.85rem;
|
|
|
+ color: #8fbcbb;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+ border-left: 3px solid #88c0d0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .progress {
|
|
|
+ background: #11161f;
|
|
|
+ padding: 6px 14px;
|
|
|
+ border-radius: 40px;
|
|
|
+ font-size: 0.85rem;
|
|
|
+ color: #d8dee9;
|
|
|
+ }
|
|
|
+
|
|
|
+ .progress span {
|
|
|
+ color: #a3be8c;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* main content area: terminal + description */
|
|
|
+ .terminal-panel {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ padding: 24px;
|
|
|
+ gap: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .terminal {
|
|
|
+ flex: 2;
|
|
|
+ min-width: 280px;
|
|
|
+ background: #01040c;
|
|
|
+ border-radius: 20px;
|
|
|
+ border: 1px solid #2b3245;
|
|
|
+ box-shadow: inset 0 0 15px rgba(0,0,0,0.5), 0 5px 10px rgba(0,0,0,0.3);
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .terminal-header {
|
|
|
+ background: #191e2a;
|
|
|
+ padding: 12px 18px;
|
|
|
+ font-size: 0.85rem;
|
|
|
+ color: #bbc2cf;
|
|
|
+ border-bottom: 1px solid #2e3548;
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .terminal-dot {
|
|
|
+ width: 12px;
|
|
|
+ height: 12px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #ec5f67;
|
|
|
+ display: inline-block;
|
|
|
+ }
|
|
|
+ .terminal-dot.yellow { background: #f9ae58; }
|
|
|
+ .terminal-dot.green { background: #8cc570; }
|
|
|
+
|
|
|
+ .terminal-body {
|
|
|
+ padding: 20px;
|
|
|
+ font-family: 'Courier New', monospace;
|
|
|
+ background: #01040c;
|
|
|
+ min-height: 380px;
|
|
|
+ color: #d8dee9;
|
|
|
+ font-size: 0.9rem;
|
|
|
+ line-height: 1.45;
|
|
|
+ white-space: pre-wrap;
|
|
|
+ word-break: break-word;
|
|
|
+ }
|
|
|
+
|
|
|
+ .command-line {
|
|
|
+ margin-top: 16px;
|
|
|
+ background: #0a0f17;
|
|
|
+ border-radius: 14px;
|
|
|
+ padding: 12px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ border: 1px solid #2a3143;
|
|
|
+ }
|
|
|
+
|
|
|
+ .prompt {
|
|
|
+ color: #8fbcbb;
|
|
|
+ font-weight: bold;
|
|
|
+ background: #121724;
|
|
|
+ padding: 6px 10px;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-size: 0.85rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .command-input {
|
|
|
+ flex: 1;
|
|
|
+ background: #03060c;
|
|
|
+ border: 1px solid #2e3a4e;
|
|
|
+ color: #e5e9f0;
|
|
|
+ padding: 8px 12px;
|
|
|
+ border-radius: 20px;
|
|
|
+ font-family: monospace;
|
|
|
+ font-size: 0.9rem;
|
|
|
+ outline: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .command-input:focus {
|
|
|
+ border-color: #88c0d0;
|
|
|
+ box-shadow: 0 0 5px #5e81ac;
|
|
|
+ }
|
|
|
+
|
|
|
+ button {
|
|
|
+ background: #2e3a48;
|
|
|
+ border: none;
|
|
|
+ padding: 8px 18px;
|
|
|
+ border-radius: 26px;
|
|
|
+ font-family: monospace;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #e5e9f0;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: 0.1s linear;
|
|
|
+ font-size: 0.8rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ button:hover {
|
|
|
+ background: #434f62;
|
|
|
+ transform: scale(0.97);
|
|
|
+ }
|
|
|
+
|
|
|
+ .action-buttons {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 260px;
|
|
|
+ background: #0e121c;
|
|
|
+ border-radius: 24px;
|
|
|
+ border: 1px solid #262e3f;
|
|
|
+ padding: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .action-title {
|
|
|
+ font-size: 1rem;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #81a1c1;
|
|
|
+ margin-bottom: 14px;
|
|
|
+ border-left: 3px solid #88c0d0;
|
|
|
+ padding-left: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .action-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .action-btn {
|
|
|
+ background: #191f2b;
|
|
|
+ text-align: left;
|
|
|
+ padding: 12px 14px;
|
|
|
+ border-radius: 18px;
|
|
|
+ font-size: 0.8rem;
|
|
|
+ width: 100%;
|
|
|
+ box-shadow: 0 1px 2px black;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-area {
|
|
|
+ background: #05080f;
|
|
|
+ border-radius: 18px;
|
|
|
+ padding: 12px;
|
|
|
+ font-size: 0.8rem;
|
|
|
+ color: #cbd5e6;
|
|
|
+ border-top: 1px solid #2c3347;
|
|
|
+ margin-top: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .warning {
|
|
|
+ color: #f0a86e;
|
|
|
+ }
|
|
|
+ .success {
|
|
|
+ color: #a3be8c;
|
|
|
+ }
|
|
|
+ .error {
|
|
|
+ color: #ec5f67;
|
|
|
+ }
|
|
|
+ hr {
|
|
|
+ border-color: #252c3b;
|
|
|
+ margin: 10px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (max-width: 780px) {
|
|
|
+ .terminal-panel { flex-direction: column; }
|
|
|
+ .command-line { flex-direction: column; align-items: stretch; }
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+<div class="game-container" id="gameRoot">
|
|
|
+ <div class="game-header">
|
|
|
+ <div class="scene-badge" id="sceneName">Сцена 1: Первое подключение</div>
|
|
|
+ <div class="progress">Статус: <span id="progressStatus">🔐 0/6</span></div>
|
|
|
+ </div>
|
|
|
+ <div class="terminal-panel">
|
|
|
+ <div class="terminal">
|
|
|
+ <div class="terminal-header">
|
|
|
+ <span class="terminal-dot"></span><span class="terminal-dot yellow"></span><span class="terminal-dot green"></span>
|
|
|
+ <span style="margin-left: 8px;">admin@hardening:~</span>
|
|
|
+ </div>
|
|
|
+ <div class="terminal-body" id="terminalOutput">
|
|
|
+ > Добро пожаловать в Hardening Challenge.<br>
|
|
|
+ > Выполняйте команды и используйте действия.<br>
|
|
|
+ > Введите <span class="success">help</span> для списка команд.<br>
|
|
|
+ > root@secured-server:~#
|
|
|
+ </div>
|
|
|
+ <div class="command-line">
|
|
|
+ <span class="prompt" id="promptUser">admin@lab:~$</span>
|
|
|
+ <input type="text" id="cmdInput" class="command-input" placeholder="введите команду..." autofocus>
|
|
|
+ <button id="runCmdBtn">⏎ Выполнить</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="action-buttons">
|
|
|
+ <div class="action-title">📋 ДОСТУПНЫЕ ДЕЙСТВИЯ (сцена)</div>
|
|
|
+ <div id="dynamicActions" class="action-list">
|
|
|
+ <!-- динамические кнопки сценария -->
|
|
|
+ </div>
|
|
|
+ <div class="message-area" id="gameMessage">
|
|
|
+ 💡 Используй команды или кнопки. Пройди все 6 сцен!
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</div>
|
|
|
+
|
|
|
+<script>
|
|
|
+ // ------------------- ИГРОВОЕ СОСТОЯНИЕ -------------------
|
|
|
+ let currentScene = 1; // 1..6
|
|
|
+ let sceneCompleted = [false, false, false, false, false, false]; // index 0 = сцена1
|
|
|
+ let globalState = {
|
|
|
+ // сцена 1
|
|
|
+ updated: false,
|
|
|
+ adminCreated: false,
|
|
|
+ sudoGranted: false,
|
|
|
+ switchedToAdmin: false,
|
|
|
+ // сцена 2
|
|
|
+ minlenSet: false,
|
|
|
+ fail2banInstalled: false,
|
|
|
+ fail2banActive: false,
|
|
|
+ // сцена 3
|
|
|
+ ufwDefaultDeny: false,
|
|
|
+ ufwSshAllowed: false,
|
|
|
+ ufwHttpHttps: false,
|
|
|
+ ufwMysqlDenied: false,
|
|
|
+ ufwEnabled: false,
|
|
|
+ // сцена 4
|
|
|
+ aideInitialized: false,
|
|
|
+ aideDbMoved: false,
|
|
|
+ auditRuleAdded: false,
|
|
|
+ auditRulePersistent: false,
|
|
|
+ aideCheckOk: false,
|
|
|
+ // сцена 5
|
|
|
+ apparmorEnforced: false,
|
|
|
+ limitsSet: false,
|
|
|
+ rebootSimulated: false,
|
|
|
+ // финальная проверка
|
|
|
+ };
|
|
|
+
|
|
|
+ // вспомогательный рендер терминала (логи)
|
|
|
+ let terminalLog = ["> Система готова. Hardening Checklist v1.0"];
|
|
|
+ function addTerminalLine(text, type = "info") {
|
|
|
+ let prefix = "";
|
|
|
+ if (type === "cmd") prefix = "$ ";
|
|
|
+ else if (type === "out") prefix = "› ";
|
|
|
+ else if (type === "error") prefix = "⚠️ ";
|
|
|
+ else if (type === "success") prefix = "✓ ";
|
|
|
+ terminalLog.push(prefix + text);
|
|
|
+ if (terminalLog.length > 28) terminalLog.shift();
|
|
|
+ const terminalDiv = document.getElementById("terminalOutput");
|
|
|
+ if (terminalDiv) terminalDiv.innerHTML = terminalLog.map(l => l + "<br>").join("");
|
|
|
+ terminalDiv.scrollTop = terminalDiv.scrollHeight;
|
|
|
+ }
|
|
|
+
|
|
|
+ function setGameMessage(msg, isError = false) {
|
|
|
+ const msgDiv = document.getElementById("gameMessage");
|
|
|
+ if (msgDiv) {
|
|
|
+ msgDiv.innerHTML = isError ? `❌ ${msg}` : `ℹ️ ${msg}`;
|
|
|
+ if (!isError) msgDiv.style.color = "#cbd5e6";
|
|
|
+ else msgDiv.style.color = "#ec5f67";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateProgressUI() {
|
|
|
+ let completedCount = sceneCompleted.filter(v => v === true).length;
|
|
|
+ document.getElementById("progressStatus").innerHTML = `🏆 ${completedCount}/6 сцен пройдено`;
|
|
|
+ const sceneNames = [
|
|
|
+ "Сцена 1: Обновление и пользователь",
|
|
|
+ "Сцена 2: Пароли + fail2ban",
|
|
|
+ "Сцена 3: UFW файрвол",
|
|
|
+ "Сцена 4: AIDE + аудит",
|
|
|
+ "Сцена 5: AppArmor + лимиты",
|
|
|
+ "Сцена 6: Финальная проверка"
|
|
|
+ ];
|
|
|
+ document.getElementById("sceneName").innerHTML = `${sceneNames[currentScene-1]}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // проверка завершения текущей сцены
|
|
|
+ function checkSceneCompletion() {
|
|
|
+ let completed = false;
|
|
|
+ if (currentScene === 1) {
|
|
|
+ if (globalState.updated && globalState.adminCreated && globalState.sudoGranted && globalState.switchedToAdmin) {
|
|
|
+ completed = true;
|
|
|
+ }
|
|
|
+ } else if (currentScene === 2) {
|
|
|
+ if (globalState.minlenSet && globalState.fail2banInstalled && globalState.fail2banActive) completed = true;
|
|
|
+ } else if (currentScene === 3) {
|
|
|
+ if (globalState.ufwDefaultDeny && globalState.ufwSshAllowed && globalState.ufwHttpHttps && globalState.ufwMysqlDenied && globalState.ufwEnabled) completed = true;
|
|
|
+ } else if (currentScene === 4) {
|
|
|
+ if (globalState.aideInitialized && globalState.aideDbMoved && globalState.auditRuleAdded && globalState.auditRulePersistent && globalState.aideCheckOk) completed = true;
|
|
|
+ } else if (currentScene === 5) {
|
|
|
+ if (globalState.apparmorEnforced && globalState.limitsSet && globalState.rebootSimulated) completed = true;
|
|
|
+ } else if (currentScene === 6) {
|
|
|
+ // финальная проверка - все сцены 1-5 должны быть пройдены
|
|
|
+ let allPrev = sceneCompleted[0] && sceneCompleted[1] && sceneCompleted[2] && sceneCompleted[3] && sceneCompleted[4];
|
|
|
+ if (allPrev) completed = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (completed && !sceneCompleted[currentScene-1]) {
|
|
|
+ sceneCompleted[currentScene-1] = true;
|
|
|
+ addTerminalLine(`✨ СЦЕНА ${currentScene} ВЫПОЛНЕНА! Переход к следующей...`, "success");
|
|
|
+ setGameMessage(`Сцена ${currentScene} пройдена! Переход к сцене ${currentScene+1 <=6 ? currentScene+1 : 6}`);
|
|
|
+ if (currentScene < 6) {
|
|
|
+ currentScene++;
|
|
|
+ updateProgressUI();
|
|
|
+ renderDynamicActions();
|
|
|
+ // сброс сообщения в сцене 6 финал
|
|
|
+ if (currentScene === 6) addTerminalLine("=== ФИНАЛЬНАЯ СЦЕНА: ЗАПУСТИТЕ ПРОВЕРКУ ===", "success");
|
|
|
+ } else if (currentScene === 6 && completed) {
|
|
|
+ addTerminalLine("🏆 ПОЗДРАВЛЯЮ! Сервер соответствует Hardening Checklist! Звание Security Auditor!", "success");
|
|
|
+ setGameMessage("Игра пройдена! Вы — гуру безопасности Linux. Обновите страницу для новой игры.");
|
|
|
+ document.getElementById("cmdInput").disabled = true;
|
|
|
+ document.getElementById("runCmdBtn").disabled = true;
|
|
|
+ }
|
|
|
+ updateProgressUI();
|
|
|
+ renderDynamicActions();
|
|
|
+ } else if (!completed && sceneCompleted[currentScene-1] === false) {
|
|
|
+ // ничего, просто ждем
|
|
|
+ } else if (sceneCompleted[currentScene-1] && currentScene<6){
|
|
|
+ // уже пройдена, возможно переход кнопкой
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // обработчик команд
|
|
|
+ function processCommand(cmd) {
|
|
|
+ let trimmed = cmd.trim().toLowerCase();
|
|
|
+ addTerminalLine(cmd, "cmd");
|
|
|
+ // HELP
|
|
|
+ if (trimmed === "help") {
|
|
|
+ addTerminalLine("Доступные команды: status, check, apt update, adduser, usermod, exit, sudo, ufw, aide, auditctl, aa-status, ulimit, fail2ban-client, и др.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (trimmed === "status") {
|
|
|
+ addTerminalLine(`--- текущая сцена ${currentScene} ---`);
|
|
|
+ if(currentScene===1) addTerminalLine(`updated:${globalState.updated} admin:${globalState.adminCreated} sudo:${globalState.sudoGranted} switched:${globalState.switchedToAdmin}`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ----- СЦЕНАРНЫЕ КОМАНДЫ (эмуляция) -----
|
|
|
+ // СЦЕНА 1
|
|
|
+ if (currentScene === 1) {
|
|
|
+ if (trimmed === "apt update && apt upgrade" || trimmed === "apt update" || trimmed === "apt upgrade") {
|
|
|
+ globalState.updated = true;
|
|
|
+ addTerminalLine("Обновление выполнено. Система актуальна.", "success");
|
|
|
+ checkSceneCompletion();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (trimmed.startsWith("adduser ")) {
|
|
|
+ let parts = trimmed.split(" ");
|
|
|
+ if (parts[1] === "admin") { globalState.adminCreated = true; addTerminalLine("Пользователь admin создан.", "success"); checkSceneCompletion(); return; }
|
|
|
+ }
|
|
|
+ if (trimmed === "usermod -aG sudo admin") { globalState.sudoGranted = true; addTerminalLine("admin добавлен в группу sudo.", "success"); checkSceneCompletion(); return; }
|
|
|
+ if (trimmed === "exit") {
|
|
|
+ if(globalState.adminCreated && globalState.sudoGranted){
|
|
|
+ globalState.switchedToAdmin = true;
|
|
|
+ addTerminalLine("Вы вышли из root и теперь работаете под admin (безопасно).", "success");
|
|
|
+ document.getElementById("promptUser").innerText = "admin@secured:~$";
|
|
|
+ checkSceneCompletion();
|
|
|
+ } else { addTerminalLine("Сначала создайте admin и дайте sudo.", "error"); }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // СЦЕНА 2
|
|
|
+ if (currentScene === 2) {
|
|
|
+ if (trimmed.includes("minlen") && (trimmed.includes("pwquality") || trimmed === "set minlen=12")) {
|
|
|
+ globalState.minlenSet = true; addTerminalLine("Политика паролей: minlen=12 установлена.", "success"); checkSceneCompletion(); return;
|
|
|
+ }
|
|
|
+ if (trimmed === "apt install fail2ban") { globalState.fail2banInstalled = true; addTerminalLine("fail2ban установлен.", "success"); checkSceneCompletion(); return; }
|
|
|
+ if (trimmed === "systemctl enable fail2ban" || trimmed === "systemctl start fail2ban" || trimmed === "fail2ban-client start") {
|
|
|
+ if(globalState.fail2banInstalled){
|
|
|
+ globalState.fail2banActive = true;
|
|
|
+ addTerminalLine("fail2ban активирован, защита SSH включена.", "success");
|
|
|
+ checkSceneCompletion();
|
|
|
+ } else addTerminalLine("Сначала установите fail2ban.", "error");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // СЦЕНА 3 UFW
|
|
|
+ if (currentScene === 3) {
|
|
|
+ if (trimmed === "ufw default deny incoming") { globalState.ufwDefaultDeny = true; addTerminalLine("UFW default deny incoming установлен.", "success"); checkSceneCompletion(); return; }
|
|
|
+ if (trimmed === "ufw allow 22/tcp") { globalState.ufwSshAllowed = true; addTerminalLine("SSH порт 22 разрешён.", "success"); checkSceneCompletion(); return; }
|
|
|
+ if (trimmed === "ufw allow 80/tcp" || trimmed === "ufw allow 443/tcp") {
|
|
|
+ globalState.ufwHttpHttps = true;
|
|
|
+ addTerminalLine("HTTP/HTTPS разрешены.", "success");
|
|
|
+ checkSceneCompletion();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (trimmed === "ufw deny 3306") { globalState.ufwMysqlDenied = true; addTerminalLine("Порт MySQL 3306 закрыт.", "success"); checkSceneCompletion(); return; }
|
|
|
+ if (trimmed === "ufw enable") {
|
|
|
+ if(globalState.ufwDefaultDeny && globalState.ufwSshAllowed){
|
|
|
+ globalState.ufwEnabled = true;
|
|
|
+ addTerminalLine("UFW включён. Правила активны.", "success");
|
|
|
+ checkSceneCompletion();
|
|
|
+ } else addTerminalLine("Сначала установите default deny и разрешите SSH.", "error");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // СЦЕНА 4 AIDE + аудит
|
|
|
+ if (currentScene === 4) {
|
|
|
+ if (trimmed === "aideinit" || trimmed === "aide -i") { globalState.aideInitialized = true; addTerminalLine("База AIDE инициализирована.", "success"); checkSceneCompletion(); return; }
|
|
|
+ if (trimmed === "mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz") { globalState.aideDbMoved = true; addTerminalLine("База AIDE перемещена.", "success"); checkSceneCompletion(); return; }
|
|
|
+ if (trimmed.includes("auditctl -w /etc/passwd")) { globalState.auditRuleAdded = true; addTerminalLine("Правило аудита для /etc/passwd добавлено.", "success"); checkSceneCompletion(); return; }
|
|
|
+ if (trimmed === "echo '-w /etc/passwd -p wa -k passwd_changes' >> /etc/audit/rules.d/hardening.rules") { globalState.auditRulePersistent = true; addTerminalLine("Правило аудита сохранено постоянно.", "success"); checkSceneCompletion(); return; }
|
|
|
+ if (trimmed === "aide --check") {
|
|
|
+ if(globalState.aideInitialized && globalState.aideDbMoved){
|
|
|
+ globalState.aideCheckOk = true;
|
|
|
+ addTerminalLine("Проверка AIDE завершена: целостность ОК.", "success");
|
|
|
+ checkSceneCompletion();
|
|
|
+ } else addTerminalLine("Сначала инициализируйте AIDE.", "error");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // СЦЕНА 5 AppArmor + лимиты
|
|
|
+ if (currentScene === 5) {
|
|
|
+ if (trimmed === "aa-enforce /etc/apparmor.d/*") { globalState.apparmorEnforced = true; addTerminalLine("AppArmor переведён в режим enforcing.", "success"); checkSceneCompletion(); return; }
|
|
|
+ if (trimmed === "echo 'admin soft nproc 100' >> /etc/security/limits.conf" || trimmed.includes("limits.conf")) {
|
|
|
+ globalState.limitsSet = true;
|
|
|
+ addTerminalLine("Лимиты процессов для admin установлены (soft 100 / hard 150).", "success");
|
|
|
+ checkSceneCompletion();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (trimmed === "reboot" || trimmed === "systemctl reboot") {
|
|
|
+ if(globalState.apparmorEnforced && globalState.limitsSet){
|
|
|
+ globalState.rebootSimulated = true;
|
|
|
+ addTerminalLine("Перезагрузка симулирована. Лимиты и AppArmor применены.", "success");
|
|
|
+ checkSceneCompletion();
|
|
|
+ } else addTerminalLine("Необходимо сначала настроить AppArmor и limits.conf.", "error");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // СЦЕНА 6 финальная кнопка проверки
|
|
|
+ if (currentScene === 6) {
|
|
|
+ if (trimmed === "final-check" || trimmed === "проверка") {
|
|
|
+ let allPass = sceneCompleted[0] && sceneCompleted[1] && sceneCompleted[2] && sceneCompleted[3] && sceneCompleted[4];
|
|
|
+ if (allPass) {
|
|
|
+ addTerminalLine("✅ ВСЕ ПРОВЕРКИ ПРОЙДЕНЫ! Сервер соответствует Hardening Checklist.", "success");
|
|
|
+ sceneCompleted[5] = true;
|
|
|
+ updateProgressUI();
|
|
|
+ addTerminalLine("🏆 ПОЗДРАВЛЯЮ! Звание Security Auditor!", "success");
|
|
|
+ setGameMessage("Игра пройдена. Вы мастер Linux Hardening!");
|
|
|
+ } else {
|
|
|
+ addTerminalLine("❌ Не все сцены пройдены. Вернитесь и выполните предыдущие сцены.", "error");
|
|
|
+ }
|
|
|
+ checkSceneCompletion();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ addTerminalLine(`Неизвестная команда или не подходит для текущей сцены: ${cmd}`, "error");
|
|
|
+ }
|
|
|
+
|
|
|
+ // кнопки быстрых действий (согласно сценарию)
|
|
|
+ function renderDynamicActions() {
|
|
|
+ const container = document.getElementById("dynamicActions");
|
|
|
+ if (!container) return;
|
|
|
+ container.innerHTML = "";
|
|
|
+ if (currentScene === 1) {
|
|
|
+ addActionBtn("apt update && apt upgrade", () => processCommand("apt update && apt upgrade"));
|
|
|
+ addActionBtn("adduser admin", () => processCommand("adduser admin"));
|
|
|
+ addActionBtn("usermod -aG sudo admin", () => processCommand("usermod -aG sudo admin"));
|
|
|
+ addActionBtn("exit (переключиться на admin)", () => processCommand("exit"));
|
|
|
+ }
|
|
|
+ else if (currentScene === 2) {
|
|
|
+ addActionBtn("Установить minlen=12 в pwquality", () => processCommand("set minlen=12"));
|
|
|
+ addActionBtn("apt install fail2ban", () => processCommand("apt install fail2ban"));
|
|
|
+ addActionBtn("systemctl enable --now fail2ban", () => processCommand("systemctl enable fail2ban"));
|
|
|
+ }
|
|
|
+ else if (currentScene === 3) {
|
|
|
+ addActionBtn("ufw default deny incoming", () => processCommand("ufw default deny incoming"));
|
|
|
+ addActionBtn("ufw allow 22/tcp", () => processCommand("ufw allow 22/tcp"));
|
|
|
+ addActionBtn("ufw allow 80/tcp && 443/tcp", () => { processCommand("ufw allow 80/tcp"); processCommand("ufw allow 443/tcp"); });
|
|
|
+ addActionBtn("ufw deny 3306", () => processCommand("ufw deny 3306"));
|
|
|
+ addActionBtn("ufw enable", () => processCommand("ufw enable"));
|
|
|
+ }
|
|
|
+ else if (currentScene === 4) {
|
|
|
+ addActionBtn("aideinit", () => processCommand("aideinit"));
|
|
|
+ addActionBtn("Переместить базу AIDE", () => processCommand("mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz"));
|
|
|
+ addActionBtn("auditctl -w /etc/passwd -p wa -k passwd_changes", () => processCommand("auditctl -w /etc/passwd -p wa -k passwd_changes"));
|
|
|
+ addActionBtn("Сохранить правило аудита", () => processCommand("echo '-w /etc/passwd -p wa -k passwd_changes' >> /etc/audit/rules.d/hardening.rules"));
|
|
|
+ addActionBtn("aide --check", () => processCommand("aide --check"));
|
|
|
+ }
|
|
|
+ else if (currentScene === 5) {
|
|
|
+ addActionBtn("aa-enforce /etc/apparmor.d/*", () => processCommand("aa-enforce /etc/apparmor.d/*"));
|
|
|
+ addActionBtn("Установить лимиты (limits.conf)", () => processCommand("echo 'admin soft nproc 100' >> /etc/security/limits.conf"));
|
|
|
+ addActionBtn("reboot (симуляция)", () => processCommand("reboot"));
|
|
|
+ }
|
|
|
+ else if (currentScene === 6) {
|
|
|
+ addActionBtn("🔍 Запустить финальную проверку (final-check)", () => processCommand("final-check"));
|
|
|
+ addActionBtn("📋 Показать статус сцен", () => {
|
|
|
+ let msg = `Сцены: 1:${sceneCompleted[0]} 2:${sceneCompleted[1]} 3:${sceneCompleted[2]} 4:${sceneCompleted[3]} 5:${sceneCompleted[4]}`;
|
|
|
+ addTerminalLine(msg);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ function addActionBtn(text, callback) {
|
|
|
+ const btn = document.createElement("button");
|
|
|
+ btn.className = "action-btn";
|
|
|
+ btn.innerText = text;
|
|
|
+ btn.onclick = () => { callback(); renderDynamicActions(); };
|
|
|
+ container.appendChild(btn);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // инициализация событий
|
|
|
+ function initGame() {
|
|
|
+ updateProgressUI();
|
|
|
+ renderDynamicActions();
|
|
|
+ const inputEl = document.getElementById("cmdInput");
|
|
|
+ const runBtn = document.getElementById("runCmdBtn");
|
|
|
+ runBtn.addEventListener("click", () => {
|
|
|
+ let val = inputEl.value;
|
|
|
+ if (val.trim() !== "") processCommand(val);
|
|
|
+ inputEl.value = "";
|
|
|
+ inputEl.focus();
|
|
|
+ });
|
|
|
+ inputEl.addEventListener("keypress", (e) => {
|
|
|
+ if (e.key === "Enter") {
|
|
|
+ let val = inputEl.value;
|
|
|
+ if (val.trim() !== "") processCommand(val);
|
|
|
+ inputEl.value = "";
|
|
|
+ }
|
|
|
+ });
|
|
|
+ addTerminalLine("Добро пожаловать! Следуйте чеклисту. Используйте кнопки или команды.", "success");
|
|
|
+ addTerminalLine("Сцена 1: обновите систему, создайте admin, дайте sudo и выполните exit.");
|
|
|
+ }
|
|
|
+ initGame();
|
|
|
+</script>
|
|
|
+</body>
|
|
|
+</html>
|