hyunsu blog

홈동향

GitLab CVSS 10.0 정규식 RCE, 직전 긴급 패치로는 막히지 않는 이유와 버전표

목차 · 8
  1. 01같은 연구자가 한 달 사이 네 번째로 찾은 정규식 결함이다
  2. 02공지는 조건을 감췄고, 탐지 문서가 Org-mode 경로를 적어 두었다
  3. 03왜 10월 12일 패치로는 막히지 않나
  4. 04버전표: 18.11~19.2 백포트는 원격 실행 하나만 막는다
  5. 05단일 노드는 중단 시간을, 소스 설치는 Ruby 를 따로 챙긴다
  6. 06패치와 별개로 로그에서 시도 흔적을 찾는다
  7. 07사내 GitLab 을 직접 운영하는 팀이 대상이고, 세부 공개를 기다릴 이유는 없다
  8. 08자주 묻는 질문

직접 설치한 GitLab CE/EE 는 10월 12일 긴급 패치를 적용했어도 다시 올려야 한다. 이번 CVE-2026-110264(CVSS 10.0)는 그 패치가 막은 CI 정규식과 다른 경로로 Ruby 정규식 엔진 Onigmo 의 정수를 넘치게 해서, 로그인하지 않은 사람도 서버에서 코드를 실행할 수 있게 만든다. 7.0 이후 모든 버전이 영향권이고 우회책이 없다. 쓰는 계열에서 이번에 나온 패치 번호 이상으로 올리는 것 말고는 방법이 없다.

01 같은 연구자가 한 달 사이 네 번째로 찾은 정규식 결함이다

9월 23일부터 한 달 동안 GitLab 은 정규식 결함 때문에 긴급 패치를 세 번 냈다. 고친 결함은 넷이고, 모두 같은 연구자가 HackerOne 버그 바운티로 제보했다. 앞선 세 건은 CI/CD 설정 파일 .gitlab-ci.yml 에 적은 정규식이 입구였다.

공지일CVECVSS내용로그인영향 계열
9월 23일CVE-2026-935779.9CI/CD 정규식 컴파일 중 정수 오버플로필요19.2~19.4
9월 23일CVE-2026-890789.9정규식 파서의 이중 해제필요19.2~19.4
10월 12일CVE-2026-9722910.0CI/CD 정규식 컴파일 중 정수 오버플로불필요19.2~19.4
10월 21일CVE-2026-11026410.0정규식 컴파일 중 정수 오버플로불필요7.0 이상 전부

앞의 세 건은 19.2~19.4 계열에만 걸렸으므로, 다른 계열을 쓰던 곳은 그냥 넘어가도 됐다. 이번 건은 범위부터 다르다. GitLab 공지는 7.0 부터 모든 버전을 영향 범위로 적었는데, 7.0 은 2014년 6월에 나온 버전이다. 해외 보도가 12년 치 릴리스가 걸렸다고 쓰는 까닭이 여기 있다.

이번 건과 CI 경로의 두 건은 모두 Onigmo 안에서 터졌다(GitLab 탐지 문서 기준). Onigmo 는 Ruby 에 들어 있는 정규식 엔진이다. Rails 앱인 GitLab 에서 Regexp 를 쓰는 곳은 전부 이 엔진을 지나므로, 사용자가 적은 글자가 패턴으로 컴파일되는 자리라면 어디든 입구가 될 수 있다.

02 공지는 조건을 감췄고, 탐지 문서가 Org-mode 경로를 적어 두었다

GitLab 공지는 특정 조건에서 로그인하지 않은 사용자가 서버에서 임의 코드를 실행할 수 있었다고만 밝혔다. 그 조건이 무엇인지는 쓰지 않았고, 취약점별 상세 이슈는 관례대로 수정 90일 뒤에 공개한다. 대신 공지에 함께 걸린 GitLab 보안팀의 탐지 문서 두 개가 공격 흐름을 꽤 자세히 적었다.

  1. 공격자가 공개 프로젝트에 Org-mode(Emacs 의 개요 문서 형식) 파일 .org 를 두고, 머리의 #+TODO:·#+SEQ_TODO:·#+TYP_TODO: 줄에 TODO 키워드를 마음대로 정의한다.
  2. 누군가 웹에서 그 파일을 열면 GitLab 이 org-ruby gem 으로 렌더링하는데, 이 요청에는 로그인이 필요 없다.
  3. org-ruby 는 키워드 목록으로 정규식을 만들 때는 이스케이프하지만, 제목 줄에서 키워드를 떼어 내는 두 번째 정규식에는 키워드를 그대로 Regexp.new 에 넘긴다. 공격자가 쓴 글자가 곧 정규식 패턴이 되는 셈이다.
  4. Onigmo 는 {0,100} 같은 범위 반복마다 반복 ID 를 붙여 부호 있는 16비트 칸에 담는데, 반복이 32,767개를 넘으면 ID 가 음수로 돌아간다.
  5. 매칭 단계에서 음수 ID 가 반복 스택 바깥을 가리켜 범위 밖 쓰기가 일어난다. 탐지 문서는 이것이 git 운영체제 사용자 권한의 코드 실행까지 이어질 수 있다고 적었다.

시도 한 번은 세 가지 중 하나로 끝난다. 렌더링이 시간 초과로 끊기거나, 워커가 죽어 Workhorse(GitLab 앞단의 리버스 프록시)가 502 를 돌려주거나, 아무 흔적 없이 지나간다. 까다로운 쪽은 마지막이다. 성공한 공격은 충돌 없이 조용히 끝날 수 있어서다.

코드가 git 사용자로 돌면 피해 범위가 넓다. 탐지 문서는 공격자가 gitlab-secrets.json, CI/CD 변수, 러너 토큰, 액세스 토큰을 가져갈 수 있고 모든 저장소에 접근할 수 있다고 정리했다. 공격자에게 필요한 것은 렌더링될 공개 프로젝트 하나뿐이라는 설명도 붙어 있다.

03 왜 10월 12일 패치로는 막히지 않나

12일 패치가 막은 값과 이번에 넘친 값이 다르기 때문이다. 12일의 CVE-2026-97229 는 CI 설정의 rules:*:regexp 에 넣은 패턴에서 출발한다. Rapid7 분석에 따르면 Onigmo 컴파일러는 반복을 펼쳐도 되는지 판단할 때 반복 본문 크기에 반복 상한을 곱하는데, 이 곱셈이 부호 있는 int 범위를 넘어 음수가 되면서 검사를 그대로 통과했다. 그 결과 약 2GiB 짜리 프로그램이 만들어지고, 이미 넘친 길이로 계산한 점프가 공격자가 넣은 바이트 위로 떨어진다. 12일 패치가 GitLab 이 묶어 배포하는 Ruby 에 넣은 변경은 이 곱셈을 넘치지 않는 나눗셈 비교로 바꾸고, 컴파일된 프로그램이 약 512MiB 를 넘으면 거절하는 것이다.

이번 결함에서 넘치는 것은 크기가 아니라 개수다. 범위 반복 ID 가 16비트 칸을 넘는 문제라서, 프로그램 크기를 보는 검사로는 걸러지지 않는다. GitLab 도 12일 패치 버전(19.4.2·19.3.5·19.2.8)을 이번 영향 범위에 그대로 넣었다.

구분CVE-2026-97229 (10월 12일)CVE-2026-110264 (10월 21일)
입구CI/CD 설정의 정규식.org 파일의 TODO 키워드
넘친 값반복 확장 크기 계산(int 곱셈)범위 반복 ID(16비트)
충돌 기록에 남는 프레임onig_compile·compile_treematch_at·headline.rb
영향 계열19.2~19.47.0 이상 전부
Ruby 쪽 수정넘치지 않는 확장 검사·컴파일 결과 크기 상한반복·null 검사 ID 개수 상한

수정은 이번에도 Ruby 쪽에서 했다. Ruby 본가는 범위 반복 ID 와 null 검사 ID 가 지나치게 많으면 명시적 오류를 내는 변경(PR 16801)을 2026년 4월 29일 master 에 병합했다. GitLab 은 이 변경을 Omnibus(공식 리눅스 패키지)가 빌드하는 Ruby 3.3 부터 4.0 까지에 백포트했다. 패치된 Ruby 는 문제의 패턴을 컴파일 단계에서 RegexpError 로 거절하고, 이때 메시지는 too many range repeat 나 too many null check 다.

04 버전표: 18.11~19.2 백포트는 원격 실행 하나만 막는다

GitLab 버전의 셋째 자리가 패치 번호다. 지금 쓰는 계열(앞 두 자리)을 찾아 셋째 자리가 둘째 열 값 이상인지 보면 된다.

지금 계열이번 수정이 든 패치 번호10월 12일 패치 번호(이것만으론 부족)같은 날 고친 다른 11건
19.51 이상없음포함
19.43 이상2포함
19.36 이상5포함
19.29 이상8빠짐
19.19 이상없음빠짐
19.010 이상없음빠짐
18.1113 이상없음빠짐
18.10 이하수정본 없음없음빠짐

마지막 열을 놓치기 쉽다. 공지가 18.11~19.2 로 백포트했다고 밝힌 보안 수정은 원격 코드 실행 하나뿐이고, 같은 날 고친 나머지 11건은 19.3 이상 계열 패치에만 있다. 이 11건의 영향 범위도 대부분 19.3 계열 수정본 이전 전부로 잡혀 있어서, 백포트 계열은 그중 해당하는 결함을 그대로 안고 간다.

그중 높음 등급 두 건은 이렇다. 하나는 OmniAuth SAML 과 LDAP 자동 연결을 함께 켠 인스턴스에서 로그인한 사용자가 기존 LDAP 계정을 가로채는 결함(8.2)이다. 다른 하나는 로그인 없이 OAuth/OIDC discovery 문서 캐시를 오염시키는 결함(7.5)이다. 중간 등급에는 낮은 권한으로 로그인한 사용자가 응답 헤더에서 내부 서버 자격 증명을 얻는 결함(4.3)도 있다. 사내 SSO 를 SAML 과 LDAP 으로 함께 붙여 둔 곳이라면 백포트에 머물지 말고 19.3 이상으로 올리는 편이 낫다.

05 단일 노드는 중단 시간을, 소스 설치는 Ruby 를 따로 챙긴다

먼저 버전과 Ruby 를 본다

bash
# Linux 패키지(Omnibus) 설치 — 여러 노드라면 Rails 가 도는 노드에서
sudo gitlab-rake gitlab:env:info

# 소스 설치
bundle exec rake gitlab:env:info RAILS_ENV=production

출력의 GitLab information 아래 Version: 줄이 GitLab 버전이고, System information 아래 Ruby Version: 줄이 Ruby 버전이다. Gitaly 처럼 Rails 가 없는 노드에서 돌리면 PostgreSQL 소켓 오류가 날 수 있다. 업그레이드를 마친 뒤에도 같은 명령으로 노드마다 버전이 올라갔는지 다시 확인한다.

단일 노드는 중단 시간을 잡는다

이번 패치에는 데이터베이스 마이그레이션이 들어 있다. 단일 노드는 마이그레이션이 끝나야 GitLab 이 올라오므로 중단 시간을 미리 잡아야 한다. 여러 노드로 구성했다면 무중단 업그레이드 절차를 쓸 수 있다. 19.3·19.4 계열 패치에는 일반 마이그레이션이 있고, 19.4 계열 패치에는 배포 뒤 마이그레이션도 붙어 있다.

18.10 이하는 정거장을 밟아 올라간다

18.10 이하에는 수정본이 없다. GitLab 은 여러 계열을 건너뛰는 업그레이드에 필수 정거장(required upgrade stop)을 두는데, 17.5 이후로는 각 메이저의 x.2·x.5·x.8·x.11 이 정거장이다. 18.11 도 정거장이라 18 계열에 있다면 18.11 의 최신 패치가 가장 가까운 목적지다. 정거장마다 백그라운드 마이그레이션이 끝나기를 기다려야 해서 하루에 끝나지 않을 수 있다. GitLab 지원팀이 관리하는 Upgrade Path 도구로 경로부터 뽑아 두자.

소스 설치는 Ruby 패치를 따로 확인한다

공개된 수정은 Omnibus 가 빌드하는 Ruby 에 넣은 패치다. 시스템 Ruby 로 소스 설치를 돌린다면 GitLab 코드만 올려서는 이 보호를 받지 못한다고 보는 편이 맞다. Omnibus 커밋은 Ruby 본가 수정이 master(4.1 개발 가지)에만 있어서 3.3·3.4·4.0 에 따로 옮겨 왔다고 적었다. Ruby PR 페이지에도 10월 22일 시점까지 이 수정이 3.4·4.0 정식 릴리스에 들어가지 않았다는 기록이 남아 있고, 한 분석 글은 Homebrew 로 설치한 Ruby 4.0.6 에서 문제의 패턴이 오류 없이 컴파일됐다고 전했다. 위 명령의 Ruby Version: 으로 버전을 확인하고, 그 Ruby 에 PR 16801 수정을 직접 적용했는지 따로 챙겨야 한다.

06 패치와 별개로 로그에서 시도 흔적을 찾는다

GitLab 은 자체 설치용 탐지 규칙 두 개를 함께 냈다. 하나는 한 클라이언트가 .org 렌더링 시간 초과를 30분 안에 다섯 번 이상 일으키는 몰림을 잡는다. 다른 하나는 Puma(GitLab 의 Rails 웹 서버) 워커 오류 로그에서 org-ruby 나 Onigmo 프레임이 찍힌 Ruby [BUG] 충돌을 잡는다. 아래는 탐지 문서에 실린 Omnibus 경로 기준 명령을 추린 것이다.

bash
EXC=/var/log/gitlab/gitlab-rails/exceptions_json.log
# .org 렌더링 시간 초과를 클라이언트별로 센다
grep '"exception.class":"Timeout::Error"' "$EXC"* \
  | grep '"extra.class_name":"OtherMarkup"' \
  | grep -E '"extra.file_name":"[^"]*\.org"' \
  | grep -o '"meta.client_id":"[^"]*"' | sort | uniq -c | sort -rn

# Puma 워커의 네이티브 충돌(넓게 잡는 검색)
PUMA=/var/log/gitlab/puma/puma_stderr.log
grep -E '\[BUG\]|Segmentation fault|Bus Error' "$PUMA"*

# 의심 저장소를 체크아웃한 곳에서: 500자 넘는 TODO 정의 줄
rg -l --glob '**/*.org' '^\#\+(TODO|SEQ_TODO|TYP_TODO):.{500,}'

소스 설치는 같은 로그를 /home/git/gitlab/log/ 아래에서, Helm 차트는 gitlab-webservice-* 파드의 webservice 컨테이너 출력에서 찾는다. 로그를 볼 때 기억해 둘 것이 세 가지다. 먼저 puma_stderr.log 는 로그 수집기로 기본 전송되지 않는 경우가 많으니, 수집 대상에 들어 있는지부터 확인한다. 수집기가 [BUG] 보고를 줄 단위로 쪼개 보내는 경우도 문제다. [BUG] 줄과 프레임 줄이 한 이벤트에 묶이지 않아 충돌 규칙이 걸리지 않으므로, 이때는 호스트별로 짧은 시간 창을 묶어 상관 분석해야 한다. 마지막으로 아무것도 걸리지 않았다고 안전하다는 뜻은 아니다. 탐지 문서는 시간 초과가 몰렸다가 조용해졌다면 공격이 멈춘 게 아니라 성공했을 수 있다고 경고한다.

흔적이 나오면 원 IP 를 막고, 문제의 .org 가 든 프로젝트를 비공개로 돌리거나 지운 다음, 재시작 전에 로그와 페이로드를 보존한다. Puma 아래 낯선 자식 프로세스, /tmp 의 git 소유 새 파일, 낯선 외부 연결, git 사용자의 crontab·SSH 키 변경 가운데 하나라도 보이면 침해로 본다. 그때는 gitlab-secrets.json, CI/CD 변수, 러너 토큰, 액세스 토큰을 모두 교체한다.

07 사내 GitLab 을 직접 운영하는 팀이 대상이고, 세부 공개를 기다릴 이유는 없다

국내에서 이 공지가 닿는 곳은 사내 서버나 클라우드 VM 에 GitLab CE/EE 를 직접 설치해 쓰는 개발·DevOps 팀이다. GitLab.com 을 쓰는 팀은 할 일이 없다. GitLab 이 이미 수정본을 올렸고, Dedicated 도 수정이 반영돼 있다고 밝혔다.

순서는 CERT-EU 권고를 따르면 된다. 인터넷에 노출된 인스턴스부터 올리고, 침해 여부 점검을 함께 한다. 그렇다고 사내망 전용 인스턴스를 뒤로 미룰 이유는 크지 않다. 탐지 문서가 그린 경로는 .org 파일 하나를 렌더링하게 하는 것이다. 저장소에 파일을 올릴 수 있는 내부 계정이나 협력사 계정이면 같은 길을 탈 수 있다고 보는 편이 안전하다.

시간도 넉넉하지 않다. The Register 는 10월 22일 기사에서 이번 결함이 실제로 악용된 정황은 아직 없다고 전했지만, GitLab 은 상세 이슈를 수정 90일 뒤에 공개하므로 2027년 1월 무렵이면 조건이 다 드러난다. 게다가 12일 결함은 Rapid7 이 공식 이미지에서 명령 실행까지 재현한 분석을 이미 공개했다. 90일을 유예 기간으로 볼 근거가 약한 셈이다. 몇 년째 업그레이드를 미룬 사내 인스턴스라면 정거장 업그레이드를 바로 일정에 올리고, 그 사이에는 탐지 문서 권고대로 공개 프로젝트와 외부 노출을 줄인다. 이것은 공식 우회책이 아니라 노출 면을 좁히는 조치일 뿐이다.

Ruby 로 서비스를 만드는 팀도 한 가지는 볼 만하다. 이번 결함은 GitLab 코드가 아니라 Onigmo 에서 터졌고, 사용자 입력이 그대로 Regexp.new 에 들어간 것이 출발점이었다. 같은 모양의 코드가 있다면 입력을 Regexp.escape 로 감싸 패턴이 아닌 글자로 다루게 한다. {·} 같은 메타 문자가 이스케이프되면 범위 반복 자체가 생기지 않는다.

08 자주 묻는 질문

19.2 계열인데 백포트 대신 19.3 으로 바로 올려도 되나

된다. 19.2 는 필수 정거장이고 다음 정거장은 19.5 라서, 19.2 에서 19.3.6 으로 가는 길에 거쳐야 할 정거장이 없다. 다만 19.3.6 에는 데이터베이스 마이그레이션이 있어 단일 노드라면 중단 시간을 잡아야 한다. 백포트인 19.2.9 와 이 점을 견주어 고르면 된다.

Org-mode 렌더링만 꺼 두는 설정은 없나

공지와 탐지 문서 어디에도 그런 설정은 없다. 탐지 문서가 권하는 것은 공개 범위와 외부 노출을 줄이는 정도다.

12일 패치 때 깔아 둔 탐지 규칙으로 충분한가

Onigmo 충돌 규칙에는 CI 경로 두 건과 이번 건의 프레임(headline.rb·match_at)이 함께 들어 있다. .org 렌더링 시간 초과 몰림을 보는 규칙은 이번 건 전용이므로 따로 깔아야 한다.