
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, — они направляются в безопасный экранировщик с удвоением кавычек.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, а не просто упущен ими, — именно поэтому он сохранялся в пакете, который устанавливается примерно девять раз в секунду.
Атакующему необходимо:
< 3.35.4 с клиентским форматтером параметров по умолчанию (paramstyle pyformat / named).SELECT/WITH/INSERT/UPDATE/MERGE, — на практике это DELETE или CTAS.Числовые параметры и любые операторы, направляемые в безопасный экранировщик, через эту уязвимость не эксплуатируются.
PoC вызывает реальный, неизменённый форматтер PyAthena (импортированный из опубликованного PyPI-релиза, а не воссозданный) и выполняет его выходные данные в локальной in-memory базе данных DuckDB. Не нужны ни аккаунт AWS, ни учётные данные, ни доступ к сети. Полное воспроизведение — две команды:
pip install pyathena==3.35.2 duckdb
python poc_pyathena_cna_demo.py --no-pause
Исходный код: poc_pyathena_cna_demo.py
Записанная демонстрация: Смотреть демо
В docstring DefaultParameterFormatter утверждается, что он экранирует параметры для предотвращения SQL-инъекций. PoC выводит этот docstring, а затем через inspect.getsource выводит _escape_presto, _escape_hive и ветку выбора префикса прямо из установленного пакета, чтобы читатель увидел противоречие в собственном исходном коде библиотеки, а не верил уведомлению на слово.
DELETEПараметр, контролируемый атакующим: missing' OR 1=1 --
DELETE FROM sessions WHERE token = 'missing\' OR 1=1 -- '
Лексер Trino распознаёт ровно один способ экранирования одинарной кавычки внутри строкового литерала в одинарных кавычках: удвоение (''). Обратный слеш не несёт экранирующего смысла. Поэтому литерал завершается на кавычке после missing\, а OR 1=1 -- разбирается как SQL. DuckDB обладает тем же свойством, и для предзаполненной таблицы sessions из двух строк результат таков:
Rows before executing generated SQL: 2
Rows after executing generated SQL: 0
Предикат WHERE нейтрализован, и удаляются все строки.
Параметр, контролируемый атакующим: nobody' UNION SELECT secret FROM admin_credentials --
CREATE TABLE leaked AS SELECT name FROM users WHERE name = 'nobody\' UNION SELECT secret FROM admin_credentials -- '
Внедрённый UNION копирует строку из таблицы, на которую исходный оператор никогда не ссылался. В демо DEMO_SECRET_VALUE из admin_credentials попадает в видимую атакующему таблицу leaked. В Athena это ограничено тем, что может читать роль IAM рабочей группы.
Те же нагрузки, направленные через SELECT и UPDATE, попадают в _escape_presto и корректно нейтрализуются удвоением кавычек:
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
Обе нагрузки остаются внутри строкового литерала. SELECT возвращает ноль строк, UPDATE изменяет ноль строк — выход не происходит. Эти контрольные проверки показывают, что стенд исправен, а дефект связан именно с выбором экранировщика, а не с настройкой теста.
Те же три нагрузки против версии 3.35.4 дают результат с удвоенными кавычками:
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 -- '
Исправление также переживает префикс с ведущим комментарием, который иначе обошёл бы определение типа оператора:
/* hi */ DELETE FROM sessions WHERE token = 'missing'' OR 1=1 -- '
Во всех случаях нагрузка остаётся данными, и инъекция не происходит.
Обновитесь до PyAthena 3.35.4 или новее:
pip install --upgrade "pyathena>=3.35.4"
Если немедленное обновление невозможно: не передавайте недоверенные данные в качестве параметров в операторы, которые не начинаются с SELECT/WITH/INSERT/UPDATE/MERGE. Для деструктивных операторов выполняйте валидацию/проверку по белому списку на стороне сервера или выполняйте операцию через путь, который не полагается на клиентский форматтер. В затронутых версиях нет флага конфигурации, изменяющего выбор экранировщика; надёжное исправление — обновление.
Примечание для проектов, которые импортируют экранировщики напрямую. Исправление 3.35.4 меняет выбор экранировщика внутри DefaultParameterFormatter.format(). Оно не меняет сам _escape_hive, который по замыслу остаётся корректным для Hive и некорректным для Trino. Поэтому любой зависимый проект, который импортирует _escape_hive или _escape_presto из pyathena.formatter и выполняет собственную диспетчеризацию по типу оператора, не устраняет уязвимость обновлением PyAthena и должен проверить собственную логику диспетчеризации на предмет того же вопроса о диалекте.
Как проверить, затронуты ли вы:
pip show pyathena # check the installed version
pip-audit # flags CVE-2026-65321 once it propagates to the advisory feeds
VulnCheck (CNA) опубликовал две оценки, обе — 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) и CVSS v3.1 = 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H).
Атака удалённая, не требует аутентификации и взаимодействия с пользователем (AV:N/PR:N/UI:N) и не требует особых предварительных условий на стороне атакующего (AC:L/AT:N). Воздействие на уязвимую систему высоко по всем трём аспектам — конфиденциальности, целостности и доступности (VC:H/VI:H/VA:H в v4.0; C:H/I:H/A:H в v3.1): внедрённый DELETE может уничтожить данные, а внедрённые CTAS/UNION SELECT могут прочитать и скопировать данные, доступные роли рабочей группы. В векторе v4.0 все метрики последующей системы равны None (SC:N/SI:N/SA:N): уязвимость ограничена границами SQL и авторизации Athena и не приводит к выполнению кода на хосте и к переходу внутрь самой AWS. Именно эта тройка SC/SI/SA:N — причина того, что v4.0 даёт 9.3, а не максимальные 10.0; это честный ответ на вопрос «означает ли это полную компрометацию системы?» — нет. Оценка v3.1 достигает 9.8 потому, что её бинарный флаг области действия (S:U/S:C) схлопывает в один бит то, что v4.0 разносит по трём отдельным метрикам последующей системы; две оценки согласованы, а не противоречат друг другу.
Одна важная оговорка, которую стоит сделать заранее: реальная эксплуатация требует, чтобы потребляющее приложение направляло недоверенный ввод в параметризованный оператор, отличный от SELECT (DELETE/CTAS/DROP/ALTER), а конкретный радиус поражения ограничен правами IAM рабочей группы Athena. Базовая оценка моделирует разумный наихудший случай; развёртывание с минимальными привилегиями страдает менее серьёзно.
Обнаружил и сообщил Rahul Karne — исследователь безопасности, старший член IEEE (IEEE Senior Member). Его исследования сосредоточены на уязвимостях инъекций и обработки входных данных в open-source-пакетах с высокой зависимостью; среди предыдущих раскрытий — CVE в confluent-kafka (1,12 млрд загрузок), datamodel-code-generator (185 млн) и WordPress-плагине ElementsKit Elementor Addons (более 1 млн активных установок).
Контакты: [email protected] · GitHub: rahulreddykarne
Запросы СМИ: [email protected]. Запись демо в высоком разрешении, PoC и дополнительные технические детали предоставляются по запросу.
| Метрика | Значение | Источник |
|---|
| Загрузок за всё время | 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 |
| Дата | Событие |
|---|
| 19 июля 2026 | Уязвимость выявлена |
| 20 июля 2026 | Сообщено мейнтейнеру |
| 20 июля 2026 | Мейнтейнер подтвердил |
| 31 июля 2026 | Исправление закоммичено |
| 31 июля 2026 | Выпущена исправленная версия 3.35.4 |
| 2 августа 2026 | CVE-2026-65321 присвоен VulnCheck |
| 3 августа 2026 | Публичное раскрытие |