//Reservation - CTF Challenge Writeup
Challenge Name: Reservation
Category: MISC
Difficulty: Easy/Medium
Flag: nullctf{why_1s_it_r3srv3d_f3eaf9630244ed5f}
>Challenge Description
I wanted to take my inexistent girlfriend to this fancy restaurant called Windows, but they keep asking me for a key PROMPT. I don't know what to do, can you help me?
We're given a Python file reservation.py that runs a socket server.
>Initial Analysis
Let's examine the provided reservation.py file:
import os
import socket
from dotenv import load_dotenv
load_dotenv()
FLAG = os.getenv("FLAG", "nullctf{aergnoujiwaegnjwkoiqergwnjiokeprgwqenjoig}")
PROMPT = os.getenv("PROMPT", "bananananannaanan")
PORT = int(os.getenv("PORT", 3001))
# This is missing from the .env file, but it still printed something, interesting
print(os.getenv("WINDIR"))
def normal_function_name_1284932tgaegrasbndefgjq4trwqerg(client_socket):
client_socket.sendall(
b"""[windows_10 | cmd.exe] Welcome good sire to our fine establishment.
Unfortunately, due to increased demand,
we have had to privatize our services.
Please enter the secret passphrase received from the environment to continue.\n""")
response = client_socket.recv(1024).decode().strip()
if response == PROMPT:
client_socket.sendall(b"Thank you for your patience. Here is your flag: " + FLAG.encode())
else:
client_socket.sendall(b"...I am afraid that is not correct. Please leave our establishment.")
client_socket.close()
def start_server(host='0.0.0.0', port=PORT):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((host, port))
server_socket.listen(1024)
print(f"[*] Listening on {host}:{port}")
while True:
client_socket, addr = server_socket.accept()
print(f"[*] Accepted connection from {addr}")
normal_function_name_1284932tgaegrasbndefgjq4trwqerg(client_socket)
if __name__ == "__main__":
start_server()
Key Observations
-
The server requires a PROMPT value - It loads
PROMPTfrom environment variables with a default fallback -
Important comment - "This is missing from the .env file, but it still printed something, interesting"
-
Windows hints everywhere:
- The code prints os.getenv("WINDIR") on startup
- The banner says [windows_10 | cmd.exe]
- The restaurant is called "Windows"
- The vulnerability - The server reads from actual system environment variables, not just a .env file
>The Eureka Moment 💡
The key insight is understanding what PROMPT means in a Windows context:
On Windows systems, there's an environment variable called PROMPT that defines how the command prompt looks in CMD.exe. The default value is:
$P$G
Where:
-
$P= Current drive and path (e.g.,C:\Users\Stefan) -
$G= Greater-than sign>
This displays as the familiar C:\> prompt in Windows CMD.
Since the remote server is running on Windows (as evidenced by WINDIR and the cmd.exe banner), it will read the system's PROMPT environment variable, which defaults to $P$G!
>Solution Approach
Step 1: Local Testing Setup
First, I created a test environment to verify my theory locally using WSL.
File: run_server.sh
#!/bin/bash
export PROMPT='$P$G'
python3 reservation.py
This script sets the PROMPT environment variable and starts the server.
Step 2: Simple Connection Test
I created a simple test script to verify the connection works:
File: simple_test.py
#!/usr/bin/env python3
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', 3001))
# Receive welcome
data = sock.recv(4096).decode()
print(data)
# Send PROMPT value
prompt = "$P$G"
print(f"Sending: {repr(prompt)}")
sock.sendall(prompt.encode() + b'\n')
# Get response
response = sock.recv(4096).decode()
print(response)
sock.close()
Step 3: Local Testing
I started the local server:
chmod +x run_server.sh
./run_server.sh
Then tested with the simple script:
python3 simple_test.py
Output:
[windows_10 | cmd.exe] Welcome good sire to our fine establishment.
Unfortunately, due to increased demand,
we have had to privatize our services.
Please enter the secret passphrase received from the environment to continue.
Sending: '$P$G'
Thank you for your patience. Here is your flag: nullctf{aergnoujiwaegnjwkoiqergwnjiokeprgwqenjoig}
✅ Success! The local test confirmed that $P$G is the correct PROMPT value.
Step 4: Remote Exploitation
Now that I verified the solution locally, I created a polished exploit script for the remote server:
File: exploit_remote.py
#!/usr/bin/env python3
import socket
import sys
def exploit(host, port):
"""Exploit the remote server to get the flag"""
print(f"[*] Connecting to {host}:{port}")
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
# Receive welcome message
data = sock.recv(4096).decode()
print("[*] Received from server:")
print(data)
# The PROMPT environment variable on Windows CMD defaults to $P$G
# This is what the server is expecting
prompt = "$P$G"
print(f"\n[*] Sending PROMPT value: {repr(prompt)}")
sock.sendall(prompt.encode() + b'\n')
# Receive flag
response = sock.recv(4096).decode()
print("\n[*] Server response:")
print(response)
# Extract and highlight the flag
if "flag" in response.lower():
import re
flag_match = re.search(r'nullctf\{[^}]+\}', response)
if flag_match:
print(f"\n[+] FLAG FOUND: {flag_match.group(0)}")
sock.close()
except Exception as e:
print(f"[-] Error: {e}")
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python3 exploit_remote.py <host> <port>")
print("\nExample: python3 exploit_remote.py 127.0.0.1 3001")
sys.exit(1)
host = sys.argv[1]
port = int(sys.argv[2])
exploit(host, port)
Step 5: Getting the Flag
Running the exploit against the remote server:
python3 exploit_remote.py 34.118.61.99 10057
Output:
[*] Connecting to 34.118.61.99:10057
[*] Received from server:
[windows_10 | cmd.exe] Welcome good sire to our fine establishment.
Unfortunately, due to increased demand,
we have had to privatize our services.
Please enter the secret passphrase received from the environment to continue.
[*] Sending PROMPT value: '$P$G'
[*] Server response:
Thank you for your patience. Here is your flag: nullctf{why_1s_it_r3srv3d_f3eaf9630244ed5f}
[+] FLAG FOUND: nullctf{why_1s_it_r3srv3d_f3eaf9630244ed5f}
🎉 FLAG CAPTURED: nullctf{why_1s_it_r3srv3d_f3eaf9630244ed5f}
>Additional Helper Scripts
During the solving process, I also created these helper scripts:
File: check_env.py
import os
print("PROMPT:", repr(os.getenv("PROMPT")))
print("WINDIR:", repr(os.getenv("WINDIR")))
This script helps verify environment variables are set correctly.
File: test_connection.py
#!/usr/bin/env python3
import socket
def test_prompt(host, port, prompt_value):
"""Test a specific prompt value"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
# Receive welcome message
data = sock.recv(4096).decode()
print("[*] Received:")
print(data)
# Send the prompt
print(f"[*] Trying PROMPT: {repr(prompt_value)}")
sock.sendall(prompt_value.encode() + b'\n')
# Receive response
response = sock.recv(4096).decode()
print("[*] Response:")
print(response)
sock.close()
if "flag" in response.lower():
print(f"\n[+] SUCCESS! The PROMPT was: {repr(prompt_value)}")
return True
return False
except Exception as e:
print(f"[-] Error: {e}")
return False
if __name__ == "__main__":
# Test locally first
print("[*] Testing common Windows PROMPT values locally...")
# Common Windows CMD PROMPT values
prompts_to_try = [
"$P$G", # Default: C:\>
"$p$g", # Lowercase version
"$P$G ", # With space
"$P$G$_", # With newline
"$P$G$S", # With space
]
host = "localhost"
port = 3001
for prompt in prompts_to_try:
if test_prompt(host, port, prompt):
break
print()
This script was useful for testing multiple PROMPT variations during the research phase.
>Key Takeaways
-
Read the hints carefully - The comment about WINDIR "printing something" was crucial
-
Context matters - The Windows/CMD.exe references weren't just flavor text
-
Environment variables are powerful - Understanding OS-specific environment variables can be key to solving challenges
-
Test locally first - Setting up a local test environment helped verify the solution before trying remote
-
Windows PROMPT variable - Learning about
$P$Gand other Windows CMD prompt formatting codes was essential
>Windows PROMPT Format Codes Reference
For those curious, here are some common Windows CMD PROMPT format codes:
-
$A- & (Ampersand) -
$B- | (pipe) -
$C- ( (Left parenthesis) -
$D- Current date -
$E- Escape code (ASCII code 27) -
$F- ) (Right parenthesis) -
$G- > (greater-than sign) -
$H- Backspace (erases previous character) -
$L- < (less-than sign) -
$N- Current drive -
$P- Current drive and path -
$Q- = (equal sign) -
$S- (space) -
$T- Current time -
$V- Windows version number -
$_- Carriage return and linefeed -
$$- $ (dollar sign)
>Conclusion
This challenge was a clever play on Windows environment variables and required understanding how the Windows command prompt works. The key was recognizing that the PROMPT environment variable in Windows CMD.exe defaults to $P$G, which the server was reading from the system environment.
The challenge name "Reservation" and the restaurant theme were perfect metaphors - we needed the right "reservation" (PROMPT value) to get into the fancy "Windows" restaurant!
Final Flag: nullctf{why_1s_it_r3srv3d_f3eaf9630244ed5f}