
SQL-инъекция в PyAthena через DefaultParameterFormatter (CVE-2026-65321)
Критичность: Critical, CVSS v4.0 9.3 / CVSS v3.1 9.8 (присвоена VulnCheck, CNA)
Вектор (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
Вектор (v3.1): CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Затронуто: PyAthena <= 3.35.3 (все версии до 3.35.3 включительно)
Исправлено в: 3.35.4
CWE: CWE-89 (Improper Neutralization of Special Elements used in an SQL Command, 'SQL Injection')
Сообщил: Rahul Karne
CNA: VulnCheck
Опубликовано: 3 августа 2026
PyAthena корректно экранировал недоверенный ввод в SELECT-запросах и некорректно — в DELETE-запросах.
PyAthena — широко используемый Python DB-API клиент для Amazon Athena — выбирает процедуру экранирования строк на основе ведущего ключевого слова оператора. Операторы, начинающиеся с SELECT, WITH, INSERT, UPDATE или MERGE, получают корректное для Trino экранирование, при котором одинарная кавычка нейтрализуется удвоением (''). Все остальные операторы, чаще всего DELETE или CREATE TABLE … AS SELECT (CTAS), попадают в ветку Hive-стиля с экранированием обратным слешем (\'). Движок Athena — Trino, который трактует обратный слеш внутри строки в одинарных кавычках как обычный символ, поэтому экранирование обратным слешем ничего не нейтрализует. Атакующий, способный влиять на строковый параметр в таком операторе, может завершить литерал и внедрить произвольный SQL без аутентификации и без взаимодействия с пользователем.
Таким образом, уязвимость отсутствует в пути чтения данных и присутствует именно в тех деструктивных типах операторов, где она наносит наибольший ущерб. Пакет загружается 22,3 миллиона раз в месяц.
PyAthena — это сторонняя клиентская библиотека сообщества для Amazon Athena. Она не является продуктом AWS, и это не уязвимость в AWS или в самой Athena.
Атакующий, контролирующий строковый параметр, передаваемый в уязвимый оператор, может выйти за пределы предназначенного строкового литерала и изменить логику оператора. Наиболее прямое и надёжно демонстрируемое последствие — несанкционированное удаление данных: нагрузка вида missing' OR 1=1 -- в запросе DELETE … WHERE token = %(token)s нейтрализует предикат WHERE и удаляет все строки, которые роль IAM рабочей группы Athena имеет право удалять (например, все строки таблицы Iceberg). В зависимости от типа оператора и прав роли атакующий также может создавать определяемые атакующим таблицы через CTAS-инъекцию и, если впоследствии сможет читать результирующую таблицу, похищать данные из других таблиц, доступных роли.
Все последствия ограничены правами рабочей группы Athena / роли IAM, которую использует клиент. Это инъекция на уровне данных в SQL-движок Athena; она не приводит к выполнению кода на хосте, где запущен PyAthena, и не влечёт компрометации самой AWS.
Кто затронут: приложения, использующие PyAthena < 3.35.4 с форматтером DefaultParameterFormatter по умолчанию (подстановка параметров на стороне клиента в стиле pyformat / named), которые (1) формируют оператор, начинающийся не с SELECT/WITH/INSERT/UPDATE/MERGE, — на практике это DELETE, CTAS, CREATE VIEW, DROP или ALTER, — и (2) передают данные, на которые влияет атакующий, в этот оператор в качестве строкового параметра.
Кто не затронут:
3.35.4 или новее.SELECT/WITH/INSERT/UPDATE/MERGE, — они направляются в безопасный экранировщик с удвоением кавычек.| Метрика | Значение | Источник |
|---|---|---|
| Загрузок за всё время | 740,6M | pepy.tech/projects/pyathena |
| Загрузок за последние 30 дней | 22,3M | pepy.tech |
| Загрузок за последние 24 часа | 221,0K | pepy.tech |
| Устойчивая скорость установки | 8,95/сек | pepy.tech |
| Известный зависимый проект | dbt-athena напрямую импортирует _escape_hive и _escape_presto из pyathena.formatter | connections_legacy.py#L23-L27 |
DefaultParameterFormatter.format() выбирает функцию экранирования строк исключительно по ведущему ключевому слову оператора. Только список разрешённых префиксов получает корректный для Trino экранировщик; все остальные операторы попадают в ветку 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}'"
SQL-движок Amazon Athena — Trino (в более ранних версиях движка — Presto). В Trino единственный способ экранировать одинарную кавычку внутри строкового литерала в одинарных кавычках — удвоить её (''); обратный слеш — это обычный символ. Поэтому _escape_hive вообще не нейтрализует кавычку для Athena: он выдаёт ... = 'missing\' OR 1=1 -- ', а Trino разбирает это как строковый литерал 'missing\', за которым следует OR 1=1 -- ', то есть управляемый атакующим SQL.
Конструкция опасна при отказе (fail-dangerous): она вносит в белый список безопасный путь, а всё остальное по умолчанию отправляет в небезопасный экранировщик. Исправление в апстриме инвертирует это в отказобезопасный (fail-safe) вариант: по умолчанию используется экранировщик Trino, а Hive-экранирование применяется только для настоящего Hive DDL, такого как CREATE DATABASE/DROP TABLE/MSCK REPAIR, при этом CTAS и CREATE VIEW трактуются как Trino. Дополнительно удаляются ведущие SQL-комментарии, чтобы префикс вида /* … */ DELETE … не мог обойти определение типа оператора.
Дело не в том, что в _escape_hive отсутствует санитизация, — она и есть санитизация. Это корректная, хорошо оформленная процедура экранирования для грамматики строковых литералов Hive, применённая к движку, который использует грамматику Trino. Инструменты отслеживания потоков данных (taint-tracking) моделируют SQL-инъекцию как попадание недоверенных данных в сток (sink) без прохождения через экранировщик; здесь же данные проходят через экранировщик на каждом пути, и экранировщик выглядит в точности как код устранения уязвимости, потому что он и есть код устранения уязвимости — просто для неверного диалекта.
Корректность диалекта не является taint-свойством, поэтому ни одно taint-правило её не проверяет. Дефект структурно невидим для CodeQL, Semgrep, Snyk и Socket, а не просто упущен ими, — именно поэтому он сохранялся в пакете, который устанавливается примерно девять раз в секунду.
Атакующему необходимо: