NOW Briefing
secret_key_base를 이용해 원격 코드 실행(RCE)까지 연결되는 과정을 재현했습니다.
Rails Active Storage 파일 읽기·RCE 취약점 (CVE-2026-66066)
■ 개요
Active Storage는 Rails에 기본 포함된 파일 첨부·저장 기능입니다. 업로드된 이미지에서 크기 정보를 뽑거나 썸네일 같은 변환본(variant)을 만들 때 외부 이미지 처리 라이브러리를 호출하는데, Rails 7.0 이후 기본 설정에서는 이 처리기로 libvips가 선택됩니다. Active Storage는 신뢰할 수 없는 업로드 파일을 libvips에 넘기면서, libvips가 자체적으로 "악성 입력에 안전하지 않다"고 표시해 둔 로더를 차단하지 않았습니다. 이 로더들은 무작위 입력 검증(퍼징)을 거치지 않아 안전성이 보장되지 않으며, 웹 이미지와 무관한 과학·전용 포맷 처리기도 포함합니다.
영향 조건은 Active Storage의 libvips 사용과 신뢰할 수 없는 사용자의 이미지 업로드 허용입니다. 별도의 변환본 생성 기능은 필요조건이 아니며, 업로드 직후 수행되는 이미지 분석만으로도 libvips가 호출될 수 있습니다. 악용에 성공하면 Rails 애플리케이션 프로세스가 읽을 수 있는 서버 파일과 환경변수가 노출될 수 있습니다. 여기에는 secret_key_base, 마스터 키, 스토리지·데이터베이스 자격증명, 서드파티 API 토큰이 포함됩니다. secret_key_base는 Rails가 쿠키와 내부 메시지를 서명·암호화할 때 사용하는 핵심 비밀값입니다. 공개된 제3자 PoC는 이 값에서 Active Storage 검증용 키를 유도해 서명값을 위조하고 원격 코드 실행으로 연결했습니다.
Rails 보안팀은 2026년 7월 29일 공지와 함께 패치 릴리스를 배포했고, CVE는 7월 30일 NVD에 등록됐습니다. 보고자인 Ethiack 팀(André Baptista, Bruno Mendes, Rafael Castilho)과 GMO Flatt Security의 RyotaK은 각각 독립적으로 이 취약점을 발견한 뒤 공개를 조율했으며, 패치 적용 시간을 확보하기 위해 기술 상세와 PoC 공개를 2026년 8월 28일로 미뤘습니다. 그러나 조율 공개 당일 제3자가 격리된 Docker 실험 환경에서 파일 읽기부터 코드 실행까지 재현하는 PoC 저장소를 공개했습니다. 현재까지 벤더가 공표한 침해 지표(IoC)나 실제 공격 악용 확인 사례는 보고되지 않았습니다.
■ 요약
| 항목 | 내용 |
|---|---|
| CVE ID | CVE-2026-66066(KindaRails2Shell) |
| CVSS 점수 | CVSS v4.0 9.5 / Critical (CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H) — CNA(GitHub) 산정, NVD 자체 분석은 대기 중 |
| 취약점 유형 | CWE-1188, 안전하지 않은 기본값으로 리소스 초기화(검증되지 않은 이미지 로더 미차단) |
| 영향/위험 |
• 구성에 따라 미인증 또는 낮은 신뢰도 사용자의 서버 임의 파일 읽기 가능 • • 탈취한 비밀값을 이용한 세션 위조 및 원격 코드 실행 가능 • 연결된 클라우드·DB 등 외부 시스템으로 내부 확산 가능 |
| 취약 버전 |
• activestorage < 7.2.3.2 • activestorage >= 8.0.0.beta1, < 8.0.5.1 • activestorage >= 8.1.0.beta1, < 8.1.3.1 • (조건: |
| 패치 버전 |
• 7.2.3.2 • 8.0.5.1 • 8.1.3.1 • (추가 요건: libvips 8.13 이상, ruby-vips 2.2.1 이상 필요) |
■ 기술 분석
패치 전 Active Storage의 vips 분석기는 ruby-vips를 적재한 뒤, 업로드된 파일 경로를 그대로 Vips::Image.new_from_file에 넘겼습니다. libvips는 파일 확장자나 선언된 content type이 아니라 실제 바이트 내용을 보고 로더를 선택합니다. 따라서 .bmp나 .png로 올린 파일이라도 내부 바이트가 다른 포맷이면 그 포맷의 로더가 동작합니다. 차단 장치가 없는 상태에서는 안전성이 검증되지 않은 로더도 선택 대상에 포함됐습니다.
공개된 제3자 PoC는 HDF5 기반 MATLAB 파일을 처리하는 matload 연산이 포함된 libvips 빌드를 전제로 합니다. 이 포맷은 데이터셋의 실제 내용을 파일 바깥의 다른 경로에서 읽어오도록 정의할 수 있습니다. 참조 경로에 /proc/1/environ 같은 서버 로컬 파일을 지정하면 이미지 변환 결과물 안에 그 파일 내용이 픽셀 값으로 실려 나옵니다. 이 조건은 공개 PoC의 재현 조건이며, Rails 보안팀은 다른 공격 체인이 존재할 가능성도 배제하지 않았습니다.
패치는 개별 로더를 하나씩 고치는 대신, 부팅 시점에 안전성이 검증되지 않은 연산 전체를 차단하는 방식을 택했습니다. 신규 파일 activestorage/lib/active_storage/vips.rb가 Vips.block_untrusted(true)를 호출하고, 분석기와 변환기가 이 파일을 먼저 요구하도록 바꿨습니다. 이 API를 제공하지 않는 구버전 환경에서는 조용히 취약한 상태로 동작하지 않고 부팅 시 예외를 발생시킵니다.
# [패치 전] activestorage/lib/active_storage/analyzer/image_analyzer/vips.rb
gem "ruby-vips"
require "ruby-vips"
# ... 생략 ...
::Vips::Image.new_from_file(file.path, access: :sequential)
# [패치 후] activestorage/lib/active_storage/analyzer/image_analyzer/vips.rb
require "active_storage/vips"
# [패치 후] activestorage/lib/active_storage/vips.rb (신규 파일)
# ... ruby-vips 적재 및 가용성 판별 생략 ...
require "image_processing/vips"
unless Vips.respond_to?(:block_untrusted)
raise <<~ERROR.squish
# ... libvips 8.13 / ruby-vips 2.2.1 이상 요구 메시지 생략 ...
ERROR
end
Vips.block_untrusted(true)
패치 적용 후에는 BMP·ICO·PSD 첨부의 변환이 Vips::Error를 발생시키고, SVG·JPEG XL·JPEG 2000·Netpbm은 이미지 크기 정보가 기록되지 않습니다. 다만 첨부·저장·다운로드 자체는 영향을 받지 않습니다.
■ PoC
공개된 제3자 PoC는 matload 연산을 사용할 수 있는 격리된 Docker 환경의 Rails 8.1.3 기본 앱에서 파일 읽기와 코드 실행을 재현합니다. 공격자 측 산출물은 512바이트 사용자 블록(user block)을 가진 HDF5 파일입니다. 여기에는 environment라는 이름의 데이터셋(1×1024 uint8)이 외부 저장 방식으로 정의돼 서버의 대상 파일 경로(/proc/1/environ)에 매핑됩니다. 사용자 블록에는 MATLAB 5.0 헤더가 기록되고, 이 바이트는 다이렉트 업로드(direct upload) 엔드포인트로 생성한, 아직 첨부되지 않은 업로드 파일 레코드(blob)에 .bmp로 선언돼 업로드됩니다.
피해자 측 트리거는 정상적인 변환본 조회입니다. PoC는 먼저 정상 PNG를 올려 얻은 변형 키(variation key)를 악성 blob ID와 결합해 변환본 URL을 요청하고, 이 정상 동작만으로 libvips가 해당 파일을 처리하면서 매핑된 파일 내용이 노출됩니다. 이후 PoC는 확보한 SECRET_KEY_BASE로 Active Storage 검증자 키를 유도해 서명값을 위조하고 역직렬화(직렬화된 객체를 복원하는 처리) 단계까지 연결하지만, 해당 구간은 아래 예시에서 제외했습니다.
# 방어용으로 단순화한 예시
with h5py.File(output, "w", userblock_size=512) as mat_file:
dataset = mat_file.create_dataset(
"environment",
shape=(1, byte_count),
dtype="<u1",
external=[("/proc/1/environ", 0, byte_count)], # 서버 파일을 데이터셋으로 매핑
)
dataset.attrs.create("MATLAB_class", b"uint8", dtype="S5")
# 산출물은 .bmp로 선언돼 이미지 업로드 경로로 전달됨
# ... 비밀값 회수 이후의 서명 위조·역직렬화 체인 생략 ...
이 코드는 실제 익스플로잇이 아니라 공개 PoC를 방어적으로 단순화한 예시입니다.
조율 공개 당일 동작하는 공개 PoC 저장소가 등장했으므로, 패치를 최우선으로 적용해야 합니다.
■ 탐지 및 점검
• Gemfile.lock에서 activestorage 버전을 확인해 7.2.3.2 / 8.0.5.1 / 8.1.3.1 미만이면 영향 가능 버전으로 분류합니다.
• Rails.application.config.active_storage.variant_processor 값이 :vips인지 확인합니다. load_defaults 7.0 이상이면 별도 설정이 없어도 기본값이 :vips입니다.
• 익명 업로드, 가입 직후 프로필 이미지 등록, 일반 사용자의 첨부 파일 업로드처럼 신뢰할 수 없는 사용자가 이미지 파일을 제출할 수 있는 기능을 확인합니다. 별도의 변환본 생성 화면이 있는지는 영향 여부를 가르는 조건이 아닙니다.
• 배포 환경에서 vips --version으로 libvips가 8.13 이상인지 확인합니다. 8.13 미만이면 패치된 Active Storage가 차단 기능을 켤 수 없어 부팅 시 예외가 발생합니다.
• 저장된 blob 중 content type은 이미지인데 실제 바이트에 HDF5 시그니처(\x89HDF\r\n\x1a\n)가 포함된 파일이 있는지 점검합니다. PoC 산출물은 이 시그니처가 파일 선두가 아니라 512바이트 뒤에 위치합니다.
• 다이렉트 업로드 엔드포인트로 미첨부 blob이 생성된 직후 해당 blob의 변환본 URL이 조회된 요청 쌍을 웹 서버 로그에서 확인합니다.
익명 업로드 폼이 없더라도, 가입 직후 프로필 이미지 업로드처럼 낮은 신뢰도의 계정이 접근하는 변환 경로가 있으면 동일하게 노출 대상입니다.
■ 대응 방안
• activestorage를 7.2.3.2 / 8.0.5.1 / 8.1.3.1 이상으로 업그레이드하고, 동일 배포 이미지에서 libvips 8.13 이상과 ruby-vips 2.2.1 이상을 함께 확보합니다.
• 즉시 업그레이드가 어려우면 임시 대응책으로 VIPS_BLOCK_UNTRUSTED 환경변수를 설정하거나 이니셜라이저에서 Vips.block_untrusted(true)를 호출합니다. 두 방법 모두 libvips 8.13 이상(이니셜라이저 방식은 ruby-vips 2.2.1 이상)이 전제입니다. ruby-vips를 분석 용도로만 쓰고 있다면 의존성 자체를 제거하는 것도 공식 권고에 포함돼 있습니다.
• 앱 프로세스가 읽을 수 있었던 모든 비밀값을 폐기하고 재발급합니다. secret_key_base, 마스터 키, 스토리지 서비스 키, 데이터베이스 자격증명, 서드파티 토큰이 대상입니다.
• 업그레이드 후 BMP·ICO·PSD 변환이 실패하면 해당 content type을 config.active_storage.variable_content_types에서 제외하는 방식을 우선 검토합니다. 특정 로더 재활성화는 신뢰할 수 있는 콘텐츠만 처리하는 경로에 한정합니다.
secret_key_base를 교체하면 기존 세션·서명 쿠키가 무효화되므로, 세션 강제 만료에 따른 사용자 영향과 재로그인 안내를 함께 계획해야 합니다.
[참고 자료]
• GHSA-xr9x-r78c-5hrm (GitHub Advisory Database)
• rails/rails commit 1c01bb5 — Disable libvips's unfuzzed loaders
• KindaRails2Shell - Critical RCE in Rails via Active Storage (Ethiack)
• CVE-2026-66066: Rails Active Storage Arbitrary File Read and RCE (HeroDevs)



