빌드하는 순간 뚫린다 — 컴파일만 해도 남의 서버에 접속하는 러스트 크레이트

빌드하는 순간 뚫린다 — 컴파일만 해도 남의 서버에 접속하는 러스트 크레이트

분류: CVE · 공급망·개발도구 · 빌드타임 코드 실행(Build-time RCE) · CVE-2026-77650 · [표준]

덱. 러스트(Rust) 패키지 하나가 “쓰기만 하면 위험한” 게 아니라 컴파일하기만 해도 위험한 형태로 공개됐습니다. append-only-vec 크레이트 0.1.9 버전은 겉으로는 평범한 자료구조 라이브러리인데, 딸려 오는 불량 의존성(rogue dependency) 이 빌드 과정에서 명령·제어(C2) 서버에 접속해 임의 코드 실행을 열어 줍니다. 코드를 실행하지도, 배포하지도 않고 그저 cargo build 한 번 돌린 개발자가 첫 희생자가 되는 전형적인 공급망 공격입니다.

개요

CVE-2026-77650은 러스트용 append-only-vec 크레이트(crate, 러스트 패키지)의 0.1.9 버전에 해당하는 취약점입니다. 공개된 설명에 따르면, 이 크레이트를 사용하는 프로젝트를 컴파일할 때 악성 코드가 실행되며, 그 원인은 이 크레이트가 명령·제어(C2, command-and-control) 서버에 등록해 임의 코드 실행을 제공하는 불량 의존성을 끌어온다는 데 있습니다.

  • 영향 대상: append-only-vec 크레이트 0.1.9 버전(crates.io 배포 러스트 패키지)
  • 취약점 클래스: 소프트웨어 공급망 공격 — 악성/불량 의존성을 통한 빌드타임 임의 코드 실행
  • 트리거 시점: 런타임이 아니라 컴파일 시점(해당 크레이트를 의존성으로 둔 프로젝트를 빌드할 때)
  • 결과: 빌드를 수행한 개발자·CI 환경에서 공격자 지정 코드 실행, C2를 통한 원격 제어 가능성
  • CVSS 3.1: 9.8(CRITICAL), 벡터 AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H — MITRE CVE 레코드 확정.
  • CWE: CWE-506(악성 코드 삽입, Embedded Malicious Code) — MITRE 확정(단일 분류, CWE-829 아님).
  • 패치/철회 상태: 0.1.9는 **게시 약 107분 만에 crates.io에서 yank(철회)**됐습니다(RUSTSEC-2026-0262). 안전 버전은 0.1.8 이하이며, 0.1.9를 대체하는 후속 정상 0.1.x는 없습니다.

분석 배지. 이 사건은 정상 유지관리자 droundy의 계정 탈취로 확인됐으며, 같은 계정탈취 캠페인이 arrayref 0.3.10·internment 0.8.7 크레이트도 함께 오염시켰습니다(“arrayref 공급망 공격” 캠페인, RustSec·safedep·stepsecurity 교차확인). 불량 하위 의존성의 실제 이름은 proc-macro1(정상 크레이트 proc-macro2의 타이포스쿼팅)이며, 이 크레이트가 프로시저 매크로가 아니라 자신의 build.rs 빌드 스크립트를 통해 빌드 시점에 코드를 실행합니다 — 이름은 “proc-macro”지만 실행 경로는 프로시저 매크로가 아니라 빌드 스크립트라는 점에 유의해야 합니다.

기술 배경 (30초)

러스트의 패키지 매니저 카고(Cargo) 는 프로젝트가 필요로 하는 라이브러리(크레이트)를 crates.io에서 자동으로 내려받아 함께 컴파일합니다. 여기서 흔히 놓치는 사실이 하나 있습니다 — 컴파일은 수동적인 작업이 아닙니다.

러스트에는 크레이트가 빌드될 때 실행되는 빌드 스크립트(build.rs) 와, 컴파일 도중 코드를 생성·실행하는 프로시저 매크로(proc-macro) 장치가 있습니다. 둘 다 개발자의 컴퓨터에서, 개발자의 권한으로, 빌드 중에 임의의 코드를 실행할 수 있습니다. 즉 어떤 크레이트를 의존성으로 추가하는 순간, 나는 그 크레이트(그리고 그것이 다시 끌어오는 하위 의존성 전부)에게 내 빌드 환경에서 코드를 돌릴 권한을 이미 내준 셈입니다.

그래서 공급망 공격에서 “빌드타임 실행”은 특히 위험합니다. 피해자가 악성 함수를 호출하거나 프로그램을 배포할 필요도 없이, cargo build·cargo test·IDE의 자동 빌드가 도는 순간 트리거됩니다. CVE-2026-77650의 핵심은 바로 이 빌드 시점 코드 실행 경로를, 정상처럼 보이는 크레이트에 딸려 온 불량 의존성이 열어 둔다는 것입니다.

CVE 분석 요약

공개 설명을 근거로 정리하면 인과는 다음과 같습니다.

  • 미끼가 되는 정상 인터페이스: append-only-vec는 “추가만 되는 벡터(append-only vector)“라는 평범하고 쓸모 있는 자료구조를 제공하는 것처럼 보입니다. 개발자는 이 API를 보고 안심하고 의존성에 추가합니다.
  • 숨은 불량 의존성: 그런데 0.1.9 버전은 정상 기능과 무관한 불량 의존성을 함께 끌어옵니다. 카고는 의존성의 의존성까지 재귀적으로 내려받으므로, 이 하위 크레이트도 자동으로 빌드 대상에 포함됩니다.
  • 빌드 시 C2 등록·코드 실행: 그 불량 의존성이 빌드 과정에서 외부 C2 서버에 접속·등록하고, 서버가 내려 주는 임의 코드를 실행할 통로를 엽니다. 공개 요지가 “컴파일할 때 악성 코드가 실행된다”고 밝히는 지점이 여기입니다.

정상 크레이트의 겉모습이 신뢰를 빌려 주고, 그 뒤에 붙은 하위 의존성이 실제 악성 행위를 수행하는 신뢰 전가(trust delegation) 구조입니다. 악성 코드는 proc-macro1이라는 불량 하위 크레이트의 build.rs 빌드 스크립트를 통해 빌드 시점에 실행됩니다 — 이름이 proc-macro2(정상, dtolnay 관리)를 노린 타이포스쿼팅이라는 점도 신뢰를 노린 위장입니다.

의존성 관계와 실행 시점만 개념적으로 도식화하면 다음과 같습니다.

// 개념용 단순화 — 실제 소스/페이로드 아님
[내 프로젝트]  ──의존──▶  append-only-vec 0.1.9  (겉: 정상 자료구조 API, droundy 계정 탈취로 오염)
                                     └──의존──▶  proc-macro1 (proc-macro2 타이포스쿼팅)
                                                     │  (cargo build 시점, build.rs 실행)
                                                     ├─▶ C2 서버에 등록
                                                     └─▶ 서버 지정 임의 코드 실행
// 피해자가 함수를 "호출"하지 않아도, 빌드가 도는 순간 트리거된다.

개념용 단순화 — 실제 페이로드/원본 코드 아님. 의존성명(proc-macro1)·실행 장치(build.rs)는 RustSec/safedep 확증, 정확한 C2 주소는 무해화를 위해 싣지 않습니다.

빠진 방어는 대단한 게 아니라 “내가 빌드하는 코드가 신뢰할 수 있는 것인가”를 의존성 트리 전체에 대해 되묻는 습관입니다. 신뢰 경계를 넘어온 코드(남의 크레이트)를 검증 없이 빌드 파이프라인에 들인 전형적인 형태입니다.

공격 시나리오 · 영향도 + 대응방안

시나리오(개념). ① 공격자가 정상처럼 보이는 크레이트 버전(0.1.9)에 불량 의존성을 심어 crates.io에 노출합니다. → ② 개발자가 이 크레이트를 의존성에 추가하고 cargo build(또는 test·IDE 자동 빌드)를 돌립니다. → ③ 카고가 하위 불량 의존성까지 내려받아 빌드하면서 악성 코드가 실행됩니다. → ④ 그 코드가 C2 서버에 등록하고, 서버가 내려 준 임의 명령을 빌드 환경에서 수행합니다. 개발자가 악성 함수를 직접 호출할 필요가 없습니다. (작동하는 코드·페이로드는 싣지 않습니다.)

영향도. 확정된 범위는 취약 버전을 의존성으로 둔 프로젝트를 컴파일하는 것만으로 임의 코드가 실행된다는 점입니다. 실행 주체는 빌드를 돌린 개발자 계정 또는 CI 러너이며, 그 권한 범위 안에서 소스코드·자격증명(환경변수·토큰·SSH 키)·서명 키 탈취, 개발 환경 지속 감염, 나아가 그 환경에서 만들어지는 산출물 오염으로 확산될 수 있습니다(합리적으로 예상되는 후속 영향 — 분석 추정). C2가 살아 있는 동안 실행되는 코드가 무엇인지는 서버가 정하므로 영향 상한을 미리 못박기 어렵다는 점이 공급망 공격의 특성입니다. 이 사건이 더 넓은 arrayref 공급망 캠페인(같은 계정탈취 수법이 arrayref 0.3.10·internment 0.8.7도 오염)의 일부라는 점에서, 이 세 크레이트 중 하나라도 의존성 트리에 있었다면 함께 점검해야 합니다.

대응방안.

  1. 근본 — 해당 버전 즉시 제거, 0.1.8 이하로 고정. append-only-vec 0.1.9 의존을 프로젝트에서 제거하는 것이 최우선입니다. Cargo.lock·의존성 트리(cargo tree)에서 이 버전이 직접·간접(다른 크레이트가 끌어오는 형태)으로 들어와 있는지 확인하고, 안전 버전인 0.1.8 이하로 고정합니다. 0.1.9는 이미 crates.io에서 yank됐으므로 신규 빌드에서는 자동으로 배제되지만, 락파일에 남아 있는 기존 참조는 직접 확인·교체해야 합니다. 같은 계정탈취 캠페인에 걸린 arrayref(안전 버전 확인 필요)·internment 0.8.7도 함께 점검합니다.
  2. 완화·사고대응(이미 빌드한 적이 있다면). 취약 버전을 한 번이라도 빌드한 개발 머신·CI는 이미 코드가 실행됐다고 가정하고 다룹니다 — 해당 환경에서 접근 가능했던 토큰·API 키·SSH/서명 키를 폐기·재발급하고, 아웃바운드 네트워크 로그에서 낯선 C2 접속 흔적을 조사하며, 필요하면 러너를 재구성(clean rebuild)합니다. 앞으로는 빌드 샌드박스화(네트워크 차단·격리 컨테이너에서 빌드), --locked/락파일 고정, 의존성 감사 도구(cargo audit, cargo vet, cargo deny) 도입으로 방어 심도를 더합니다.
  3. 탐지 관점. 빌드 중 발생하는 예상 밖의 아웃바운드 네트워크 연결(컴파일 단계에서 외부 접속은 정상이 아닙니다), 새로 추가·변경된 하위 의존성, build.rs/proc-macro가 있는 크레이트의 갑작스러운 유입을 신호로 봅니다. 락파일 diff 리뷰와 빌드 환경의 이그레스(egress) 모니터링이 실전 탐지점입니다.
  4. 공지 감시. crates.io 보안 공지·RustSec advisory(RUSTSEC-2026-0262)·arrayref 캠페인 관련 issue를 계속 확인해 추가 오염 버전 정보를 즉시 반영합니다.

논평 — 왜 이 실수가 반복되나

이 사건은 특정 러스트 크레이트 하나의 문제가 아니라, “의존성을 추가하는 행위 = 남의 코드에 내 빌드 환경을 내주는 행위” 라는 생태계 공통의 신뢰 모델에서 자랍니다. 우리는 다른 언어 생태계에서 이미 같은 뿌리를 여러 번 봤습니다 — npm의 타이포스쿼팅·포스트인스톀 스크립트 악용, PyPI의 setup.py 실행 악성 패키지, 그리고 여러 CI 파이프라인 오염 사고. 표면은 언어마다 다르지만 공통점은 하나입니다: 패키지 매니저가 코드를 내려받아 실행하는 지점이 곧 공격면이라는 것.

러스트가 메모리 안전으로 유명하다는 점이 오히려 함정이 됩니다. “러스트니까 안전하겠지”라는 인상이 빌드 시점 신뢰 문제라는 전혀 다른 축의 위험을 가립니다. 메모리 안전은 내가 짠 코드가 런타임에 저지르는 실수를 막아 주지만, 빌드타임에 남의 코드가 무엇을 하는지는 언어의 안전성과 무관합니다. CVE-2026-77650은 “안전한 언어”라는 방패가 공급망 신뢰라는 다른 문에는 걸리지 않는다는 걸 보여 줍니다.

결국 이는 우리가 다른 글에서 반복해 짚은 것과 같은 계열 — 신뢰 경계를 넘어온 것(외부 신원 주장이든, 외부 크레이트든)을 자기 손으로 다시 확인하지 않은 것 — 입니다. 소셜 로그인 플러그인이 “로그인했다는 주장”을 검증 없이 믿었다면, 여기서는 빌드 파이프라인이 “이 의존성은 안전하다는 암묵적 가정”을 검증 없이 믿었습니다.

결론

한 줄 요약. 어떤 크레이트를 의존성에 추가하는 것은 그 크레이트와 그 하위 의존성 전부에게 내 빌드 환경에서 코드를 돌릴 권한을 주는 일입니다. CVE-2026-77650은 정상처럼 보이는 크레이트(append-only-vec 0.1.9)가 그 권한을 빌려, 불량 의존성이 컴파일 시점에 C2로 임의 코드를 실행하게 한 공급망 공격입니다.

이 한 버전은 철회되고 고쳐지겠지만, 똑같은 사고방식은 다음 크레이트·npm 패키지·CI 액션에서 또 나타납니다 — 그래서 우리는 개별 악성 패키지가 아니라 “빌드가 도는 순간 무엇이 실행되는가”를 되묻는 방법을 남깁니다. 의존성 트리와 빌드 이그레스를 보는 눈이 곧 방어입니다.

References / 참고자료

  1. MITRE CVE 레코드 — CVE-2026-77650 (이 글이 재구성한 1차출처 메타, 상태 PUBLISHED, 등재 2026-08-21). CVSS 9.8·CWE-506·영향 버전 근거. https://cveawg.mitre.org/api/cve/CVE-2026-77650
  2. RustSec Advisory Database — RUSTSEC-2026-0262. yank 상태(게시 약 107분 후 철회)·안전 버전(≤0.1.8)·불량 의존성(proc-macro1, build.rs 실행) 1차출처. https://rustsec.org/advisories/RUSTSEC-2026-0262.html
  3. Rust 공식 블로그, “Supply chain attack on arrayref” (2026-08-20) — 계정탈취 캠페인·arrayref/internment 동반 오염 근거. https://blog.rust-lang.org/2026/08/20/supply-chain-attack-on-arrayref
  4. 공급망 방어 도구 문서 — cargo audit(RustSec), cargo vet, cargo deny, Cargo.lock --locked 빌드. 대응방안 근거(일반 실무 레퍼런스).

이 글은 위 1차출처(MITRE CVE 레코드·RustSec advisory·Rust 공식 블로그)를 우리 관점에서 다시 쓰고 논평을 더한 것입니다. 특정 문장·코드를 그대로 옮기지 않았으며, 작동하는 익스플로잇/페이로드는 싣지 않았습니다. 도식은 개념 설명용입니다. 이 초안은 RN-11 독립 팩트체크(MITRE CVE 레코드 + RustSec advisory 직접 대조, 2026-08-31)를 통과했습니다.