SQL injection from Flask request data in manual SQL construction

Critical Risk SQL Injection
pythonflasksql-injectiondatabaseweb

What it is

SQL injection vulnerability in Flask applications where user-controlled request data is concatenated or formatted into SQL strings without parameters, allowing attackers to inject arbitrary SQL commands.

from flask import Flask, request, jsonify
import sqlite3

app = Flask(__name__)

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']

    # VULNERABLE: Direct string concatenation
    query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"

    conn = sqlite3.connect('app.db')
    cursor = conn.cursor()
    cursor.execute(query)
    user = cursor.fetchone()
    conn.close()

    if user:
        return jsonify({'status': 'success', 'user_id': user[0]})
    return jsonify({'status': 'failed'})

@app.route('/products')
def get_products():
    category = request.args.get('category', '')
    min_price = request.args.get('min_price', '0')

    # VULNERABLE: String formatting
    query = ("SELECT * FROM products WHERE category = '{}' "
             "AND price >= {}").format(category, min_price)

    conn = sqlite3.connect('app.db')
    cursor = conn.cursor()
    cursor.execute(query)
    products = cursor.fetchall()
    conn.close()

    return jsonify(products)

# Malicious inputs:
# username: admin' OR '1'='1' --
# category: electronics'; DROP TABLE products; --
from flask import Flask, request, jsonify
import sqlite3

app = Flask(__name__)

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']

    # SECURE: Parameterized query with ? placeholders
    query = "SELECT * FROM users WHERE username = ? AND password = ?"

    conn = sqlite3.connect('app.db')
    cursor = conn.cursor()
    cursor.execute(query, (username, password))
    user = cursor.fetchone()
    conn.close()

    if user:
        return jsonify({'status': 'success', 'user_id': user[0]})
    return jsonify({'status': 'failed'})

@app.route('/products')
def get_products():
    category = request.args.get('category', '')
    min_price = request.args.get('min_price', '0')

    # SECURE: Parameterized query with ? placeholders
    query = "SELECT * FROM products WHERE category = ? AND price >= ?"

    conn = sqlite3.connect('app.db')
    cursor = conn.cursor()
    cursor.execute(query, (category, min_price))
    products = cursor.fetchall()
    conn.close()

    return jsonify(products)

💡 Why This Fix Works

The vulnerable code uses string concatenation and formatting to build SQL queries with user input. The fixed version uses parameterized queries with ? placeholders and implements proper input validation.

Why it happens

User-controlled Flask request data is concatenated or formatted into SQL strings without parameters, allowing attackers to inject arbitrary SQL.

Root causes

Request Data Concatenation in SQL

User-controlled Flask request data is concatenated or formatted into SQL strings without parameters, allowing attackers to inject arbitrary SQL.

Preview example – PYTHON
# VULNERABLE: Direct request data concatenation
@app.route('/user/<user_id>')
def get_user(user_id):
    query = f"SELECT * FROM users WHERE id = {user_id}"
    cursor.execute(query)
    return cursor.fetchone()

Form Data in SQL String Construction

Using Flask request.form or request.args data directly in SQL string construction without proper parameterization or validation.

Preview example – PYTHON
# VULNERABLE: Form data in SQL construction
@app.route('/search', methods=['POST'])
def search_products():
    search_term = request.form['search']
    category = request.form['category']
    query = ("SELECT * FROM products WHERE name LIKE '%" +
             search_term + "%' AND category = '" + category + "'")
    return execute_query(query)

Fixes

1

Use Parameterized Queries with DB-API

Replace string concatenation with parameterized queries. With DB-API, use cursor.execute('SELECT ... WHERE id = %s', (user_id,)). Do not interpolate request data.

View implementation – PYTHON
# SECURE: Parameterized query
@app.route('/user/<user_id>')
def get_user(user_id):
    cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
    return cursor.fetchone()
2

Use SQLAlchemy with Bound Parameters

With SQLAlchemy, use text() with bound parameters or the ORM query builder to safely handle user input.

View implementation – PYTHON
# SECURE: SQLAlchemy with bound parameters
from sqlalchemy import text

@app.route('/search', methods=['POST'])
def search_products():
    search_term = request.form['search']
    category = request.form['category']

    query = text("SELECT * FROM products WHERE name LIKE :search AND category = :category")
    result = db.session.execute(query, {
        'search': f'%{search_term}%',
        'category': category
    })
    return result.fetchall()

Detect This Vulnerability in Your Code

Sourcery automatically identifies sql injection from flask request data in manual sql construction and many other security issues in your codebase.