A community translation can run attacker-chosen code in an exported Godot game
Godot 게임에서 커뮤니티 번역 파일로 임의 코드 실행 가능
Godot 4.1 이상에서 .po 번역 파일의 Plural-Forms 헤더를 일반 코드처럼 평가해, 악성 번역 파일이 게임 프로세스 안에서 공격자 지정 GDScript를 실행할 수 있다는 분석입니다. 작성자는 공식 4.7 빌드에서 실행을 재현했으며, 확인 당시 수정 사항은 아직 병합되지 않았다고 밝혔습니다.
- 주제
에디터 노트
문제의 직접 원인은 번역 파일을 일반 표현식 평가기에 넘기는 구현입니다. 작성자는 4.7 공식 빌드에서 악성 헤더가 GDScript 초기화 코드를 실행하는 과정을 재현했고, 게임이 번역을 불러와 복수형 문자열을 조회하는 조건도 구체적으로 제시합니다. 다만 7.8 점수는 작성자 평가이며 실제 런타임 검증은 4.7에 한정됩니다. 4.1 이후 버전은 소스 경로로 판단한 범위입니다. 함수 제한 PR은 아직 병합 전이어서 수정 완료로 볼 수 없고, gettext 문법을 직접 검증하자는 제안은 원인이 된 범용 평가기 의존을 끊는 해법입니다.
AI 요약
Godot의 .po 번역 파일은 데이터를 담는 형식이지만, Plural-Forms 헤더를 처리하는 과정에서 코드 실행 경로가 생깁니다. 작성자는 Godot 4.7 공식 Windows 배포 빌드에서 이를 재현했으며, 게임이 커뮤니티 번역을 불러오고 복수형 문자열을 조회하는 것만으로 악성 GDScript가 게임과 같은 권한으로 실행됐다고 보고합니다.
취약점이 발생하는 과정
GNU gettext 표준에서 Plural-Forms는 복수형 문구를 고르는 산술식이어야 합니다. 비교, 논리 연산, 산술 연산, 조건 연산자와 변수 n을 허용하며 함수 호출은 허용하지 않습니다. 그러나 Godot의 번역 로더는 헤더 문법을 검증하지 않고 저장합니다. 복수형 문자열을 처음 조회하면 PluralRules가 이 문자열을 일반 Expression 평가기에 넘깁니다. 평가기는 등록된 Variant 유틸리티 함수를 호출할 수 있어, gettext 문법에서는 잘못된 seed(123456789)도 실행합니다.
작성자는 4.7-stable의 공식 빌드에서 두 가지를 시험했습니다. 첫 번째 테스트에서는 헤더에 seed(123456789)를 넣어 게임의 전역 난수 생성기 상태를 지정한 값으로 바꿨습니다. 10회 중 10회 성공했고 게임은 계속 실행됐습니다. 두 번째 테스트는 180바이트 Variant 데이터를 bytes_to_var_with_objects()로 디코딩했습니다. 데이터에는 공격자 지정 GDScript 소스가 들어간 GDScript 객체가 포함됐고, 디코딩 과정에서 스크립트가 컴파일된 뒤 정적 초기화 코드를 실행했습니다. 초기화 코드는 메모리 표식 하나를 설정했으며, 표식은 tr_n() 호출 뒤 10회 모두 나타났습니다. 작성자는 이를 게임 프로세스 안에서 임의 GDScript가 실행되는 증거로 제시합니다.
호출 경로와 영향 범위
작성자가 요약한 Godot 4.7 소스 경로는 다음과 같습니다. translation_loader_po.cpp가 헤더를 문법 검사 없이 보관하고, translation.cpp가 복수형 규칙을 조회합니다. plural_rules.cpp는 이를 함수 호출을 허용하는 일반 Expression으로 파싱합니다. expression.cpp는 등록된 Variant 함수를 호출하고, marshalls.cpp는 지정된 클래스와 속성을 가진 객체 데이터를 디코딩합니다. 마지막으로 gdscript.cpp가 script/source 속성을 설정하면서 소스를 컴파일하고 정적 초기화 코드를 실행합니다. Godot 문서도 신뢰할 수 없는 입력에 bytes_to_var_with_objects()를 사용하면 원격 코드 실행으로 이어질 수 있다고 경고한다고 글은 지적합니다.
위험 대상은 게임에 포함되지 않은 .po 파일을 불러온 뒤 복수형 문자열을 표시하는 내보내기 게임입니다. 커뮤니티 번역, 현지화 팩, 모드가 사례로 제시됩니다. 악성 입력은 별도 스크립트나 네이티브 코드, 외부 참조가 아니라 Plural-Forms 헤더 안에 들어간 180바이트 데이터입니다. 플레이어가 번역을 설치하거나 선택해야 하므로 사용자 상호작용은 필요하지만, 실행된 코드는 게임 프로세스가 읽고 수정할 수 있는 자원에 접근할 수 있고 게임을 종료할 수도 있습니다.
완화책과 수정 현황
작성자는 신뢰할 수 없는 .po 파일을 불러오기 전에 Plural-Forms 헤더를 gettext 문법에 맞춰 검증하거나 제거하라고 제안합니다. msgfmt --check-style 검사라면 악성 식을 거부한다고 설명합니다. 이 방식은 일반 표현식 평가기의 허용 함수 목록에 안전성을 맡기는 대신 헤더가 허용하는 문법만 받아들이게 합니다.
작성자의 CVSS 3.1 평가는 7.8 High이며 벡터는 AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H입니다. 파일이 게임 안의 번역 로더에 전달되므로 공격 벡터를 Local로 분류했고, 번역 선택이 필요해 사용자 상호작용을 Required로 산정했습니다. 이는 작성자 본인의 평가라고 명시돼 있습니다.
2026년 10월 24일 기준 작성자가 확인한 master와 4.x 안정판에는 문제가 남아 있었습니다. 함수 호출 범위를 제한하는 PR #123130은 seed() 호출을 막지 못한다고 했고, PR #123857은 순수 수학 함수만 허용해 두 재현 사례를 차단한다고 설명합니다. 10월 26일 기준 두 PR 모두 병합되지 않았습니다. 작성자는 함수 범위 제한이 다른 호출 지점의 조건에도 의존한다며 gettext 전용 문법 파서를 더 직접적인 방안으로 봅니다. 전체 실행 경로는 소스상 4.1부터 존재하지만, 런타임 검증은 4.7-stable에서 했고 3.5에서는 동등한 경로를 찾지 못했다고 밝혔습니다.
공개 경과
작성자는 2026년 9월 23일 재현 자료와 원인 분석을 비공개로 보고했고, 12월 22일 공개할 예정이라고 알렸습니다. 9월 24일에는 문법 관련 보충과 시연 영상을 보냈지만 답변을 받지 못했다고 합니다. 10월 26일 Godot 연락처 페이지가 보안 메일함에도 기여 규칙을 적용한다고 안내한 뒤, 보고서가 LLM 지원으로 작성됐다고 공개한 작성자는 회신을 기대하기 어렵다고 판단해 공개 시점을 앞당겼습니다. 재현 자료는 GitHub 저장소에 공개됐고, 공식 릴리스 바이너리와 샌드박스에서 검증했다고 밝혔습니다.
원문: Offprint / 번역·요약: Trawling