
SQL injection in PyAthena via DefaultParameterFormatter (CVE-2026-65321)
Severity: Critical, CVSS v4.0 9.3 / CVSS v3.1 9.8 (assigned by VulnCheck, the CNA)
Vector (v4.0): CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Vector (v3.1): CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Affected: PyAthena <= 3.35.3 (all versions through 3.35.3)
Fixed in: 3.35.4
CWE: CWE-89 (Improper Neutralization of Special Elements used in an SQL Command, 'SQL Injection')
Reported by: Rahul Karne
CNA: VulnCheck
Published: August 3, 2026
PyAthena escaped untrusted input correctly in SELECT queries and incorrectly
in queries.
DELETEPyAthena, the widely used Python DB-API client for Amazon Athena, selects its
string-escaping routine from the statement's leading keyword. Statements
beginning with SELECT, WITH, INSERT, UPDATE, or MERGE receive
Trino-correct escaping, in which a single quote is neutralized by doubling it
(''). Every other statement, most commonly a DELETE or a CREATE TABLE … AS SELECT (CTAS), falls through to Hive-style backslash escaping (\').
Athena's engine is Trino, which treats a backslash inside a single-quoted string
as an ordinary character, so backslash escaping neutralizes nothing. An attacker
who can influence a string parameter in such a statement can terminate the
literal and inject arbitrary SQL, with no authentication and no user
interaction.
The flaw is therefore absent from the read path and present in exactly the destructive statement types where it does the most damage. The package is pulled 22.3 million times a month.
PyAthena is a third-party community client library for Amazon Athena. It is not an AWS product, and this is not a vulnerability in AWS or in Athena itself.
An attacker who controls a string parameter passed to a vulnerable statement can
break out of the intended string literal and alter the statement's logic. The
most direct and reliably demonstrable impact is unauthorized data deletion:
a payload such as missing' OR 1=1 -- in a DELETE … WHERE token = %(token)s
query neutralizes the WHERE predicate and deletes every row the Athena
workgroup's IAM role is permitted to delete (for example, all rows of an Iceberg
table). Depending on the statement type and the role's permissions, an attacker
may also be able to create attacker-defined tables via CTAS injection and, where
they can subsequently read the resulting table, exfiltrate data from other
tables the role can access.
All impact is bounded by the permissions of the Athena workgroup / IAM role the client uses. This is a data-plane injection into Athena's SQL engine; it does not yield code execution on the host running PyAthena, nor compromise of AWS itself.
Who is affected: Applications using PyAthena < 3.35.4 with the default
DefaultParameterFormatter (client-side pyformat / named parameter
substitution) that (1) construct a statement not starting with
SELECT/WITH/INSERT/UPDATE/MERGE, in practice DELETE, CTAS,
CREATE VIEW, DROP, or ALTER, and (2) pass attacker-influenced data as a
string parameter to that statement.
Who is not affected:
3.35.4 or later.SELECT/WITH/INSERT/UPDATE/MERGE, these route to the safe
quote-doubling escaper.| Metric | Value | Source |
|---|---|---|
| Downloads, all-time | 740.6M | pepy.tech/projects/pyathena |
| Downloads, last 30 days | 22.3M | pepy.tech |
| Downloads, last 24 hours | 221.0K | pepy.tech |
| Sustained install rate | 8.95/second | pepy.tech |
| Notable downstream | dbt-athena imports _escape_hive and _escape_presto from pyathena.formatter directly | connections_legacy.py#L23-L27 |
DefaultParameterFormatter.format() selects the string-escaping function purely
from the statement's leading keyword. Only an allowlist of prefixes receives the
Trino-correct escaper; every other statement falls through to Hive-style
backslash escaping.
# src/pyathena/formatter.py, DefaultParameterFormatter.format(), lines ~271-275 (v3.35.2)
operation_upper = operation.upper()
if operation_upper.startswith(("SELECT", "WITH", "INSERT", "UPDATE", "MERGE")):
escaper = _escape_presto # safe: doubles single quotes
else:
escaper = _escape_hive # UNSAFE for Trino: backslash-escapes quotes
# src/pyathena/formatter.py, lines ~157-165 (v3.35.2)
def _escape_hive(val: str) -> str:
escaped = (
val.replace("\\", "\\\\")
.replace("'", "\\'") # produces \' (not a quote escape in Trino)
.replace("\r", "\\r")
.replace("\n", "\\n")
.replace("\t", "\\t")
)
return f"'{escaped}'"
Amazon Athena's SQL engine is Trino (Presto in earlier engine versions). In
Trino, the only escape for a single quote inside a single-quoted string literal
is to double it (''); a backslash is a literal character. _escape_hive
therefore does not neutralize a quote at all for Athena, it emits
... = 'missing\' OR 1=1 -- ', which Trino parses as the string literal
'missing\' followed by OR 1=1 -- ', i.e. attacker-controlled SQL.
The design is fail-dangerous: it allowlists the safe path and defaults
everything else to the unsafe escaper. The upstream fix inverts this to
fail-safe (default to the Trino escaper; use Hive escaping only for genuine Hive
DDL such as CREATE DATABASE/DROP TABLE/MSCK REPAIR, while treating CTAS and
CREATE VIEW as Trino), and additionally strips leading SQL comments so a
/* … */ DELETE … prefix cannot defeat statement-type detection.
_escape_hive is not missing sanitization, it is sanitization. It is a
correct, well-formed escaping routine for Hive's string-literal grammar, applied
to an engine that uses Trino's. Taint-tracking tools model SQL injection as
untrusted data reaching a sink without passing through an escaper; here the data
passes through an escaper on every path, and the escaper looks exactly like
remediation code because it is remediation code, for the wrong dialect.
Dialect correctness is not a taint property, so no taint rule evaluates it. The defect is structurally invisible to CodeQL, Semgrep, Snyk, and Socket rather than merely overlooked by them, which is why it persisted in a package installed roughly nine times per second.
An attacker needs:
< 3.35.4 with the default client-side
parameter formatter (pyformat / named paramstyle).SELECT/WITH/INSERT/UPDATE/MERGE, in practice DELETE or CTAS.Numeric parameters, and any statement routed to the safe escaper, are not exploitable via this flaw.
The PoC calls the real, unmodified PyAthena formatter (imported from the published PyPI release, not a reconstruction) and executes its output against a local in-memory DuckDB database. No AWS account, no credentials, no network access. Full reproduction is two commands:
pip install pyathena==3.35.2 duckdb
python poc_pyathena_cna_demo.py --no-pause
Source: poc_pyathena_cna_demo.py
Recorded demo: Watch the demo
DefaultParameterFormatter's docstring states that it escapes parameters to
prevent SQL injection. The PoC prints that docstring, then prints
_escape_presto, _escape_hive, and the prefix-selection branch directly from
the installed package via inspect.getsource, so the reader sees the
contradiction in the library's own source rather than taking the advisory's word
for it.
DELETE predicate breakoutAttacker-controlled parameter: missing' OR 1=1 --
DELETE FROM sessions WHERE token = 'missing\' OR 1=1 -- '
Trino's lexer recognizes exactly one escape for a single quote inside a
single-quoted string literal: doubling it (''). A backslash carries no escaping
meaning. The literal therefore terminates at the quote following missing\, and
OR 1=1 -- is parsed as SQL. DuckDB shares this property, and against a seeded
two-row sessions table the result is:
Rows before executing generated SQL: 2
Rows after executing generated SQL: 0
The WHERE predicate is neutralized and every row is deleted.
Attacker-controlled parameter: nobody' UNION SELECT secret FROM admin_credentials --
CREATE TABLE leaked AS SELECT name FROM users WHERE name = 'nobody\' UNION SELECT secret FROM admin_credentials -- '
The injected UNION copies a row out of a table the original statement never
referenced. In the demo, DEMO_SECRET_VALUE from admin_credentials lands in
the attacker-visible leaked table. On Athena this is bounded by what the
workgroup's IAM role can read.
The same payloads routed through SELECT and UPDATE reach _escape_presto
and are correctly neutralized by quote doubling:
SELECT name FROM users WHERE name = 'nobody'' UNION SELECT secret FROM admin_credentials -- '
UPDATE update_control SET token = 'x'' OR 1=1 -- ' WHERE id = 999
Both payloads stay inside the string literal. SELECT returns zero rows and
UPDATE changes zero rows, no breakout. These controls establish that the
harness is sound and that the defect is specific to escaper selection, not to
the test setup.
The same three payloads against 3.35.4 all produce doubled-quote output:
DELETE FROM sessions WHERE token = 'missing'' OR 1=1 -- '
CREATE TABLE leaked AS SELECT name FROM users WHERE name = 'nobody'' UNION SELECT secret FROM admin_credentials -- '
The fix also survives a leading-comment prefix, which would otherwise defeat statement-type detection:
/* hi */ DELETE FROM sessions WHERE token = 'missing'' OR 1=1 -- '
In every case the payload is contained as data and no injection occurs.
Upgrade to PyAthena 3.35.4 or later:
pip install --upgrade "pyathena>=3.35.4"
If you cannot upgrade immediately: avoid passing untrusted data as parameters
to any statement that does not begin with SELECT/WITH/INSERT/UPDATE/MERGE.
For destructive statements, validate/allowlist input server-side or perform the
operation through a path that does not rely on the client-side formatter. There
is no configuration flag that changes the escaper selection in affected versions;
upgrading is the reliable fix.
Note for projects that import the escapers directly. The 3.35.4 fix changes
escaper selection inside DefaultParameterFormatter.format(). It does not
change _escape_hive itself, which remains correct-for-Hive and wrong-for-Trino
by design. Any downstream project that imports _escape_hive or
_escape_presto from pyathena.formatter and performs its own statement-type
dispatch is therefore not remediated by upgrading PyAthena, and should audit
its own dispatch logic against the same dialect question.
How to check whether you are affected:
pip show pyathena # check the installed version
pip-audit # flags CVE-2026-65321 once it propagates to the advisory feeds
VulnCheck (the CNA) published two scores, both Critical: CVSS v4.0 = 9.3
(CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N) and
CVSS v3.1 = 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H).
The attack is remote and unauthenticated with no user interaction
(AV:N/PR:N/UI:N) and needs no special attacker-side preconditions
(AC:L/AT:N). Impact on the vulnerable system is high across confidentiality,
integrity, and availability (VC:H/VI:H/VA:H in v4.0; C:H/I:H/A:H in v3.1): an
injected DELETE can destroy data, and an injected CTAS/UNION SELECT can read
and copy data the workgroup's role can access. In the v4.0 vector the
subsequent-system metrics are all None (SC:N/SI:N/SA:N), the flaw is confined
to Athena's SQL and authorization boundary and does not yield code execution
on the host or a pivot into AWS itself. That SC/SI/SA:N triple is exactly why
v4.0 lands at 9.3 rather than a maxed-out 10.0, and it is the honest answer to
"does this mean full system compromise?", it does not. The v3.1 score reaches
9.8 because its binary scope flag (S:U/S:C) collapses into a single bit what
v4.0 splits across three separate subsequent-system metrics; the two scores are
consistent, not contradictory.
One caveat worth stating proactively: real-world exploitability requires the
consuming application to route untrusted input into a non-SELECT parameterized
statement (DELETE/CTAS/DROP/ALTER), and the concrete blast radius is
bounded by the Athena workgroup's IAM permissions. The base score models the
reasonable worst case; a least-privilege deployment is affected less severely.
| Date | Event |
|---|---|
| July 19, 2026 | Vulnerability identified |
| July 20, 2026 | Reported to maintainer |
| July 20, 2026 | Maintainer acknowledged |
| July 31, 2026 | Fix committed |
| July 31, 2026 | Patched version 3.35.4 released |
| August 2, 2026 | CVE-2026-65321 assigned by VulnCheck |
| August 3, 2026 | Public disclosure |
Discovered and reported by Rahul Karne, security researcher and IEEE Senior Member. His research focuses on injection and input-handling flaws in high-dependency open-source packages, prior disclosures include CVEs in confluent-kafka (1.12B downloads), datamodel-code-generator (185M), and the ElementsKit Elementor Addons WordPress plugin (1M+ active installations).
Contact: [email protected] · GitHub: rahulreddykarne
Media inquiries: [email protected]. High-resolution demo recording, PoC, and additional technical detail available on request.