Skip to main content

What is SQL Injection?

A SQL injection attack consists of inserting or “injecting” SQL code via the input data from the client to the application. A successful SQL injection exploit can read sensitive data from the database and modify database data (Insert/Update/Delete). SQL injection attacks are a type of injection attack in which SQL commands are injected into data-plane input to affect the execution of predefined SQL commands.
SQL injection is one of the most critical web application vulnerabilities. It can lead to complete database compromise, data theft, and unauthorized access.

Vulnerable Code in Normo Unsecure PWA

The application contains three SQL injection vulnerabilities in user_management.py:

Vulnerability 1: Username Check (Line 20)

user_management.py
cur.execute(f"SELECT * FROM users WHERE username = '{username}'")
This line uses string formatting to construct the SQL query, allowing attackers to inject malicious SQL code through the username parameter.

Vulnerability 2: Password Check (Line 25)

user_management.py
cur.execute(f"SELECT * FROM users WHERE password = '{password}'")
Similar to the username check, the password parameter is directly interpolated into the SQL query without sanitization.

Vulnerability 3: Feedback Insertion (Line 45)

user_management.py
cur.execute(f"INSERT INTO feedback (feedback) VALUES ('{feedback}')")
User-submitted feedback is directly inserted into the SQL query, creating another injection point.

How to Test for SQL Injection

1

Test with OR 1=1

In the login form, try entering these values:
  • Username: 105 OR 1=1
  • Password: 105 OR 1=1
This attempts to make the SQL query always evaluate to true:
SELECT * FROM users WHERE username = '105' OR 1=1 AND password = '105' OR 1=1;
2

Test with Quote Injection

Try these SQL injection patterns:
  • " OR ""="
  • ' OR '1'='1
You may need to try different combinations of ' and " to ensure the backend SQL query constructs are syntactically correct.
3

Test for Destructive Operations

Only test in controlled environments!
Try injection that could drop tables:
  • 105; DROP TABLE users
4

Look for Runtime Errors

Runtime errors are a sign that the vulnerability can be exploited. If you see SQL syntax errors in the application response, the input is being processed unsafely.

Exploitation Examples

Input:
  • Username: admin' --
  • Password: anything
Resulting Query:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything'
The -- comments out the rest of the query, bypassing the password check entirely.
Input:
  • Username: ' OR '1'='1
  • Password: ' OR '1'='1
Resulting Query:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '' OR '1'='1'
This returns all users in the database since the condition is always true.
Input in feedback form:
test'); DROP TABLE users; --
Resulting Query:
INSERT INTO feedback (feedback) VALUES ('test'); DROP TABLE users; --')
This attempts to delete the entire users table after inserting the feedback.

How to Fix SQL Injection

The key to preventing SQL injection is to never concatenate user input directly into SQL queries. Always use parameterized queries or prepared statements.

Secure Implementation

Here’s how to fix each vulnerable function:
def retrieveUsers(username, password):
    con = sql.connect("database_files/database.db")
    cur = con.cursor()
    
    # Use parameterized queries with ? placeholders
    cur.execute("SELECT * FROM users WHERE username = ?", (username,))
    if cur.fetchone() == None:
        con.close()
        return False
    else:
        cur.execute("SELECT * FROM users WHERE password = ?", (password,))
        if cur.fetchone() == None:
            con.close()
            return False
        else:
            con.close()
            return True

Countermeasures

1

Use Parameterized Queries

Never construct queries with concatenation or string formatting:
# WRONG
cur.execute(f"SELECT * FROM users WHERE username = '{username}'")

# CORRECT
cur.execute('SELECT * FROM users WHERE username = ?', (username,))
2

Implement Defensive Data Handling

Validate and sanitize all user input before processing:
  • Whitelist allowed characters
  • Enforce length limits
  • Validate data types
3

Require Authentication

Require authentication before accepting any form of input that interacts with the database.
4

Use a Secure API Layer

Implement an API with built-in security as the interface to the SQL database. Modern frameworks like Flask with SQLAlchemy provide secure database abstractions.
5

Regular Code Reviews

Conduct regular code reviews specifically looking for SQL injection vulnerabilities. Search for:
  • f-strings with database queries
  • String concatenation in SQL statements
  • Direct user input in queries
6

Additional Security Measures

  • Update backend languages (some PHP versions are particularly vulnerable)
  • Salt database table names with random strings
  • Implement principle of least privilege for database users
  • Use Web Application Firewalls (WAF) as an additional layer

References

W3Schools SQL Injection Examples

Comprehensive examples of SQL injection attacks with explanations

File Locations

user_management.py
file
Line 20: Vulnerable username check with f-string interpolationLine 25: Vulnerable password check with f-string interpolationLine 45: Vulnerable feedback insertion with f-string interpolationLines 9-11: Secure example using parameterized queries (insertUser function)

Build docs developers (and LLMs) love