[CTF] WU - Work Emails !
CTF InterCampus Ynov 2024
Difficulty Level : Hard
Challenge Category : Web
Description :
I like secret clubs! they are the place where I can meet with my nerd friends 0x0 I have made this little website as our secret cloud, where me and my friends can download some secret files with some specific secret ways! don't forget to use your work email for communication..
Solution Steps
Step 1: Initial Reconnaissance
- Check
/robots.txt
or/sitemap.xml
:- By visiting
/sitemap.xml
, we discover 50 pages. - Using brute-forcing techniques with tools like
Burp Suite
or Python, locate a valid page at/secure-channel
.
- By visiting

- Access
/secure-channel
:- This page requires a password, and a note indicates that employees should send an email to
[email protected]
using their work email. - The password is sent back to the user’s personal email.
- This page requires a password, and a note indicates that employees should send an email to

Step 2: Bypass Authentication
-
Email Spoofing:
- Use an email spoofing tool like https://emkei.cz/ to send an email to
[email protected]
. - Use the following format:
- Subject: GID
- Body: Your personal email address (e.g., [email protected]).
- Use an email spoofing tool like https://emkei.cz/ to send an email to
-
Receive the Password:
- The server responds with the password
S3cr3tOSTCLub!!
.
- The server responds with the password
-
Login to
/secure-channel
:- Use the password to gain access.

Step 3: Exploit JSON Injection
- Error Analysis:
- Upon accessing
/secure-channel
, an error message hints at JSON-based logic:Only {"accountType":"admin"} allowed here, {request.args.get('username')}!
- The application uses a
profile
cookie containing JSON data:{"accountType": "user", "username": "<username>"}
- Upon accessing

-
Craft a JSON Injection Payload:
- Modify the
profile
cookie to:{"accountType": "admin", "username": "admin"}
- Encode the JSON into Base64 and set it as the
profile
cookie.
- Modify the
-
Execute the Payload:
- Use browser DevTools to modify the
profile
cookie or intercept the request with Burp Suite. - Reload the
/secure-channel
page to access admin privileges.
- Use browser DevTools to modify the
Step 4: Analyze the Download Service
-
Attempt File Download:
- The admin panel provides a file download service but filters filenames containing:
flag
(case insensitive).- Illegal characters like
..
.
- The admin panel provides a file download service but filters filenames containing:
-
Source Code Review:
- By downloading
app.py
, we analyze the logic:.txt
files are forced to uppercase.- The keyword
flag
is forbidden, but ligatures (e.g.,fl
) can bypass this filter. - Absolute paths override restrictions.
- By downloading

app.py code :
from flask import Flask, request, jsonify, make_response, render_template, session, send_file, redirect, send_from_directory
import os
import json
import base64
# import sys
from pathlib import Path
import subprocess
app = Flask(__name__)
app.secret_key = os.urandom(16)
def is_admin(data):
return data and data.get("accountType") == "admin"
def read_file(filename: str):
if not filename:
return 0, "Please enter a file name."
if 'flag' in filename.lower() or 'emails.py' in filename.lower() or 'emails.log' in filename.lower():
return 0, "Error! file can't be read."
if '..' in filename:
return 0, "Error! Illegal characters!"
directory, name = os.path.split(filename)
if name.lower().endswith('.txt'):
filename = directory + "/" + name.upper()
file_path = os.path.join(os.getcwd(), filename)
if not Path(file_path).is_file():
return 0, "Error! file can't be found."
print(file_path)
return 1, file_path
def encode_base64(data: str):
if data:
return base64.b64encode(data.encode('utf-8')).decode('utf-8')
def decode_base64(data: str):
if data:
return base64.b64decode(data.encode('utf-8')).decode('utf-8')
def is_logged_in(session):
return session.get('logged_in') == True
def login_page():
return render_template('login.html')
@app.route('/')
def index():
return render_template('index.html')
@app.route('/secure-channel', methods=['GET', 'POST'])
def secure_channel():
if request.method == 'POST':
if request.form.get('password') == os.getenv('SECURE_CHANNEL_PASS'):
session['logged_in'] = True
if not is_logged_in(session):
return login_page()
username = request.args.get('username')
profile = request.cookies.get("profile")
if profile:
profile = json.loads(decode_base64(profile))
if not profile or (username and profile and username != profile.get('username')):
json_data = f'{{"accountType": "user", "username": "{username}"}}'
resp = make_response(redirect('/secure-channel'))
resp.set_cookie('profile', encode_base64(json_data))
return resp
try:
if is_admin(profile):
username = profile.get('username')
if request.method == "GET":
return render_template('secure-channel.html', user=username)
elif request.method == "POST":
filename = request.form.get('file')
file_content = read_file(filename)
if file_content[0] == 0:
return render_template('secure-channel.html', user=username, error=file_content[1])
else:
return send_file(file_content[1], as_attachment=True)
else:
return render_template('error.html', error="Only {\"accountType\":\"admin\"} allowed here, {request.args.get('username')}!")
except Exception as e:
# exc_type, exc_obj, exc_tb = sys.exc_info()
# fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
# print(exc_type, fname, exc_tb.tb_lineno)
# return f"Error decoding JSON! Error Code: 0x1337, {str(e)}"
return "Error decoding JSON! Error Code: 0x1337"
@app.route('/sitemap.xml')
def sitemap():
return send_from_directory('static', 'sitemap.xml')
if __name__ == '__main__':
subprocess.Popen(["python", os.path.dirname(os.path.abspath(__file__)) + "/emails.py"])
app.run(host="0.0.0.0", port=80, debug=False)
Step 5: Bypass Filters and Retrieve the Flag
-
Use Ligatures:
- Use the ligature
fl
(Unicode U+FB02), which converts toFL
in uppercase. - Input the filename
flag.txt
, which becomesFLAG.TXT
and bypasses the filter.
- Use the ligature
-
Provide Absolute Path:
- Construct the absolute path
/home/access/secrets/flag.txt
. - Submit the following in the file input box:
/home/access/secrets/flag.txt
- Construct the absolute path
-
Retrieve the Flag:
- The application processes the input, reads the file, and returns the contents of the flag file.