flawopen.com/Incidents/postgresql-psql-injection-beyondtrust-treasury-december-2024
CVE-2025-1094: PostgreSQL psql SQL 인젝션
앱이 이스케이프된 텍스트로 psql 입력을 만들 때 잘못된 UTF-8이 libpq 이스케이프를 우회해 SQL 또는 \!을 삽입할 수 있었습니다. 2025년 2월 13일 수정됐습니다.
당신이 하는 모든 말을 따옴표로 감싸 명령으로 오해받지 않게 하는 것이 일인 통역사를 떠올려 보세요. 그런데 통역사가 모르는 문자로 된 단어를 건네면 헷갈려서 따옴표 하나를 빠뜨린다는 사실을 알게 됩니다. 이제 당신의 말은 따옴표 밖으로 빠져나와 지시로 읽힙니다. 통역사는 누군가 시험해 본 모든 입력에 대해서는 설계대로 정확히 일했는데도 말입니다.
핵심 개념 및 용어
PQescapeLiteral- 값을 따옴표로 감싸고 이스케이프해 SQL 텍스트에 붙여 넣을 수 있게 하는 libpq 함수입니다. PQescapeIdentifier, PQescapeString, PQescapeStringConn도 영향을 받았습니다.
잘못된 UTF-8- 선언된 인코딩에서 올바르지 않은 바이트 시퀀스입니다. 이스케이프 코드와 psql은 이런 바이트가 뒤따르는 문자를 몇 개나 삼키는지에 대해 서로 다르게 판단했습니다.
psql 메타 명령- 서버가 아니라 psql 클라이언트가 처리하는 백슬래시 명령입니다. \!는 줄의 나머지를 운영체제 셸 명령으로 실행합니다.
CVE-2024-12356- BeyondTrust Privileged Remote Access와 Remote Support의 치명적인 명령 인젝션 결함입니다. Rapid7의 테스트에서는 이를 통해 코드 실행에 이르려면 CVE-2025-1094가 필요했습니다.
바인딩된 매개변수- SQL 텍스트와 분리되어 서버로 전송되는 값입니다. 따옴표나 이스케이프에 관한 판단이 아예 일어나지 않습니다.
프레임워크 매핑
사고 타임라인
BeyondTrust가 일부 Remote Support SaaS 인스턴스 침해를 조사한 뒤 CVE-2024-12356에 대한 공지 BT24-10을 발표합니다. 미 재무부는 나중에 이 사고가 자신의 시스템까지 미쳤다고 보고했습니다.
CISA가 CVE-2024-12356을 알려진 악용 취약점 목록에 추가합니다.
PostgreSQL이 CVE-2025-1094를 수정한 17.3, 16.7, 15.11, 14.16, 13.19를 배포하고 Rapid7이 분석을 발표합니다.
단계별 공격 실행 흐름
입력이 이스케이프 함수에 도달한다
애플리케이션이 공격자의 영향을 받은 텍스트를 받아 SQL 문을 만들기 위해 PQescapeLiteral이나 비슷한 libpq 함수에 넘깁니다.
잘못된 UTF-8이 따옴표를 숨긴다
PostgreSQL 공지에 따르면, 작은따옴표 앞에 조작된 잘못된 멀티바이트 시퀀스를 두면 이스케이프 루틴이 그 따옴표를 이스케이프하지 않고 남겼습니다.
psql이 리터럴을 일찍 닫는다
문장이 psql로 전달되면 이스케이프되지 않은 따옴표가 문자열 리터럴을 닫고, 나머지 입력은 SQL이나 psql 명령으로 해석됩니다.
메타 명령이 셸을 실행한다
Rapid7은 주입된 텍스트에 \! 메타 명령을 넣을 수 있음을 보였습니다. psql은 이를 자신이 실행 중인 머신에서 운영체제 명령으로 실행합니다.
소스 코드 비교: 취약한 구현 vs 보안 패치
psycopg 3를 쓴 설명용 Python이며 BeyondTrust의 코드가 아닙니다. 취약한 버전은 공지가 지목한 위험한 패턴, 즉 libpq로 값을 이스케이프해 SQL 텍스트에 끼워 넣고 그 텍스트를 psql로 넘기는 흐름을 재현합니다. 강화된 버전은 두 부분을 모두 없앱니다. 어떤 처리보다 먼저 잘못된 UTF-8을 거부하고, 실제 드라이버를 통해 값을 바인딩된 매개변수로 보내므로 속일 수 있는 이스케이프 단계도, 경로 위의 psql 메타 명령 파서도 없습니다. 이스케이프를 계속 쓰는 코드라면 libpq 업그레이드가 여전히 중요합니다.
import subprocess
import psycopg
def lookup_user(conn: psycopg.Connection, name: bytes) -> bytes:
# libpq의 PQescapeLiteral은 17.3/16.7/15.11/14.16/13.19 이전에
# 잘못된 UTF-8 바이트 시퀀스 뒤에 숨은 따옴표를 무력화하지 못했습니다.
literal = conn.pgconn.escape_literal(name)
script = b"SELECT id FROM users WHERE name = " + literal + b";\n"
# psql은 입력을 스크립트로 실행하며 \! 같은 메타 명령도 처리합니다.
result = subprocess.run(["psql", "-At", "-d", "app"], input=script,
capture_output=True, check=True)
return result.stdout
import psycopg
MAX_NAME_CHARS = 128
def lookup_user(conn: psycopg.Connection, name: bytes) -> list[int]:
try:
text = name.decode("utf-8") # reject malformed input at the boundary
except UnicodeDecodeError:
raise ValueError("name is not valid UTF-8") from None
if len(text) > MAX_NAME_CHARS:
raise ValueError("name too long")
with conn.cursor() as cur:
# 값은 SQL 텍스트와 분리된 바인딩 매개변수로 전달되고,
# psql 프로세스(따라서 메타 명령 파서)도 관여하지 않습니다.
cur.execute("SELECT id FROM users WHERE name = %s", (text,))
return [row[0] for row in cur.fetchall()]
엔지니어링 및 시스템 보안 강화 체크리스트
- ✓애플리케이션 데이터로 입력을 만들어 subprocess로 psql을 호출하는 곳을 찾아 바인딩된 매개변수를 쓰는 데이터베이스 드라이버로 바꾸고, 새 호출이 생기면 실패하는 CI 규칙을 추가합니다.
- ✓선언된 인코딩에서 올바르지 않은 입력은 파서, 이스케이프 함수, 데이터베이스 호출에 닿기 전에 경계에서 거부하고, b'\xc0\''를 보내 검증 오류를 기대하는 테스트를 추가합니다.
- ✓서버만이 아니라 libpq와 클라이언트 도구도 업그레이드합니다. CVE-2025-1094는 PostgreSQL 17.3, 16.7, 15.11, 14.16, 13.19에서 수정되었으며, 애플리케이션 호스트의 클라이언트 패키지도 업데이트해야 합니다.
- ✓아직 이스케이프를 없앨 수 없는 곳에는, 잘못된 UTF-8 뒤에 따옴표를 붙여 이스케이프 경로에 넣고 결과가 거부되거나 안전하게 인용되는지 확인하는 회귀 테스트를 둡니다.
- ✓특권 원격 접근 도구를 공격 표면의 일부로 취급합니다. 관리 인터페이스를 제한하고, 벤더 공지가 오면 바로 패치하며, API 키가 예상치 못한 위치에서 쓰이는지 모니터링합니다.