GitLab CVE-2026-19478: Critical GraphQL Flaw Under Active Exploitation
인증 없는 GraphQL 메서드 호출 취약점의 원인, 실제 악용 신호, OSINT 노출 자산 검색, 탐지와 대응
TL;DR
- GitLab은 CVE-2026-19478을 CVSS 9.4의 Critical 취약점으로 평가했다. 특정 조건에서 인증하지 않은 원격 공격자가 공개 프로젝트와 사용자 데이터를 변경하거나 삭제할 수 있다.
- 영향 범위는 GitLab CE/EE 18.2 이상 18.11.11 미만, 19.0.x 중 19.0.8 미만, 19.1.x 중 19.1.6 미만, 19.2.x 중 19.2.4 미만이다.
- watchTowr는 패치 공개 약 이틀 뒤 honeypot에서 실제 악용 시도를 포착했다고 밝혔다. 이는 인터넷상의 공격 시도를 뒷받침하지만, 광범위한 고객 침해가 확인됐다는 뜻은 아니다.
- Self-Managed 운영자는 즉시 수정 버전 이상으로 업그레이드하고,
graphql_json.log에서@gl_introduced가 포함된 외부 요청과 직후의 프로젝트·사용자 상태 변경을 함께 조사해야 한다. - 자사 도메인·보유 IP 대역을 Shodan, Censys, urlscan, GitHub Code Search에서 교차 검색하면 CMDB에서 빠진 인터넷 노출 GitLab을 찾는 데 도움이 된다. 검색 결과만으로 취약하다고 판정하거나 제3자 GraphQL endpoint를 능동 검증해서는 안 된다.
- 공개된 수정 코드를 기준으로 확인되는 핵심 primitive는 서버 전체의 임의 코드 실행이 아니라, 공격자가 고른 이름과 일치하는 내부 객체의 공개 zero-argument method 호출이다.
악용 현황을 어떻게 읽어야 하나
GitLab은 2026년 8월 17일 18.11.11, 19.0.8, 19.1.6, 19.2.4를 긴급 공개했다. GitLab.com과 GitLab Dedicated는 당시 이미 수정 버전을 사용하고 있었고, 직접 조치가 필요한 대상은 Self-Managed CE/EE다.
SecurityWeek가 인용한 watchTowr 관측에 따르면 약 8월 19일 honeypot에서 CVE-2026-19478 악용 시도가 포착됐다. 이 글에서 말하는 active exploitation은 이 관측을 가리킨다. GitLab의 원문 권고가 고객 침해를 확인한 것은 아니며, 8월 26일자 CISA KEV 원본 피드에도 이 CVE는 아직 등재되지 않았다. 따라서 “공격 시도가 실제 인터넷에서 관측됐다”와 “대규모 침해가 확인됐다”를 구분해야 한다.
| 취약 브랜치 | 수정 기준 |
|---|---|
| 18.2 이상, 18.11.11 미만 | 18.11.11 이상 |
| 19.0.x, 19.0.8 미만 | 19.0.8 이상 |
| 19.1.x, 19.1.6 미만 | 19.1.6 이상 |
| 19.2.x, 19.2.4 미만 | 19.2.4 이상 |
18.2~18.10처럼 지원이 끝난 minor release에 머물러 있다면 단순히 patch 숫자만 비교하지 말고 GitLab의 required upgrade stop을 따라 지원되는 수정 버전으로 이동해야 한다.
OSINT로 인터넷 노출 GitLab 찾기
이 절차의 목적은 자사 소유이거나 명시적인 조사 허가를 받은 범위에서 누락된 Self-Managed GitLab을 찾는 것이다. 먼저 조직의 apex domain, 알려진 GitLab FQDN, 보유 CIDR·ASN, 클라우드 계정과 인증서 도메인을 조사 범위로 확정한다. example.com, gitlab.example.com, 203.0.113.0/24, YOUR_ORG는 실제 자사 값으로 바꾼다.
| 소스 | 방어용 검색 예시 | 확인할 것 |
|---|---|---|
| Shodan | http.title:"Sign in · GitLab" hostname:"example.com" |
IP, port, hostname, TLS 인증서 이름, banner의 수집 시각(last seen) |
| Shodan | http.title:"Sign in · GitLab" net:"203.0.113.0/24" |
보유 대역에서 CMDB에 없는 웹 서비스. 로그인 제목이 현지화·커스텀된 경우 GitLab hostname:"example.com"도 보조 검색 |
| Censys Platform | host.services.cert.names = "gitlab.example.com" |
인증서 SAN에 알려진 GitLab 이름이 나타나는 host와 service. 알려진 gitlab.example.com:443도 직접 조회 |
| urlscan | page.domain:example.com AND page.title:GitLab AND date:>now-180d |
기존 공개 scan의 최종 domain, IP, redirect, page title, scan date |
| GitHub Code Search | "gitlab.example.com" org:YOUR_ORG |
소스·문서·IaC·CI 설정에 남은 hostname과 clone/API URL |
| GitHub Code Search | "gitlab.example.com" path:.gitlab-ci.yml org:YOUR_ORG |
오래된 runner·mirror·migration 설정이 가리키는 shadow 또는 stale instance |
| 일반 검색엔진 | site:example.com "Sign in · GitLab" 또는 site:example.com inurl:users/sign_in |
검색색인·cache에 남은 과거 또는 현재의 공개 endpoint |
Shodan은 service banner를, Censys는 구조화된 host·service·certificate 관측치를 검색하므로 결과가 오래됐거나 CDN·reverse proxy 때문에 실제 origin과 다를 수 있다. Censys는 현재 Platform과 이전 Legacy Search의 필드 문법이 다르므로 오래된 블로그의 query를 그대로 재사용하지 말고 현재 문서의 schema를 확인한다. 검색엔진에 나오지 않는다고 비노출인 것도 아니다.
urlscan에서는 이미 존재하는 공개 scan을 검색하는 데 그친다. 내부 hostname, token이 붙은 URL, 관리자 endpoint를 새 public scan으로 제출하면 그 자체가 정보 유출이 될 수 있다. GitHub 검색도 우선 자사 org: 또는 승인된 enterprise 범위로 제한하고, 발견한 secret 값을 보고서나 ticket에 복사하지 말고 별도의 credential incident 절차로 다룬다.
검색 결과는 다음 순서로 검증한다.
- FQDN·IP·port·certificate SAN·redirect를 CMDB, DNS, cloud load balancer, WAF와 대조해 중복을 제거한다.
- 자산 소유자와 운영 환경을 확인한다. 소유권이 확인되지 않은 host에는 request를 보내지 않는다.
- 관리 화면 또는 해당 서버 내부에서
sudo gitlab-rake gitlab:env:info를 실행해 edition과 정확한 version을 확인한다. 로그인 제목이나 favicon만으로 GitLab 또는 취약 버전을 판정하지 않는다. 공개 노출 + 영향 버전 + public project/user 존재 + 취약 기간 로그 부족인 자산을 최우선으로 패치·조사한다.- 발견 시각, OSINT 소스, 관측 시각, FQDN/IP/port, 소유자, 내부 확인 version, 수정 여부, 외부 노출 기간, 로그 보존 상태와 ticket ID를 기록한다.
OSINT hit는 노출 가능성에 대한 단서이지 CVE-2026-19478의 취약 또는 침해 판정이 아니다. 확인을 위해 제3자 /api/graphql에 query를 보내거나 공개 객체 변경을 시도하지 않는다. 소유 자산에서도 버전 확인과 로그 분석을 우선하고, 별도 승인된 테스트 환경이 아닌 운영 시스템에는 exploit payload를 사용하지 않는다.
근본 원인: 안전해 보이는 fallback보다 메서드 탐색이 먼저였다
@gl_introduced directive는 rolling deployment 중 frontend가 아직 배포되지 않은 GraphQL field를 먼저 요청해도 이전 backend가 실패하지 않도록 18.2에 도입됐다. 미래 버전의 field를 만나면 이를 임시로 제거한 뒤, 원래 응답 위치에는 null을 돌려주는 것이 의도였다.
취약 버전의 lib/gitlab/graphql/version_filter/future_field_fallback.rb는 존재하지 않는 field 이름을 그대로 동적 fallback field로 만들었다.
GraphQL::Schema::Field.new(
owner: self,
name: name,
type: GraphQL::Types::Boolean,
fallback_value: nil
)
문제는 GitLab 19.2.2가 고정한 graphql-ruby 2.6.3의 해석 순서다. fallback_value를 반환하기 전에 GraphQL wrapper와 내부 application object에 같은 이름의 메서드가 있는지 검사하고, 있으면 public_send로 호출한다.
elsif inner_object.respond_to?(@method_sym)
inner_object.public_send(@method_sym)
elsif @fallback_value != NOT_CONFIGURED
@fallback_value
결과적으로 외부 GraphQL field 이름이 내부 Ruby model의 메서드 선택자로 바뀌었다. 만들어진 fallback field에는 인자가 정의되지 않았으므로 소스에서 직접 확인되는 범위는 공개 zero-argument method 호출이다. 공개 조회가 가능한 Alice의 Project 또는 User 객체에 도달한 Mallory가 이 동작을 유발하면, 정상 authorization mutation을 거치지 않고 상태 변경 메서드가 실행될 수 있다.
unauthenticated GraphQL request
└─ attacker-controlled missing field + @gl_introduced
└─ FutureFieldFallback creates a dynamic field
└─ graphql-ruby resolves a same-named object method first
└─ public Project/User state can be modified or deleted
GitLab은 이 결과를 공개 프로젝트와 사용자 데이터의 원격 변경·삭제로 설명했다. 다만 공개된 소스와 권고만으로 host RCE, private repository 접근, 전체 instance 장악까지 확인되는 것은 아니다. CVE 분류가 CWE-94여도 실제 대응 범위는 검증된 primitive와 vendor가 명시한 영향에 맞춰야 한다.
수정 방식
19.2 계열의 수정 커밋은 동적 field가 일반 객체 해석 경로로 들어가지 않도록 Resolvers::NilResolver를 지정한다.
GraphQL::Schema::Field.new(
owner: self,
name: name,
resolver_class: Resolvers::NilResolver
)
새 resolver는 항상 nil만 반환한다. 회귀 테스트도 Types::QueryType의 동일 이름 메서드가 호출되지 않는지 확인한다. 단순한 denylist가 아니라, fallback의 의미를 “어떤 이름이 들어와도 null만 반환”하도록 구조적으로 고정한 수정이다.
탐지와 조사
Linux package 설치에서는 GitLab이 GraphQL query 문자열을 /var/log/gitlab/gitlab-rails/graphql_json.log에 기록한다. 먼저 보존본을 만든 뒤 다음과 같이 @gl_introduced 사용을 좁힐 수 있다.
sudo zgrep -hF '@gl_introduced' /var/log/gitlab/gitlab-rails/graphql_json.log*
이 directive 자체는 정상 GitLab frontend도 사용할 수 있으므로 단독 IOC가 아니다. 다음 항목을 같은 시간축에서 확인한다.
graphql_json.log의 미래 버전 지정, schema에 없는 비정상 field 이름,graphql:unknowncaller- NGINX
gitlab_access.log의/api/graphql요청 시각·source IP·status·correlation ID audit_json.log와 관리자 감사 이벤트의 프로젝트 삭제, 공개 범위, 멤버·사용자 상태 변경- 해당 시각 전후의 repository ref, protected branch, merge 기록, CI/CD 설정과 deploy key 변경
- trusted mirror, signed tag, 배포 artifact, backup과 현재 repository 상태의 차이
기본 NGINX access log에는 POST body가 남지 않으므로 여기에서 @gl_introduced 문자열이 검색되지 않는 것은 안전하다는 증거가 아니다. 반대로 directive가 검색됐다는 사실만으로 성공한 공격이라고 단정해서도 안 된다. 요청 이후 실제 객체 상태가 바뀌었는지까지 확인해야 한다.
Helm 배포는 Webservice pod의 subcomponent="graphql_json" 로그를 수집한다. 로그 보존 기간이 짧거나 중앙 수집이 누락됐다면 “증거 없음”과 “침해 없음”을 분리해 기록한다.
대응 우선순위
- 현재 버전을 확인하고 18.11.11, 19.0.8, 19.1.6, 19.2.4 또는 이후의 비영향 버전으로 즉시 업그레이드한다.
- 즉시 패치가 어렵다면 신뢰할 수 있는 proxy에서
/api/graphql의 비인증 접근을 제한하고 public visibility를 임시로 축소한다. 기능 영향이 있으며 패치를 대체하지 않는다. - 업그레이드 전후 GraphQL·NGINX·audit 로그와 database·repository snapshot을 보존한다.
- 공개 프로젝트의 branch, tag, merge 기록, CI/CD 구성과 배포 결과물을 trusted mirror 또는 clean backup과 비교한다.
git fsck는 객체 구조의 무결성을 검사할 뿐, 권한 없이 이뤄진 논리적 변경을 판별하지는 못한다. - 비인가 사용자·프로젝트 변경 또는 추가 침해 정황이 확인되면 clean administrative host에서 복구하고 관련 access token, deploy key, runner credential을 폐기·재발급한다.
Linux package 설치의 버전은 다음 vendor 제공 명령으로 확인할 수 있다.
sudo gitlab-rake gitlab:env:info
이번 patch release에는 새 migration이 없고 multi-node 환경은 무중단 업그레이드가 가능하다고 GitLab은 설명한다. 다만 Omnibus package는 기본적으로 서비스를 중지하고 reconfigure한 뒤 다시 시작하므로 운영 절차와 rollback plan을 함께 준비해야 한다.
검증 범위와 한계
이 분석은 GitLab 18.2.0, 19.2.2, 19.2.4의 공개 release source, 도입·수정 commit, graphql-ruby 2.6.3의 field resolution 코드를 대조한 결과다. 파괴적인 public object 변경을 실행하는 PoC는 포함하거나 실행하지 않았다.
현재 공개 근거는 honeypot을 향한 악용 시도를 뒷받침하지만 특정 조직의 성공적 침해, 공격자 attribution, 대규모 campaign을 입증하지 않는다. 패치 여부와 침해 여부는 별도 질문이므로, 수정 버전으로 올린 뒤에도 취약 버전이 노출됐던 기간의 기록을 조사해야 한다.
References
- GitLab Critical Patch Release: 19.2.4, 19.1.6, 19.0.8, 18.11.11
- GitLab CNA record: CVE-2026-19478
- GitLab fix: Prevent calling object method when resolving fallback field
- GitLab introducing change: Filter GraphQL nodes based on the @introduced directive
- SecurityWeek: Critical GitLab Flaw Exploited Shortly After Disclosure
- GitLab log system
- GitLab upgrade paths
- CISA Known Exploited Vulnerabilities Catalog
- Shodan Search Query Fundamentals
- Censys Platform Quick Start Guide
- GitHub Code Search syntax
- urlscan Search API Reference