From 620d7be6eb1baa5e911749ffc865d6c67453b407 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Muris=20Sladi=C4=87?=
<72603967+msladic1@users.noreply.github.com>
Date: Fri, 6 Dec 2024 13:55:51 +0100
Subject: [PATCH] Adding the Log manager with DB schema to monitor connections
When the DB is deployed and the Log Manager hosted on some server (now using Flask) all new connections are saved in the DB by ssh_module.py. The log_manager.py then fetches them and displays on the web dashboard that also needs to be hosted somewhere. Web dashboard is a simple web app that displays the tables of the connections to shelLM honeypot, as well the history of the session in the Commands_and_Answers table.
---
.../shellm_sessions_answers.sql | 44 ++
.../shellm_sessions_attacker_session.sql | 30 ++
.../shellm_sessions_commands.sql | 44 ++
.../shellm_sessions_shellm_session.sql | 49 ++
.../shellm_sessions_ssh_session.sql | 46 ++
Log Manager/dashboard.html | 104 +++++
Log Manager/db_credentials.env | 3 +
Log Manager/log_manager.py | 112 +++++
Log Manager/script.js | 388 ++++++++++++++++
Log Manager/ssh_module.py | 419 ++++++++++++++++++
Log Manager/style.css | 93 ++++
11 files changed, 1332 insertions(+)
create mode 100644 Log Manager/Database Schema/shellm_sessions_answers.sql
create mode 100644 Log Manager/Database Schema/shellm_sessions_attacker_session.sql
create mode 100644 Log Manager/Database Schema/shellm_sessions_commands.sql
create mode 100644 Log Manager/Database Schema/shellm_sessions_shellm_session.sql
create mode 100644 Log Manager/Database Schema/shellm_sessions_ssh_session.sql
create mode 100644 Log Manager/dashboard.html
create mode 100644 Log Manager/db_credentials.env
create mode 100644 Log Manager/log_manager.py
create mode 100644 Log Manager/script.js
create mode 100644 Log Manager/ssh_module.py
create mode 100644 Log Manager/style.css
diff --git a/Log Manager/Database Schema/shellm_sessions_answers.sql b/Log Manager/Database Schema/shellm_sessions_answers.sql
new file mode 100644
index 0000000..d69846b
--- /dev/null
+++ b/Log Manager/Database Schema/shellm_sessions_answers.sql
@@ -0,0 +1,44 @@
+-- MySQL dump 10.13 Distrib 8.0.40, for Win64 (x86_64)
+--
+-- Host: localhost Database: shellm_sessions
+-- ------------------------------------------------------
+-- Server version 8.0.40
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!50503 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `answers`
+--
+
+DROP TABLE IF EXISTS `answers`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!50503 SET character_set_client = utf8mb4 */;
+CREATE TABLE `answers` (
+ `answer_id` int NOT NULL AUTO_INCREMENT,
+ `command_id` int DEFAULT NULL,
+ `answer` text NOT NULL,
+ PRIMARY KEY (`answer_id`),
+ KEY `command_id` (`command_id`),
+ CONSTRAINT `answers_ibfk_1` FOREIGN KEY (`command_id`) REFERENCES `commands` (`command_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2024-12-06 9:42:51
diff --git a/Log Manager/Database Schema/shellm_sessions_attacker_session.sql b/Log Manager/Database Schema/shellm_sessions_attacker_session.sql
new file mode 100644
index 0000000..b9b6746
--- /dev/null
+++ b/Log Manager/Database Schema/shellm_sessions_attacker_session.sql
@@ -0,0 +1,30 @@
+-- MySQL dump 10.13 Distrib 8.0.40, for Win64 (x86_64)
+--
+-- Host: localhost Database: shellm_sessions
+-- ------------------------------------------------------
+-- Server version 8.0.40
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!50503 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `attacker_session`
+--
+
+DROP TABLE IF EXISTS `attacker_session`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!50503 SET character_set_client = utf8mb4 */;
+CREATE TABLE `attacker_session` (
+ `attacker_session_id` int NOT NULL AUTO_INCREMENT,
+ `src_ip` varchar(45) NOT NULL,
+ PRIMARY KEY (`attacker_session_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=224 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
diff --git a/Log Manager/Database Schema/shellm_sessions_commands.sql b/Log Manager/Database Schema/shellm_sessions_commands.sql
new file mode 100644
index 0000000..e1b698a
--- /dev/null
+++ b/Log Manager/Database Schema/shellm_sessions_commands.sql
@@ -0,0 +1,44 @@
+-- MySQL dump 10.13 Distrib 8.0.40, for Win64 (x86_64)
+--
+-- Host: localhost Database: shellm_sessions
+-- ------------------------------------------------------
+-- Server version 8.0.40
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!50503 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `commands`
+--
+
+DROP TABLE IF EXISTS `commands`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!50503 SET character_set_client = utf8mb4 */;
+CREATE TABLE `commands` (
+ `command_id` int NOT NULL AUTO_INCREMENT,
+ `shellm_session_id` int DEFAULT NULL,
+ `command` text NOT NULL,
+ PRIMARY KEY (`command_id`),
+ KEY `shellm_session_id` (`shellm_session_id`),
+ CONSTRAINT `commands_ibfk_1` FOREIGN KEY (`shellm_session_id`) REFERENCES `shellm_session` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=153 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2024-12-06 9:42:50
diff --git a/Log Manager/Database Schema/shellm_sessions_shellm_session.sql b/Log Manager/Database Schema/shellm_sessions_shellm_session.sql
new file mode 100644
index 0000000..c2b4ea0
--- /dev/null
+++ b/Log Manager/Database Schema/shellm_sessions_shellm_session.sql
@@ -0,0 +1,49 @@
+-- MySQL dump 10.13 Distrib 8.0.40, for Win64 (x86_64)
+--
+-- Host: localhost Database: shellm_sessions
+-- ------------------------------------------------------
+-- Server version 8.0.40
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!50503 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `shellm_session`
+--
+
+DROP TABLE IF EXISTS `shellm_session`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!50503 SET character_set_client = utf8mb4 */;
+CREATE TABLE `shellm_session` (
+ `id` int NOT NULL AUTO_INCREMENT,
+ `ssh_session_id` int DEFAULT NULL,
+ `model` varchar(255) NOT NULL,
+ `start_time` datetime NOT NULL,
+ `end_time` datetime NOT NULL,
+ `attacker_id` int DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `ssh_session_id` (`ssh_session_id`),
+ KEY `fk_attacker` (`attacker_id`),
+ CONSTRAINT `fk_attacker` FOREIGN KEY (`attacker_id`) REFERENCES `attacker_session` (`attacker_session_id`),
+ CONSTRAINT `shellm_session_ibfk_1` FOREIGN KEY (`ssh_session_id`) REFERENCES `ssh_session` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2024-12-06 9:42:51
diff --git a/Log Manager/Database Schema/shellm_sessions_ssh_session.sql b/Log Manager/Database Schema/shellm_sessions_ssh_session.sql
new file mode 100644
index 0000000..fb6063f
--- /dev/null
+++ b/Log Manager/Database Schema/shellm_sessions_ssh_session.sql
@@ -0,0 +1,46 @@
+-- MySQL dump 10.13 Distrib 8.0.40, for Win64 (x86_64)
+--
+-- Host: localhost Database: shellm_sessions
+-- ------------------------------------------------------
+-- Server version 8.0.40
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!50503 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `ssh_session`
+--
+
+DROP TABLE IF EXISTS `ssh_session`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!50503 SET character_set_client = utf8mb4 */;
+CREATE TABLE `ssh_session` (
+ `id` int NOT NULL AUTO_INCREMENT,
+ `username` varchar(255) NOT NULL,
+ `time_date` datetime NOT NULL,
+ `src_ip` varchar(45) NOT NULL,
+ `dst_ip` varchar(45) NOT NULL,
+ `src_port` int DEFAULT NULL,
+ `dst_port` int DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=227 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2024-12-06 9:42:50
diff --git a/Log Manager/dashboard.html b/Log Manager/dashboard.html
new file mode 100644
index 0000000..52f04a0
--- /dev/null
+++ b/Log Manager/dashboard.html
@@ -0,0 +1,104 @@
+
+
+
+
+
+ Log Manager Dashboard
+
+
+
+ Log Manager Dashboard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID |
+ Username |
+ Time & Date |
+ Source IP |
+ Destination IP |
+ Destination Port |
+
+
+
+
+
+
+
+
+
+
+
+ ID |
+ ssh_session_id |
+ model |
+ start_time |
+ end_time |
+ attacker_id |
+
+
+
+
+
+
+
+
+
+
+
+ command_id |
+ Command |
+ answer_id |
+ Answer |
+
+
+
+
+
+
+
+
+
+
+
+ command_id |
+ shellm_session_id |
+ Command |
+
+
+
+
+
+
+
+
+
+
+
+ answer_id |
+ command_id |
+ answer |
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Log Manager/db_credentials.env b/Log Manager/db_credentials.env
new file mode 100644
index 0000000..30ab09c
--- /dev/null
+++ b/Log Manager/db_credentials.env
@@ -0,0 +1,3 @@
+MYSQL_USER=root
+MYSQL_PASSWORD=RokeriSMoravu123!
+MYSQL_HOST=localhost
diff --git a/Log Manager/log_manager.py b/Log Manager/log_manager.py
new file mode 100644
index 0000000..b2c0eed
--- /dev/null
+++ b/Log Manager/log_manager.py
@@ -0,0 +1,112 @@
+from flask import Flask, jsonify, render_template
+from flask_cors import CORS # Import Flask-CORS
+import ssh_module
+import mysql.connector
+from datetime import datetime, timedelta
+from dotenv import load_dotenv
+import os
+
+# Load environment variables from .env file
+load_dotenv('db_credentials.env')
+
+MYSQL_USER = os.getenv('MYSQL_USER')
+MYSQL_PASSWORD = os.getenv('MYSQL_PASSWORD')
+MYSQL_HOST = os.getenv('MYSQL_HOST')
+
+def connect_to_db():
+ # Connect to the MySQL server
+ conn = mysql.connector.connect(
+ host=MYSQL_HOST,
+ user=MYSQL_USER,
+ password=MYSQL_PASSWORD,
+ database='shellm_sessions'
+ )
+ cursor = conn.cursor()
+
+ return conn, cursor
+
+app = Flask(__name__)
+
+CORS(app)
+
+# Route to fetch SSH sessions from the database
+@app.route('/ssh_sessions', methods=['GET'])
+def get_ssh_sessions():
+ conn, cursor = connect_to_db()
+ if conn is None:
+ return jsonify({"error": "Database connection failed"}), 500
+
+ try:
+ cursor.execute("SELECT * FROM ssh_session ORDER BY id DESC;")
+ sessions = cursor.fetchall() # Fetch data as a list of dictionaries
+ return jsonify(sessions)
+ except Exception as e:
+ return jsonify({"error": str(e)}), 500
+ finally:
+ cursor.close()
+ conn.close()
+
+@app.route('/shellm_sessions', methods=['GET'])
+def get_shellm_sessions():
+ conn, cursor = connect_to_db()
+ cursor.execute("SELECT * FROM shellm_session ORDER BY id DESC;")
+ sessions = cursor.fetchall()
+ cursor.close()
+ conn.close()
+
+ return jsonify(sessions)
+
+@app.route('/commands', methods=['GET'])
+def get_commands():
+ conn, cursor = connect_to_db()
+ cursor.execute("SELECT * FROM commands ORDER BY command_id DESC;")
+ commands = cursor.fetchall()
+ cursor.close()
+ conn.close()
+
+ return jsonify(commands)
+
+@app.route('/answers', methods=['GET'])
+def get_answers():
+ conn, cursor = connect_to_db()
+ cursor.execute("SELECT * FROM answers ORDER BY answer_id DESC;")
+ answers = cursor.fetchall()
+ cursor.close()
+ conn.close()
+
+ return jsonify(answers)
+
+# Route to render the dashboard
+@app.route('/', methods=['GET'])
+def dashboard():
+ return render_template('dashboard.html')
+
+@app.route('/commands_answers/', methods=['GET'])
+def get_commands_answers(shellm_session_id):
+ conn, cursor = connect_to_db()
+
+ # Fetch commands and their associated answers
+ cursor.execute("""
+ SELECT c.command_id, c.command, a.answer_id, COALESCE(a.answer, 'No answer') AS answer
+ FROM commands c
+ LEFT JOIN answers a ON c.command_id = a.command_id
+ WHERE c.shellm_session_id = %s
+ """, (shellm_session_id,))
+
+ results = cursor.fetchall()
+ conn.close()
+
+ # Return as a JSON response
+ # Return as a JSON response
+ return jsonify([
+ {
+ "command_id": row[0],
+ "command": row[1],
+ "answer_id": row[2],
+ "answer": row[3]
+ }
+ for row in results
+ ])
+
+if __name__ == "__main__":
+ app.run(debug=True)
\ No newline at end of file
diff --git a/Log Manager/script.js b/Log Manager/script.js
new file mode 100644
index 0000000..cb74775
--- /dev/null
+++ b/Log Manager/script.js
@@ -0,0 +1,388 @@
+// Function to fetch SSH session data from the Flask server
+function fetchSSH_Sessions() {
+ fetch('http://127.0.0.1:5000/ssh_sessions')
+ .then(response => response.json())
+ .then(data => {
+ displaySSHSessions(data);
+ })
+ .catch(error => {
+ console.error('Error fetching SSH sessions:', error);
+ });
+}
+
+// Function to fetch shelLM sessions data from the Flask server
+function fetchShelLM_Sessions() {
+ fetch('http://127.0.0.1:5000/shellm_sessions')
+ .then(response => response.json())
+ .then(data => {
+ displayshelLMSessions(data);
+ })
+ .catch(error => {
+ console.error('Error fetching shelLM sessions:', error);
+ });
+}
+
+// Function to fetch Commands data from the Flask server
+function fetchCommands() {
+ fetch('http://127.0.0.1:5000/commands')
+ .then(response => response.json())
+ .then(data => {
+ displayCommands(data);
+ })
+ .catch(error => {
+ console.error('Error fetching Commands:', error);
+ });
+}
+
+// Function to fetch Answers data from the Flask server
+function fetchAnswers() {
+ fetch('http://127.0.0.1:5000/answers')
+ .then(response => response.json())
+ .then(data => {
+ displayAnswers(data);
+ })
+ .catch(error => {
+ console.error('Error fetching Answers:', error);
+ });
+}
+
+// Function to display SSH sessions in the table
+function displaySSHSessions(sessions) {
+ console.log("Sessions data received:", sessions);
+
+ const table = document.getElementById("ssh-sessions-table"); // Reference to the table
+ const tableBody = document.getElementById("ssh-sessions-table-body");
+
+ if (!table || !tableBody) {
+ console.error("Table or table body with ID 'ssh-sessions-table' or 'ssh-sessions-table-body' not found.");
+ return;
+ }
+
+ // Make sure the table is visible
+ table.style.display = "table"; // Unhide the table
+
+ // Clear previous entries
+ tableBody.innerHTML = "";
+
+ // Check if 'sessions' is an array and contains valid data
+ if (Array.isArray(sessions) && sessions.length > 0) {
+ sessions.forEach((session, index) => {
+ console.log(`Processing session ${index}:`, session);
+
+ if (!Array.isArray(session) || session.length < 7) {
+ console.error("Invalid session data:", session);
+ return;
+ }
+
+ const row = document.createElement("tr");
+
+ const idCell = document.createElement("td");
+ idCell.textContent = session[0];
+
+ const usernameCell = document.createElement("td");
+ usernameCell.textContent = session[1];
+
+ const timeDateCell = document.createElement("td");
+ timeDateCell.textContent = session[2];
+
+ const srcIPCell = document.createElement("td");
+ srcIPCell.textContent = session[3];
+
+ const dstIPCell = document.createElement("td");
+ dstIPCell.textContent = session[4];
+
+ const dstPortCell = document.createElement("td");
+ dstPortCell.textContent = session[6];
+
+ row.appendChild(idCell);
+ row.appendChild(usernameCell);
+ row.appendChild(timeDateCell);
+ row.appendChild(srcIPCell);
+ row.appendChild(dstIPCell);
+ row.appendChild(dstPortCell);
+
+ tableBody.appendChild(row);
+ });
+
+ console.log("Table populated successfully.");
+ } else {
+ console.error("Invalid data structure received:", sessions);
+ }
+}
+
+
+// Function to display SSH sessions in the table
+function displayshelLMSessions(sessions) {
+ console.log("Sessions data received:", sessions);
+
+ const table = document.getElementById("shellm-sessions-table"); // Reference to the table
+ const tableBody = document.getElementById("shellm-sessions-table-body");
+
+ if (!table || !tableBody) {
+ console.error("Table or table body with ID 'shellm-sessions-table' or 'shellm-sessions-table-body' not found.");
+ return;
+ }
+
+ // Make sure the table is visible
+ table.style.display = "table"; // Unhide the table
+
+ // Clear previous entries
+ tableBody.innerHTML = "";
+
+ // Check if 'sessions' is an array and contains valid data
+ if (Array.isArray(sessions) && sessions.length > 0) {
+ sessions.forEach((session, index) => {
+ console.log(`Processing session ${index}:`, session);
+
+ if (!Array.isArray(session) || session.length < 6) {
+ console.error("Invalid session data:", session);
+ return;
+ }
+
+ const row = document.createElement("tr");
+
+ const idCell = document.createElement("td");
+ idCell.textContent = session[0];
+ idCell.style.cursor = "pointer";
+ idCell.addEventListener("click", () => fetchAndDisplayCommandsAnswers(session[0]));
+
+ const ssh_session_idCell = document.createElement("td");
+ ssh_session_idCell.textContent = session[1];
+
+ const modelCell = document.createElement("td");
+ modelCell.textContent = session[2];
+
+ const start_timeCell = document.createElement("td");
+ start_timeCell.textContent = session[3];
+
+ const end_timeCell = document.createElement("td");
+ end_timeCell.textContent = session[4];
+
+ const attacker_idCell = document.createElement("td");
+ attacker_idCell.textContent = session[5];
+
+ row.appendChild(idCell);
+ row.appendChild(ssh_session_idCell);
+ row.appendChild(modelCell);
+ row.appendChild(start_timeCell);
+ row.appendChild(end_timeCell);
+ row.appendChild(attacker_idCell);
+
+ tableBody.appendChild(row);
+ });
+
+ console.log("Table populated successfully.");
+ } else {
+ console.error("Invalid data structure received:", sessions);
+ }
+}
+
+// Function to display SSH sessions in the table
+function displayCommands(sessions) {
+ console.log("Commands data received:", sessions);
+
+ const table = document.getElementById("commands-table"); // Reference to the table
+ const tableBody = document.getElementById("commands-table-body");
+
+ if (!table || !tableBody) {
+ console.error("Table or table body with ID 'commands-table' or 'commands-table-body' not found.");
+ return;
+ }
+
+ // Make sure the table is visible
+ table.style.display = "table"; // Unhide the table
+
+ // Clear previous entries
+ tableBody.innerHTML = "";
+
+ // Check if 'sessions' is an array and contains valid data
+ if (Array.isArray(sessions) && sessions.length > 0) {
+ sessions.forEach((session, index) => {
+ console.log(`Processing command ${index}:`, session);
+
+ if (!Array.isArray(session) || session.length < 3) {
+ console.error("Invalid commands data:", session);
+ return;
+ }
+
+ const row = document.createElement("tr");
+
+ const idCell = document.createElement("td");
+ idCell.textContent = session[0];
+
+ const shellm_session_idCell = document.createElement("td");
+ shellm_session_idCell.textContent = session[1];
+
+ const commandCell = document.createElement("td");
+ commandCell.textContent = session[2];
+
+ row.appendChild(idCell);
+ row.appendChild(shellm_session_idCell);
+ row.appendChild(commandCell);
+
+ tableBody.appendChild(row);
+ });
+
+ console.log("Table populated successfully.");
+ } else {
+ console.error("Invalid data structure received:", sessions);
+ }
+}
+
+// Function to display SSH sessions in the table
+function displayAnswers(sessions) {
+ console.log("Answers data received:", sessions);
+
+ const table = document.getElementById("answers-table"); // Reference to the table
+ const tableBody = document.getElementById("answers-table-body");
+
+ if (!table || !tableBody) {
+ console.error("Table or table body with ID 'answers-table' or 'answers-table-body' not found.");
+ return;
+ }
+
+ // Make sure the table is visible
+ table.style.display = "table"; // Unhide the table
+
+ // Clear previous entries
+ tableBody.innerHTML = "";
+
+ // Check if 'sessions' is an array and contains valid data
+ if (Array.isArray(sessions) && sessions.length > 0) {
+ sessions.forEach((session, index) => {
+ console.log(`Processing answer ${index}:`, session);
+
+ if (!Array.isArray(session) || session.length < 3) {
+ console.error("Invalid answer data:", session);
+ return;
+ }
+
+ const row = document.createElement("tr");
+
+ const idCell = document.createElement("td");
+ idCell.textContent = session[0];
+
+ const command_idCell = document.createElement("td");
+ command_idCell.textContent = session[1];
+
+ const answerCell = document.createElement("td");
+ answerCell.textContent = session[2];
+
+ row.appendChild(idCell);
+ row.appendChild(command_idCell);
+ row.appendChild(answerCell);
+
+ tableBody.appendChild(row);
+ });
+
+ console.log("Table populated successfully.");
+ } else {
+ console.error("Invalid data structure received:", sessions);
+ }
+}
+
+// Function to fetch and display commands and answers for a shellm session
+function fetchAndDisplayCommandsAnswers(shellmSessionID) {
+ console.log("Fetching commands and answers for ShellM Session ID:", shellmSessionID);
+
+ fetch(`http://127.0.0.1:5000/commands_answers/${shellmSessionID}`)
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return response.json();
+ })
+ .then((data) => {
+ const tableBody = document.getElementById("commands-answers-table-body");
+ const commandsAnswersTable = document.getElementById("commands-answers-table");
+
+ if (!tableBody || !commandsAnswersTable) {
+ console.error("Commands and Answers table elements not found in the DOM.");
+ return;
+ }
+
+ // Clear previous entries
+ tableBody.innerHTML = "";
+
+ // Show the table
+ commandsAnswersTable.style.display = "table";
+
+ // Populate the table with the received data
+ if (Array.isArray(data) && data.length > 0) {
+ data.forEach((entry) => {
+ const row = document.createElement("tr");
+
+ // Create and populate cells
+ const commandIDCell = document.createElement("td");
+ commandIDCell.textContent = entry.command_id || "N/A";
+
+ const commandCell = document.createElement("td");
+ commandCell.textContent = entry.command || "N/A";
+
+ const answerIDCell = document.createElement("td");
+ answerIDCell.textContent = entry.answer_id || "N/A";
+
+ const answerCell = document.createElement("td");
+ answerCell.textContent = entry.answer || "N/A";
+
+ // Append cells to the row
+ row.appendChild(commandIDCell);
+ row.appendChild(commandCell);
+ row.appendChild(answerIDCell);
+ row.appendChild(answerCell);
+
+ // Append the row to the table body
+ tableBody.appendChild(row);
+ });
+ } else {
+ // If no data is found, show a "No data available" message
+ const row = document.createElement("tr");
+ const noDataCell = document.createElement("td");
+ noDataCell.textContent = "No commands or answers found for this session.";
+ noDataCell.colSpan = 4;
+ row.appendChild(noDataCell);
+ tableBody.appendChild(row);
+ }
+ })
+ .catch((error) => console.error("Error fetching commands and answers:", error));
+}
+
+// Function to hide all tables
+function hideAllTables() {
+ const tables = document.querySelectorAll('table');
+ tables.forEach(table => table.style.display = 'none');
+}
+
+// Add event listeners to the buttons
+document.getElementById('sshSessionsBtn').addEventListener('click', () => {
+ hideAllTables();
+ fetchSSH_Sessions();
+});
+document.getElementById('shelLM_SessionsBtn').addEventListener('click', () => {
+ hideAllTables();
+ fetchShelLM_Sessions();
+});
+document.getElementById('commandsBtn').addEventListener('click', () => {
+ hideAllTables();
+ fetchCommands();
+});
+document.getElementById('answersBtn').addEventListener('click', () => {
+ hideAllTables();
+ fetchAnswers();
+});
+
+// Function to handle ID click
+function handleIDClick(shellmSessionID) {
+ console.log("ShellM Session ID clicked:", shellmSessionID);
+
+ const commandsAnswersSection = document.getElementById("commands-answers-section");
+ commandsAnswersSection.style.display = "block"; // Show the new button
+
+ const commandsAnswersBtn = document.getElementById("commandsAnswersBtn");
+ commandsAnswersBtn.onclick = () => fetchAndDisplayCommandsAnswers(shellmSessionID); // Attach handler
+}
+
+// Ensure the page is ready for actions
+document.addEventListener("DOMContentLoaded", () => {
+ console.log("Log Manager Dashboard ready.");
+});
diff --git a/Log Manager/ssh_module.py b/Log Manager/ssh_module.py
new file mode 100644
index 0000000..7c8ddc8
--- /dev/null
+++ b/Log Manager/ssh_module.py
@@ -0,0 +1,419 @@
+import paramiko
+import mysql.connector
+from datetime import datetime, timedelta
+from dotenv import load_dotenv
+import os
+
+
+# Load environment variables from .env file
+load_dotenv('db_credentials.env')
+
+MYSQL_USER = os.getenv('MYSQL_USER')
+MYSQL_PASSWORD = os.getenv('MYSQL_PASSWORD')
+MYSQL_HOST = os.getenv('MYSQL_HOST')
+
+
+def parse_last_output(last_output):
+ sessions = []
+
+ # Process the output line by line
+ for line in last_output.splitlines():
+ # Skip empty lines or irrelevant lines
+ if not line.strip() or line.startswith("wtmp") or "reboot" in line:
+ continue
+
+ parts = line.split() # Split the line into words
+
+ # Extract details based on fixed column positions
+ username = parts[0]
+ terminal = parts[1]
+ src_ip = parts[2] if parts[2] != ":0" else "127.0.0.1" # Default to localhost for local logins
+ time_date_start = " ".join(parts[3:8]) # Combine the time and date parts
+
+ # Convert time strings to MySQL-compatible format
+ time_date_start = datetime.strptime(time_date_start, "%a %b %d %H:%M:%S %Y").strftime("%Y-%m-%d %H:%M:%S")
+
+ # Destination IP (your server IP or localhost for testing)
+ dst_ip = "olympus.felk.cvut.cz"
+ dst_port = 1337
+
+ # Append parsed data as a tuple
+ sessions.append((username, time_date_start, src_ip, dst_ip, dst_port))
+
+ return sessions
+
+
+def connect_to_db():
+ # Connect to the MySQL server
+ conn = mysql.connector.connect(
+ host=MYSQL_HOST,
+ user=MYSQL_USER,
+ password=MYSQL_PASSWORD,
+ database='shellm_sessions'
+ )
+ cursor = conn.cursor()
+
+ return conn, cursor
+
+
+# Function to insert data into MySQL
+def insert_into_ssh_session(data):
+ conn, cursor = connect_to_db()
+
+ # Insert data into ssh_session table
+ for session in data:
+ cursor.execute("""
+ INSERT INTO ssh_session (username, time_date, src_ip, dst_ip, dst_port)
+ VALUES (%s, %s, %s, %s, %s)
+ """, session)
+
+ # Commit and close
+ conn.commit()
+ cursor.close()
+ conn.close()
+
+
+# Function to insert data into MySQL
+def insert_into_attacker_session(data):
+ conn, cursor = connect_to_db()
+
+ # Insert data into ssh_session table
+ for session in data:
+ src_ip = session[2]
+ cursor.execute("""
+ INSERT INTO attacker_session (src_ip)
+ VALUES (%s)
+ """, (src_ip,))
+
+ # Commit and close
+ conn.commit()
+ cursor.close()
+ conn.close()
+
+
+# Function to count how many new connections were to ssh since last database entry
+def count_newest(data):
+ conn, cursor = connect_to_db()
+
+ cursor.execute("""
+ SELECT time_date FROM ssh_session ORDER BY id DESC LIMIT 1;
+ """)
+
+ latest_entry = cursor.fetchone()
+
+ if latest_entry:
+ time_date = latest_entry[0]
+
+ # Check if it's a datetime object or a string
+ if isinstance(time_date, datetime):
+ # If it's a datetime object, format it as a string
+ time_date_str = time_date.strftime(f"%Y-%m-%d %H:%M:%S")
+ else:
+ # If it's already a string, use it directly
+ time_date_str = str(time_date)
+
+ counter = 0
+
+ # Go through ssh logs and stop when old entries are reached
+ for session in data:
+ if session[1] <= time_date_str: # time_date_str - the specific date is only for testing
+ break
+ else:
+ counter += 1
+
+ # Commit and close
+ conn.commit()
+ cursor.close()
+ conn.close()
+
+ return counter
+
+
+def create_filenames(data):
+ filenames = []
+
+ for session in data:
+ # Extract the time_date value from the session tuple
+ time_date = session[1] # Assuming time_date is the second element in the tuple
+
+ # Convert the time_date to a string if it's not already
+ if isinstance(time_date, datetime):
+ time_date = time_date + timedelta(seconds=1)
+ time_date_str = time_date.strftime(f"%Y-%m-%d_%H-%M-%S")
+ else:
+ time_date_str = str(time_date).replace(" ", "_").replace(":", "-")
+
+ # Create the filename using the formatted time_date
+ filename = f"historySSH_{time_date_str}.txt"
+ filenames.append(filename)
+
+ return filenames
+
+
+def get_latest_sessions_ids(counter):
+ ids = []
+
+ conn, cursor = connect_to_db()
+ cursor.execute("""
+ SELECT id FROM ssh_session ORDER BY id DESC LIMIT %s;
+ """,(counter,))
+ ids = [row[0] for row in cursor.fetchall()]
+
+ return ids
+
+
+def get_latest_attackers_ids(counter):
+ ids = []
+
+ conn, cursor = connect_to_db()
+ cursor.execute("""
+ SELECT attacker_session_id FROM attacker_session ORDER BY attacker_session_id DESC LIMIT %s;
+ """,(counter,))
+ ids = [row[0] for row in cursor.fetchall()]
+
+ return ids
+
+
+def parse_historylog(filename, ssh):
+ stdin, stdout, stderr = ssh.exec_command(f"cat /opt/demos/shelLM/2024_shellm/historylogs/{filename}")
+ conversation = stdout.read().decode('utf-8')
+ conversation_error = stderr.read().decode('utf-8')
+
+ commands = []
+ answers = []
+ timestamps = []
+ start = ""
+
+ lines = conversation.splitlines()
+
+ previous_line = None
+ output = None
+
+ for line in lines:
+
+ if "For the last login date use" in line:
+ line_parts = line.split()
+ start = line_parts[-2] + " " + line_parts[-1]
+
+ # Check if the line contains a command
+ if line.strip().endswith('>'): # Command line ends with '>'
+
+ # First make sure if there is a multiline output to put it to answers
+ if output is not None:
+ answers.append(output)
+ output = None
+
+ split_line = line.split()
+
+ command = split_line[1:-2]
+ command = ' '.join(command)
+
+ if previous_line is not None and ":~" in previous_line:
+ output = split_line[0]
+ answers.append(output)
+ output = None
+
+ # print(line)
+ # print(command)
+
+ # For the exit command just put ""
+ if command == "exit":
+ answers.append("")
+
+ # Extract timestamp from the command line
+ try:
+ timestamp = split_line[-2] + split_line[-1]
+ timestamps.append(timestamp)
+ except (IndexError, ValueError):
+ pass # Ignore lines without valid timestamps
+
+ commands.append(command)
+
+ previous_line = line
+
+ else:
+ # Collect lines of output for the current command
+ if previous_line is not None:
+ if ":~" in previous_line:
+ output = line.strip()
+ previous_line = line
+ else:
+ output = output + "\n" + line.strip()
+ previous_line = line
+
+ # Determine start and end times
+ timestamps = [timestamps[0], timestamps[-1]]
+ start_time = start if start else timestamps[0] if timestamps else None
+ end_time = timestamps[1] if timestamps else None
+
+ # print(timestamps)
+ # print(commands)
+ # print(answers)
+
+ return commands, answers, start_time, end_time
+
+
+def insert_into_commands_and_answers(commands, shellm_session_id, answers):
+ conn, cursor = connect_to_db()
+
+ try:
+ # Ensure we have a matching number of commands and answers
+ if len(commands) != len(answers):
+ raise ValueError("The number of commands and answers must be equal.")
+
+ answer_counter = 0
+
+ # Insert each command and its corresponding answer
+ for command in commands:
+ # Insert command into 'commands' table
+ cursor.execute("""
+ INSERT INTO commands (shellm_session_id, command)
+ VALUES (%s, %s)
+ """, (shellm_session_id, command))
+
+ # Fetch the last inserted 'command_id'
+ cursor.execute("""
+ SELECT LAST_INSERT_ID();
+ """)
+ command_id = cursor.fetchone()[0] # Extract the value from the tuple
+
+ # Insert answer into 'answers' table
+ cursor.execute("""
+ INSERT INTO answers (command_id, answer)
+ VALUES (%s, %s)
+ """, (command_id, answers[answer_counter]))
+
+ answer_counter += 1
+
+ # Commit the transaction
+ conn.commit()
+
+ except Exception as e:
+ print(f"An error occurred: {e}")
+ conn.rollback() # Rollback in case of error
+
+ finally:
+ # Clean up resources
+ cursor.close()
+ conn.close()
+
+
+def insert_into_shellm_session(start_time, end_time, latest_session_ids, latest_attacker_ids):
+ conn, cursor = connect_to_db()
+
+ ssh_session_id = latest_session_ids[0]
+ latest_attacker_id = latest_attacker_ids[0]
+
+ cursor.execute("""
+ INSERT INTO shellm_session (ssh_session_id, model, start_time, end_time, attacker_id)
+ VALUES (%s, %s, %s, %s, %s)
+ """, (ssh_session_id, "FT GPT-3.5-16k", start_time, end_time, latest_attacker_id))
+
+ conn.commit()
+ cursor.close()
+ conn.close()
+
+
+def get_latest_shellm_session():
+
+ conn, cursor = connect_to_db()
+
+ cursor.execute("""
+ SELECT id FROM shellm_session ORDER BY id DESC LIMIT 1;
+ """)
+
+ shellm_session_id = cursor.fetchone()
+
+ # Commit and close
+ conn.commit()
+ cursor.close()
+ conn.close()
+
+ return shellm_session_id
+
+
+def format_datetime_for_db(raw_time):
+ # Step 1: Remove angle brackets
+ cleaned_time = raw_time.strip('<>')
+
+ # Step 2: Insert a space between the date and time
+ formatted_time = cleaned_time[:10] + ' ' + cleaned_time[10:]
+
+ # Step 3: Convert to datetime object (validates the format)
+ datetime_obj = datetime.strptime(formatted_time, f"%Y-%m-%d %H:%M:%S.%f")
+
+ # Step 4: Format to MySQL-compatible DATETIME string
+ return datetime_obj.strftime(f"%Y-%m-%d %H:%M:%S")
+
+
+def get_logs_from_server():
+ ssh = paramiko.SSHClient()
+ ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+
+ # Load your private key to log in to remote server with logs
+ private_key = paramiko.RSAKey.from_private_key_file('C:\\Users\\Muris\\.ssh\\id_rsa')
+
+ # Connect to the SSH server using key-based authentication
+ ssh.connect(hostname="olympus.felk.cvut.cz", port=902, username="muris", pkey=private_key)
+
+ # Execute commands as before
+ stdin, stdout, stderr = ssh.exec_command("docker exec 9c85812a9ad8 last -F")
+ error_output = stderr.read().decode('utf-8')
+ docker_history = stdout.read().decode('utf-8')
+
+ ssh_data = parse_last_output(docker_history)
+ counter = count_newest(ssh_data)
+ ssh_data = ssh_data[:counter][::-1]
+
+ stdin, stdout, stderr = ssh.exec_command(f"ls /opt/demos/shelLM/2024_shellm/historylogs")
+ shellm_histories = stdout.read().decode('utf-8')
+ error_getting_histories = stderr.read().decode('utf-8')
+
+ # Get just the last created histories. Ignore earlier. This is to know exact file names of history logs.
+ shellm_histories = shellm_histories.split()
+ shellm_histories = shellm_histories[-counter:][::1]
+ # print(shellm_histories)
+
+ if counter > 0:
+ if docker_history:
+ insert_into_ssh_session(ssh_data)
+ insert_into_attacker_session(ssh_data)
+ else:
+ print(error_output)
+
+ # filenames = create_filenames(ssh_data)
+ # print(filenames)
+
+ # Get ids of the latest created session. They are FKs in shellm_session table. Invert them to get them in right order.
+ latest_sessions_ids = get_latest_sessions_ids(counter)
+ latest_sessions_ids = latest_sessions_ids[:][::-1]
+ latest_attacker_ids = get_latest_attackers_ids(counter)
+ latest_attacker_ids = latest_attacker_ids[:][::-1]
+ # print(latest_sessions_ids)
+
+ for filename in shellm_histories:
+ commands, answers, start_time, end_time = parse_historylog(filename, ssh)
+
+ start_time = format_datetime_for_db(start_time)
+ end_time = format_datetime_for_db(end_time)
+
+ insert_into_shellm_session(start_time, end_time, latest_sessions_ids, latest_attacker_ids)
+
+ shellm_session_id = get_latest_shellm_session()
+ # print(shellm_session_id)
+
+ insert_into_commands_and_answers(commands, shellm_session_id[0], answers)
+
+ # Pop the processed ssh_session
+ latest_sessions_ids = latest_sessions_ids[1:]
+ latest_attacker_ids = latest_attacker_ids[1:]
+
+
+ # log_file_content = stdout.read().decode()
+
+ ssh.close()
+
+ return docker_history#, log_file_content
+
+if __name__ == "__main__":
+ get_logs_from_server()
\ No newline at end of file
diff --git a/Log Manager/style.css b/Log Manager/style.css
new file mode 100644
index 0000000..0ce352e
--- /dev/null
+++ b/Log Manager/style.css
@@ -0,0 +1,93 @@
+body {
+ font-family: Arial, sans-serif;
+ margin: 0;
+ padding: 0;
+ background-color: #f9f9f9;
+ color: #333;
+}
+
+h1 {
+ text-align: center;
+ padding: 20px;
+ background-color: #4CAF50;
+ color: white;
+ margin: 0;
+}
+
+.container {
+ max-width: 1200px;
+ margin: 20px auto;
+ padding: 20px;
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+}
+
+.button-container {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 20px;
+}
+
+.button-container button {
+ background-color: #4CAF50;
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ margin: 0 10px;
+ border-radius: 5px;
+ font-size: 16px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.button-container button:hover {
+ background-color: #45a049;
+}
+
+table {
+ border-collapse: collapse;
+ width: 100%;
+ margin-top: 20px;
+}
+
+th, td {
+ border: 1px solid #ddd;
+ padding: 12px;
+ text-align: left;
+}
+
+th {
+ background-color: #4CAF50;
+ color: white;
+}
+
+tr:nth-child(even) {
+ background-color: #f2f2f2;
+}
+
+tr:hover {
+ background-color: #ddd;
+}
+
+#commands-answers-table {
+ display: none;
+}
+
+.table-header {
+ margin-top: 30px;
+ text-align: center;
+ font-size: 18px;
+ color: #555;
+}
+
+footer {
+ text-align: center;
+ margin-top: 40px;
+ padding: 10px 0;
+ background-color: #4CAF50;
+ color: white;
+ position: relative;
+ bottom: 0;
+ width: 100%;
+}