
Injection SQL dans PyAthena via DefaultParameterFormatter (CVE-2026-65321)
Sévérité : Critique, CVSS v4.0 9.3 / CVSS v3.1 9.8 (attribué par VulnCheck, le CNA)
Vecteur (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
Vecteur (v3.1) : CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Affecté : PyAthena <= 3.35.3 (toutes les versions jusqu'à la 3.35.3 incluse)
Corrigé dans : 3.35.4
CWE : CWE-89 (Mauvaise neutralisation des éléments spéciaux utilisés dans une commande SQL, « Injection SQL »)
Signalé par : Rahul Karne
CNA : VulnCheck
Publié : 3 août 2026
PyAthena échappait correctement les entrées non fiables dans les requêtes SELECT et incorrectement dans les requêtes DELETE.
PyAthena, le client Python DB-API largement utilisé pour Amazon Athena, sélectionne sa routine d'échappement de chaînes en fonction du mot-clé d'ouverture de l'instruction. Les instructions commençant par SELECT, WITH, INSERT, UPDATE ou MERGE reçoivent un échappement correct pour Trino, dans lequel un guillemet simple est neutralisé en le doublant (''). Toute autre instruction, le plus souvent un DELETE ou un CREATE TABLE … AS SELECT (CTAS), retombe sur l'échappement par antislash de style Hive (\'). Le moteur d'Athena est Trino, qui traite un antislash à l'intérieur d'une chaîne entre guillemets simples comme un caractère ordinaire, de sorte que l'échappement par antislash ne neutralise rien. Un attaquant qui peut influencer un paramètre de chaîne dans une telle instruction peut terminer le littéral et injecter du SQL arbitraire, sans authentification et sans interaction de l'utilisateur.
La faille est donc absente du chemin de lecture et présente précisément dans les types d'instructions destructives où elle cause le plus de dégâts. Le paquet est téléchargé 22,3 millions de fois par mois.
PyAthena est une bibliothèque cliente communautaire tierce pour Amazon Athena. Ce n'est pas un produit AWS, et il ne s'agit pas d'une vulnérabilité dans AWS ni dans Athena lui-même.
Un attaquant qui contrôle un paramètre de chaîne transmis à une instruction vulnérable peut sortir du littéral de chaîne prévu et modifier la logique de l'instruction. L'impact le plus direct et le plus facilement démontrable est la suppression non autorisée de données : une charge utile telle que missing' OR 1=1 -- dans une requête DELETE … WHERE token = %(token)s neutralise le prédicat WHERE et supprime toutes les lignes que le rôle IAM du workgroup Athena est autorisé à supprimer (par exemple, toutes les lignes d'une table Iceberg). Selon le type d'instruction et les permissions du rôle, un attaquant peut également être en mesure de créer des tables définies par l'attaquant via l'injection CTAS et, lorsqu'il peut ensuite lire la table résultante, d'exfiltrer des données d'autres tables auxquelles le rôle a accès.
Tout l'impact est délimité par les permissions du workgroup Athena / du rôle IAM qu'utilise le client. Il s'agit d'une injection dans le plan de données du moteur SQL d'Athena ; elle ne permet pas l'exécution de code sur l'hôte exécutant PyAthena, ni le compromis d'AWS lui-même.
Qui est concerné : Les applications utilisant PyAthena < 3.35.4 avec le DefaultParameterFormatter par défaut (substitution de paramètres pyformat / named côté client) qui (1) construisent une instruction ne commençant pas par SELECT/WITH/INSERT/UPDATE/MERGE, en pratique DELETE, CTAS, CREATE VIEW, DROP ou ALTER, et (2) transmettent des données influencées par l'attaquant comme paramètre de chaîne à cette instruction.
Qui n'est pas concerné :
3.35.4 ou une version ultérieure.SELECT/WITH/INSERT/UPDATE/MERGE ; celles-ci passent par l'échappeur sûr à doublement de guillemets.DefaultParameterFormatter.format() sélectionne la fonction d'échappement de chaînes uniquement à partir du mot-clé d'ouverture de l'instruction. Seule une liste blanche de préfixes reçoit l'échappeur correct pour Trino ; toute autre instruction retombe sur l'échappement par antislash de style Hive.
# 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}'"
Le moteur SQL d'Amazon Athena est Trino (Presto dans les versions antérieures du moteur). Dans Trino, le seul échappement pour un guillemet simple à l'intérieur d'un littéral de chaîne entre guillemets simples est de le doubler ('') ; un antislash est un caractère littéral. _escape_hive ne neutralise donc pas du tout un guillemet pour Athena ; il émet ... = 'missing\' OR 1=1 -- ', que Trino analyse comme le littéral de chaîne 'missing\' suivi de OR 1=1 -- ', c'est-à-dire du SQL contrôlé par l'attaquant.
La conception est dangereuse en cas de défaillance (fail-dangerous) : elle met le chemin sûr en liste blanche et fait retomber tout le reste par défaut sur l'échappeur non sûr. Le correctif en amont inverse cette logique pour être sûr par défaut (fail-safe) (échappeur Trino par défaut ; échappement Hive uniquement pour le DDL Hive authentique tel que CREATE DATABASE/DROP TABLE/MSCK REPAIR, tout en traitant CTAS et CREATE VIEW comme du Trino), et supprime en outre les commentaires SQL de tête afin qu'un préfixe /* … */ DELETE … ne puisse pas déjouer la détection du type d'instruction.
_escape_hive n'est pas une sanitisation manquante, c'est de la sanitisation. C'est une routine d'échappement correcte et bien formée pour la grammaire des littéraux de chaîne de Hive, appliquée à un moteur qui utilise celle de Trino. Les outils d'analyse par suivi de flux (taint tracking) modélisent l'injection SQL comme des données non fiables atteignant un puits sans passer par un échappeur ; ici, les données passent par un échappeur sur tous les chemins, et l'échappeur ressemble exactement à du code de remédiation parce que c'en est un, mais pour le mauvais dialecte.
La correction du dialecte n'est pas une propriété de flux (taint), donc aucune règle de taint ne l'évalue. Le défaut est structurellement invisible pour CodeQL, Semgrep, Snyk et Socket, et non simplement négligé par eux, ce qui explique pourquoi il a persisté dans un paquet installé environ neuf fois par seconde.
Un attaquant a besoin de :
< 3.35.4 avec le formateur de paramètres côté client par défaut (paramstyle pyformat / named).SELECT/WITH/INSERT/UPDATE/MERGE, en pratique DELETE ou CTAS.Les paramètres numériques, ainsi que toute instruction routée vers l'échappeur sûr, ne sont pas exploitables via cette faille.
La PoC appelle le véritable formateur PyAthena non modifié (importé de la version publiée sur PyPI, pas une reconstruction) et exécute sa sortie contre une base de données DuckDB locale en mémoire. Aucun compte AWS, aucun identifiant, aucun accès réseau. La reproduction complète se fait en deux commandes :
pip install pyathena==3.35.2 duckdb
python poc_pyathena_cna_demo.py --no-pause
Source : poc_pyathena_cna_demo.py
Démo enregistrée : Regarder la démo
La docstring de DefaultParameterFormatter déclare qu'il échappe les paramètres afin de prévenir l'injection SQL. La PoC affiche cette docstring, puis affiche _escape_presto, _escape_hive et la branche de sélection de préfixe directement depuis le paquet installé via inspect.getsource, afin que le lecteur constate la contradiction dans le code source de la bibliothèque lui-même plutôt que de croire l'avis sur parole.
DELETEParamètre contrôlé par l'attaquant : missing' OR 1=1 --
DELETE FROM sessions WHERE token = 'missing\' OR 1=1 -- '
Le lexer de Trino ne reconnaît qu'un seul échappement pour un guillemet simple à l'intérieur d'un littéral de chaîne entre guillemets simples : le doublement (''). Un antislash ne porte aucune signification d'échappement. Le littéral se termine donc au guillemet qui suit missing\, et OR 1=1 -- est analysé comme du SQL. DuckDB partage cette propriété, et contre une table sessions préalablement remplie de deux lignes, le résultat est :
Rows before executing generated SQL: 2
Rows after executing generated SQL: 0
Le prédicat WHERE est neutralisé et chaque ligne est supprimée.
Paramètre contrôlé par l'attaquant : nobody' UNION SELECT secret FROM admin_credentials --
CREATE TABLE leaked AS SELECT name FROM users WHERE name = 'nobody\' UNION SELECT secret FROM admin_credentials -- '
Le UNION injecté copie une ligne depuis une table que l'instruction d'origine ne référençait jamais. Dans la démo, DEMO_SECRET_VALUE de admin_credentials atterrit dans la table leaked visible par l'attaquant. Sur Athena, cela est délimité par ce que le rôle IAM du workgroup peut lire.
Les mêmes charges utiles routées via SELECT et UPDATE atteignent _escape_presto et sont correctement neutralisées par le doublement des guillemets :
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
Les deux charges utiles restent à l'intérieur du littéral de chaîne. SELECT renvoie zéro ligne et UPDATE ne modifie aucune ligne, sans évasion. Ces témoins établissent que le harnais de test est fiable et que le défaut est spécifique à la sélection de l'échappeur, et non à la configuration du test.
Les mêmes trois charges utiles contre la 3.35.4 produisent toutes une sortie à guillemets doublés :
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 -- '
Le correctif survit également à un préfixe de commentaire en tête, qui sinon déjouerait la détection du type d'instruction :
/* hi */ DELETE FROM sessions WHERE token = 'missing'' OR 1=1 -- '
Dans chaque cas, la charge utile est contenue comme des données et aucune injection ne se produit.
Mettez à niveau vers PyAthena 3.35.4 ou une version ultérieure :
pip install --upgrade "pyathena>=3.35.4"
Si vous ne pouvez pas mettre à niveau immédiatement : évitez de transmettre des données non fiables comme paramètres à toute instruction qui ne commence pas par SELECT/WITH/INSERT/UPDATE/MERGE. Pour les instructions destructives, validez / mettez en liste blanche les entrées côté serveur ou effectuez l'opération via un chemin qui ne dépend pas du formateur côté client. Il n'existe aucun indicateur de configuration qui modifie la sélection de l'échappeur dans les versions affectées ; la mise à niveau est le correctif fiable.
Note pour les projets qui importent directement les échappeurs. Le correctif 3.35.4 modifie la sélection de l'échappeur à l'intérieur de DefaultParameterFormatter.format(). Il ne modifie pas _escape_hive lui-même, qui reste correct pour Hive et incorrect pour Trino par conception. Tout projet en aval qui importe _escape_hive ou _escape_presto depuis pyathena.formatter et effectue sa propre répartition par type d'instruction n'est donc pas corrigé par la mise à niveau de PyAthena, et doit auditer sa propre logique de répartition face à la même question de dialecte.
Comment vérifier si vous êtes concerné :
pip show pyathena # check the installed version
pip-audit # flags CVE-2026-65321 once it propagates to the advisory feeds
VulnCheck (le CNA) a publié deux scores, tous deux Critiques : 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) et CVSS v3.1 = 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H).
L'attaque est distante, sans authentification et sans interaction de l'utilisateur (AV:N/PR:N/UI:N) et ne nécessite aucune condition préalable particulière côté attaquant (AC:L/AT:N). L'impact sur le système vulnérable est élevé en confidentialité, intégrité et disponibilité (VC:H/VI:H/VA:H en v4.0 ; C:H/I:H/A:H en v3.1) : un DELETE injecté peut détruire des données, et un CTAS/UNION SELECT injecté peut lire et copier des données auxquelles le rôle du workgroup a accès. Dans le vecteur v4.0, les métriques des systèmes subséquents sont toutes None (SC:N/SI:N/SA:N) ; la faille est confinée à la frontière SQL et d'autorisation d'Athena et ne permet pas l'exécution de code sur l'hôte ni un pivot vers AWS lui-même. Ce triplet SC/SI/SA:N est précisément la raison pour laquelle la v4.0 atterrit à 9.3 plutôt qu'à un 10.0 maximal, et c'est la réponse honnête à la question « est-ce que cela signifie un compromis complet du système ? » : non. Le score v3.1 atteint 9.8 parce que son indicateur binaire de portée (S:U/S:C) réduit à un seul bit ce que la v4.0 répartit entre trois métriques distinctes des systèmes subséquents ; les deux scores sont cohérents, pas contradictoires.
Une réserve qu'il convient de mentionner de manière proactive : l'exploitabilité dans le monde réel exige que l'application consommatrice achemine une entrée non fiable vers une instruction paramétrée non-SELECT (DELETE/CTAS/DROP/ALTER), et le rayon d'explosion concret est délimité par les permissions IAM du workgroup Athena. Le score de base modélise le pire cas raisonnable ; un déploiement à moindre privilège est affecté de manière moins sévère.
Découverte et signalée par Rahul Karne, chercheur en sécurité et membre senior de l'IEEE. Ses recherches portent sur les défauts d'injection et de gestion des entrées dans les paquets open source à forte dépendance ; ses divulgations précédentes incluent des CVE dans confluent-kafka (1,12 milliard de téléchargements), datamodel-code-generator (185 millions) et le plugin WordPress ElementsKit Elementor Addons (plus d'un million d'installations actives).
Contact : [email protected] · GitHub : rahulreddykarne
Demandes des médias : [email protected]. Un enregistrement de démonstration haute résolution, la PoC et des détails techniques supplémentaires sont disponibles sur demande.
| Métrique | Valeur | Source |
|---|
| Téléchargements cumulés | 740.6M | pepy.tech/projects/pyathena |
| Téléchargements, 30 derniers jours | 22.3M | pepy.tech |
| Téléchargements, 24 dernières heures | 221.0K | pepy.tech |
| Taux d'installation continu | 8.95/second | pepy.tech |
| Projets en aval notables | dbt-athena importe _escape_hive et _escape_presto directement depuis pyathena.formatter | connections_legacy.py#L23-L27 |
| Date | Événement |
|---|
| 19 juillet 2026 | Vulnérabilité identifiée |
| 20 juillet 2026 | Signalée au mainteneur |
| 20 juillet 2026 | Accusé de réception du mainteneur |
| 31 juillet 2026 | Correctif validé |
| 31 juillet 2026 | Version corrigée 3.35.4 publiée |
| 2 août 2026 | CVE-2026-65321 attribué par VulnCheck |
| 3 août 2026 | Divulgation publique |