SQL Injection in Flask Applications
Flask's lightweight nature means database access is entirely developer-controlled. Whether using raw SQLite, psycopg2, or SQLAlchemy, Flask apps are vulnerable to SQL injection when queries are built with string formatting instead of parameterized statements.
Scan Your Flask AppHow SQL Injection Manifests in Flask
Flask apps often use SQLAlchemy, but developers bypass its ORM with text() and execute() for complex queries. String formatting in these raw queries is the primary SQL injection vector. Direct database access with sqlite3, psycopg2, or mysql-connector using f-strings or .format() is common in Flask tutorials, leading developers to adopt insecure patterns from the start. Flask-SQLAlchemy's session.execute() with text() is particularly dangerous when combined with string formatting, as developers assume the ORM provides protection even for raw queries.
Real-World Impact
A Flask REST API for a mobile app used f-strings to build SQLAlchemy text() queries for a product search feature. An attacker used error-based SQL injection to extract the database structure, then performed a UNION-based attack to read user credentials. The API had no rate limiting, allowing the attacker to automate the extraction of the entire database.
Step-by-Step Fix
Use SQLAlchemy ORM queries
The ORM automatically parameterizes all queries.
# UNSAFE - raw SQL with f-string
results = db.session.execute(
text(f"SELECT * FROM users WHERE name = '{name}'")
)
# SAFE - SQLAlchemy ORM
results = User.query.filter_by(name=name).all()
# SAFE - SQLAlchemy ORM with complex filters
results = User.query.filter(
User.name.ilike(f'%{search}%')
).all()Parameterize text() queries
When raw SQL is necessary, use bound parameters with text().
from sqlalchemy import text
# UNSAFE
result = db.session.execute(
text(f"SELECT * FROM products WHERE price < {max_price}")
)
# SAFE - named parameters
result = db.session.execute(
text("SELECT * FROM products WHERE price < :max_price"),
{"max_price": max_price}
)Parameterize direct database access
Use ? or %s placeholders with parameter tuples for direct database connections.
import sqlite3
# UNSAFE
conn = sqlite3.connect('app.db')
cursor = conn.execute(f"SELECT * FROM users WHERE id = {user_id}")
# SAFE
cursor = conn.execute("SELECT * FROM users WHERE id = ?", (user_id,))Validate dynamic identifiers
Allowlist column and table names that cannot be parameterized.
ALLOWED_COLUMNS = {'name', 'email', 'created_at'}
@app.route('/api/users')
def list_users():
sort_by = request.args.get('sort', 'created_at')
if sort_by not in ALLOWED_COLUMNS:
sort_by = 'created_at'
users = User.query.order_by(getattr(User, sort_by)).all()
return jsonify([u.to_dict() for u in users])Prevention Best Practices
1. Use SQLAlchemy ORM methods which are automatically parameterized. 2. When using text(), always use :param syntax with bound parameters. 3. Never use f-strings, .format(), or % formatting in SQL queries. 4. Use Flask-SQLAlchemy for proper integration and session management. 5. Apply principle of least privilege to database user accounts.
How to Test
1. Search for f-strings and .format() in SQL queries: grep -rn "text(f\"\|execute(f\"\|.format(" --include="*.py" 2. Test endpoints with ' OR 1=1 -- payloads. 3. Check if error messages expose SQL details (disable debug mode in production). 4. Use sqlmap to automate SQL injection testing. 5. Use Vibe App Scanner to detect SQL injection in your Flask application.
Frequently Asked Questions
Does SQLAlchemy prevent SQL injection?
SQLAlchemy ORM methods (query.filter(), session.add()) automatically parameterize queries and are safe. However, text() with string formatting, literal_column(), and raw execute() calls bypass this protection. Always use bound parameters with raw SQL.
Is Flask debug mode a SQL injection risk?
Flask debug mode itself does not cause SQL injection, but it exposes detailed error messages including SQL queries and stack traces. This information helps attackers craft SQL injection payloads. Always disable debug mode in production (app.debug = False).
Should I use an ORM or raw SQL in Flask?
Use an ORM (SQLAlchemy) as your default. It prevents SQL injection automatically and provides a cleaner API. Only use raw SQL for truly complex queries that the ORM cannot express, and always use parameterized queries when you do.
Related Security Resources
Is Your Flask App Vulnerable to SQL Injection?
VAS automatically scans for sql injection vulnerabilities in Flask applications and provides step-by-step remediation guidance with code examples.
Scans from $5, results in minutes. Get actionable fixes tailored to your Flask stack.