FlawOpen

flawopen.com/Teardowns/cve-2026-102437-deepseek-reasonix-git-filter-command-injection

● CVE-2026-102437 · CVSS 7.8[2] · 높음
보안 연구 · FlawOpen

CVE-2026-102437: Reasonix diff 보기 Git 명령 주입

조작된 .gitattributes/.git/config 쌍은 Reasonix가 diff를 표시할 때 Git clean 필터를 실행할 수 있습니다. npm 패키지는 1.39.3, Studio는 2.21.0으로 업데이트하세요.[1], [2], [3], [6], [7]

💡 알기 쉬운 설명 (ELI5)

두 라벨만 비교한다고 약속한 분류대를 떠올려 보세요. 소포에는 내용을 비교하기 전에 등록된 도우미를 실행하라는 경로 메모가 붙을 수 있습니다. Reasonix는 Git의 일부 도우미를 비활성화했지만 파일별 clean 필터는 별도의 경로였습니다. diff 화면이 Git에 파일 준비를 요청할 때 이 필터가 개발자 권한으로 실행될 수 있었습니다.[1], [3]

핵심 개념 및 용어

Git clean 필터
작업 트리의 파일 내용을 저장소에 저장된 형식으로 바꾸기 위해 Git이 실행할 수 있는 구성된 변환기입니다.[1], [3]
.gitattributes
저장소에서 추적되는 파일로, 특정 경로에 이름이 지정된 필터를 할당할 수 있습니다.[1]
.git/config
필터가 호출할 명령을 정의할 수 있는 저장소 로컬 Git 설정입니다. 일반 clone은 이 로컬 파일을 복사하지 않습니다.[1], [3]
Diff 뷰어
Git에 파일의 작업 트리 내용을 준비하고 커밋된 내용과 비교하도록 요청하는 기능입니다.[1]

프레임워크 매핑

  • 이 가이드에서 설명하는 약점의 CWE 참조: CWE-78

근본 원인 분석 (Root Cause)

Reasonix는 git diff 호출에 --no-ext-diff 와 --no-textconv 를 적용했지만, 이 옵션은 외부 diff와 텍스트 변환 메커니즘을 제어하며 Git의 clean 콘텐츠 필터를 비활성화하지 않습니다. 작업 공간 변경 화면은 선택한 경로에 대해 git diff 를 호출했습니다. 저장소 내용이 .gitattributes 로 해당 경로에 필터를 할당하고 로컬 Git 설정이 그 드라이버의 명령을 정의하면 Git은 비교 blob을 만들 때 해당 명령을 실행할 수 있습니다. CVE는 로컬 공격자를 설명하며 사용자가 diff를 열어야 합니다. GitLab 공개 내용에 따르면 일반 clone은 .git/config 를 전달하지 않습니다. 아카이브, 동기화 폴더, 캐시 또는 미리 구성된 devcontainer에는 이 파일이 포함될 수 있습니다. 동일한 사용자 권한을 가진 로컬 에이전트가 저장소의 로컬 설정을 작성하는 경우도 설명합니다. 영향은 Reasonix 사용자 권한으로 로컬 명령이 실행되는 것이며, GitLab 호스팅 저장소를 열기만 해도 원격 코드가 실행되는 것은 아닙니다.[1], [3]

단계별 공격 실행 흐름

단계 1

작업 공간에 신뢰할 수 없는 저장소 상태가 들어옴

개발자가 추적 중인 attributes가 파일에 필터를 할당하고 로컬 Git 설정이 그 필터를 정의하는 프로젝트를 엽니다. 유지 관리자 안내는 두 요소 모두를 요구하며 일반 clone만으로 로컬 설정이 전달되지는 않습니다.[1], [3]

단계 2

개발자가 파일 diff를 요청함

Reasonix 변경 화면이 선택한 파일에 대해 Git diff 경로를 실행합니다. 이 UI 동작이 CVE에서 요구하는 사용자 상호작용입니다.[1], [3]

단계 3

Git이 clean 필터를 적용함

Git은 비교할 내용을 준비하면서 설정된 clean 필터를 호출할 수 있습니다. --no-ext-diff 와 --no-textconv 는 다른 메커니즘을 비활성화하므로 이 필터를 막지 못합니다.[1], [3]

단계 4

필터가 사용자 권한으로 실행됨

설정된 필터 명령이 Reasonix 프로세스의 권한으로 실행됩니다. 문서화된 결과는 해당 사용자 권한으로 로컬 명령이 실행되는 것입니다.[1], [2], [3]

소스 코드 비교: 취약한 구현 vs 보안 패치

이 인자 빌더는 설명용 예시이며 그대로 적용할 수 있는 패치가 아닙니다. 호출자는 .gitattributes, .git/info/attributes, 전역/시스템 속성 파일이 선택한 모든 driver를 열거하고, 전체 검색을 성공적으로 마친 뒤에만 discoveryComplete를 설정해야 합니다. 이 함수는 전달된 이름을 검증하고 명령줄 설정 재정의를 생성할 뿐 driver 집합을 찾아내거나 검증하지 않습니다. 취약한 호출은 GitLab 공개 자료에서 인용했으며, 방어 패턴은 유지관리자 지침과 병합된 hardening PR을 바탕으로 새로 종합한 것입니다.[1], [3], [4], [5]

소스 코드 발췌: desktop/workspace_changes.go

취약한 구현
// GitLab Threat Research가 인용한 소스 호출입니다. 전체 함수가 아니라 호출 지점입니다.
args := []string{"-C", base, "diff", "--no-ext-diff", "--no-textconv", "--relative", "HEAD", "--", filepath.FromSlash(rel)}
보안 강화 패치
// Go 인자 빌더 예시이며 공급업체 패치를 그대로 옮긴 것이 아닙니다.
package gitguard

import (
    "errors"
    "fmt"
    "path/filepath"
    "regexp"
)

// 호출자는 유효 속성이 선택한 모든 필터를 찾아 전체 집합을 전달해야 합니다.
// 이 도우미는 모든 Git 설정 범위에서 해당 driver 키를 무력화할 뿐입니다.
var filterDriverName = regexp.MustCompile(`^[A-Za-z0-9_-]{1,64}$`)

func hardenedDiffArgs(base, rel string, drivers []string, discoveryComplete bool) ([]string, error) {
    if !discoveryComplete {
        return nil, errors.New("cannot safely diff: filter-driver discovery failed")
    }
    args := []string{"-C", base}
    seen := map[string]struct{}{}
    for _, driver := range drivers {
        if !filterDriverName.MatchString(driver) {
            return nil, fmt.Errorf("cannot safely diff: unsupported Git filter driver name %q", driver)
        }
        // Git config 하위 섹션 이름은 대소문자를 구분하므로 driver 이름의 대소문자를 변환하지 않습니다.
        key := driver
        if _, ok := seen[key]; ok {
            continue
        }
        seen[key] = struct{}{}
        for _, setting := range []string{"clean", "smudge", "process", "required"} {
            args = append(args, "-c", "filter."+driver+"."+setting+"=")
        }
    }
    return append(args, "diff", "--no-ext-diff", "--no-textconv", "--relative", "HEAD", "--", filepath.FromSlash(rel)), nil
}

엔지니어링 및 시스템 보안 강화 체크리스트

출처