hyunsu blog

홈동향

GitLab 만 올리고 Ruby 는 그대로, Alpine 재구축 사례로 본 점검 순서

목차 · 7
  1. 01앞 글 뒤로 실제 공격 사례와 Ruby 업스트림 수정이 새로 나왔다
  2. 02Alpine 의 GitLab 은 두 번 올리고도 보름 동안 열려 있었다
  3. 03수정이 Ruby 안에 있어서 GitLab 버전만으로는 판단이 안 된다
  4. 04내 GitLab 이 어느 쪽인지 네 단계로 가린다
  5. 05흔적이 모자라자 Alpine 은 침해로 보고 새로 지었다
  6. 06Ruby 를 직접 챙기는 GitLab 이라면 지금 할 일은 Ruby 를 올리는 것이다
  7. 07자주 묻는 질문

GitLab 을 Ruby 와 따로 설치해 돌린다면, GitLab 을 수정 버전으로 올려도 CVE-2026-97229 는 남는다. GitLab 의 수정은 자기가 배포하는 패키지와 컨테이너 이미지 속 Ruby 에 넣은 패치였고, 소스 설치나 자체 빌드 이미지는 10월 23일 나온 Ruby 수정 릴리스까지 받아야 막힌다. Alpine Linux 는 이 차이를 놓쳐 GitLab 을 올린 뒤에도 닷새 더 취약했고, 그사이 공격자가 다시 들어오려 했다.

01 앞 글 뒤로 실제 공격 사례와 Ruby 업스트림 수정이 새로 나왔다

GitLab CVSS 10.0 정규식 RCE, 직전 긴급 패치로는 막히지 않는 이유와 버전표 뒤로 새로 나온 것은 두 가지다. 하나는 Ruby 가 10월 23일 4.0·3.4·3.3 세 라인에 낸 Onigmo 수정 릴리스다. 앞 글을 쓸 무렵에는 Ruby 정식 릴리스에 이 수정이 없어서, 소스 설치 운영자에게는 올릴 버전이 없었다. 다른 하나는 Alpine Linux 가 10월 28일 올린 사고 보고로, 자기 GitLab 서버가 10월 20~21일 CVE-2026-97229 악용 시도를 받았다는 내용이다.

이 글은 Alpine 사례가 드러낸 'Ruby 를 따로 쓰는 GitLab' 의 빈틈과 그 점검 순서만 다룬다. GitLab 라인별 수정 번호는 앞 글의 표를 그대로 쓰면 된다. 아래에서 버전은 라인과 패치 번호로 적는다. '19.3 라인 패치 5' 는 19.3 라인에서 패치 번호가 5 인 릴리스를 뜻한다.

02 Alpine 의 GitLab 은 두 번 올리고도 보름 동안 열려 있었다

GitLab 이 CVE-2026-97229 수정을 낸 날은 10월 12일이다. Alpine 은 CVE 가 공개된 13일에 바로 업그레이드했지만 목표를 잘못 골라, 수정이 든 19.3 라인 패치 5 가 아니라 패치 4 로 올렸다. 22일에 패치 6 으로 다시 올렸을 때는 Ruby 가 발목을 잡았다. Alpine 이 직접 만든 GitLab 이미지에는 GitLab 이 손본 Ruby 대신 업스트림 그대로의 Ruby 가 들어 있었다. 그래서 서버는 27일 외부 접근을 막을 때까지 취약했다.

날짜일어난 일
10월 12일GitLab 이 19.4·19.3·19.2 라인 수정 릴리스 공개
10월 13일CVE 공개. Alpine 이 패치 5 대신 패치 4 로 잘못 올림
10월 20일새로 가입한 계정이 서버를 살피기 시작
10월 21일본 공격. 조작한 CI 설정으로 작업자 프로세스가 8번 죽음(그중 4번은 익스플로잇 중간 단계까지 감). 502 오류와 CI 작업 실패
10월 22일19.3 라인 패치 6 으로 올림. Ruby 는 그대로
10월 23일공격 계정 차단. 같은 날 Ruby 업스트림 수정 릴리스
10월 24일저장소·패키지·21일 릴리스를 GitLab 밖 기록과 대조
10월 25~26일공격자 재진입 시도 실패. 26일에 서버가 여전히 취약함을 확인
10월 27일개발자·빌드 서버 말고는 접근 차단, 재구축 시작
10월 28일재구축 완료. 전체 비밀번호 초기화, GitLab 비밀 키 교체

공격자는 새로 만든 계정 하나로 움직였다. Alpine 은 그 공격자가 Alpine 의 GitLab 컨테이너 이미지를 내려받은 점을 들어, 익스플로잇을 Alpine 빌드에 맞춰 조정했을 수 있다고 봤다. 이번 공격자는 계정을 만들어 썼지만, GitLab 은 이 취약점의 CVSS 벡터에 '권한 필요 없음(PR:N)' 을 매겨 로그인 없이도 가능한 공격으로 평가했다. 회원 가입을 닫는 것만으로 충분하다고 볼 근거는 없는 셈이다.

03 수정이 Ruby 안에 있어서 GitLab 버전만으로는 판단이 안 된다

Onigmo 는 Ruby 에 내장된 정규식 엔진이다. Ruby 공지에 따르면 조작한 패턴을 컴파일하는 동안 메모리가 깨질 수 있고, GitLab 서버에서는 이것이 원격 코드 실행으로 이어질 수 있다. CVE-2026-97229 에 해당하는 버그는 컴파일된 패턴 크기의 정수 오버플로다. GitLab 은 CI 설정에 적힌 정규식을 평가하다 이 버그를 밟는다. CVSS 는 GitLab 이 매긴 10.0 이다.

닿는 조건은 Ruby 공지가 좁혀 두었다. 공격자가 고른 문자열로 정규식을 컴파일할 때, 곧 Regexp.new·Regexp.union·Regexp.compile 에 넘기거나 /#{input}/ 처럼 리터럴에 끼워 넣을 때만 문제가 된다. 개발자가 써 둔 정규식만 쓰는 앱은 해당하지 않는다. 다만 Regexp.timeout 은 이 버그를 막지 못하고, 짧은 패턴으로도 터지는 경우가 있어 길이 제한도 믿을 방어가 못 된다.

GitLab 이 수정을 어디에 실었는지 보면 버전만으로 안 되는 이유가 보인다. 10월 12일 패치 노트의 버그 수정 목록에는 Ruby 3.3·3.4 용 정규식 패치를 더하는 항목이 Omnibus 와 CNG(Cloud Native GitLab 이미지 빌드) 쪽에 있다. 10월 21일 릴리스에도 3.3·3.4·4.0 용 반복 ID 오버플로 패치가 같은 자리에 들어갔다. 애플리케이션 코드가 아니라, GitLab 이 직접 빌드해 싣는 Ruby 를 고친 셈이다.

설치 방식Ruby 를 대는 쪽할 일
GitLab.com·GitLab DedicatedGitLab없음
공식 Linux 패키지(Omnibus)·공식 이미지GitLab 이 패치해 번들GitLab 만 수정 버전으로
소스 설치운영자가 설치한 RubyGitLab 과 Ruby 둘 다
직접 또는 배포판이 만든 이미지(예: alpinelinux/gitlab)이미지 안의 배포판 Ruby둘 다 올리고 이미지 다시 빌드·받기

Ruby 쪽에서 무엇이 언제 고쳐졌는지는 Ruby 공지의 표를 라인별로 옮기면 이렇다.

GitLab CVERuby 쪽 버그4.03.43.3
CVE-2026-89078교대 연산자 뒤 파싱이 실패할 때 이중 해제첫 릴리스부터패치 12패치 13
CVE-2026-93577컴파일된 패턴 크기의 정수 오버플로패치 8패치 12패치 13
CVE-2026-97229바로 위와 같은 버그패치 8패치 12패치 13
CVE-2026-110264반복이 많은 패턴에서 내부 ID 오버플로패치 9패치 12패치 13
넷 모두 막는 최소—패치 9패치 12패치 13

4.0 라인은 패치 8 에 머물면 CVE-2026-110264 가 남는다. 3.3 은 원래 보안 수정만 받는 단계인데, GitLab 이 CVE 를 매긴 버그라 예외로 정규식 수정을 넣었다고 Ruby 는 밝혔다. 3.2 는 2026년 4월 1일 지원이 끝나 이번 수정 릴리스가 없다. Ruby 공지는 3.2 이하가 영향을 받는지 따로 적지 않았지만, GitLab 은 CVE-2026-110264 의 영향 범위를 GitLab 7.0 부터로 잡았다. GitLab 소스 설치 문서는 10월 30일 현재 17.5 이후 필요한 Ruby 를 3.2 로 적고 있으니, 그 최소 요건에 맞춰 둔 서버라면 라인을 3.3 이상으로 옮기는 편이 낫다.

Ruby 는 이번 수정에 자기 CVE 번호를 요청하지 않았고, 당분간 GitLab 의 CVE 번호로 추적하라고 했다. 이미지 스캐너나 배포판 보안 트래커에서 Ruby 패키지를 찾을 때도 GitLab CVE 번호로 찾아야 한다는 뜻이다. 실제로 OSV 의 Alpine 항목(ALPINE-CVE-2026-97229)은 gitlab 이 아니라 ruby 패키지를 영향 대상으로 올려 두었다.

04 내 GitLab 이 어느 쪽인지 네 단계로 가린다

  1. 설치 방식부터 본다. 공식 Linux 패키지나 공식 이미지라면 GitLab 버전만 앞 글 표와 맞추면 된다.
  2. gitlab:env:info 로 GitLab 버전과 Ruby 버전을 함께 본다.
  3. Ruby 를 따로 쓰면 Ruby 번호가 판단 기준이다.
  4. 컨테이너는 태그 이름이 아니라 받은 시점과 digest 로 판단한다.
bash
# 공식 Linux 패키지(Omnibus)
sudo gitlab-rake gitlab:env:info

# 소스 설치 — GitLab 설치 디렉터리에서
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production

출력에서 System information 아래 Ruby Version: 줄과 GitLab information 아래 Version: 줄을 본다. 공식 패키지라면 Ruby 줄의 번호는 기준이 아니다. GitLab 이 번들 Ruby 에 패치를 직접 넣었으니, GitLab 버전이 수정 버전 이상이면 된다. 소스 설치는 반대로, Ruby 번호가 위 표의 '넷 모두' 행보다 낮으면 GitLab 이 최신이어도 취약하다.

컨테이너는 같은 태그라도 내용이 바뀔 수 있다. Alpine 은 alpinelinux/gitlab 의 19.3 라인 패치 6 태그를 10월 27일 Ruby 3.3 패치 13 을 넣어 다시 게시했고, 그보다 먼저 받은 같은 태그와 더 오래된 태그는 모두 취약하다고 밝혔다. 태그 이름으로는 둘을 구별할 수 없으니 생성 시각과 digest, 컨테이너 안의 Ruby 를 직접 본다.

bash
# 받아 둔 이미지의 생성 시각과 digest
docker image inspect --format '{{.Created}} {{.RepoDigests}}' "alpinelinux/gitlab:$TAG"

# 실행 중인 컨테이너 안의 Ruby
docker exec "$CONTAINER" ruby -v

10월 30일 Docker Hub 의 alpinelinux/gitlab 페이지에는 19.3 라인 패치 7 태그도 새로 보였다. 페이지에 Ruby 버전은 적혀 있지 않으니, 어느 태그든 받은 뒤 ruby -v 로 확인하는 편이 낫다.

Alpine 패키지의 Ruby 를 쓰는 서버나 이미지라면 apk upgrade 로 수정판을 받는다. Alpine 이 밝힌 브랜치별 수정 Ruby 는 다음과 같다.

Alpine 브랜치수정이 든 Ruby
3.213.3 라인 패치 13
3.22·3.23·3.24·edge3.4 라인 패치 12

GeekNews 가 정리한 Hacker News 토론에는, 태그는 버전이 아니니 이미지를 SHA digest 로 고정하고 바뀌면 알림을 받자는 의견이 있었다. 작은 팀이라면 이 방식이 되돌리기에도 낫다. 문제가 생기면 직전 digest 로 돌아가면 되고, '다시 받으라' 는 공지가 나오면 내 digest 가 새것인지 바로 비교할 수 있다.

05 흔적이 모자라자 Alpine 은 침해로 보고 새로 지었다

Alpine 이 본 증상은 작업자 프로세스 비정상 종료 8번, 502 오류, CI 작업 실패였다. GitLab 도 10월 12일 패치 노트에 이 CVE 의 악용 시도를 잡는 탐지 규칙 여섯 개를 실었는데, 그중에 'Onigmo native worker crash' 와 'ci regexp guard rejection' 이 있다. Alpine 사례에서 밖으로 드러난 신호도 결국 정규식을 처리하던 작업자가 죽는 일이었다.

코드가 실행된 흔적은 나오지 않았다. 시간 지연 검사에서 이상한 지연이 없었고, 예상 밖의 파일·키·훅·프로세스도 없었으며, 확인할 수 있는 웹·API 응답 가운데 비공개 데이터를 내준 것도 없었다. 그래도 Alpine 은 '안 뚫렸다' 고 결론 내리지 않았다. 성공한 익스플로잇은 프로세스를 죽이지도 웹 로그를 남기지도 않을 수 있다. 게다가 공격 당일의 백그라운드 작업 로그와 이전 컨테이너의 파일시스템 일부가 사라졌고, 바깥으로 나간 연결은 Alpine 도 호스팅 업체도 기록하지 않았다.

공격이 성공했다면 새어 나갔을 수 있는 것으로 Alpine 은 다음을 꼽았다.

  • 전체 사용자의 이메일·이름·IP
  • 비밀번호 해시와 2FA 비밀값
  • CI/CD 변수와 러너 토큰
  • 웹훅·통합·푸시 미러 자격 증명, 해시로 저장된 개인 액세스 토큰
  • 비공개 저장소, 기밀 이슈와 머지 요청

재구축하면서 Alpine 은 모든 사용자 비밀번호를 초기화하고 GitLab 비밀 키를 바꿨다. 다른 곳에서 샌 비밀번호로 자동 로그인을 시도한 기록도 봤다며, 같은 비밀번호를 다른 서비스에서 썼다면 그쪽도 바꾸라고 했다.

공급망은 GitLab 밖의 기록으로 따로 확인했다. 10월 20~22일 들어온 aports 커밋을, 공격자 손이 닿지 않는 개발자 복제본과 빌더 45대의 기록에 맞춰 봤다. 10월 20~24일 빌드한 패키지와 10월 21일 안정 릴리스 네 개(ISO 와 공식 alpine 이미지의 바탕인 minirootfs 포함)도 검증된 커밋에서 나왔다. 패키지 서명 키는 GitLab 서버에 둔 적이 없다. 다만 GitLab 은 문을 닫은 27일까지 취약했으므로, Alpine 은 같은 검증을 그날까지 넓히는 중이다.

Alpine 이 밝힌 재발 방지책은 넷이다.

  • 치명 등급 GitLab 보안 릴리스는 하루 안에 적용하고, 그때마다 Ruby 같은 구성 요소 패치가 따로 필요한지 본다.
  • GitLab 호스트의 바깥 연결과 DB 연결을 기록한다.
  • 로그를 14일보다 길게 보관한다.
  • 릴리스 태그에 서명해 GitLab 없이도 검증할 수 있게 한다.

06 Ruby 를 직접 챙기는 GitLab 이라면 지금 할 일은 Ruby 를 올리는 것이다

대상은 셀프호스팅 GitLab 을 소스로 설치했거나, Dockerfile 에서 배포판 Ruby 위에 GitLab 을 얹은 이미지를 쓰는 팀이다. 이런 팀이 GitLab 보안 공지만 보고 끝내면 Alpine 의 10월 22일과 같은 상태가 된다. 지금 Ruby 를 위 표의 '넷 모두' 기준 이상으로 올리고, 이미지라면 다시 빌드한 뒤 컨테이너 안에서 ruby -v 로 확인한다. 공식 Linux 패키지·공식 이미지·GitLab.com 을 쓰면 Ruby 쪽으로 따로 할 일은 없고, GitLab 버전만 앞 글 표에 맞추면 된다.

GitLab 밖의 Ruby 서비스도 같은 버그에 닿을 수 있다. 사용자 입력으로 정규식을 만드는 자리가 있다면 Ruby 를 올리는 것이 답이고, 당장 못 올리면 그 자리에서 사용자 입력을 정규식으로 컴파일하지 말라는 것이 Ruby 의 권고다. Regexp.timeout 만 걸어 둔 상태는 방어로 치지 않는 편이 맞다. 그런 자리는 아래처럼 먼저 추려 볼 수 있다.

bash
# 사용자 입력이 정규식으로 컴파일될 수 있는 후보 찾기
grep -rnE 'Regexp\.(new|union|compile)|/#\{' app lib

10월 12일 이후 인터넷에 열린 GitLab 이 GitLab 이나 Ruby 쪽 수정 없이 돌았다면, 그 기간의 작업자 비정상 종료 기록부터 찾는다. 기록이 있는데 바깥 연결 로그도 이전 컨테이너도 남아 있지 않다면, Alpine 처럼 침해로 보고 다시 짓는 편이 낫다. 이때는 비밀번호 초기화와 GitLab 비밀 키 교체에 더해, 노출 목록에 든 CI/CD 변수 속 클라우드·배포 자격 증명과 러너 토큰도 바꾸는 편이 낫다. 업그레이드할 때 이전 컨테이너를 지우지 말고 멈춰만 두면, Alpine 이 잃은 파일시스템 증거를 남길 수 있다.

공식 alpine 베이스 이미지만 쓰는 팀은 지금 할 일이 없다. Alpine 은 10월 21일 릴리스와 24일까지 빌드한 패키지에서 변조를 찾지 못했고, 서명 키는 애초에 GitLab 서버에 없었다. 다만 검증을 27일까지 넓히는 중이니 후속 공지는 지켜볼 만하다. Alpine 에 기여하느라 gitlab.alpinelinux.org 계정이 있다면, 재개 후 로그인 화면의 'Forgot your password?' 로 새 비밀번호를 정하면 된다. 자세한 절차는 Alpine 이 따로 안내하겠다고 했다.

결정하는 쪽에서 보면, Ruby 를 직접 관리하는 설치에는 GitLab 보안 릴리스마다 구성 요소 패치까지 확인하는 일이 따라붙는다. 그 손이 없다면 공식 패키지로 옮기는 쪽이 길게 보면 가볍지만 이전 작업이 따로 드니, 이번 대응은 Ruby 를 올리는 것으로 먼저 끝내고 이전은 나중에 따로 따지는 편이 낫다.

07 자주 묻는 질문

소스 설치 문서는 Ruby 3.2 를 적는데, 3.3 으로 옮겨도 되나

3.2 에는 이번 수정 릴리스가 없으니 남는 길은 라인을 올리는 것뿐이다. GitLab 이 10월 12일 공식 패키지·이미지용으로 낸 정규식 패치도 Ruby 3.3·3.4 용이었다. GitLab 이 직접 빌드해 싣는 Ruby 가 이미 그 라인이라는 뜻이다. 그래도 문서 요건과 다른 라인이니, 운영 서버에 바로 바꾸기보다 같은 GitLab 버전으로 스테이징에서 먼저 돌려 보는 편이 안전하다.

노드를 여러 대로 나눈 GitLab 은 어디서 확인하나

GitLab 문서는 gitlab:env:info 를 GitLab Rails 가 도는 노드에서 실행하라고 한다. 다른 노드에서 돌리면 PostgreSQL 소켓 오류가 날 수 있다. Ruby 를 따로 설치한 구성이라면 Ruby 도 노드마다 따로 깔리니, Rails·Sidekiq 를 돌리는 노드마다 ruby -v 를 보는 편이 확실하다.