
개요
| 항목 | 내용 |
| CVE ID | CVE-2026-68771 |
| 대상 | ComfyUI ≤ v0.23.0 |
| 취약점 유형 | CWE-502 (Deserialization of Untrusted Data) |
| CVSS 3.1 | 9.8 (Critical) – AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| 인증 요구 | 없음 (Unauthenticated) |
| 패치 | PR #14543, 커밋 94ee49b |
ComfyUI는 Stable Diffusion류 디퓨전 모델을 노드 그래프로 조립해서 돌리는 오픈소스 GUI/백엔드입니다. 이 CVE는 ComfyUI에 내장된 학습용 노드인 LoadTrainingDataset이, 디스크에서 읽어온 .pkl 파일을 torch.load()로 역직렬화하는 과정에서 안전장치(weights_only=True)를 빼먹었기 때문에 발생합니다. 그리고 결정적으로, 그 .pkl 파일을 공격자가 인증 없이 직접 서버에 업로드할 수 있다는 점이 이 취약점을 "치명적"으로 만듭니다.
즉 이 취약점은 단일 버그가 아니라 두 개의 독립적인 결함이 이어진 체인입니다.
- 파일 업로드 엔드포인트가 업로드되는 파일의 내용이나 확장자를 전혀 검증하지 않는다.
- 학습 데이터셋 로더가 그 파일을 pickle 기반으로 역직렬화하면서 안전 플래그를 켜지 않는다.
하나씩, 쉽게 뜯어보겠습니다.
왜 torch.load()가 위험한가
PyTorch의 torch.load()는 내부적으로 파이썬의 pickle 모듈을 사용합니다. pickle은 단순한 데이터 포맷이 아니라, "역직렬화할 때 이 객체를 어떻게 다시 만들지"를 실행 가능한 명령어(opcode) 스트림으로 저장합니다.
그중 REDUCE라는 opcode는 역직렬화 과정에서 (callable, args) 튜플을 만나면 그 callable을 args로 바로 호출합니다. 즉 pickle 파일 안에 이런 식의 객체를 심어두면,

이 파일을 누군가 pickle.load() (또는 torch.load())로 "불러오기"만 해도 os.system(...)이 실행됩니다. 압축을 풀거나 파일을 읽는 것뿐인데, 그 행위 자체가 코드 실행으로 이어지는 것이 pickle 역직렬화의 본질적인 위험입니다.
이 문제를 막기 위해 최신 PyTorch는 torch.load(f, weights_only=True)라는 옵션을 제공합니다. 이 옵션을 켜면 텐서·숫자·기본 컨테이너 같은 "안전한 타입"만 복원을 허용하고, 임의의 파이썬 객체(__reduce__를 가진 객체 포함)는 걸러냅니다. 문제는 옵션을 켜지 않으면 기본값이 False인 PyTorch 버전도 많다는 점이고, ComfyUI의 이 노드가 정확히 그 함정에 빠져 있었습니다.
소스코드 분석 ①: 취약한 로더 — comfy_extras/nodes_dataset.py
패치 전 코드의 핵심 부분입니다.

한 줄씩 쉽게 풀어보면:
- folder_name은 ComfyUI 워크플로우 그래프를 통해 사용자(=공격자)가 직접 입력하는 노드 파라미터입니다. 기본값은 "training_dataset"이지만 임의 문자열을 넣을 수 있습니다.
- os.listdir(dataset_dir)로 그 폴더 안에서 shard_*.pkl 패턴에 맞는 파일을 전부 긁어모읍니다. 파일을 "누가" "언제" 만들었는지는 전혀 확인하지 않습니다.
- 가장 중요한 줄: shard_data = torch.load(f). 여기엔 weights_only=True가 없습니다. 즉 파일 안에 latents(텐서), conditioning(텐서+딕셔너리) 외에 어떤 파이썬 객체가 들어 있어도 그대로 복원을 시도합니다. 앞서 본 Payload 클래스처럼 __reduce__를 가진 객체가 들어 있으면, 이 한 줄에서 바로 임의 코드가 실행됩니다.
이 코드만 보면 "그래도 서버 디스크에 악성 .pkl을 올릴 방법이 있어야 공격이 성립하지 않나?"라는 의문이 들 겁니다. 바로 그 방법이 두 번째 결함입니다.
소스코드 분석 ②: 검증 없는 업로드 — server.py의 /upload/image

이름은 /upload/image, 함수 이름도 image_upload지만, 실제로는 이미지인지 전혀 검사하지 않습니다.
- get_dir_by_type("output")을 호출하면 업로드 대상 디렉터리가 ComfyUI의 output 폴더로 결정됩니다. 공격자는 요청의 type 필드에 output을 넣기만 하면 됩니다.
- subfolder 파라미터는 공격자가 자유롭게 지정할 수 있고, 이 값이 그대로 LoadTrainingDataset의 folder_name과 매칭되는 하위 폴더가 됩니다.
- filename도 공격자가 지정한 그대로 사용됩니다. shard_0001.pkl처럼 LoadTrainingDataset이 찾는 패턴(shard_ 접두사 + .pkl 확장자)에 맞춰주기만 하면 됩니다.
- 유일한 보안 검사는 os.path.commonpath 비교뿐이며, 이건 "output 폴더 바깥으로 못 나가게" 막는 경로 탈출 방지용이지 "어떤 내용의 파일을 올리는지"와는 아무 상관이 없습니다. 확장자 화이트리스트도, MIME 타입 검사도, 파일 시그니처 검사도 없습니다.
- 그리고 이 라우트는 @routes.post("/upload/image")로 등록되어 있을 뿐, 별도의 인증 미들웨어가 걸려 있지 않습니다. ComfyUI를 기본 설정(인증 없음)으로 네트워크에 노출해뒀다면 누구나 호출할 수 있습니다.
결국 이 엔드포인트는 "이미지 업로드용"이라는 이름과 무관하게, 원하는 바이트를 원하는 파일명으로 output 디렉터리 임의 하위 폴더에 떨어뜨릴 수 있는 범용 파일 쓰기 기능이 되어버립니다.
두 결함이 만나는 지점
따로 보면 각각 "검증 로직이 허술한 업로드"와 "역직렬화 안전 플래그 누락"일 뿐이지만, 둘을 이으면 완전한 공격 체인이 됩니다. 전체 흐름은 첨부된 다이어그램과 같습니다.

- 공격자가 __reduce__로 악성 페이로드를 심은 pickle 파일을 shard_0001.pkl이라는 이름으로, type=output, subfolder=<임의폴더명> 파라미터와 함께 /upload/image에 올립니다. 인증 불필요.
- 서버는 내용 검증 없이 output/<임의폴더명>/shard_0001.pkl에 그대로 저장합니다.
- 공격자가 /prompt로 LoadTrainingDataset 노드가 포함된 워크플로우를 큐에 넣으면서, folder_name을 1번에서 쓴 <임의폴더명>과 동일하게 지정합니다. 이 역시 인증 불필요.
- 워크플로우가 실행되며 노드가 해당 폴더의 shard_*.pkl을 찾아 torch.load(f)로 엽니다. weights_only=True가 없으므로 pickle의 REDUCE opcode가 그대로 실행됩니다.
- 공격자가 심어둔 (callable, args)가 호출되며, ComfyUI 프로세스 권한으로 임의 명령이 실행됩니다.
업로드부터 실행까지 전 구간에 인증이 필요 없기 때문에, ComfyUI 인스턴스에 네트워크로 접근만 할 수 있으면 공격이 성립합니다.
패치 분석
공식 수정은 PR #14543, 커밋 94ee49b로 병합되었고, 변경된 줄은 딱 한 줄입니다.

PR 설명에 따르면 이 LoadTrainingDataset의 torch.load() 호출이 코드베이스 전체에서 weights_only=True가 빠진 유일한 지점이었다고 합니다(comfy/utils.py, comfy/sd1_clip.py의 다른 torch.load() 호출은 이미 이 플래그를 쓰고 있었음). weights_only=True가 켜지면 pickle 복원 과정에서 텐서·숫자·리스트/딕셔너리 같은 안전한 타입만 허용되고, __reduce__를 가진 임의 객체의 복원 자체가 거부됩니다. 즉 4번 단계에서 REDUCE opcode가 실행되기 전에 역직렬화가 차단됩니다.
다만 이 패치는 "업로드 검증 부재"라는 첫 번째 결함은 건드리지 않습니다. /upload/image는 여전히 임의 파일을 output 디렉터리에 쓸 수 있습니다. 다만 그 파일이 더 이상 LoadTrainingDataset을 통한 코드 실행으로 이어지지는 않는다는 점에서, 이번 패치는 공격 체인의 마지막 고리(역직렬화)를 끊는 방식의 수정입니다.
'CVE 취약점' 카테고리의 다른 글
| CVE-2026-29057: Next.js Rewrites HTTP Request Smuggling 분석 (0) | 2026.10.05 |
|---|---|
| CVE-2026-61500: Rejetto HFS 세션 위조를 통한 RCE (0) | 2026.10.04 |
| CVE-2026-67401 분석 — cPanel & WHM EmailTrack SQL Injection, 메일 계정이 root가 되기까지 (1) | 2026.10.02 |
| CVE-2026-12227: Visual Composer Website Builder 인증 없는 LFI 취약점 분석 (0) | 2026.10.01 |
| CVE-2026-87902 분석: WordPress Core 인증 없는 LFI, 그리고 조건부 RCE (0) | 2026.09.30 |