Injection Flaws in Flask
How Injection Flaws Manifests in Flask
Injection flaws in Flask applications typically occur when untrusted data is sent to an interpreter as part of a command or query. Flask's flexibility and Python's dynamic nature create several injection vectors that developers must guard against.
SQL Injection via Raw Queries
The most common injection flaw in Flask apps involves SQL injection through database queries. When developers use string formatting or concatenation to build SQL queries, attackers can manipulate input to execute arbitrary SQL:
@app.route('/search')
def search():
query = request.args.get('q')
# VULNERABLE: Direct string interpolation
results = db.execute(f"SELECT * FROM users WHERE name LIKE '%{query}%'" ).fetchall()
return render_template('results.html', results=results)An attacker could submit q as %' OR '1'='1 to return all users, or %' UNION SELECT password FROM users-- to extract sensitive data.
Command Injection via Shell Operations
Flask applications that execute shell commands with user input are vulnerable to command injection. This often occurs when processing file uploads, system information, or external integrations:
@app.route('/convert')
def convert():
filename = request.args.get('file')
# VULNERABLE: Shell injection possible
os.system(f'convert {filename} output.png')
return 'Conversion complete'An attacker could submit file as image.png; rm -rf / or image.png && cat /etc/passwd to execute arbitrary commands.
Template Injection via Jinja2
Flask uses Jinja2 templating, which can be vulnerable to Server-Side Template Injection (SSTI) when user input is rendered unsafely:
@app.route('/greet')
def greet():
name = request.args.get('name')
# VULNERABLE: SSTI if name contains template code
return render_template('greet.html', name=name)If name contains {{ 7 * 7 }}, the template will render 49. More sophisticated payloads can lead to remote code execution.
LDAP Injection
Applications using LDAP for authentication or directory services can suffer LDAP injection:
@app.route('/ldap_search')
def ldap_search():
username = request.args.get('username')
# VULNERABLE: LDAP injection possible
filter = f"(&(objectClass=person)(uid={username}))"
results = ldap.search_s(base_dn, ldap.SCOPE_SUBTREE, filter)
return str(results)An attacker could submit username as *)(uid=admin)(* to bypass authentication or extract sensitive directory information.
Flask-Specific Detection
Detecting injection flaws in Flask requires both static analysis and dynamic testing. Here's how to identify these vulnerabilities in your Flask applications.
Static Code Analysis
Review your Flask codebase for dangerous patterns. Use tools like bandit or custom scripts to find vulnerable code:
import ast
import os
def find_vulnerable_patterns(filepath):
with open(filepath) as f:
tree = ast.parse(f.read(), filename=filepath)
vulnerabilities = []
for node in ast.walk(tree):
# SQL injection patterns
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Mod):
if "execute" in ast.dump(node):
vulnerabilities.append((node.lineno, "SQL injection via string formatting"))
# Command injection patterns
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
if node.func.id in ['system', 'popen', 'call']:
vulnerabilities.append((node.lineno, "Command injection via shell execution"))
return vulnerabilities
# Scan all Python files in Flask app
for root, dirs, files in os.walk('.'):
for file in files:
if file.endswith('.py'):
results = find_vulnerable_patterns(os.path.join(root, file))
for lineno, issue in results:
print(f"{file}:{lineno} - {issue}")Dynamic Testing with middleBrick
middleBrick provides automated black-box scanning that can detect injection flaws without requiring source code access or credentials:
# Scan your Flask API endpoints
middlebrick scan https://your-flask-app.com/api
# Scan with specific focus on injection vulnerabilities
middlebrick scan --focus injection https://your-flask-app.com
# Integrate into your development workflow
middlebrick scan --ci --fail-below B https://staging.your-flask-app.commiddleBrick tests for SQL injection, command injection, template injection, and other injection vectors by sending crafted payloads to your API endpoints and analyzing responses for vulnerability indicators.
Manual Testing Techniques
Complement automated scanning with manual testing:
- SQL injection: Try
' OR '1'='1,'; DROP TABLE users; --, and numeric payloads - Command injection: Try
|| ls,&& whoami, and semicolon-separated commands - Template injection: Try
{{7*7}},{{config}}, and Jinja2 filters - LDAP injection: Try asterisk patterns and parenthesis manipulation
Monitor application responses for error messages, unexpected behavior, or execution delays that indicate successful exploitation.
Flask-Specific Remediation
Fixing injection flaws in Flask requires using safe coding practices and Flask's built-in security features. Here are specific remediation techniques for common injection vulnerabilities.
SQL Injection Prevention
Always use parameterized queries or ORM methods instead of string concatenation:
from flask import Flask, request
from sqlalchemy import create_engine, text
app = Flask(__name__)
engine = create_engine('postgresql://user:pass@localhost/db')
@app.route('/search')
def search():
query = request.args.get('q')
# SECURE: Parameterized query
results = engine.execute(
text("SELECT * FROM users WHERE name ILIKE :pattern"),
{'pattern': f'%{query}%'}
).fetchall()
return render_template('results.html', results=results)
# Using SQLAlchemy ORM (even more secure)
from sqlalchemy.orm import sessionmaker
from models import User
@app.route('/search_orm')
def search_orm():
query = request.args.get('q')
Session = sessionmaker(bind=engine)
session = Session()
# SECURE: ORM handles parameterization automatically
results = session.query(User).filter(User.name.ilike(f'%{query}%')).all()
return render_template('results.html', results=results)Command Injection Prevention
Never use shell=True or string interpolation for command execution. Use subprocess with argument lists:
import subprocess
from flask import Flask, request
app = Flask(__name__)
@app.route('/convert')
def convert():
filename = request.args.get('file')
# SECURE: subprocess with argument list, no shell=True
try:
result = subprocess.run(
['convert', filename, 'output.png'],
capture_output=True,
check=True
)
return 'Conversion complete'
except subprocess.CalledProcessError as e:
return f'Error: {e}', 400
# For file operations, validate filename strictly
import re
def is_valid_filename(name):
# Only allow alphanumeric, hyphens, underscores, and single dots
return bool(re.match(r'^[\-\w]+\.[a-zA-Z0-9]{1,4}$', name))
@app.route('/upload')
def upload():
filename = request.args.get('file')
if not is_valid_filename(filename):
return 'Invalid filename', 400
# Now it's safe to process the file
return process_file(filename)Template Injection Prevention
Escape user input in templates and validate template context:
from flask import Flask, request, escape
app = Flask(__name__)
@app.route('/greet')
def greet():
name = request.args.get('name')
# SECURE: Escape user input
safe_name = escape(name)
return render_template('greet.html', name=safe_name)
# For dynamic template rendering, use a whitelist
ALLOWED_TEMPLATES = {'greet', 'welcome', 'about'}
@app.route('/render')
def render():
template_name = request.args.get('template')
if template_name not in ALLOWED_TEMPLATES:
return 'Invalid template', 400
return render_template(template_name)LDAP Injection Prevention
Use parameterized LDAP queries and validate input:
import ldap3
from flask import Flask, request
app = Flask(__name__)
server = ldap3.Server('ldap://localhost')
connection = ldap3.Connection(server)
@app.route('/ldap_search')
def ldap_search():
username = request.args.get('username')
# Validate input - only allow alphanumeric and certain characters
if not re.match(r'^[a-zA-Z0-9_.@-]+$', username):
return 'Invalid username format', 400
# SECURE: Use safe search filters
base_dn = "ou=users,dc=example,dc=com"
search_filter = f"(uid={username})"
if connection.search(base_dn, search_filter, attributes=['cn', 'mail']):
return str(connection.entries)
else:
return 'User not found', 404Input Validation and Sanitization
Implement comprehensive input validation using Flask-WTF or similar libraries:
from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField
from wtforms.validators import DataRequired, Length, NumberRange
class SearchForm(FlaskForm):
query = StringField('Query', validators=[
DataRequired(),
Length(min=1, max=100),
# Custom validator for SQL injection patterns
Regexp(r'^[a-zA-Z0-9\s\-_\.]+$',
message="Invalid characters in search query")
])
page = IntegerField('Page', validators=[
NumberRange(min=1, max=100)
])
@app.route('/secure_search', methods=['GET', 'POST'])
def secure_search():
form = SearchForm(request.form)
if form.validate():
query = form.query.data
# Safe to use in database queries
results = search_database(query)
return render_template('results.html', results=results)
else:
return 'Invalid input', 400Frequently Asked Questions
How does Flask's automatic escaping help prevent injection attacks?
Flask automatically escapes variables in Jinja2 templates by default, converting special characters to HTML entities (e.g., < becomes <). This prevents XSS attacks when rendering user input. However, this escaping only applies to template rendering, not to SQL, command, or LDAP injection vulnerabilities. Developers must still use parameterized queries, avoid shell=True, and validate all input for non-template contexts.
Can middleBrick detect injection flaws in my Flask application without access to the source code?
Yes, middleBrick performs black-box scanning that tests your Flask API endpoints without requiring source code or credentials. It sends malicious payloads to detect SQL injection, command injection, template injection, and other injection flaws by analyzing responses for vulnerability indicators like error messages, unexpected behavior, or execution delays. The scan takes 5-15 seconds and provides a security risk score with prioritized findings and remediation guidance.