Lambda SQL Injection via PyMySQL cursor.execute()

CRITICAL

Lambda event data flows to PyMySQL cursor.execute() without parameterization, enabling SQL injection against RDS MySQL or Aurora MySQL backends via the pure-Python driver.

Rule Information

Language
Python
Category
AWS Lambda
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythonawslambdasql-injectionpymysqlmysqlrdsaurorataint-analysisinter-proceduralCWE-89OWASP-A03
CWE References

Interactive Playground

Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.

pathfinder scan --ruleset python/PYTHON-LAMBDA-SEC-013 --project .
1
2
3
4
5
6
7
8
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

About This Rule

Understanding the vulnerability and how it is detected

This rule detects SQL injection vulnerabilities in AWS Lambda functions where untrusted event data flows into PyMySQL cursor.execute() calls without proper parameterization, enabling SQL injection against RDS MySQL, Aurora MySQL, or any MySQL-compatible backend accessed via the pure-Python PyMySQL driver.

PyMySQL is the most commonly used MySQL driver in AWS Lambda functions because it is a pure-Python library that requires no compiled extensions, making it easy to package as a Lambda deployment artifact or Lambda Layer. Lambda functions triggered by API Gateway, SQS, SNS, S3, and other sources receive attacker-controllable event data (event.get("body"), event.get("queryStringParameters"), event["Records"]).

When this event data is concatenated or f-stringed into the SQL string before being passed to cursor.execute(), the PyMySQL driver receives a pre-built SQL string with the injected payload already embedded. PyMySQL's own escaping (connection.escape()) is only applied when values are passed through the parameters mechanism. Embedding values in the SQL string bypasses all driver-level protection.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Full Database Exfiltration

An attacker who controls any SQL fragment can use UNION SELECT to read from any table accessible to the Lambda's MySQL user. PyMySQL provides no protection when event data is embedded in the SQL string, making all accessible tables vulnerable to exfiltration in a single injected query.

2

Authentication Bypass in Serverless Authentication Flows

Lambda functions often implement stateless authentication by querying user records via PyMySQL. Injecting ' OR '1'='1'-- into username or password fields causes the query to return all rows, bypassing authentication for any user account in the table.

3

Data Modification via Stacked Queries

PyMySQL can execute multiple statements when execute_multiple is enabled. An attacker can inject additional INSERT, UPDATE, or DELETE statements after the original query to modify application data, create backdoor accounts, or corrupt records. Even without stacked queries, subquery injection can achieve data modification in some contexts.

4

Lambda Warm Instance Exploitation

PyMySQL connections cached across warm Lambda invocations mean that a successful injection in one invocation can observe the results of data modifications made in subsequent invocations using the same connection, enabling multi-step attacks across what appear to be independent Lambda executions.

How to Fix

Recommended remediation steps

  • 1Always pass Lambda event data as the second argument to cursor.execute() as a tuple, never by concatenating it into the SQL string.
  • 2Use PyMySQL's DictCursor for named column access rather than index-based access, which reduces the risk of using wrong column values in query construction.
  • 3Grant the Lambda's MySQL user the minimum necessary privileges (SELECT on specific tables) and avoid GRANT ALL or SUPER privileges.
  • 4Cache the PyMySQL connection at module level and use connection.ping(reconnect=True) to reuse it across warm invocations without re-authenticating.
  • 5Enable RDS MySQL general query logging selectively or use Performance Schema to audit queries and detect injection patterns.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule performs inter-procedural taint analysis with global scope. Sources are Lambda event dictionary access calls: calls("event.get"), calls("event.__getitem__"), including event.get("body"), event.get("queryStringParameters"), event.get("pathParameters"), and event["Records"]. The sink is calls("*.execute") matching PyMySQL cursor objects with the tainted SQL string tracked via .tracks(0) (the query string argument). Sanitizers include explicit int() or float() type conversion and connection.escape() when correctly applied. The analysis follows taint through string concatenation, f-string interpolation, variable assignments, and function boundaries.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

CWE Top 25
CWE-89 ranked #3 in 2023 Most Dangerous Software Weaknesses
OWASP Top 10
A03:2021 - Injection
PCI DSS v4.0
Requirement 6.2.4 - protect against injection attacks
NIST SP 800-53
SI-10: Information Input Validation
AWS Security Best Practices
Validate all inputs; apply least-privilege database permissions

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Lambda SQL Injection via PyMySQL cursor.execute()

PyMySQL is a pure-Python implementation of the MySQL protocol, requiring no compiled C extensions. This makes it ideal for Lambda deployments because it packages cleanly as a zip archive or Lambda Layer without platform-specific binary dependencies. The injection risk is identical to other MySQL drivers: when event data is embedded in the SQL string rather than passed as parameters, PyMySQL receives a pre-built injected query and executes it as-is.
connection.escape() applies MySQL escaping to a value, but using it correctly requires escaping every single piece of user data, applying it at the right point in string construction, and never missing any input. The parameterization mechanism (second argument to execute()) handles this automatically and correctly for all values. Parameterization is always preferred over manual escaping.
PyMySQL does not implement true prepared statements (compile-once, execute-many server-side prepared statements). Instead, it uses client-side escaping when values are passed as the second argument to cursor.execute(). This is still safe against injection because the escaping is applied by the driver before the SQL is sent to MySQL. For batch operations, use cursor.executemany() with a SQL template and a list of value tuples.
Initialize the connection at module level (outside lambda_handler) and reuse it across warm invocations using connection.ping(reconnect=True). Lambda functions share no state between concurrent invocations (each invocation runs in its own execution environment), so connection limits are a function of concurrent Lambda instances, not request throughput. Use RDS Proxy to pool connections and prevent exhausting MySQL's max_connections limit at high concurrency.
For dynamic IN clauses, build the placeholder string programmatically and pass the values as a flat tuple: placeholders = ','.join(['%s'] * len(ids_list)), then cursor.execute(f"SELECT * FROM t WHERE id IN ({placeholders})", tuple(ids_list)). Validate that ids_list contains only integers before use. Never format the actual values into the SQL string, only the placeholder count.

New feature

Get these findings posted directly on your GitHub pull requests

The Lambda SQL Injection via PyMySQL cursor.execute() rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works