SQL Injection Detection with Bandit Rule B608: Beyond the Basics

SQL Injection Detection with Bandit Rule B608: Beyond the Basics

Your entire database can be wiped by a single malicious HTTP request. We’re often been so focused on scaling our Python application that we miss a classic SQL injection vulnerability in some user search endpoint. One '; DROP TABLE users; -- comment later, and three months of customer data vanishes into the digital ether.

That’s why Bandit’s B608 rule exists and why SQL injection remains one of the most dangerous and persistent vulnerabilities in web applications.

The Deceptive Simplicity of SQL Injection

SQL injection looks so innocent in code reviews. You see a line that builds a database query by combining strings, and it seems like straightforward programming. What could go wrong?

# Vulnerable - B608 flags this pattern
user_id = request.GET['id']
query = "SELECT * FROM users WHERE id = '%s'" % user_id
cursor.execute(query)

# Secure alternative - parameterized query
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))

Everything, as it turns out. SQL injection isn’t really about databases, it’s about the fundamental disconnect between data and code. When you concatenate user input directly into SQL queries, you’re giving users the ability to modify the logic of your database operations.

How Attackers Exploit String-Built Queries

Consider a simple user lookup function that searches for customers by name. During normal operation, someone searches for “Smith” and gets back a list of customers with that last name.

But attackers don’t provide normal input. They understand that your string concatenation is creating executable SQL code, so they craft input designed to change the meaning of your query. Instead of searching for a name, they might provide input that closes the original query, adds their own SQL commands, and comments out the rest of your intended logic.

The database doesn’t know that part of the query came from user input, it just sees SQL commands and executes them. If your application connects to the database with administrative privileges, those malicious commands run with full access to your data.

Why ORMs Aren’t Magic Bullets

Many developers think that using an Object-Relational Mapping (ORM) library automatically protects them from SQL injection. While ORMs do provide better security defaults, they don’t eliminate the vulnerability entirely.

The problem occurs when developers need functionality that their ORM doesn’t support cleanly, so they drop down to raw SQL queries using the ORM’s raw() method or execute() function with string-formatted queries. Suddenly, all the security benefits of the ORM disappear.

Beyond Parameterized Queries

Everyone knows that parameterized queries prevent SQL injection, but understanding why they work helps you apply the principle correctly. Parameterized queries separate SQL logic from data by sending them to the database through different channels.

# Safe parameterized queries
cursor.execute("SELECT * FROM users WHERE age > %s AND city = %s", 
               (min_age, city_name))

# Safe with SQLAlchemy ORM
users = session.query(User).filter(
    User.age > min_age,
    User.city == city_name
)

This approach makes it impossible for user input to change the meaning of your query because the SQL structure is already locked in before any user data gets processed.

The Broader Security Picture

SQL injection is often the gateway to much larger security breaches. Once attackers can execute arbitrary SQL commands, they can often read configuration files, access other databases, or even execute operating system commands through database features.

This is why Bandit treats SQL injection as a medium to high severity issue.

Learning from Bandit Findings

When Bandit’s B608 rule flags potential SQL injection, don’t just fix the immediate issue. Use it as an opportunity to review your application’s data access patterns. Are you consistently using parameterized queries? Do you have a clear policy for when raw SQL is acceptable?

The goal is to to build applications that are fundamentally resistant to injection attacks. Every B608 finding is a chance to strengthen not just that specific code, but your team’s understanding of secure database interaction.

Subscribe to the Newsletter

Get the latest posts and insights delivered straight to your inbox.