SK쉴더스 로고
ADT캡스캡스홈
SK쉴더스

Gitea 패치 적용 기능 Git 훅 설치 취약점 (CVE-2026-60004)

EQST Now | 2026.07.30

NOW Briefing

Brief 1 셀프호스팅 Git 저장소 플랫폼 Gitea의 패치 적용 기능에서 임의 명령 실행 취약점(CVE-2026-60004)이 공개됐습니다. 업로드된 패치 내용이 Git 훅 파일로 설치되면서 원격 코드 실행(RCE)으로 이어집니다.
Brief 2 저장소 쓰기 권한만 있으면 Gitea 서비스 계정 권한으로 명령을 실행할 수 있고, 이 경우 설정 파일의 비밀값과 데이터베이스·OAuth 자격증명, 서버에 마운트된 전체 저장소가 노출될 수 있습니다. 기본 설치가 회원가입을 허용하기 때문에 외부인이 계정을 직접 만들어 이 권한을 확보할 수 있습니다.
Brief 3 1.27.1 이상으로 즉시 업그레이드해야 합니다. 패치 전에 외부에 노출돼 있던 인스턴스는 침해를 가정하고 설정 파일 비밀값과 연동 자격증명을 폐기·재발급해야 합니다.

Gitea 패치 적용 기능 Git 훅 설치 취약점 (CVE-2026-60004)

■ 개요

Gitea는 자체 서버에 직접 설치해 운영하는(셀프호스팅) Git 저장소 플랫폼입니다. 2026년 7월 28일 공개된 공식 보안 어드바이저리에 따르면 이번 취약점은 사용자가 패치 텍스트를 올려 커밋으로 반영하는 기능(diffpatch)에서 발생하며, 1.17부터 1.27.0까지의 모든 버전이 영향을 받습니다.

악용에 성공하면 공격자는 Gitea 프로세스를 구동하는 운영체제 계정 권한으로 임의 명령을 실행할 수 있습니다. 성립 조건은 저장소 쓰기 권한, Git 2.32 이상, diffpatch 경로 활성화, 쓰기·실행이 모두 가능한 임시 파일시스템입니다. 기본 설치가 회원가입을 허용하고, 외부인이 계정을 만들고 자기 소유 저장소를 생성하는 것만으로 쓰기 권한 조건을 스스로 충족할 수 있기 때문에 CVSS 벡터는 권한 불필요로 산정됐습니다.

어드바이저리 본문에는 동작하는 Python PoC 코드 전문이 함께 실렸습니다. Gitea는 사내 개발 인프라의 중심에 놓이는 서비스여서, 한 번의 명령 실행이 소스 코드 전체와 CI 자격증명 노출로 이어질 수 있습니다. 타임라인은 7월 26일 수정 커밋 병합·백포트, 7월 27일 1.27.1 릴리스, 7월 28일 어드바이저리 공개, 7월 29일 외부 언론 보도 순입니다. 현재 공개 정보로는 실제 공격에 악용된 사례가 확인되지 않았습니다.

■ 요약

항목 내용
CVE ID CVE-2026-60004 (GHSA-rcr6-4jqh-j84m)
CVSS 점수 CVSS v3.1 9.8 / Critical (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
취약점 유형 CWE-94, 패치 적용 경로를 통한 Git 훅 설치형 코드 실행
영향/위험 • Gitea 서비스 계정 권한으로 임의 명령 실행 가능
• 설정 파일 app.ini의 비밀값 및 데이터베이스 자격증명 노출 가능
• OAuth·연동 자격증명, 마운트된 저장소 전체 접근 가능
• 악용 전제: 저장소 쓰기 권한, Git 2.32 이상, diffpatch 경로 활성, 임시 경로 쓰기·실행 가능
취약 버전 Gitea 1.17 ~ 1.27.0
패치 버전 1.27.1 (2026-07-27 릴리스, 임시 복제본을 작업 디렉터리가 있는 형태(non-bare)로 변경)

■ 기술 분석

서버는 POST /api/v1/repos/{owner}/{repo}/diffpatch 요청을 받으면 대상 저장소를 임시 경로에 복제한 뒤 git apply로 패치를 인덱스(커밋 전 변경 내용을 담는 Git의 준비 영역)에 적용합니다. 그런데 이 임시 복제본은 작업 디렉터리가 없는 저장소 형태(bare 클론)로 만들어집니다. 패치 적용 로직은 인덱스만 갱신하겠다는 --cached--index를 함께 지정하며, Git 2.32 이상에서는 여기에 3-way 병합 폴백(패치가 그대로 적용되지 않을 때 원본과 수정본을 비교해 병합을 재시도하는 동작) 옵션 -3이 추가됩니다.

cmdApply := gitcmd.NewCommand("apply", "--index", "--recount", "--cached",
    "--ignore-whitespace", "--whitespace=fix", "--binary")
if git.DefaultFeatures().CheckVersionAtLeast("2.32") {
    cmdApply.AddArguments("-3")
}

동일한 패치를 두 번 제출하면 이미 인덱스에 등록된 경로를 다시 신규 추가하게 되어 동일 경로 추가 충돌(add/add)이 발생합니다. 이때 -3 폴백 경로가 --cached의 의도를 무시하고 인덱스에 있는 경로를 실제 파일로 기록합니다. bare 클론에서는 저장소 루트가 곧 Git 내부 디렉터리($GIT_DIR)이므로, 이 파일은 Git이 실제로 참조하는 훅 디렉터리에 그대로 떨어집니다. 패치가 지정한 경로는 인덱스가 기록될 때 Git이 자동 실행하는 훅 스크립트 hooks/post-index-change이고, 파일 모드가 실행 권한(100755)으로 지정되기 때문에 패치 본문에 담긴 명령이 Gitea 계정 권한으로 실행됩니다.

7월 26일 수정 커밋은 취약한 로직을 고치는 대신 임시 복제본의 형태를 바꿨습니다. bare 대신 작업 디렉터리를 갖는 형태로 복제하도록 인자를 true에서 false로 변경해, 체크아웃된 파일이 훅 디렉터리가 아닌 별도 작업 디렉터리에 기록되게 했습니다.

/* [패치 전] v1.27.0 - services/repository/files/patch.go */
if err := t.Clone(ctx, opts.OldBranch, true); err != nil {
    return nil, err
}

/* [패치 후] v1.27.1 */
// here must NOT use bare repo, because the following git commands might operate working tree ("--index") directly
if err := t.Clone(ctx, opts.OldBranch, false); err != nil {
    return nil, err
}

■ PoC

공개된 PoC가 만드는 악성 산출물은 훅 경로를 새 파일로 추가하는 통합 diff 텍스트 한 건입니다. 대상 경로를 훅 스크립트 이름으로 지정하고, 파일 모드를 실행 가능하게 선언합니다. 발동 트리거는 별도 기법이 아니라 정상적인 diffpatch API 요청 자체이며, 같은 패치를 반복 제출하는 것이 앞서 설명한 충돌 조건을 만듭니다.

# 방어용으로 단순화한 악성 패치 산출물 골격
"diff --git a/hooks/post-index-change b/hooks/post-index-change\n"
"new file mode 100755\n"
f"index {'0'*40}..{blob_oid(hook)}\n"
"--- /dev/null\n"
"+++ b/hooks/post-index-change\n"
# ... 훅 본문(명령 실행 및 결과 반출 로직) 생략 ...

명령 실행 결과는 화면에 직접 반환되지 않고, 저장소의 별도 브랜치(refs/heads/output- 접두사)에 Git 오브젝트로 기록된 뒤 인증된 HTTP Git 프로토콜의 git fetch로 회수됩니다. 공격자가 만든 저장소에 용도가 불분명한 브랜치와 파일이 생기는 것이 이 공격의 관측 가능한 흔적입니다.

⚠️ WARN

이 코드는 실제 익스플로잇이 아니라 공개 PoC의 검증 관점을 방어적으로 단순화한 예시입니다.
어드바이저리에 실행 가능한 PoC 전문이 이미 공개돼 있으므로, 검증 결과를 기다리지 말고 즉시 패치를 적용해야 합니다.

■ 탐지 및 점검

• 관리자 화면 또는 gitea --version으로 현재 버전이 1.27.1 미만인지 확인합니다.

• 리버스 프록시와 웹 액세스 로그에서 POST 메서드의 /diffpatch 요청을 추출하고, 같은 저장소에 짧은 간격으로 2회 이상 들어온 사례를 우선 조사합니다.

• Gitea 서비스 계정으로 생성된 예상치 못한 자식 프로세스(셸, 네트워크 전송 도구, 스크립트 인터프리터)를 단말 탐지·대응(EDR) 또는 리눅스 감사 기록(auditd)에서 확인합니다.

• 저장소 디렉터리와 Gitea 임시 경로에서 hooks/post-index-change 파일이 존재하는지 점검합니다.

• 최근 생성된 계정과 해당 계정이 만든 저장소를 조회하고, 그 저장소에 생성된 비정상 브랜치와 커밋을 확인합니다.

✅ CHECK

임시 복제본은 요청 처리 후 삭제되므로, 훅 파일이 남아 있지 않다는 사실만으로 침해가 없었다고 판정하면 안 됩니다.
요청 로그와 프로세스 실행 기록을 함께 확인해야 판정이 가능합니다.

■ 대응 방안

• Gitea를 1.27.1 이상으로 업그레이드합니다. 이번 수정은 릴리스 노트의 SECURITY 항목이 아니라 MISC 항목의 refactor: git patch apply (#38637) (#38638)로 기재돼 있어, 릴리스 노트만 훑어보면 놓치기 쉽습니다.

• 즉시 업그레이드가 어렵다면 회원가입을 비활성화하거나 관리자 승인 방식으로 전환하고 인스턴스의 외부 노출을 차단합니다. 다만 기존 쓰기 권한 사용자에게는 효력이 없는 임시 대응책입니다.

• 패치 전에 외부에 노출돼 있던 인스턴스는 침해를 가정하고 app.iniSECRET_KEY·토큰 서명 키, 데이터베이스 비밀번호, OAuth 클라이언트 시크릿, Gitea Actions 러너 등록 토큰을 모두 폐기·재발급합니다.

• Gitea 임시 작업 경로가 실행 가능해야 성립하는 조건이므로, 해당 마운트에 noexec 적용이 가능한지 검토합니다.

• 조직 멤버십과 저장소 쓰기 권한 부여 내역을 재검토해, 외부 협력 계정에 불필요한 쓰기 권한이 남아 있지 않은지 확인합니다.

[참고 자료]

Gitea Security Advisory GHSA-rcr6-4jqh-j84m

services/repository/files/patch.go (v1.27.0)

services/repository/files/patch.go (v1.27.1)

Gitea Release v1.27.1

The Hacker News - New Gitea RCE Lets Repository Writers Plant a Git Hook to Run Shell Commands

  • #EQST_NOW
  • #취약점

관련 서비스

더 많은 보안 인사이트

SK쉴더스 유튜브 채널에서 확인하세요.

SK쉴더스 유튜브 채널에서 확인하세요.
보안 트렌드와 대응방법

매월 뉴스레터로 확인하세요.

매월 뉴스레터로 확인하세요.