GitLab 인증 사용자 RCE 취약점 (CVE-2024) 분석: ipynbdiff와 Oj 파서의 치명적 결합

GitLab 인증 사용자 RCE 취약점은 공격자가 조작된 Jupyter Notebook(.ipynb) 파일을 커밋한 뒤, 관리자나 다른 사용자가 해당 커밋의 Diff 페이지를 열 때 서버 권한으로 임의 코드를 실행하는 문제다. 이 공격은 GitLab 내부 ipynbdiff 모듈이 JSON 데이터를 처리하는 과정에서 Ruby의 고성능 JSON 파서인 Oj gem의 C 확장 모듈 메모리 손상 취약점을 건드려 발생한다.

1. GitLab 인증 사용자 RCE 취약점 개요 및 발생 배경

2026년 7월 24일, 사이버보안 연구소 depthfirst가 실제 공격 코드(PoC)를 공개하면서 이 취약점이 널리 알려졌다. GitLab은 6주 전인 2026년 6월 10일 이미 패치를 배포했지만, PoC 공개로 구체적인 공격 경로가 드러났다. 이 취약점의 핵심은 Ruby 언어 자체의 문제가 아니라 성능 향상을 위해 C 언어로 작성된 네이티브 확장 모듈인 Oj gem의 내부 결함에 있다.

눈여겨볼 점은 해당 버그가 2021년 8월 8일 Oj Parser 코드에 처음 들어온 뒤 약 5년간 발견되지 않고 방치됐다는 사실이다. GitLab은 이 수정을 일반적인 보안 패치로 분류해 별도의 CVE나 CVSS 점수를 매기지는 않았다. 하지만 인증된 사용자가 서버 제어권을 완전히 가져올 수 있는 원격 코드 실행(RCE)이 가능해 실질적으로는 매우 위험하다. depthfirst 연구소는 실제 야외 공격 사례는 아직 못 찾았지만, GitLab 내부에서 RCE 재현에 성공해 위험성을 확인했다.

2. ipynbdiff와 Oj 파서를 통한 공격 메커니즘 기술 분석

공격은 GitLab 저장소에 올라간 .ipynb 파일에서 시작한다. 주피터 노트북 파일은 기본적으로 JSON 구조체로 저장된다. GitLab은 사용자가 웹 브라우저에서 두 커밋 사이의 변경 사항을 시각적으로 비교하려면 내부 모듈인 ipynbdiff를 사용한다. 이 모듈은 저장소에 있는 .ipynb JSON 데이터를 읽어 장기간 실행되는 Puma 워커 프로세스 안의 Oj::Parser.usual.parse 함수로 넘겨 렌더링을 진행한다.

이 과정에서 공격자가 정교하게 조작한 JSON 데이터가 애플리케이션 프로세스 내부 Oj 파서가 관리하는 C 메모리 영역에 닿게 된다. 여기서 두 가지 메모리 손상 취약점이 엮여 RCE로 이어진다.

첫 단계는 1,024바이트 고정 크기 중첩 스택 영역을 넘어 데이터를 쓰는 버퍼 오버플로(Out-of-bounds write) 취약점이다. 공격자는 이를 이용해 파서의 start 콜백 함수 포인터를 임의로 조작할 수 있다.

두 번째는 메모리 주소를 유출하는 힙 포인터 노출 취약점이다. 65,565바이트 길이의 아주 긴 객체 키를 부호 있는 16비트 필드에 저장할 때 값이 29로 잘리는 키 길이 변환 오류가 생긴다. 이 오류 때문에 시스템은 유효한 힙 포인터를 반환하고, GitLab은 이 값을 Diff 화면에 그대로 렌더링하여 공격자에게 보여준다.

공격자는 유출된 힙 포인터로 libc 라이브러리 메모리 위치를 정확히 알 수 있다. 이어 앞선 버퍼 오버플로를 이용해 start 콜백을 system() 함수로 돌려 서버에서 임의 쉘 명령어를 실행하는 RCE 체인을 완성한다.

3. 영향받는 버전 및 상세 완화 대책

이 취약점은 자체 구축(Self-managed) 버전의 GitLab CE 및 EE 사용자 전체에 영향을 준다. Free부터 Ultimate까지 모든 티어 사용자가 타깃이 될 수 있다. 특히 Puma를 실행하는 Webservice 이미지 내 버전을 정확히 확인하는 것이 중요하다.

구분 영향을 받는 버전 (Vulnerable) 패치 및 해결 버전 (Fixed)
GitLab CE/EE 15.2.0 ~ 18.11.4, 19.0.1 18.10.8, 18.11.5, 19.0.2
Oj gem 모듈 3.13.0 ~ 3.17.1 3.17.3 이상

취약점에 대응하려면 엔지니어가 다음 단계들을 수행해야 한다.

  1. Webservice 이미지 버전 확인: Helm 차트나 Operator 버전이 아니라, 실제 Puma 워커가 돌아가는 Webservice 컨테이너 내부의 GitLab 및 Oj gem 버전을 직접 확인한다.
  2. 최신 버전 업그레이드: 영향을 받는 버전 중 15.2에서 18.9 사이 버전은 보안 백포트가 제공되지 않으니 지원되는 최신 릴리스(18.10.8, 18.11.5, 19.0.2 중 하나)로 마이그레이션한다.
  3. Oj gem 업데이트: Ruby 환경에서 Oj gem 버전을 3.17.3 이상으로 올려 C 확장 모듈의 메모리 손상 결함을 제거한다.
  4. 진입점 차단: 즉각적인 업그레이드가 힘든 환경에서는 신뢰할 수 없는 사용자가 올린 주피터 노트북(.ipynb) 파일의 Diff 렌더링 경로를 엄격히 제한하거나 막아 공격 벡터를 없앤다.

4. 전문가 인사이트 및 보안 권장사항

이번 사례는 Ruby처럼 메모리 안전성이 높은 언어를 쓰더라도 성능 최적화를 위해 도입한 C 네이티브 확장 모듈이 전체 시스템 보안 구멍이 될 수 있음을 시사한다. 단순 파일 조회나 Diff 렌더링 같은 일반적인 기능이 RCE라는 치명적인 공격 경로로 바뀔 수 있다는 점을 짚고 넘어가야 한다. 위협 모델링을 설계할 때 애플리케이션 레벨뿐만 아니라 내부에서 쓰는 서드파티 파서와 라이브러리의 메모리 관리 영역까지 범위를 넓혀야 한다.

공개된 PoC는 x86-64 아키텍처의 GitLab 18.11.3 버전을 기준으로 만들어졌다. Puma 프로세스가 재시작되면 라이브러리 베이스 주소가 바뀌어 즉각적인 무작위 공격은 다소 어렵지만, 동일 아키텍처 내에서는 오프셋 조정만으로 충분히 이식이 가능하다. ARM64 아키텍처는 호출 규약(Calling Convention)과 힙 동작 방식이 달라 공격 난이도가 높다. 하지만 이를 완전히 안전하다고 보면 안 된다.

메모리 검색 효율에 따라 공격 성공 시간도 달라진다. 최신 2-Worker 환경은 5~10분이면 충분하지만, 오래된 환경은 최대 1~2시간이 걸릴 수 있다. 공격자가 인내심을 갖고 시도하면 충분히 실현 가능한 시나리오다.

GitLab 운영자는 단순 버전 체크를 넘어 Webservice 내부 실제 구동 환경을 점검해야 한다. 신뢰할 수 없는 입력값이 C 확장 모듈 파서로 바로 전달되는 경로를 최소화하는 제로 트러스트 관점이 필요하다. 지금 서버의 Oj gem 버전과 GitLab 릴리스 버전을 대조해 패치 여부를 확인하고, 안내된 최신 버전으로 업데이트해야 한다.

댓글 남기기