4 min read

[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

  1. 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.
  1. 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.

Step 2: Bypass Authentication

  1. Email Spoofing:

  2. Receive the Password:

    • The server responds with the password S3cr3tOSTCLub!!.
  3. Login to /secure-channel:

    • Use the password to gain access.

Step 3: Exploit JSON Injection

  1. 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>"}
      
  1. 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.
  2. 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.

Step 4: Analyze the Download Service

  1. Attempt File Download:

    • The admin panel provides a file download service but filters filenames containing:
      • flag (case insensitive).
      • Illegal characters like ...
  2. 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., ) can bypass this filter.
      • Absolute paths override restrictions.

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

  1. Use Ligatures:

    • Use the ligature (Unicode U+FB02), which converts to FL in uppercase.
    • Input the filename flag.txt, which becomes FLAG.TXT and bypasses the filter.
  2. 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
      
  3. Retrieve the Flag:

    • The application processes the input, reads the file, and returns the contents of the flag file.