본문으로 건너뛰기
NEW · Blast Radius: 유출된 API 키 대응 플레이북 (AWS·GCP·Azure)
블로그로 돌아가기

GhostAction 사고 분석: 악성 워크플로가 Git 이력의 키를 수집하는 방식

공개 워크플로 100개의 네 버전과 우버 저장소의 변경 기록을 대조했습니다. Git 이력에서 삭제된 키를 수집하는 경로, 전송 실패를 정상 종료로 처리하는 동작, 자격증명의 권한에 따른 대응 순서를 설명합니다.

김동현
작성자
김동현
발행
12 분2,841 어절
Share:
GhostAction 사고 분석: 악성 워크플로가 Git 이력의 키를 수집하는 방식

사고 개요

2026년 10월 9일 06:25:55(KST), 우버의 공개 저장소 uber/athenadriver에 Security Audit이라는 악성 GitHub Actions 워크플로가 추가됐습니다. 이 워크플로는 저장소에서 API 키와 토큰을 찾아 외부 IP로 전송하도록 작성돼 있습니다. 현재 파일뿐 아니라 키를 추가하거나 삭제한 과거 커밋도 검색합니다.

GitHub 공개 이벤트에서 악성 커밋이 기본 브랜치에 반영된 시각과 다음 날 이전 커밋으로 복구된 시각을 확인할 수 있습니다. 같은 커밋 메시지와 날짜로 찾은 다른 저장소 100개에서도 같은 IP로 값을 보내는 코드를 확인했습니다. 파일 해시를 비교하면 네 가지 버전으로 나뉩니다. 100개는 확인한 코드의 수이며 실제 유출이 발생한 조직 수를 뜻하지 않습니다. 우버 저장소는 이 표본에 포함되지 않습니다.

워크플로를 변경할 수 있는 계정은 CI가 읽을 값과 전송할 목적지도 바꿀 수 있습니다. 삭제한 키가 Git 이력에 남아 있거나 실행 중 배포용 시크릿을 받는다면 현재 파일만 점검해서는 노출 범위를 알 수 없습니다. 네트워크를 차단한 재현 환경에서도 삭제한 합성 키가 전송 본문에 들어갔습니다. 전송을 대신한 함수가 실패를 반환했는데도 스크립트는 정상 종료했습니다.

아래에서는 공개 코드와 변경 기록, 합성 값으로 재현한 결과를 설명합니다. 실제 악성 워크플로의 통신 기록과 키 발급처의 로그는 확보하지 못했습니다. 따라서 유효한 키가 유출됐는지, 그 키로 다른 시스템에 접근했는지는 추가 확인이 필요합니다.

항목사고 개요
관찰한 변경보안 점검 이름의 자격증명 수집 워크플로 추가
변경 대상공개 저장소의 .github/workflows/security-audit.yml
변경 기록을 확인한 사례우버 저장소의 기본 브랜치 반영·부모 커밋으로 복귀
코드 확인 범위서로 다른 저장소 100개 표본, SHA-256 기준 4종, 우버 별도 사례
수집 경로현재 파일, 로컬 참조의 Git 패치, 일부 직접 참조한 시크릿
전송 목적지193.32.204[.]199, HTTP POST
피해 확대 조건실제 실행·값 접근·외부 전송·자격증명의 유효성과 권한
미확인 항목최초 접근 수단, 실제 악성 실행·수신, 유효 키 유출, 후속 침해, 전체 규모

워크플로 변경이 자격증명 수집으로 이어지는 경로

악성 코드는 새로 추가한 .github/workflows/security-audit.yml 파일에 들어 있습니다. 실행 조건은 push와 수동 실행(workflow_dispatch)입니다. push에는 브랜치나 경로를 제한하는 필터가 없습니다. 실행 환경은 ubuntu-latest이며 actions/checkout@v4에 fetch-depth: 0을 지정했습니다. 이 설정만으로 실제 실행을 확인할 수는 없으므로 실행 기록을 함께 살펴봐야 합니다.

GhostAction의 변경·수집·전송 경로. 기본 브랜치 반영은 공개 기록으로 확인했고 실제 악성 실행과 서버 수신은 확인하지 못했습니다.

워크플로 파일에는 CI가 실행할 명령이 들어 있습니다. 이 파일을 바꿀 수 있다면 자격증명을 검색하고 외부로 전송하는 명령도 추가할 수 있습니다. 따라서 저장소의 쓰기 권한을 점검할 때는 워크플로 변경을 누가 승인하는지, 실행 중 어떤 시크릿을 받는지, 러너가 외부로 통신할 수 있는지도 확인해야 합니다.

우버 저장소에 남은 변경 기록

아래 표에는 표본의 커밋 날짜와 우버 저장소의 공개 이벤트를 정리했습니다. Git의 committer date는 작성자가 설정할 수 있습니다. 따라서 커밋 날짜만으로 실제 공격 시각이나 워크플로 실행 시각을 특정하기는 어렵습니다. 시각은 UTC와 한국시간(KST)을 함께 표기했습니다.

UTCKST기록과 판단 근거
10월 8일 13:12:51~22:56:2610월 8일 22:12:51~9일 07:56:26표본 100건의 committer date 최소·최대
10월 8일 21:25:5410월 9일 06:25:54우버 커밋 e3c0dfa…에 워크플로 1개, 30줄 추가
10월 8일 21:25:5510월 9일 06:25:55PushEvent 23413193405: master의 head가 부모 ed473510…에서 악성 커밋으로 변경
10월 9일 16:56:4410월 10일 01:56:44PushEvent 23527033545: head가 악성 커밋에서 부모 커밋으로 복귀
10월 9일 16:56:4410월 10일 01:56:44공개 실행 목록의 Pages 작업이 부모 커밋을 대상으로 시작, 결과 success
10월 11일 조회 시점10월 11일 조회 시점현재 master는 부모 커밋, 해당 경로의 기본 이력 조회는 0건, 악성 SHA의 원본은 HTTP 200

첫 PushEvent의 actor는 henrywoo이며 복구 이벤트의 actor는 tyvsmith입니다. GitHub가 기록한 계정 이름만으로 당시 계정을 사용한 사람이나 계정 침해 경위를 알 수는 없습니다. 커밋 API에는 verified: false, reason: unsigned가 표시됩니다. 서명이 없다는 사실도 계정 탈취의 증거로 사용할 수는 없습니다.

복구 이벤트의 before에는 악성 커밋이, head에는 그 부모 커밋이 기록돼 있습니다. 조회 시점의 기본 브랜치도 부모 커밋을 가리킵니다. 따라서 기본 브랜치가 악성 변경 이전으로 돌아갔다는 점은 확인할 수 있습니다. 다만 어떤 명령으로 복구했는지, 다른 참조와 포크까지 정리했는지는 이 기록에 남아 있지 않습니다.

네 가지 버전에서 달라지는 수집 범위

확보한 파일 100개의 SHA-256을 비교하면 네 가지 버전으로 나뉩니다. 버전마다 검색하는 이력과 직접 참조하는 시크릿이 다르므로 대응 시 해당 저장소의 원본을 확인해야 합니다.

버전표본 수AWS 문맥 수집이력 입력 200,000줄 제한직접 참조하는 Actions secrets
A96있음있음없음
B1있음있음GHCR_TOKEN, GHCR_USERNAME
C2없음없음없음
D1없음없음FTP 관련 5개 이름

우버에서 확보한 파일의 해시는 A와 같습니다. B는 A에 특정 시크릿을 읽는 코드를 추가했습니다. C는 git log 출력을 바로 검색하고 AWS 키 주변의 줄을 수집하는 코드는 포함하지 않습니다. D는 C에 FTP 관련 시크릿을 읽는 코드를 추가했습니다.

D가 직접 참조하는 이름은 FTP_PORT, FTP_PROTOCOL, FTP_SERVER, FTP_USERNAME, MYSECRETPASSWORD입니다. 포트나 프로토콜, 사용자명만 확보했다고 서버에 로그인할 수 있는 것은 아닙니다. 실제로 제공된 값과 접속 대상을 확인하고 인증에 필요한 조합이 유효했는지 살펴봐야 합니다.

B·D는 ${{ secrets.NAME }}으로 이름을 지정한 시크릿을 읽습니다. 모든 시크릿의 이름을 찾아 순서대로 가져오는 코드는 없습니다. 지정한 값이 실행 중 제공됐는지, 환경 승인과 시크릿 접근 제한이 적용됐는지 확인해야 합니다. A·C는 시크릿을 직접 참조하지 않지만 파일과 Git 이력을 검색합니다. 따라서 Actions secrets가 비어 있어도 이력에 남은 키는 수집될 수 있습니다.

삭제된 키와 주변 값을 수집하는 방식

삭제된 줄도 검색 입력에 남습니다

A·B 버전은 checkout 이후 git log의 출력을 변수에 저장합니다.

bash
gl=$(git log -p --all 2>/dev/null | head -200000)

-p 옵션은 각 커밋에서 추가하거나 삭제한 줄을 출력합니다. 키를 현재 파일에서 지웠더라도 삭제한 줄에 값이 남아 있으면 검색에 걸립니다. --all은 로컬 참조와 HEAD에서 도달할 수 있는 커밋을 포함하므로 다른 브랜치의 키도 검색할 수 있습니다. 다만 가져오지 않은 참조나 참조를 잃은 객체까지 복원하지는 않습니다.

C·D는 git log -p --all 출력을 바로 grep에 전달합니다. A·B에 있는 이력 입력 200,000줄 제한은 적용하지 않습니다. 네 버전 모두 이력에서 찾은 문자열은 정렬하고 중복을 제거한 뒤 300개까지만 남깁니다. 현재 파일에서 찾은 값에는 이 개수 제한이 없으므로 전체 전송 본문이 300개 값으로 제한되는 것은 아닙니다. 노출 범위를 조사할 때는 실행 당시 가져온 참조와 커밋 패치도 확인해야 합니다.

현재 파일에서 삭제된 합성 키가 Git 패치에 남아 수집 본문으로 들어가는 경로

정규식 경고만으로 수집 실패를 판단할 수 없습니다

검색 정규식에는 AWS ID와 Git 호스팅 토큰, Google·AI 서비스의 API 키, Slack·SendGrid 키 형식이 포함돼 있습니다. A·B는 AWS 시크릿과 세션 토큰을 대입하는 구문도 검색합니다. 현재 파일을 검색할 때 A·B는 대소문자를 구분하지 않으며 C·D는 구분합니다.

OpenRouter와 AWS 대입문 패턴에는 PCRE 계열의 비캡처 그룹인 (?:…)가 사용됐지만 실제 명령은 grep -E입니다. GNU grep 3.11로 실행하면 ? at start of expression 경고가 발생합니다. 그러나 이 경고가 나와도 합성 OpenAI·Anthropic·OpenRouter 키와 AWS ID는 검색됐습니다. 합성 AWS 시크릿 대입문은 직접 검색에서 누락됐습니다. 이 결과는 재현에 사용한 입력과 구현에서 확인한 동작입니다.

키 ID 주변의 값도 함께 가져옵니다

A·B의 ctx·hctx는 AWS ID가 있는 줄의 앞뒤 두 줄도 가져옵니다. 현재 파일과 Git 이력에서 각각 최대 150줄을 남깁니다. 재현에 사용한 AWS 시크릿 대입문은 직접 검색에서 빠졌지만 AWS ID 바로 다음 줄에 있어 이 경로로 전송 본문에 포함됐습니다.

C·D에는 주변 줄을 가져오는 코드가 없습니다. 같은 합성 입력에서 AWS ID는 수집했지만 옆 줄의 시크릿 대입문은 수집하지 못했습니다. A·B의 노출 범위를 확인할 때는 정규식에 일치한 값과 함께 주변 줄에 있던 값도 살펴봐야 합니다. 이 값들이 실제 인증에 사용할 수 있는 키 쌍인지는 발급처에서 확인해야 합니다.

전송에 실패해도 스크립트는 정상 종료할 수 있습니다

전송 여부는 full에서 공백과 줄바꿈을 제거한 뒤 값이 남아 있는지로 결정합니다. 네 버전 모두 REPO= 정보가 들어가며 A·B에는 AKIA_CTX_START·AKIA_CTX_END도 들어갑니다. 따라서 키를 찾지 못해도 저장소 정보가 남아 전송을 시도합니다. 키가 없는 합성 입력에서도 같은 동작을 확인했습니다.

전송 명령은 curl -s -m 20 -X POST --data-binary "$full"입니다. 끝에 || true가 붙어 있어 전송 명령이 실패해도 성공 상태를 반환합니다. 또한 --fail 옵션을 사용하지 않아 HTTP 오류 응답만으로는 curl이 오류 상태를 반환하지 않습니다.

우버의 워크플로는 shell을 지정하지 않았습니다. GitHub 문서에 따르면 Linux의 기본 명령은 bash -e {0}이며 pipefail은 포함되지 않습니다. grep이 값을 찾지 못해 1을 반환해도 마지막 sort가 0을 반환하면 파이프라인은 성공할 수 있습니다. 합성 입력을 사용한 비교에서도 bash -e는 빈 검색 결과 이후 명령을 계속 실행했습니다. 같은 변수 대입에 -e -o pipefail을 적용하면 종료코드 1로 중단됐습니다.

격리 재현에서 확인한 전송 본문

네 버전의 run 코드를 같은 합성 입력으로 실행했습니다. 검색할 파일은 읽기 전용으로 제공했습니다. Git 명령에는 원격 저장소가 없는 임시 저장소에서 만든 패치를 고정 입력으로 제공했고 Actions secrets는 빈 문자열이나 합성 값으로 치환했습니다. curl 대신 전송 본문을 저장하고 종료코드 7을 반환하는 셸 함수를 사용했습니다. 컨테이너의 네트워크도 차단했습니다.

이 실험에서는 코드가 어떤 값을 모으는지, 전송할 본문을 어떻게 만드는지, 전송 실패 후 어떤 상태로 종료하는지 확인했습니다. 실제 GitHub 러너의 자격증명을 읽거나 공격자 서버에 접속하지는 않았습니다.

합성 조건ABCD
파일·이력·직접 참조한 시크릿에 키 없음전송 함수 호출전송 함수 호출전송 함수 호출전송 함수 호출
이력에서 삭제된 OpenAI 형식 표식본문 포함본문 포함본문 포함본문 포함
다른 브랜치의 AWS ID본문 포함본문 포함본문 포함본문 포함
AWS ID 다음 줄의 시크릿 대입문본문 포함본문 포함미포함미포함
이름을 지정한 시크릿에 합성 값 제공참조 없음2개 값 포함참조 없음5개 값 포함
전송 함수가 종료코드 7 반환스크립트 종료 0스크립트 종료 0스크립트 종료 0스크립트 종료 0

네 버전에 세 가지 입력 조건을 적용해 총 12회 실행했습니다. 각 실행의 전송 본문과 바이트 수, SHA-256, 종료 상태는 사본으로 보관했습니다. 별도로 명령을 비교한 실험에서는 이력의 200,001번째 줄에 둔 표식이 검색 입력에서 빠졌습니다. 고유 일치값 301개를 입력하면 결과는 300개로 줄었습니다.

네 가지 버전의 12개 격리 실행에서 전송 대체 함수는 7을 반환했지만 스크립트는 모두 0으로 종료했습니다. 실제 서버 전송 결과는 아닙니다.

자격증명의 권한에 따라 달라지는 피해 범위

악성 파일을 발견했다고 키 유출까지 확인한 것은 아닙니다. 워크플로가 실행됐는지, 값이 외부로 전송됐는지, 전송 당시 키가 유효했는지 확인해야 합니다. 이후 사용 여부도 별도 조사 대상입니다. 현재 확보한 근거로 확인할 수 있는 내용은 다음과 같습니다.

판단 단계확보한 근거현재 판단
수집·전송 코드 존재100개 고정 SHA의 원본 파일코드 증거 확보
우버 기본 브랜치 반영·복귀커밋·두 PushEvent·현재 head 대조반영 후 부모 커밋으로 복귀
삭제된 값·문맥의 수집 능력합성 입력의 캡처 본문해당 재현 환경에서 확인
실제 악성 워크플로 실행현재 공개 실행 목록확인 불가
외부 서버 수신·유효 키 유출실제 요청 본문·통신·발급처 상태 미확보확인 불가
다른 시스템 접근·배포·API 사용발급처와 대상 시스템의 감사 기록 미확보확인 불가

10월 8일~10일의 공개 실행 목록을 조회하면 부모 커밋을 대상으로 한 pages build and deployment 한 건이 반환됩니다. 결과는 success지만 악성 커밋의 Security Audit 실행 기록과는 다릅니다. 현재 목록에 악성 워크플로가 없다는 이유만으로 과거에도 실행되지 않았다고 결론 내릴 수는 없습니다.

자격증명별 가능한 영향

노출된 키가 계속 유효하다면 공격자가 할 수 있는 일은 키의 권한에 따라 달라집니다. 아래 표는 자격증명별로 가능한 영향과 확인할 자료를 정리했습니다. 이 사건에서 아래 피해가 실제로 발생했다는 뜻은 아닙니다.

수집 대상유효성과 권한에 따른 가능한 영향추가 확인 자료
AWS ID와 인접 시크릿·세션 값허용된 클라우드 리소스 접근·변경키 상태, 연결된 정책, 클라우드 감사 기록; ID만으로 인증 가능 여부 판정 불가
GitHub·GitLab 토큰 형식허용된 저장소·조직에 접근, 쓰기 권한이 있으면 추가 변경토큰 범위, 접근 가능한 저장소, 감사 기록
GHCR_TOKEN 직접 참조권한이 허용하는 컨테이너 접근·발행실제 제공 값, 패키지 권한, 레지스트리 발행 기록
FTP 관련 값 직접 참조유효한 인증 조합과 권한에 따른 서버 파일 접근·변경서버·계정 범위, 인증·파일 변경 기록
AI·Google·Slack·SendGrid 키 형식해당 서비스가 허용하는 API 사용, 데이터 접근 또는 비용 발생키 제한과 권한, API·사용량·접근 기록

대응 순서는 키가 현재 유효한지, 어떤 권한이 있는지를 기준으로 정해야 합니다. 패키지를 발행할 수 있는 토큰이라면 비인가 발행과 추가 공급망 변경을 조사해야 합니다. 읽기 권한만 있는 키라면 접근 가능한 데이터와 조회 기록부터 확인합니다. 같은 키를 여러 프로젝트에서 사용했다면 해당 프로젝트도 조사 대상에 포함합니다.

파일 삭제와 정상 완료만으로 조사를 끝낼 수 없는 이유

코드를 지워도 키는 계속 유효할 수 있습니다

파일에서 키를 지워도 Git 이력에는 값이 남을 수 있습니다. 발급처에서 폐기하지 않았다면 그 값으로 인증할 수도 있습니다. 공개 이력에 남은 키는 악성 CI가 실행되기 전부터 노출돼 있었을 가능성이 있습니다. 따라서 악성 워크플로를 차단한 뒤에도 노출 가능성이 있는 키를 찾아 폐기 여부를 판단해야 합니다.

코드 밖의 시크릿도 실행 조건을 확인해야 합니다

B·D는 코드 검색 결과와 함께 이름을 지정한 시크릿도 전송 본문에 넣습니다. 시크릿을 코드 밖에 보관해도 악성 워크플로가 값을 받으면 수집될 수 있습니다. 어떤 워크플로가 어떤 실행 조건과 승인을 거쳐 값을 받는지 살펴봐야 합니다. A·C에도 Git 이력 검색은 남아 있으므로 시크릿 직접 참조만 검사해서는 수집 코드를 모두 찾기 어렵습니다.

CI의 성공 표시로 유출 여부를 판단할 수 없습니다

재현에서는 전송 함수가 실패해도 네 버전 모두 종료코드 0을 반환했습니다. 키가 없는 입력에서도 전송 함수를 호출했습니다. 따라서 CI가 정상 완료됐다고 유출이 없었다고 판단해서는 안 됩니다. 외부 요청이 있었다는 기록도 유효한 키가 포함됐는지까지 보여주지는 않습니다. 실행의 head SHA와 당시 코드, 전송 내용, 발급처의 키 상태를 함께 확인해야 합니다.

복구 후에는 당시 커밋과 실행 기록을 확인해야 합니다

우버의 기본 브랜치는 부모 커밋으로 돌아갔습니다. 그러나 악성 커밋의 SHA로 요청하면 해당 파일을 여전히 내려받을 수 있습니다. 현재 파일이 없어도 과거 변경과 실행 기록은 조사해야 합니다. 이벤트의 before·head와 당시 실행 SHA를 사용하면 이전 상태를 확인할 수 있습니다. 악성 객체가 남아 있다는 사실만으로 워크플로가 지금도 자동 실행된다고 판단할 수는 없습니다.

침해 지표와 대응 우선순위

유형값 또는 동작적용 범위
목적지193.32.204[.]199확보한 네 버전 공통
요청 경로/?c=new 또는 경로 생략A·B / C·D
파일 경로.github/workflows/security-audit.yml확보 표본 공통
표시 이름Security Audit이름 단독 판정 불가
수집 구분자AKIA_CTX_START, AKIA_CTX_ENDA·B만 해당
수집·전송 조합Git 패치 검색·다수 키 정규식·본문 조립·외부 POST버전 차이를 포함한 내용 검토
이름을 지정한 시크릿 참조레지스트리 2개 또는 FTP 관련 5개 이름B·D만 해당

의심 워크플로를 찾으면 변경 기록과 해당 러너의 외부 통신을 함께 확인해야 합니다. 이 IP는 확보한 네 버전에서 확인한 목적지입니다. 같은 기능을 가진 코드가 다른 IP를 사용할 수 있으므로 목적지 하나만 차단해서는 충분하지 않습니다. 파일명이나 해시 하나로 검색해도 다른 버전을 놓칠 수 있습니다. C·D에는 문맥 구분자가 없지만 같은 IP로 검색 결과를 전송합니다.

확인 수준별 초기 조치

확보한 상황우선 조치다음 판단에 필요한 자료
의심 워크플로 추가 확인, 실행 여부 미확인추가 실행 차단, 변경 권한 검토·회수, 원본·커밋·이벤트 보존실행 ID, head SHA, 시작 시각, 조직 감사 기록
실행 확인, 전송 내용 미확보당시 파일·참조·제공 시크릿으로 노출 가능 범위 산정, 활성 고권한 자격증명부터 회수·교체 판단원본 버전, 러너 통신, 시크릿 제공 범위, 발급처 상태
외부 요청 확인, 유효 키 포함 여부 미확인요청과 실행 연결, 본문 또는 노출 가능한 값 확인; 요청 유무만으로 피해 확정 금지요청 본문, 프록시·러너 기록, 자격증명 유효성
활성 자격증명의 외부 노출 확인발급처에서 폐기·교체, 해당 권한의 사용 기록 조사API 호출, 로그인, 저장소·패키지·서버 변경 기록
비인가 사용 확인대상 시스템과 같은 자격증명을 사용한 프로젝트로 조사 확대추가 접근·권한 변경·배포 기록

악성 실행이 확인됐고 활성 고권한 키에 접근할 수 있었다면 전송 본문을 확보할 때까지 회수 판단을 미뤄서는 안 됩니다. 다만 코드 사본만으로 실제 유출이 확정됐다고 발표하기는 어렵습니다. 노출 가능성이 있는 키의 발급처와 담당자를 확인하고 권한에 따라 회수와 조사 순서를 정해야 합니다.

재발을 막으려면 워크플로 변경 승인과 브랜치 규칙, 계정·토큰의 쓰기 권한, 시크릿 제공 조건, 러너의 외부 통신을 점검해야 합니다. GITHUB_TOKEN을 읽기 전용으로 설정해도 파일에 남은 키나 별도로 받은 시크릿의 전송은 막지 못합니다. 워크플로를 변경할 수 있는 계정이 실행 환경의 자격증명에도 접근할 수 있는지 확인해야 합니다.

결론: 코드 복구 이후에는 자격증명을 확인해야 합니다

GhostAction의 악성 코드는 현재 파일과 Git 이력에서 키를 찾고 일부 버전에서는 Actions secrets도 함께 가져갑니다. 기본 브랜치를 복구해도 키가 폐기되지는 않습니다. 워크플로를 제거한 뒤에는 당시 접근할 수 있었던 자격증명을 확인하고 현재 유효성과 권한을 점검해야 합니다.

이번 조사에서는 공개 저장소 100개에서 네 가지 수집 코드 버전을 확인했습니다. 우버 저장소에서는 악성 커밋의 반영과 복구 기록을 별도로 확인했습니다. 합성 값을 사용한 재현에서는 삭제한 키가 전송 본문에 포함됐고 전송 함수가 실패해도 스크립트는 정상 종료했습니다. 실제 유출과 이후 사용 여부는 실행·통신·발급처 로그로 추가 확인해야 합니다.

우리 환경에 남은 키를 확인하려면

사고 대응에서는 어떤 키가 어디에 노출됐는지, 아직 사용할 수 있는지, 누가 조치할지를 알아야 합니다. Cremit은 연결한 GitHub·GitLab·Bitbucket·AWS S3·Slack·Jira 등에서 노출된 자격증명을 찾습니다. 발견 위치와 지원 유형의 유효성 확인 결과를 인벤토리에서 관리할 수 있습니다. AWS 액세스 키와 GCP API 키는 지원 범위에서 권한 정보도 확인할 수 있어 회수할 키와 조사할 시스템의 우선순위를 정하는 데 도움이 됩니다.

발견한 키에 담당자를 지정하고 발급처에서 폐기·교체를 진행합니다. 키를 찾았다는 사실과 현재 사용할 수 있는 키인지 확인한 결과를 구분해 관리해야 합니다.

월 2GB 무료 스캔으로 우리 환경에 노출된 키를 확인해 보세요. 카드 등록 없이 시작할 수 있습니다. 연동 범위와 대응 절차는 Cremit의 시크릿 스캐닝에서 확인할 수 있습니다.

조사 범위와 근거 자료

GitHub 검색 API에서 커밋 메시지 "Add security audit workflow"와 committer date 2026년 10월 8일~9일을 조건으로 조회했습니다. 기본 관련도 순으로 반환된 첫 페이지 100건에서 각 SHA의 .github/workflows/security-audit.yml을 내려받았습니다. 100개 모두 HTTP 200을 반환했고 저장소도 서로 다릅니다. 각 파일의 검색 명령과 본문 조립 코드, POST 목적지를 비교하고 파일의 SHA-256으로 버전을 분류했습니다. 이 표본은 무작위로 추출한 결과나 캠페인 전체를 조사한 결과가 아닙니다.

버전파일 SHA-256크기
A6d258db70677c99c08c9db37e002751eb0f0fb7c0cab2a4effd7c8fa8d115d191,837바이트
Bd614306dd52bf3884157d9fcff682d7cdeb097e13da1527727a0e0c37bf9eabb1,995바이트
C434b328d9d8c22b080fd945962323fe9452a6769c4d7bac44063b33abd8df17b1,239바이트
Dc986e10092eed61478490f684a73e7e6f4cd98f419a88bf2b1eefcb2c1d359211,639바이트

우버의 악성 커밋 SHA는 e3c0dfa777d650d566aec11858ca2fd869b68ee6이며 부모 커밋은 ed473510065ed1101f60270e27f6601eb1f41cea입니다. 커밋 API에는 파일 한 개와 30줄을 추가하고 삭제한 줄은 없는 것으로 기록돼 있습니다. 공개 이벤트와 실행 목록의 조회 응답도 사본으로 보관했습니다.

표본 목록에는 저장소와 커밋 SHA, 원본 URL, 조회 상태, 수집 시각, 파일 해시, 버전, 직접 참조하는 시크릿 이름을 기록했습니다. 재현에는 GNU grep 3.11·Bash 5.2.37을 설치한 네트워크 차단 컨테이너와 Git 2.39.5의 임시 저장소를 사용했습니다. 저장한 전송 본문에는 합성 값만 들어 있습니다.

검색 API가 반환한 전체 일치 건수에는 코드를 확인하지 않은 결과도 포함됩니다. 따라서 피해 저장소 수로 사용할 수는 없습니다. 첫 페이지에서 확인한 버전 비율을 전체 검색 결과나 비공개 저장소에 적용할 수도 없습니다. 공개 이벤트의 보존·반환 범위와 사라진 실행 자료, 조회 시점의 브랜치 상태만으로는 실제 실행과 유출 규모를 복원하기 어렵습니다. 계정 침해 경위와 피해를 확인하려면 GitHub 감사 기록과 러너 통신, 전송 본문, 발급처 로그가 추가로 필요합니다.

공개 원자료

명령과 플랫폼의 동작 근거

네트워크에 공유해보세요LinkedInX

이어서 읽기

다음 글을 메일로 받아보세요

Cremit의 월간 자격증명 보안 브리프. 한 통에 핵심만 담습니다.

이메일을 판매하거나 외부에 넘기지 않으며, 언제든 수신거부할 수 있습니다.