@app.route('/login', methods=['POST'])deflogin_user(): username =DBClean(request.form['username']) password =DBClean(request.form['password']) conn =get_db() c = conn.cursor() sql =f"SELECT * FROM users WHERE username='{username}' AND password='{password}'" c.executescript(sql) user = c.fetchone()if user: c.execute(f"SELECT sessionid FROM activesessions WHERE username=?", (username,)) active_session = c.fetchone()if active_session: session_id = active_session[0]else: c.execute(f"SELECT username FROM users WHERE username=?", (username,)) user_name = c.fetchone()if user_name: session_id =str(uuid.uuid4()) c.executescript(f"INSERT INTO activesessions (sessionid, timestamp) VALUES ('{session_id}', '{datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}')")else:flash("A session could be not be created")returnlogout() session['username']= username session['session_id']= session_id conn.commit()returnredirect(url_for('files'))else:flash('Username or password is incorrect')returnredirect(url_for('home'))
1. Vulnerability arises from the use of string concatenation to construct the SQL query in the login_user() function. line 9
2. executescript() and execute() methods are both used in Python's sqlite3 to execute SQL statements against an SQLite database. line 10
executescript():
Used to execute multiple SQL statements or a complete SQL script at once.
It allows the execution of multiple SQL statements separated by semicolons (;) or newlines (\n) in a single call.
The executescript() method can handle DDL (Data Definition Language) statements like CREATE TABLE or ALTER TABLE, as well as DML (Data Manipulation Language) statements like INSERT, UPDATE, or DELETE.
It returns no result set.
execute():
Used to execute a single SQL statement.
It is suitable for executing individual SQL statements or parameterized queries.
The SQL statement can be a DDL or DML statement, depending on the desired operation.
It can return result sets for queries, allowing you to fetch the retrieved data using methods like fetchone(), fetchall(), or fetchmany().
Main difference:
executescript() allow multiple SQL statements in one input string
The DBCleanfunction do wrong filter by removing ',"and Space.
then replacing backslashes to ' .
After setup local webserver to debug app, flask app will create database on tmp:
DATABASE ='/tmp/database.db'
Table creation statements define the structure for users, active sessions, and files in the database:
app.py line 165-167
c.execute("CREATE TABLE IF NOT EXISTS users (username text, password text)")c.execute("CREATE TABLE IF NOT EXISTS activesessions (sessionid text, username text, timestamp text)")c.execute("CREATE TABLE IF NOT EXISTS files (filename text PRIMARY KEY, data blob, sessionid text)")
To verify the success of our initial injection payload, we can target the /login endpoint with username POST parameter.
Used to deserialize a serialized object back into a python object, untrusted pickle data can execute arbitrary code, leading to security vulnerabilities.
Code execution
@app.route('/download/<filename>/<sessionid>', methods=['GET'])defdownload_file(filename,sessionid): conn =get_db() c = conn.cursor()(*) c.execute(f"SELECT * FROM activesessions WHERE sessionid=?", (sessionid,)) active_session = c.fetchone()if active_session isNone:flash('No active session found')returnredirect(url_for('home'))(*) c.execute(f"SELECT data FROM files WHERE filename=?",(filename,)) file_data = c.fetchone()if file_data isNone:flash('File not found')returnredirect(url_for('files')) file_blob = pickle.loads(base64.b64decode(file_data[0]))returnsend_file(io.BytesIO(file_blob), download_name=filename, as_attachment=True)
We need to create two payloads using our SQL injection👏
Our controlled file_data[0](base64 pickle payload) will be loaded bypickle.loads().
After injecting data into the tables, the last step consists of sending a GET request to the /download/<filename>/<sessionid> endpoint, resulting code execution. 👏