
PyAthena의 DefaultParameterFormatter를 통한 SQL 인젝션 (CVE-2026-65321)
심각도: Critical, CVSS v4.0 9.3 / CVSS v3.1 9.8 (CNA인 VulnCheck이 배정)
벡터 (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 (SQL 명령에서 사용되는 특수 요소의 부적절한 중화, 'SQL 인젝션')
신고자: Rahul Karne
CNA: VulnCheck
공개일: 2026년 8월 3일
PyAthena는 SELECT 쿼리에서는 신뢰할 수 없는 입력을 올바르게 이스케이프했지만
DELETE 쿼리에서는 잘못 이스케이프했습니다.
Amazon Athena용으로 널리 사용되는 Python DB-API 클라이언트인 PyAthena는 명령문의 시작 키워드에 따라 문자열 이스케이프 루틴을 선택합니다. SELECT, WITH, INSERT, UPDATE, MERGE로 시작하는 명령문은 작은따옴표를 두 번 사용하여 중화하는('') Trino에 올바른 이스케이프를 적용받습니다. 그 외의 모든 명령문, 가장 일반적으로는 DELETE 또는 CREATE TABLE … AS SELECT(CTAS)가 Hive 스타일의 백슬래시 이스케이프(\')를 사용합니다. Athena의 엔진은 Trino이며, Trino는 작은따옴표 문자열 내부의 백슬래시를 일반 문자로 취급하므로 백슬래시 이스케이프는 아무것도 중화하지 못합니다. 이러한 명령문의 문자열 파라미터에 영향을 미칠 수 있는 공격자는 리터럴을 종료하고 임의의 SQL을 주입할 수 있으며, 인증이나 사용자 상호 작용이 필요 없습니다.
따라서 이 결함은 읽기 경로에는 존재하지 않으며, 가장 큰 피해를 줄 수 있는 파괴적인 명령문 유형에 정확히 존재합니다. 이 패키지는 한 달에 2,230만 회 다운로드됩니다.
PyAthena는 Amazon Athena용 서드파티 커뮤니티 클라이언트 라이브러리입니다. AWS 제품이 아니며, 이는 AWS 또는 Athena 자체의 취약점이 아닙니다.
취약한 명령문에 전달되는 문자열 파라미터를 제어하는 공격자는 의도된 문자열 리터럴을 벗어나 명령문의 로직을 변경할 수 있습니다. 가장 직접적이고 확실하게 입증 가능한 영향은 무단 데이터 삭제입니다: DELETE … WHERE token = %(token)s 쿼리에서 missing' OR 1=1 -- 같은 페이로드는 WHERE 조건을 무력화하고 Athena 워크그룹의 IAM 역할이 삭제할 수 있는 모든 행을 삭제합니다(예: Iceberg 테이블의 모든 행). 명령문 유형과 역할의 권한에 따라 공격자는 CTAS 주입을 통해 공격자가 정의한 테이블을 생성할 수도 있으며, 이후 결과 테이블을 읽을 수 있는 경우 역할이 접근할 수 있는 다른 테이블의 데이터를 유출할 수도 있습니다.
모든 영향은 클라이언트가 사용하는 Athena 워크그룹 / IAM 역할의 권한에 의해 제한됩니다. 이는 Athena의 SQL 엔진에 대한 데이터 플레인 주입이며, PyAthena를 실행하는 호스트에서의 코드 실행이나 AWS 자체의 손상을 가져오지 않습니다.
영향을 받는 대상: 기본 DefaultParameterFormatter(클라이언트 측 pyformat / named 파라미터 치환)를 사용하는 PyAthena < 3.35.4 애플리케이션 중 (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는 pyathena.formatter에서 _escape_hive와 _escape_presto를 직접 임포트 | connections_legacy.py#L23-L27 |
DefaultParameterFormatter.format()는 명령문의 시작 키워드만으로 문자열 이스케이프 함수를 선택합니다. 허용 목록(allowlist)에 있는 접두사만 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}'"
Amazon Athena의 SQL 엔진은 Trino입니다 (이전 엔진 버전에서는 Presto). Trino에서 작은따옴표 문자열 리터럴 내부의 작은따옴표를 이스케이프하는 유일한 방법은 두 번 사용하는 것('')이며, 백슬래시는 리터럴 문자입니다. 따라서 _escape_hive는 Athena에서 따옴표를 전혀 중화하지 못하며, ... = 'missing\' OR 1=1 -- '을 생성합니다. Trino는 이를 문자열 리터럴 'missing\' 뒤에 OR 1=1 -- '가 이어지는 것으로 파싱합니다. 즉, 공격자가 제어하는 SQL입니다.
이 설계는 실패 시 위험(fail-dangerous)합니다: 안전한 경로를 허용 목록으로 지정하고 그 외의 모든 것은 안전하지 않은 이스케이퍼를 기본값으로 사용합니다. 업스트림 수정은 이를 실패 시 안전(fail-safe) 방식으로 반전시킵니다 (Trino 이스케이퍼를 기본값으로 사용하고 CREATE DATABASE/DROP TABLE/MSCK REPAIR와 같은 진정한 Hive DDL에만 Hive 이스케이프를 사용하며, CTAS와 CREATE VIEW는 Trino로 취급). 또한 앞부분의 SQL 주석을 제거하여 /* … */ DELETE … 접두사가 명령문 유형 감지를 우회하지 못하도록 합니다.
_escape_hive는 검증(sanitization)이 누락된 것이 아니라, 그 자체가 검증입니다. Hive의 문자열 리터럴 문법에 대해 올바르고 잘 구성된 이스케이프 루틴이지만, Trino 문법을 사용하는 엔진에 적용되었습니다. 오염 추적(taint-tracking) 도구는 SQL 인젝션을 이스케이퍼를 통과하지 않고 싱크(sink)에 도달하는 신뢰할 수 없는 데이터로 모델링합니다. 여기서 데이터는 모든 경로에서 이스케이퍼를 통과하며, 그 이스케이퍼는 잘못된 방언(dialect)에 대한 수정 코드이기 때문에 정확히 수정 코드처럼 보입니다.
방언의 정확성은 오염(taint) 속성이 아니므로 어떤 오염 규칙도 이를 평가하지 않습니다. 이 결함은 CodeQL, Semgrep, Snyk, Socket이 단순히 간과한 것이 아니라 구조적으로 보이지 않으며, 이것이 초당 약 9회 설치되는 패키지에서 오랫동안 지속된 이유입니다.
공격자에게 필요한 조건:
pyformat / named paramstyle)를 사용하는 PyAthena < 3.35.4 대상 애플리케이션.SELECT/WITH/INSERT/UPDATE/MERGE로 시작하지 않는 명령문, 실제로는 DELETE 또는 CTAS를 구성하는 코드 경로.숫자 파라미터와 안전한 이스케이퍼로 라우팅되는 모든 명령문은 이 결함으로 악용할 수 없습니다.
PoC는 실제의 수정되지 않은 PyAthena 포맷터(재구성이 아닌 공개된 PyPI 릴리스에서 임포트)를 호출하고 그 출력을 로컬 인메모리 DuckDB 데이터베이스에서 실행합니다. AWS 계정, 자격 증명, 네트워크 접근이 필요 없습니다. 전체 재현은 두 개의 명령으로 충분합니다:
pip install pyathena==3.35.2 duckdb
python poc_pyathena_cna_demo.py --no-pause
소스: poc_pyathena_cna_demo.py
녹화 데모: 데모 보기
DefaultParameterFormatter의 docstring은 SQL 인젝션을 방지하기 위해 파라미터를 이스케이프한다고 명시합니다. PoC는 해당 docstring을 출력한 다음, inspect.getsource를 통해 설치된 패키지에서 직접 _escape_presto, _escape_hive 및 접두사 선택 분기를 출력합니다. 따라서 독자는 권고(advisory)의 말을 믿는 대신 라이브러리 자체 소스의 모순을 직접 확인할 수 있습니다.
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은 원래 명령문이 전혀 참조하지 않았던 테이블에서 행을 복사합니다. 데모에서는 admin_credentials의 DEMO_SECRET_VALUE가 공격자에게 보이는 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는 0개의 행을 반환하고 UPDATE는 0개의 행을 변경하며, 탈출이 발생하지 않습니다. 이 대조 실험은 테스트 하네스가 정상이며 결함이 테스트 설정이 아닌 이스케이퍼 선택에 국한된다는 것을 입증합니다.
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 -- '
모든 경우에 페이로드는 데이터로 포함되며 인젝션이 발생하지 않습니다.