01. Executive Summary
•
사건개요
◦
2026.03.19 (UTC) 개발·보안 환경에서 사용되는 오픈소스 보안 스캐너인 Trivy의 공급망이 침해되어 공식 배포 채널을 통해 악성 버전의 파일 배포
▪
CI/CD 파이프라인에서 실행된 악성 Trivy가 탈취한 자격증명 정보들을 기반으로 NPM, PyPI, OpenVSX, Github Actions 등 다수의 생태계를 연쇄적으로 감염시킨 공급망 공격 사례
▪
악성 파일은 ‘환경변수 기반 정보ʼ, ‘Git/SSH 자격증명ʼ, ‘클라우드 자격증명(AWS, GCP, Azure)ʼ, ‘쿠버네틱스 설정ʼ, ‘암호화폐 지갑 정보ʼ, ‘DB 관련 정보ʼ, ‘CI/CD설정’, ‘로컬 계정 정보’ 등 각종 자격증명 정보 탈취
◦
Trivy 공급망 공격(CVE-2026-33634)를 시작으로 연쇄적인 공급망 공격이 발생됨에 따라 시간 흐름에 따라 5개의 피해 사례별로 분석 진행
▪
(공격흐름) : ① Trivy 공급망 공격(CVE-2026-33634) → ② NPM CanisterWorm 확산 → ③ Checkmarx/OpenVSX → ④ LiteLLM PyPI 감염 → ⑤ Telnyx PyPI 감염
•
대응방안
◦
(감염 여부 점검)
▪
자동화 파이프라인에서 하기 노출 시간 동안 다운로드, 설치, 또는 실행한 아티팩트 유무 점검 필요
구분 | 대상 | 노출 시간 (UTC) | 노출 시간 (KST) |
Aqua / Trivy | Trivy v0.69.4 | 2026-03-19 18:22 ~ 21:42 | 2026-03-20 03:22 ~ 06:42 |
Aqua / GitHub Action | trivy-action | 2026-03-19 17:43 ~ 2026-03-20 05:40 | 2026-03-20 02:43 ~ 14:40 |
Aqua / GitHub Action | setup-trivy | 2026-03-19 17:43 ~ 21:44 | 2026-03-20 02:43 ~ 06:44 |
Aqua / Docker Hub | trivy v0.69.5, v0.69.6 | 2026-03-22 15:43 ~ 2026-03-23 01:40 | 2026-03-23 00:43 ~ 10:40 |
Checkmarx / GitHub Action | kics-github-action
ast-github-action | 2026-03-23 12:58 ~ 16:50 | 2026-03-23 21:58 ~ 2026-03-24 01:50 |
Checkmarx / OpenVSX | ast-results-2.53.0.vsix
cx-dev-assist-1.7.0.vsix | 2026-03-23 02:53 ~ 15:41 | 2026-03-23 11:53 ~ 2026-03-24 00:41 |
LiteLLM / PyPI | LiteLLM v1.82.7, v1.82.8 | 2026-03-24 10:39 ~ 16:00 | 2026-03-24 19:39 ~ 2026-03-25 01:00 |
Telnyx / PyPI | Telnyx v4.87.1, 4.87.2 | 2026-03-27 03:51:28 ~ 10:13 | 2026-03-27 12:51:28 ~ 19:13 |
NPM | [별첨] Trivy 공급망 공격 영향 대상 목록 참고 | - | - |
▪
시스템에 연결된 Github 계정에 tpcp-docs 리포지터리 생성 여부 확인
▪
감염 추정 시 영향 받은 아티팩트 즉시 삭제 후 안전한 버전으로 업데이트, 잠정 노출된 자격 증명 정보 및 비밀번호 교체
◦
(시스템 이상 여부 점검)
▪
하기 파일 발견 시 즉시 삭제
파일 | ~/.local/share/pgmon/service.py | ~/.config/sysmon/sysmon.py | /tmp/pglog | /tmp/.pg_state | %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe |
서비스 | ~/.config/systemd/user/pgmon.service | ~/.config/systemd/user/sysmon.service |
◦
(네트워크 통신 확인) : IoC 정보 참고하여 통신 이력 점검
02. Background
2.1. 주요 타임라인
공격 대상 | 일자 (UTC) | 내용 | 출처 |
Trivy | 2026년 2월 말 ~ 2026.03.01 | • 공격자가 Trivy의 GitHub Actions 환경의 잘못된 구성 설정을 악용하여 권한 있는 액세스 토큰을 탈취하여 저장소 자동화 및 릴리스 프로세스에 대한 접근 권한 확보
• Trivy에서 사고 확인 후 자격 증명 정보 교체 작업을 수행했으나, 교체가 완전히 동시에 이루어지지 않아서 공격자가 새로운 유효한 자격 증명 정보를 확보했을 가능성 제기 | |
Trivy | 2026.03.19 | • 공격자는 탈취한 자격 증명을 악용하여 악성 버전인 Trivy v0.69.4를 릴리스하고 aquasecurity/trivy-action 76/77개 태그, aquasecurity/setup-trivy 기존 태그 7개 (v0.2.0~v0.2.6)를 모두 악성 Commit으로 푸시
• checkmarx[.]zone, models.litellm[.]cloud 악성 C2 도메인 등록 | |
NPM | 2026.03.20 ~ | • 공격자가 탈취한 NPM 자격증명을 이용해 NPM에 자기전파형 웜(CansiterWorm)을 유포하여 @EmilGroup 28종, @opengov 16종, 기타 스코프 패키지 등 총 66개 이상의 패키지 감염 | |
Trivy | 2026.03.22 | • 공격자가 별도로 탈취한 Docker Hub 자격증명을 이용해 GitHub 릴리스 없이 aquasec/trivy:0.69.5, v0.69.6 도커 이미지 직접 푸시 | |
Checkmarx / OpenVSX | 2026.03.23 | • Checkmarx의 kics-github-action, ast-github-action 및 OpenVSX 확장 프로그램 2종(cx-dev-assist 1.7.0, ast-results 2.53.0)에서 악성코드 감염 정황 발견 | |
LiteLLM | 2026.03.24 | • PyPI의 LiteLLM v1.82.7, v1.82.8 패키지에서 악성코드 감염 정황 발견 | |
Trivy | 2026.03.26 | • 미국 CISA에서 해당 취약점(CVE-2026-33634)을 KEV(Known Exploited Vulnerability)에 등재 | |
Telnyx | 2026.03.27 | • PyPI의 Telnyx v4.87.1, v4.87.2 패키지에서 악성코드 감염 정황 발견
| |
Trivy | 2026.03.27 | • 싱가포르 CSA에서 해당 캠페인을 “Ongoing 'TeamPCP' Supply-Chain Campaign”으로 추적 |
2.2. TeamPCP 정보
•
이번 캠페인에서 TeamPCP Cloud Stealer 사용 흔적이 발견되어 공격자를 TeamPCP로 추적중
•
2026년 2월 Flare의 보고서에서 언급된 TeamPCP(PCPcat, ShellFoce, DeadCatx3)는 2025년 12월부터 클라우드 네이티브 환경을 대상으로 하는 대규모 공격 수행
◦
웜을 이용하여 노출된 Docker API, Kubernetes 클러스터, Ray 대시보드, Redis 서버, React2Shell 취약점 등 악용
•
◦
Data Leak Site의 경우 CIPHERFORCE 랜섬웨어 그룹의 사이트를 같이 사용하고 있어 두 그룹이 같은 계열사이거나 협업하는 것으로 추정 중
03. Trivy 공급망 공격(CVE-2026-33634) 세부 설명
•
(주요 원인)
◦
이번 침해는 2026년 2월 말에 발생한 공격의 연장선으로 당시 공격자는 GitHub Actions 환경 설정 문제를 악용해 privileged token 탈취
▪
잘못 구성된 GitHub Actions workflow를 악용하였고, 해당 workflow는 저장소 비밀 정보에 대한 접근 권한을 보유한 상태
◦
3월 1일 사건 공개 이후 Trivy 측에서 자격 증명 교체 작업을 수행했지만 모든 자격 증명이 동시에 폐기되지 않았음
▪
공격자가 유효한 토큰을 사용하여 교체 기간 동안 새로 교체된 비밀 키를 유출했을 가능성이 있으며, 이로 인해 공격자가 접근 권한을 보유한 채로 3월 19일에 공격을 실행한 것으로 추정
•
(공급망 공격 과정)
◦
Trivy v0.69.4 침해
▪
actions/checkout의 참조를 가짜 Commit(70379aad)으로 바꾸는 (1885610c) Commit을 Push
▪
가짜 Commit에는 타이포스쿼팅된 도메인으로부터 악성 Go 소스 파일을 다운로드하는 작업이 포함됨
▪
goreleaser가 바이너리 유효성 검사를 통과할 수 있도록 -skip=validate 추가한 commit을 v0.69.4로 태깅 후 Release 파이프라인 실행
•
해당 release는 Trivy의 정규 배포 채널인 GHCR, ECR Public, Docker Hub(0.69.4, latest 태그 둘다), deb/rpm packages, get.trivy.dev등을 통해 배포
•
공격자가 v0.70.0 악성 버전 배포를 시도했으나 이는 사전에 차단
◦
trivy-action 태그 하이재킹
▪
공격자는 77개의 버전 태그 중 76개를 악성 Commit에 강제로 푸시하여 entrypoint.sh에 Infostealer를 Injection
▪
삽입된 악성코드는 정상 Trivy scan 보다 먼저 실행되어 악성 행위 수행
◦
setup-trivy 릴리스 교체 작업 수행
▪
기존 태그 7개 (v0.2.0~v0.2.6) 모두 악성 Commit으로 강제 푸시
▪
교체된 악성 action.yaml에는 trivy-action에서와 동일한 Infostealer가 정식 Trivy 설치 전에 실행되는 환경 설정(Setup environment) 단계에 삽입
◦
Trivy v.0.69.5, v0.69.6 도커 이미지 배포
▪
공격자는 v0.69.4 버전에 사용된 것과 동일한 C2 도메인을 사용하는 aquasec/trivy:0.69.5 and aquasec/trivy:0.69.6 페이로드 생성
▪
별도로 탈취한 Docker Hub 자격 증명 정보를 이용해 Github을 거치지 않고 Docker Hub에 직접 푸시하여, 이와 관련한 Github 태그나 릴리스는 존재하지 않음
•
(악성코드 동작 과정)
1.
Github Runner 관련 프로세스별 env, ssh 등 자격 증명 정보 수집하여 시스템 환경(GitHub Hosted Runners / Self Hosted Runners) 식별
2.
식별된 환경에 따라 추가 동작 수행
•
GitHub Hosted Runner 환경 : GitHub Actions Runner.Work 프로세스 메모리 덤프 Python Script 실행
•
Self Hosted Runner 환경
◦
(trivy) : 지속성 유지 및 추가 작업을 위한 sysmon.py 등록 및 실행
◦
(trivy-action, setup-trivy) : 각종 자격 증명 정보를 수집하는 TeamPCP Cloud Stealer 실행
3.
수집한 정보가 담긴 파일은 하이브리드 암호화(수집 데이터는 AES-256-CBC 암호화, 세션 키는 RSA-4096 공개키로 암호화) 수행하여 tpcp.tar.gz 파일로 생성 후 C2에 전송
•
C2에 데이터 전송 실패 시 탈취한 GitHub PAT(Personal Access Token) 이용하여 GitHub에 tpcp-docs 저장소 생성 후 탈취 데이터 업로드 절차 수행
3.1. Trivy 관련 악성코드 분석: Trivy v0.69.4 상세 분석
1) Trivy v0.69.4
•
각종 자격 증명 정보 수집 → 환경(GitHub Hosted Runner / Self Hosted Runner)에 따른 추가동작 결정 → 수집 데이터 하이브리드 암호화(AES-256-CBC 방식 암호화, 세션 키는 RSA-4096 공개키로 암호화) 수행 후 C2로 정보 탈취
•
동작 과정
◦
Go 언어로 작성된 ELF 파일로 정상 Trivy 실행 전에 악성 코드가 추가되어 _SCAND 라는 환경변수를 확인하여 1 이라는 값 확인 시 동작
악성 행위 수행 부분
◦
현재 쉘 프로세스 PID 및 GitHub Actions Runner* 관련 프로세스(Runner.Worker, Runner.Listener, runsvc, run.sh) PID 수집
GitHub Actions는 GitHub 안에서 돌아가는 자동화 서비스이고, GitHub Actions Runner는 그 자동화를 실제로 실행하는 머신/프로세스
◦
각 대상 pid 별로 /proc/<pid>/environ 내용 확인 후 env 또는 ssh 포함된 값 수집
▪
환경변수의 값이 실제 파일을 가리킬 경우에는 해당 파일 내용도 수집
◦
각종 자격 증명 정보 수집
▪
npm·PyPI·GitHub 토큰, SSH 키, 클라우드 인증정보(AWS, GCP, Azure), .env 파일, 쿠버네틱스 설정, 암호화폐 지갑정보(비트코인, 이더리움, 솔라나 등 10종), DB 인증정보, CI/CD 설정 등
◦
Github Runner 환경인지 확인하여 환경에 따라 다른 파이썬 스크립트 실행
▪
Github Hosted Runner 환경인 경우 : GitHub Actions Runner.Work 프로세스 메모리 덤프 파이썬 스크립트 실행
▪
Github Hosted Runner 환경이 아닌 경우(Self Hosted Runner 환경인 경우) : 지속성 유지 및 추가 작업을 위한 파이썬 스크립트(sysmon.py) 및 서비스(system.service) 설치 및 실행
◦
수집 데이터 존재 시 AES-256-CBC 방식 암호화, 세션 키 파일은 RSA-4096 공개키로 암호화하는 하이브리드 암호화 수행하여 C2(https://scan.aquasecurtiy[.]org)로 정보 탈취
▪
rand를 이용해 세션키로 사용할 32바이트 랜덤 값 생성
▪
수집 데이터를 AES-256-CBC 방식으로 암호화하여 payload.enc 파일 생성
•
세션키를 PassPhrase로 사용해 PBKDF2 기반 AES Key, IV 파생하여 적용
▪
세션키는 하드코딩된 공개키를 이용해 OAEP 패딩 모드의 RSA 암호화하여 session.key.enc 파일로 저장
◦
탈취 데이터 전송 실패 시(응답코드가 2xx가 아닌 경우) 환경변수에 INPUT_GITHUB_PAT존재 시 PAT(Personal Access Token) 이용하여 GitHub에 업로드 절차 수행
1.
GitHub Personal Access Token(PAT) 존재 시 GitHub API를 이용해 tpcp-docs라는 공개 저장소 생성 시도
2.
PAT 소유자의 GitHub username 조회 후 현재 시각 기반 tag(data-20060102150405)로 release 생성
3.
앞서 생성한 tpcp.tar.gz 파일을 release asset으로 업로드
탈취 데이터 전송 실패 시 동작
2-1) Github Hosted Runner 기반 Python Script
•
Github Hosted Runner 환경인 경우 실행되는 GitHub Actions Runner.Work 프로세스 메모리 덤프 파이썬 스크립트(MD5: 818271154971537a9b52e908b1463508)
◦
Trivy-Action에서 사용된 파일과 동일한 기능을 수행하며 코드 작성법에서 차이
▪
Runner.Worker에 해당하는 /proc 발견 시 pid 정보 수집하여, 해당 pid의 /proc/{pid}/maps, /proc/{pid}/mem 접근
▪
maps를 읽으면서 읽기 가능한 메모리 영역만 해당 영역의 시작-끝 주소를 mem에서 읽은 후 stdout으로 출력
Github Hosted Runner 환경 프로세스 메모리 덤프 파이썬 스크립트
2-2) Self Hosted Runner 기반 Python Script - sysmon.py(Persistence)
•
Github Runner 환경이 아닌 Self Hosted Runner 환경인 경우 지속성 유지 및 추가 작업을 위해 사용하는 페이로드
◦
CanisterWorm에서 사용하는 service.py와 동일한 파일
•
동작 과정
1.
최초 1회 300초(5분) 대기
2.
C2에 접속하여 응답 데이터를 읽어왔을 때 "http"로 시작하면 URL로 판단하여 그대로 반환, 아닐 경우 None 반환
•
C2(C_URL) : https://tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]io/
•
해당 C2는 ICP Canister(tdtqy-oyaaa-aaaae-af2dq-cai)에 접근하는 주소로, 평소에는 정상 유튜브 링크를 작성하고 악성 페이로드 사용 시에 C2 도메인으로 교체하여 사용하는 것으로 추정
•
분석 시점 기준 해당 도메인은 접속 불가이며 VirusTotal 조회 시 발견된 시점에는 정상 유튜브 링크가 작성되었던 것으로 확인
3.
C2에서 읽어온 URL 주소가 이전에 접근한 주소와 다르고 URL 내에 “youtube.com”(킬 스위치 목적)이 들어가 있지 않은 경우에 동작 수행
•
전달받은 URL에서 파일 다운로드하여 TARGET(/tmp/pglog)에 저장 후 실행권한 755 부여하고 분리된 세션으로 실행
•
접근했던 URL은 STATE(/tmp/.pg_state)에 기록
4.
3000초(50분) 대기 후 과정 2~3 반복
sysmon.py 내용
3.2. Trivy 관련 악성코드 분석: trivy-action 상세 분석
1) entrypoint.sh
•
각종 자격 증명 정보 수집 → 환경(GitHub Hosted Runner / Self Hosted Runner)에 따른 추가동작 결정 → 수집 데이터 하이브리드 암호화(AES-256-CBC 방식 암호화, 세션 키는 RSA-4096 공개키로 암호화) 수행 후 C2로 정보 탈취
•
동작 과정
◦
현재 쉘 프로세스 PID 및 GitHub Actions Runner 관련 프로세스(Runner.Worker, Runner.Listener, runsvc, run.sh) PID 수집하여 /tmp/runner_collected_<pid>.txt 파일에 저장
◦
각 대상 pid 별로 /proc/<pid>/environ 내용 확인 후 env 또는 ssh 포함된 값 수집
▪
환경변수의 값이 실제 파일을 가리킬 경우에는 해당 파일 내용도 수집
◦
시스템이 Github Hosted Runner 환경인지 여부에 따라 다른 파이썬 스크립트를 실행하여 각각 다른 데이터 수집
▪
수집한 데이터는 /tmp/runner_collected_<pid>.txt 파일에 저장
1.
Github Hosted Runner 환경인 경우 : GitHub Actions Runner.Work 프로세스 메모리 덤프 파이썬 스크립트 실행
•
파이썬 실행 결과 반환되는 runner 메모리 또는 내부 구조에서 isSecret: true 형태의 secret 객체 직접 복원 시도
2.
Github Hosted Runner 환경이 아닌 경우(Self Hosted Runner 환경) : TeamPCP Cloud Stealer 파이썬 스크립트 실행하여 각종 자격증명 정보 수집
◦
수집 데이터 파일은 AES-256-CBC 방식 암호화, 세션 키 파일은 RSA-4096 공개키로 암호화하는 하이브리드 암호화 수행하여 C2(https://scan.aquasecurtiy[.]org)로 정보 탈취
▪
openssl rand를 이용해 32바이트 랜덤 값(session.key) 생성
▪
수집 데이터를 AES-256-CBC 방식으로 암호화하여 payload.enc 파일 생성
•
session.key를 PassPhrase로 사용해 PBKDF2 기반 AES Key, IV 파생하여 적용
▪
session.key는 하드코딩된 공개키가 저장된 파일 p(public key)를 이용해 OAEP 패딩 모드의 RSA 암호화하여 session.key.enc 파일로 저장
▪
payload.enc, session.key.enc 파일을 압축하여 tpcp.tar.gz 파일 생성 후 C2(https://scan.aquasecurtiy[.]org)로 전송하여 데이터 탈취
◦
탈취 데이터 전송 실패 시(응답코드가 2xx가 아닌 경우) 환경변수에 INPUT_GITHUB_PAT존재 시 PAT(Personal Access Token) 이용하여 GitHub에 업로드 절차 수행
1.
GitHub Personal Access Token(PAT) 존재 시 GitHub API를 이용해 tpcp-docs라는 공개 저장소 생성 시도
2.
PAT 소유자의 GitHub username 조회 후 현재 시각 기반 tag(data-YYYYMMDDHHMMSS)로 release 생성
3.
앞서 생성한 tpcp.tar.gz 파일을 release asset으로 업로드
entrypoint.sh (MD5 : d761a6a7ae9f2254bd81ac234033a8b8) 내용
2-1) Github Hosted Runner Linux 기반 Python Script
•
Github Hosted Runner 환경인 경우 실행되는 GitHub Actions Runner.Work 프로세스 메모리 덤프 파이썬 스크립트(MD5: c77d1f7b23732cd8a958487ac92438e6)
◦
Runner.Worker에 해당하는 /proc 발견 시 pid 정보 수집하여, 해당 pid의 /proc/{pid}/maps, /proc/{pid}/mem 접근
◦
maps를 읽으면서 읽기 가능한 메모리 영역만 해당 영역의 시작-끝 주소를 mem에서 읽은 후 stdout으로 출력
GitHub Actions Runner.Work 프로세스 메모리 덤프 파이썬 스크립트 내용
2-2) Self Hosted Runner 기반 Python Script
•
Github Runner 환경이 아닌 Self Hosted Runner 환경인 경우 실행되는 TeamPCP Cloud Stealer(MD5: 9f6075c462ca52dc56e5fce10328ac3b)
◦
피해 시스템 환경에서 각종 자격 증명 정보를 수집하는 악성코드
◦
코드 하단의 주석에서 TeamPCP Cloud Stealer라는 문자열 확인
수집 대상
3.3. Trivy 관련 악성코드 분석: setup-trivy 상세 분석
1) action.yaml
•
setup-trivy는 Github Actions workflow에서 호출해서 사용하는 설치용 action이며, runner가 action.yaml에 정의된 step들을 순차 실행하여 Trivy 바이너리 설치 및 후처리 수행
•
action.yaml 파일의 Setup Environment 단계에 악성 코드가 삽입되어 runner 환경의 자격증명 및 민감정보 탈취 행위 수행
◦
변경 내용
04. NPM CanisterWorm 확산
•
2026년 3월 20일 20시 45분 (UTC), NPM에서 새로운 웜(CanisterWorm)으로 인한 다수의 패키지 감염 정황 발견
◦
해당 악성코드는 공격자가 Trivy 공급망 공격을 통해 획득한 NPM 자격 증명 정보를 악용하여 배포한 것으로 추정
◦
스스로 확산하는 악성코드가 C2 dead-drop*을 수행하기 위해 ICP Canister**를 C2로 이용하는 점에서 CanisterWorm으로 명명
◦
영향 받은 패키지 : @EmilGroup 28개 패키지, @opengov 16개 패키지, @teale.io/eslint-config, @airtm/uuid-base32, @pypestream/floating-ui-dom 등 66개 이상 패키지
* Dead Drop : 공격자가 직접 통신하지 않고 기존의 정상 웹 서비스에 명령, 데이터 또는 C2 주소 정보를 게시하고 감염된 시스템에서 이를 조회하도록 하는 간접 전달 방식
** ICP Canister : Internet Computer(ICP) 블록체인 상에서 실행되는 스마트 컨트랙트 실행 단위로 애플리케이션 코드와 상태 데이터를 함께 담아 실행 및 저장하는 배포 객체
4.1. NPM 패키지 악성코드(CanisterWorm) 상세 분석
•
감염된 패키지 중 22종의 패키지를 확보하여 4종의 index.js, 3종의 deploy.js, 1종의 service.py 악성코드 파일 분석
악성 npm 패키지 파일 구조
분석 파일 정보
1) index.js - Execution, Collection
•
지속성 확보 및 추가 작업을 위한 악성코드(service.py)를 설치하고 자동 확산을 위한 악성코드(deploy.js) 실행
◦
확보한 index.js 파일 3종 중 2종의 경우 코드는 동일하나 작성된 주석의 내용이 상이하여 해시 값도 상이
◦
초기 버전으로 추정되는 파일(MD5: 46e7a5c4cf645b77f24023eef873f56f)의 경우 지속성 확보 및 추가 작업을 위한 악성코드 설치 기능만 발견
•
동작 과정
1.
지속성 확보 및 추가 작업을 위한 악성코드 설치 및 실행
a.
하드코딩된 BASE64_PAYLOAD를 base64 디코딩하여 ~/.local/share/pgmon/service.py 파일로 저장
b.
user systemd 서비스 파일 생성 : ~/.config/systemd/user/pgmon.service
c.
pgmon.service 서비스 등록 및 실행
i.
systemctl --user daemon-reload
ii.
systemctl --user enable pgmon.service
iii.
systemctl --user start pgmon.service
2.
자동 확산을 위한 악성코드 실행
a.
NPM 토큰 수집
i.
.npmrc 파일 : ~/.npmrc, ./.npmrc, /etc/npmrc 에서 _authToken= 또는 :_authToken= 패턴 탐색하여 토큰 수집
ii.
환경 변수 : NPM_TOKEN, NPM_TOKENS 또는 NPM 과 TOKEN이 모두 포함된 변수에서 토큰 수집
iii.
config : npm config get //registry.npmjs.org/:_authToken 에서 토큰 수집
b.
NPM 토큰 발견 시 deploy.js 실행
•
npm 토큰 탐색하여 존재 시 scripts/deploy.js가 있는지 확인하여 발견 시 백그라운드 detached 프로세스로 실행
•
수집한 토큰은 NPM_TOKENS 환경변수로 저장
index.js (MD5 : 55405de62427ac56106f0fdb1c33dedd) 내용
index.js (MD5 : 46e7a5c4cf645b77f24023eef873f56f) 내용
2) deploy.js - Lateral Movement
•
피해 시스템의 NPM 토큰을 탈취하여 해당 계정의 패키지에 악성코드를 심어 정상 패키지로 위장하여 배포하는 동작 수행
◦
일반적인 deploy.js의 역할은 현재 내 패키지를 빌드하고 npm에 배포
•
동작 과정
1.
NPM 토큰 수집 후 해당 토큰의 계정 및 패키지 식별
a.
환경변수에서 NPM 토큰(NPM_TOKENS, NPM_TOKEN) 수집
b.
확보한 토큰의 계정명 식별 후 토큰 유효성 확인
•
https://registry.npmjs.org/-/whoami 엔드포인트에 Bearer 토큰 사용하여 요청
c.
해당 토큰의 계정이 maintainer로 등록된 패키지들을 250개 단위로 식별
2.
NPM 패키지 감염 및 확산 시도
a.
현재 로컬의 package.json, README.md 파일 백업
b.
식별해놓은 패키지의 README.md, package.json 파일을 악성 파일로 덮어쓴 후 현재 Release된 버전보다 x.x.1 높은 버전으로 변경하고 publish하여 Release 배포
c.
로컬 시스템의 파일을 전에 백업해두었던 package.json, README.md 파일로 복원하여 흔적 은폐
deploy.js (MD5 : 958c8f4f9145a7d67692db172f73c650) 내용
•
확보한 다른 샘플 2종도 동일한 기능을 수행하는 것으로 확인되나, 그 중 파일 1종(MD5: df43394b926e609e6ad020b157b151a1)의 경우 코드 구성이 상이
◦
주석, 로깅, 에러 출력 등 운영 목적으로 작성된 코드가 다수 발견
◦
주석과 구현이 일부 일치하지 않으며, 주석이 지나치게 상세하고, 로깅 기능 등에 각종 이모지가 사용된 점 등을 보아 LLM의 도움을 받아 작성한 코드로 추정
deploy.js (MD5: df43394b926e609e6ad020b157b151a1) 내용
3) service.py - Persistence
•
Trivy v0.69.4.에서 발견된 지속성 유지 및 추가 작업을 위해 사용하는 페이로드(sysmon.py, MD5 : b676c0703f8e4d6a198aa370ca4f5405)와 동일한 파일로 파일명만 상이
•
동작 과정
1.
최초 1회 300초(5분) 대기
2.
C2에 접속하여 응답 데이터를 읽어왔을 때 "http"로 시작하면 URL로 판단하여 그대로 반환, 아닐 경우 None 반환
•
C2(C_URL) : https://tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]io/
•
해당 C2는 ICP Canister(tdtqy-oyaaa-aaaae-af2dq-cai)에 접근하는 주소로, 평소에는 정상 유튜브 링크를 작성하고 악성 페이로드 사용 시에 C2 도메인으로 교체하여 사용하는 것으로 추정
•
분석 시점 기준 해당 도메인은 접속 불가이며 VirusTotal 조회 시 발견된 시점에는 정상 유튜브 링크가 작성되었던 것으로 확인
3.
C2에서 읽어온 URL 주소가 이전에 접근한 주소와 다르고 URL 내에 “youtube.com”(킬 스위치 목적)이 들어가 있지 않은 경우에 동작 수행
•
전달받은 URL에서 파일 다운로드하여 TARGET(/tmp/pglog)에 저장 후 실행권한 755 부여하고 분리된 세션으로 실행
•
접근했던 URL은 STATE(/tmp/.pg_state)에 기록
4.
3000초(50분) 대기 후 과정 2~3 반복
05. Checkmarx/OpenVSX
•
TeamPCP는 탈취한 GitHub 개인 액세스 토큰(PAT)을 사용하여 Checkmarx에서 개발한 오픈 소스 인프라 코드 보안 스캐너인 KICS 공격
◦
3월 23일 12:53 (UTC): Checkmarx의 OpenVSX* 확장 프로그램인 cx-dev-assist** 1.7.0 및 ast-results*** 2.53.0가 OpenVSX 계정을 통해 12초 간격으로 게시
◦
3월 23일 12:58 ~ 16:50(UTC): KICS-github-action가 영향을 받은 것으로 확인되었으며 해당 저장소는 16:50(UTC)에 폐쇄
◦
3월 23일 19:24(UTC): KICS-github-action 저장소가 복구되었으며 관리자는 문제가 해결되었다고 안내
◦
3월 23일 22:25(UTC): Sysdig에서 ast-github-action(v2.3.28)도 영향을 받은 것으로 보고
◦
◦
3월 24일 9:00(UTC): Checkmarx는 KICS GitHub 액션 및 OpenVSX 플러그인 관련 문제를 해결하는 보안 업데이트 발표
* OpenVSX : VS Code 계열 IDE에서 사용하는 확장 프로그램을 배포 및 설치할 수 있도록 하는 벤더 중립의 오픈 소스 플랫폼
** cx-dev-assist : 개발자가 IDE에서 코드를 작성하는 동안 실시간 취약점 식별, 수정 가이드를 제공하는 개발자용 보안 보조 확장 프로그램
*** ast-results : Checkmarx One에서 수행한 보안 스캔 결과를 IDE로 불러와 취약점을 확인하고 조치할 수 있게 해주는 확장 프로그램
5.1. Checkmarx/OpenVSX 악성코드 분석 : kics-github-action, ast-github-action 상세분석
•
KICS-github-action과 ast-github-action은 action.yml 파일만 상이하고 핵심 동작을 수행하는 setup.sh 파일은 동일한 파일로 확인
1) action.yml - Execution
•
kics-github-action 및 ast-github-action에서 발견된 악성 커밋 확인 시 action.yml에 “Prepare Environment” 라는 이름의 setup.sh 파일을 실행하는 작업 추가
◦
•
kics-github-action action.yml 추가 내용
변경 내용
•
ast-github-action action.yml 추가 내용
변경 내용
2) setup.sh
•
setup.sh 파일은 Hosted runner 환경의 자격증명 및 민감정보 탈취 행위 수행
•
trivy-action의 entrypoint.sh 코드에서 C2와 Self Hosted Runner 환경에서 실행하는 Python Script만 다르고 동일한 동작을 수행하는 코드로 확인되어 상세 동작은 해당 내용 참고
◦
C2 : checkmarx[.]zone
setup.sh (MD5: 0e12b77dc2ee0908b4d5827b6a4c00db) 내용
3-1) Github Hosted Runner Linux 기반 Python Script
•
setup.sh에서 Github Hosted Runner 환경인지 여부에 따라 다른 파이썬 스크립트 실행
•
Github Hosted Runner 환경에서 실행되는 페이로드(MD5: c77d1f7b23732cd8a958487ac92438e6)
◦
Runner.Worker에 해당하는 /proc 발견 시 pid 정보 수집하여, 해당 pid의 /proc/{pid}/maps, /proc/{pid}/mem 접근
◦
maps를 읽으면서 읽기 가능한 메모리 영역만 해당 영역의 시작-끝 주소를 mem에서 읽은 후 stdout으로 출력
3-2) Self Hosted Runner 기반 Python Script - TeamPCP Cloud Stealer
•
setup.sh에서 Github Hosted Runner 환경인지 여부에 따라 다른 파이썬 스크립트 실행
•
Github Hosted Runner가 아닌(Self Hosted Runner) 환경에서 실행되는 페이로드(MD5: b7a04d81eb2be05d1d4b8e05ec6b22c7)
◦
trivy-action 악성코드에서 발견된 TeamPCP Cloud Stealer의 각종 자격 증명 정보 수집 기능에 피해자 시스템의 쿠버네티스 환경을 장악하여 지속성 확보하는 기능 추가
1.
각종 자격 증명 정보 수집
•
환경변수·SSH 키·클라우드 자격증명·Kubernetes 토큰·데이터베이스 비밀번호 등 각종 자격증명 탈취 수행
•
trivy-action 악성코드에서 발견된 TeamPCP Cloud Stealer 동작과 동일하여 세부 항목은 해당 내용 참고
2.
수집한 AWS 자격증명을 통한 추가 정보 수집
•
AWS 환경 변수 및 IMDSv2를 통해 확보한 IAM role 자격증명을 이용하여 Secrets Manager의 secret 목록 및 SSM Parameter Store의 파라미터 정보 조회 시도
3.
수집한 Kubernetes 자격증명을 통한 클러스터 정보 수집 및 각 노드에 지속성 확보를 통한 클러스터 내부 장악 시도
•
Kubernetes ServiceAccount(SA) 토큰을 이용한 클러스터 API 직접 조회 후 노드별 지속성 유지용 악성코드 드롭 시도 (클러스터 내부 확산)
◦
토큰 경로와 CA 인증서를 읽고 Kubernetes API를 이용하여 Kubernetes 클러스터 직접 조회
◦
Kubernetes Secret 및 네임스페이스별 Secret 조회
▪
/api/v1/secrets, /api/v1/namespaces 조회 후 각 네임스페이스마다 /api/v1/namespaces/{ns}/secrets 재조회
◦
Kubernetes Node를 조회하여 지속성 확보 작업 수행
▪
각 노드마다 kube-system 네임스페이스에 새로운 Privileged Pod 생성
•
노드 호스트 파일시스템 직접 접근을 위해 privileged, hostPID, hostNetwork, hostPath("/") 설정
▪
해당 Pod는 지속성 확보를 위한 drop_cmd 과정 수행
1.
호스트의 /root/.config/sysmon, /root/.config/systemd/user 디렉토리 생성
2.
하드코딩된 Base64 인코딩 데이터를 디코딩해서 호스트의 /root/.config/sysmon/sysmon.py로 저장
3.
sysmon.py에 실행권한 700 부여
4.
호스트 시스템 내에 python3 또는 python 경로 확인
5.
sysmon.py를 실행하는 /root/.config/systemd/user/sysmon.service 작성
6.
systemctl --user daemon-reload, systemctl enable --now sysmon.service 실행
•
현재 감염 호스트의 사용자 홈에도 동일한 sysmon.py 및 sysmon.service를 생성하여 지속성 확보 시도
◦
파일 설치 : ~/.config/sysmon/sysmon.py
◦
systemd 유닛 : ~/.config/systemd/user/sysmon.service (서비스명: System Telemetry Service)
4) sysmon.py
•
지속성 유지 및 추가 작업을 위해 사용하는 페이로드
•
◦
사용된 C2가 ICP Canister에서 타이포스쿼팅이 적용된 C2(https://checkmarx[.]zone/raw)로 변경된 점만 상이
5.2. Checkmarx/OpenVSX 악성코드 분석 : OpenVSX 확장 프로그램 상세분석
1) environmentAuthChecker.js - Downloader
•
Checkmarx의 두 개의 손상된 확장 프로그램( ast-results v2.53.0및 cx-dev-assist v1.7.0)에 동일한 악성 페이로드(environmentAuthChecker.js) 포함
ast-results-2.53.0 내부 envrionmentAuthCheck.js 파일
cx-dev-assist-1.7.0 내부 environmentAuthChecker.js 파일
•
확장 프로그램 활성화 시 activateCore.js에서 악성 파일인 environmentAuthChecker.js실행하는 메커니즘
•
해당 페이로드는 피해자 시스템에서 하나 이상의 Github 또는 클라우드 제공업체(AWS, GCP, Azure)에 대한 자격 증명을 가지고 있는지 확인
◦
자격 증명 확인 시 주요 Javascript 패키지 관리자인 npx, bunx, pnpx 또는 yarn dlx를 통해 C2(checkmarx[.]zone/static/checkmarx-util-1.0.4.tgz)에 추가 페이로드 다운로드
environmentAuthChecker.js 내용
2) index.js - Collection, Exfiltration
•
checkmarx-util-1.0.4에서 발견되는 악성 페이로드(index.js)는 TeamPCP Cloud Stealer 보다는 적은 범위의 각종 자격 증명 정보 탈취
◦
수집 정보 : 환경변수/SSH/Git/AWS/Kubernetes/GCP/Azure/Docker/npm/Vault/DB/히스토리/VPN/지갑 정보
◦
수집한 자격 증명 정보는 하이브리드 암호화를 통해 tpcp.tar.gz 파일로 암호화 후 C2( checkmarx[.]zone/vsx)로 유출
•
GITHUB_ACTIONS 환경변수가 없을 경우 지속성 유지 목적의 sysmon.js 파일 및 sysmon.service 서비스 생성 및 설정
◦
파일 설치 : ~/.config/sysmon/sysmon.js (파일 확장자는 잘못 설정한 것으로 추정)
◦
systemd 유닛 : ~/.config/systemd/user/sysmon.service (서비스명: System Telemetry Service)
index.js 내용
3) sysmon.js - Persistence
•
지속성 유지 및 추가 작업을 위해 사용하는 페이로드
•
동작 과정
1.
C2에 접속하여 읽어온 응답 데이터에 “youtube” 문자열 포함 시 이후 동작 생략
•
C2(C_URL) : https://checkmarx[.]zone/raw
•
평소에는 정상 유튜브 링크를 작성하여 숨어있다가 악성 페이로드 사용 시에 C2 도메인으로 교체하여 사용하는 것으로 추정
•
분석 시점 기준 해당 도메인은 접속 불가이며 VirusTotal 조회 시 발견된 시점에는 정상 유튜브 링크가 작성되었던 것으로 확인
2.
“youtube” 문자열 미포함 시 /tmp 폴더에 임시 파일 생성 후 해당 내용 작성
•
해당 파일에 실행권한 부여 후 subprocess.popen()으로 실행
3.
3000초(50분) 대기 후 과정 1~2 반복
sysmon.js 내용
06. LiteLLM PyPI 감염
•
(공격 개요)
◦
LiteLLM은 오픈소스 Python 라이브러리로, OpenAI, Anthropic, Vertex AI, Bedrock 등 100여 개 이상의 LLM을 하나의 통합 인터페이스로 호출할 수 있게 해주는 프록시 게이트웨이
◦
2026년 3월 24일, 공식 PyPI의 litellm==1.82.7, litellm==1.82.8 패키지에 악성 코드가 포함된 상태로 게시
◦
v1.82.7은 10:39(UTC), v1.82.8은 10:52(UTC)에 게시됐고, 이후 PyPI가 약 40분 내 격리 조치를 적용했으며 LiteLLM 측은 16:00(UTC)까지 해당 버전 삭제 조치 진행
•
(사고 현황)
◦
FutureSearch 측이 Cursor에서 MCP 플러그인을 시험하던 중 uvx가 최신 버전의 LiteLLM을 의존성으로 내려받아 로드하면서 이상 행위 발견되어 신고
◦
최초 이상 징후는 Python 시작 직후 RAM 고갈과 대량의 Python 프로세스 생성으로 나타났으며, 이는 litellm_init.pth가 자식 Python 프로세스를 다시 호출하면서 발생한 비의도적 fork bomb 현상
◦
공개 직후 신고 이슈가 봇 댓글로 도배되고 닫히는 교란 행위가 발생했으며 이후 LiteLLM 측은 관련 키 교체, Maintainer 계정 조치, 문제 버전 삭제 및 신규 릴리스 중단 조치 수행
•
(사고 원인)
◦
LiteLLM 공식 조사 기준 이번 사고는 CI/CD 보안 스캔에 사용하던 Trivy 의존성과 연결된 것으로 보이며, 공격자가 노출된 자격증명으로 PyPI에 직접 악성 패키지를 게시한 것으로 추정
▪
LiteLLM 측 후속 설명 기준 주요 배경 요인은 CircleCI 기반 공유 CI/CD 환경, 환경변수에 저장된 정적 PyPI·GHCR·Docker 자격증명, 그리고 버전이 고정되지 않은 Trivy 의존성으로 확인
•
(악성코드 특징)
◦
악성 페이로드는 환경변수·SSH 키·클라우드 자격증명·Kubernetes 토큰·데이터베이스 비밀번호 등 각종 자격증명 탈취, 지속성 유지 및 Kubernetes 확산 기능까지 포함한 백도어
▪
import 시 실행되도록 하는 조건(v1.82.7)에서 별도로 import 없이도 파이썬 실행 시 자동 실행되도록 하는 구조(v1.82.8)로 발전
6.1. LiteLLM PyPI 악성코드 상세 분석
1-1) proxy_server.py - Initial Access (LiteLLM v1.82.7)
•
LiteLLM v1.82.7의 litellm/proxy/proxy_server.py Line 128 ~ Line 139에 추가된 악성코드
•
다른 파일에서 litellm.proxy.proxy_server import 시 실행
◦
추가된 악성 base64 인코딩 페이로드를 디코딩하여 p.py 파일로 작성 후 subprocess.run()을 이용하여 실행
proxy_server.py 정상 버전 (v1.82.6)
proxy_server.py 악성 버전 (v1.82.7)
proxy_server.py (Line 128 ~ Line 139) 내용
1-2) litellm_init.pth - Initial Access (LiteLLM v1.82.8)
•
LiteLLM v1.82.8에 추가 배포된 파일로, .pth 파일은 다른 파일에서 import 하는 등 명시적으로 사용하지 않더라도 Python 인터프리터 시작 시 site.py에 의해 자동으로 처리
•
base64 인코딩된 페이로드를 디코딩하여 p.py 파일로 작성 후 subprocess.run()을 이용해서 실행
◦
해당 base64 페이로드는 proxy_server.py에서 발견되는 base64 페이로드와 동일
litellm_init.pth 내용
2) Base64 Payload - Execution, Exfiltration
•
proxy_server.py, litellm_init.pth 파일에서 base64 디코딩하여 실행하는 파이썬 스크립트
•
추가 페이로드(TeamPCP Cloud Stealer)를 실행하여 수집한 정보를 하이브리드 암호화를 적용하여 탈취
•
proxy_server.py에서 발견되는 base64 페이로드는 실제로 사용된 페이로드와 주석 처리된 2개의 페이로드 존재
◦
실제로 사용된 base64 페이로드는 나머지 두 페이로드에서 사용하는 exec() 방식을 통한 추가 페이로드 실행에서 subprocess.run() 방식을 통한 실행으로 변경
◦
주석 처리된 두 base64 페이로드는 서로 내부에서 사용한 B64_SCRIPT 값 외 나머지 코드 동일
▪
데이터 임시 저장 파일로 “collected_vars.txt”를 사용
•
동작 과정
◦
B64_SCRIPT 데이터(TeamPCP Cloud Stealer)를 디코딩하여 실행한 결과를 파일 c(collected)로 저장
◦
openssl rand를 이용해 32바이트 랜덤 값(session.key) 생성
◦
collected 데이터를 AES-256-CBC 방식으로 암호화하여 payload.enc 파일 생성
▪
session.key 파일 내용을 PassPhrase로 사용해 PBKDF2 기반 AES 키, IV 파생하여 적용
◦
session.key는 하드코딩된 공개키가 저장된 파일 p(public key)를 이용해 OAEP 패딩 모드의 RSA 암호화하여 session.key.enc 파일로 저장
▪
본문 데이터는 AES-256-CBC 방식 암호화, 세션 키로 사용한 session.key 파일은 RSA-4096 공개키로 암호화하는 하이브리드 암호화
◦
payload.enc, session.key.enc 파일을 압축하여 tpcp.tar.gz 파일 생성 후 C2(https://models.litellm[.]cloud/)로 전송하여 데이터 탈취
▪
공식 사이트 : litellm.ai
proxy_server.py 내 첫 번째 base64 페이로드
proxy_server.py 내 주석 처리된 두 번째, 세 번째 base64 페이로드
3) TeamPCP Cloud Stealer - Collection, Lateral Movement
•
trivy-action 악성코드에서 발견된 TeamPCP Cloud Stealer의 각종 자격 증명 정보 수집 기능에 피해자 시스템의 쿠버네티스 환경을 장악하여 지속성 확보하는 기능 추가
◦
Checkmarx kics-github-action, ast-github-action에서 발견되는 TeamPCP Cloud Stealer와 동일한 기능 수행하여 상세 동작은 해당 내용 참고
1.
각종 자격 증명 정보 수집
•
환경변수·SSH 키·클라우드 자격증명·Kubernetes 토큰·데이터베이스 비밀번호 등 각종 자격증명 탈취 수행
•
Trivy-Action 악성코드에서 발견된 TeamPCP Cloud Stealer 동작과 동일하여 해당 내용 참고
2.
수집한 AWS 자격증명을 통한 추가 정보 수집
•
AWS 환경 변수 및 IMDSv2를 통해 확보한 IAM role 자격증명을 이용하여 Secrets Manager의 secret 목록 및 SSM Parameter Store의 파라미터 정보 조회 시도
3.
수집한 Kubernetes 자격증명을 통한 클러스터 정보 수집 및 각 노드에 지속성 확보를 통한 클러스터 내부 장악 시도
•
Kubernetes ServiceAccount(SA) 토큰을 이용한 클러스터 API 직접 조회 후 노드별 지속성 유지용 악성코드 드롭 시도 (클러스터 내부 확산)
◦
토큰 경로와 CA 인증서를 읽고 Kubernetes API를 이용하여 Kubernetes 클러스터 직접 조회
◦
Kubernetes Secret 및 네임스페이스별 Secret 조회
▪
/api/v1/secrets, /api/v1/namespaces 조회 후 각 네임스페이스마다 /api/v1/namespaces/{ns}/secrets 재조회
◦
Kubernetes Node를 조회하여 지속성 확보 작업 수행
▪
각 노드마다 kube-system 네임스페이스에 새로운 Privileged Pod 생성
•
노드 호스트 파일시스템 직접 접근을 위해 privileged, hostPID, hostNetwork, hostPath("/") 설정
▪
해당 Pod는 지속성 확보를 위한 drop_cmd 과정 수행
1.
호스트의 /root/.config/sysmon, /root/.config/systemd/user 디렉토리 생성
2.
PERSIST_B64에 저장된 Base64 인코딩 데이터를 디코딩해서 호스트의 /root/.config/sysmon/sysmon.py로 저장
3.
sysmon.py에 실행권한 700 부여
4.
호스트 시스템 내에 python3 또는 python 경로 확인
5.
sysmon.py를 실행하는 /root/.config/systemd/user/sysmon.service 작성
6.
systemctl --user daemon-reload, systemctl enable --now sysmon.service 실행
•
현재 감염 호스트의 사용자 홈에도 동일한 sysmon.py 및 sysmon.service를 생성하여 지속성 확보 시도
◦
파일 설치 : ~/.config/sysmon/sysmon.py
◦
systemd 유닛 : ~/.config/systemd/user/sysmon.service (서비스명: System Telemetry Service)
4) sysmon.py - Persistence
•
지속성 유지 및 추가 작업을 위해 사용하는 페이로드
◦
LiteLLM proxy_server.py에서 주석 처리된 base64 페이로드의 진행 과정 상에서 발견되는 sysmon.py는 Hex 값을 RC4 암호화(Key: “nigger”)하여 해당 샘플과 동일한 base64 페이로드 생성
•
◦
사용된 C2가 ICP Canister에서 타이포스쿼팅이 적용된 C2(https://checkmarx[.]zone/raw)로 변경된 점만 상이
07. Telnyx PyPI 감염
•
(공격 개요)
◦
Telnyx는 음성 통화, 메시징, 전화번호, 영상 등 실시간 통신 기능을 API 형태로 제공하는 클라우드 통신 플랫폼
◦
2026년 3월 27일 03시 51분 28초(UTC), 악성코드가 포함된 Telnyx Python SDK의 무단 버전 두 개(4.87.1 및 4.87.2)가 PyPI에 게시, 같은 날 10시 13분(UTC)에 격리 조치
▪
파이썬 패키지만 손상되었으며, 인프라, 네트워킹, 서비스 및 기타 API는 침해되지 않은 것으로 알려짐
◦
악성코드는 telnyx/_client.py에 삽입되었으며, 모든 실행 경로가 모듈 범위에서 실행되도록 설정되어 있어 import telnyx를 통해 악성코드 실행 가능
7.1. Telnyx PyPI 악성코드 상세 분석
1) _client.py
•
Telnyx v4.87.1, v4.87.2의 _client.py 파일의 코드는 동일하며 setup() 함수 호출 시 대소문자 표기가 달라서 해시 값이 상이
•
동작
◦
운영체제 환경에 따라 다른 동작 수행
1.
Windows : setup() 함수 실행
•
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe 파일 존재 확인하여 있을 경우 종료
•
현재 시간이 msbuild.exe.lock 파일의 수정 시간으로 부터 43,200초(12시간) 미만일 경우 종료
◦
해당 시간 이상일 경우 msbuild.exe.lock 파일에 현재 시간 기록
◦
해당 파일 숨김 속성(attrib +h) 설정
•
C2(https://83.142.209[.]203:8080/hangup.wav) 접근하여 파일 읽어와서 msbuild.exe.tmp 파일로 저장
•
읽어온 wav 파일에서 오디오 데이터 영역의 데이터를 읽어와 base64 디코딩 후 첫 8바이트는 Key로 사용하고 나머지 데이터를 XOR 복호화 하여 msbuild.exe 생성 후 CREATE_NO_WINDOW 플래그(0x08000000)와 함께 실행
2.
Linux, macOS : FetchAudio() 함수 실행
•
_client.py line 459에 삽입된 base64 인코딩 페이로드(_p)를 디코딩하여 실행
_clinet.py line 459에 삽입된 base64 인코딩 페이로드 일부
_client.py 내용 일부
2-1) Windows 환경 : msbuild.exe - RAT
•
분석 시점에는 해당 C2에 접근할 수 없어 wav 파일을 확인하지는 못하였으나 알려진 해시값을 통해 msbuild.exe 파일 확보
•
해당 실행 파일은 탐지를 피하기 위해 여러 난독화 및 회피 기법을 사용하는 로더
◦
DJB2 해시 기반 API 해석, 시스템 콜 사용, Windows 이벤트 추적(ETW) 종료
DJB2 해시 기반 API 로드
EtwEventWrite 호출
•
exe 내부에 삽입된 PNG 파일에서 값을 추출하여 조합한 쉘코드를 dllhost.exe에 삽입하여 실행
dllhost.exe 실행 및 PNG 데이터 로드
exe 내부에 포함된 PNG 파일
•
Akamai에 의하면 해당 쉘코드는 RAT 백도어 역할을 수행하는 것으로 확인되며 수집한 데이터는 C2(checkmarx[.]zone/telemetry/checkmarx.json)로 전송하는 것으로 추정
◦
하드코딩된 RC4 Key를 사용하여 내장된 구성 파일을 복호화하여 사용
▪
RC4 Key : 33 1a b9 c0 32 cf 95 c8 9d 87 7e e0 5b 46 f8 d8
복호화된 구성 파일 내용
◦
확인된 기능
명령어 | 동작 |
0x12 | 파일 읽기 |
0x17 | 프로세스 종료 |
0x18 | 레지스트리 읽기 |
0x2A | 프로세스 생성 |
0x2B | 암호화 및 데이터 유출 |
0x47 | 파이프를 통한 쉘 실행 |
2-2) Linux, macOS 환경 : FetchAudio()에서 실행하는 Python Script
•
Trivy-action의 entrypoint.sh 파일과 유사하게 추가 페이로드를 실행하고 해당 결과를 수집한 파일을 하이브리드 암호화하여 tpcp.tar.gz 파일로 압축 후 C2로 전송하는 동작 수행
◦
추가 페이로드 실행 시 하드코딩된 base64 페이로드를 디코딩해서 실행하던 방식에서, C2를 통해 페이로드를 받아서 실행하는 방식으로 변환
•
C2로 사용된 IP(83.142.209[.]203)는 checkmarx[.]zone 하고 연결되었던 IP로 확인
•
동작 과정
◦
C2(http://83.142.209[.]203:8080/ringtone.wav) 접근해서 temp.wav 파일로 다운로드
◦
temp.wav 파일을 base64 디코딩 후 첫 8바이트를 Key로 하여 나머지 데이터를 XOR 복호화한 데이터를 실행하여 실행 결과를 collected(c)에 저장
◦
데이터 수집 성공 시 하이브리드 암호화(AES-256-CBC + RSA-4096 OAEP) 적용하여 수집 데이터, 세션키 암호화 데이터를 tpcp.tar.gz 파일로 압축 후 C2(http://83.142.209[.]203:8080/)에 전송
FetchAudio python 스크립트
08. IoC, Indicator of Compromise
NO | Type | DATA | Info |
1 | MD5
SHA256 | 805c08686e755c063a0bb460bdf9dcc4
822dd269ec10459572dfaaefe163dae693c344249a0161953f0d5cdd110bd2a0 | trivy v0.69.4 linux 64bit |
2 | MD5
SHA256 | 8bddcad83361397840fdb1ee97d8a6a6
f7084b0229dce605ccc5506b14acd4d954a496da4b6134a294844ca8d601970d | trivy v0.69.4 linux 32bit |
3 | MD5
SHA256 | 818271154971537a9b52e908b1463508
46975e946d77485a9d43434b73b112e95183e331962b0dafc371f05a1ffbbb16 | (trivy v0.69.4) Github Hosted Runner 환경 메모리 정보 추출 파이썬 스크립트 |
4 | MD5
SHA256 | d761a6a7ae9f2254bd81ac234033a8b8
18a24f83e807479438dcab7a1804c51a00dafc1d526698a66e0640d1e5dd671a | (trivy-action) entrypoint.sh |
5 | MD5
SHA256 | 9f6075c462ca52dc56e5fce10328ac3b
83d7ad6f3ff22a6e0e021fa4541a23cdecac1ad4d0d4b018a7b7e92292cc8ccc | (trivy-action, setup-trivy) Self Hosted Runner 환경 TeamPCP Cloud Stealer |
6 | MD5
SHA256 | c77d1f7b23732cd8a958487ac92438e6
d8c45b34ed3637c2a57ab532155c2e3933308b2e3ee6a977ef427f9078b843cd | (trivy-action, setup-trivy, ast-github-action) Github Hosted Runner 환경 메모리 정보 추출 파이썬 스크립트 |
7 | MD5
SHA256 | 1d39656cc34bb9ef36f4992fd774f5b7
ef8a2c83882852c92d01a7356ca7a362aef98d1eae332ab48f993ea0ef3d8fe0 | (setup-trivy) action.yaml |
8 | MD5
SHA256 | f5560871f6002982a6a2cc0b3ee739f7
a0d229be8efcb2f9135e2ad55ba275b76ddcfeb55fa4370e0a522a5bdee0120b | (LiteLLM) proxy_server.py |
9 | MD5
SHA256 | cde4951bee7e28ac8a29d33d34a41ae5
71e35aef03099cd1f2d6446734273025a163597de93912df321ef118bf135238 | (LiteLLM) litellm_init.pth |
10 | MD5
SHA256 | 7cac57b2d328bd814009772dd1eda429
d6fc0ff06978742a2ef789304bcdbe69a731693ad066a457db0878279830d6a9 | (LiteLLM) base64_payload_1_decoded |
11 | MD5
SHA256 | 0f0223eb0c5254769fc7fe9ae3d26e3f
472f6cd3e735a347aeda0f05ac6cb3d59069ce3cae119f97d0c4a758a4cf1ae7 | (LiteLLM) base64_payload_2_decoded |
12 | MD5
SHA256 | ae60726d04bc06d0c623e567e8e9b241
8eaf4c4d0b82620bcda29b97896e2da0a754205c035721479f7ceafb817e4466 | (LiteLLM) base64_payload_3_decoded |
13 | MD5
SHA256 | 6c9da8de6797d4a446d962d5f3f0c22c
ce681356c8870af8583ed753fff330e2897f4629c45677f8065649a47b20a111 | (LiteLLM) TeamPCP Cloud Stealer |
14 | MD5
SHA256 | 30808ef5a07e62d46e7ba6a82d9e3e9e
a5dd088bc7daa4253fc44a99f8f8008b4f16ae04f345354f455e751fdc90a9a4 | (LiteLLM) TeamPCP Cloud Stealer |
15 | MD5
SHA256 | 0a487dcd6ef8f98d7fcee0583592a89b
0f6223cd6c875b31e64f8d2b9e8798a82e972c9ab14553a812ebb0d5094c5d91 | (LiteLLM) TeamPCP Cloud Stealer |
16 | MD5
SHA256 | 8ca60ad84b6f6b3b9352e77cd8682d81
6cf223aea68b0e8031ff68251e30b6017a0513fe152e235c26f248ba1e15c92a | (LiteLLM) sysmon.py |
17 | MD5
SHA256 | ae4c00eca1bb6083855deb4281c4d1bd
c0ca84eaa706d25465711f0f66c7b5d897e219ae239041c5419583291ab752cd | (kics-github-action) action.yml |
18 | MD5
SHA256 | e5ac316962931b524227aa20491d24dd
72dab34c6b995cc63314eb95c9a2e2316ddfe10c3dc6d1029c7658fa677fb59f | (ast-github-action) action.yml |
19 | MD5
SHA256 | 0e12b77dc2ee0908b4d5827b6a4c00db
15ccf5d10f23dd6b69574d21c2ba516fd3d0dbf9fa597a02338f41c3b95d78aa | (kics-github-action, ast-github-action) setup.sh |
20 | MD5
SHA256 | b7a04d81eb2be05d1d4b8e05ec6b22c7
eb6fde7c1ff4c275b5ec11e604c20593d8855eeae3be2d83bbe859a1487548cf | (kics-github-action, ast-github-action) TeamPCP Cloud Stealer |
21 | MD5
SHA256 | 767f50799df6aef4f2cb121ba7c01ad2
42246762d83296f47f608f34ea8e966e1b71212948e4156dea1619d01accb52d | (kics-github-action, ast-github-action) sysmon.py |
22 | MD5
SHA256 | 81bfd2c98536a306357a54746d2b3d07
65bd72fcddaf938cefdf55b3323ad29f649a65d4ddd6aea09afa974dfc7f105d | (OpenVSX) ast-results-2.53.0.vsix |
23 | MD5
SHA256 | 60fe39ec4452813f849bde618e3b5963
744c9d61b66bcd2bb5474d9afeee6c00bb7e0cd32535781da188b80eb59383e0 | (OpenVSX) cx-dev-assist-1.7.0.vsix |
24 | MD5
SHA256 | 49e06269c77e9f028aef505f2c92d5a6
527f795a201a6bc114394c4cfd1c74dce97381989f51a4661aafbc93a4439e90 | (OpenVSX) environmentAuthChecker.js |
25 | MD5
SHA256 | 0fccc8e3a03896f45726203074ae225d
0d66d8c7e02574ff0d3443de0585af19c903d12466d88573ed82ec788655975c | (OpenVSX) checkmarx-util-1.0.4.tgz |
26 | MD5
SHA256 | 05bacbe163ef0393c2416cbd05e45e74
cd6af6c9ba149673ff89a1f1ccc8ec40a265a3b54ad455fbef28dc2967a98e45 | (OpenVSX) index.js |
27 | MD5
SHA256 | 8d317fb5e199aed37bf87a4ebc630b49
2c89570dc5e4b7ce816e2d59e552ae146b59a5c4664b0ba502f98ce4354e1af | (OpenVSX) sysmon.js |
28 | MD5
SHA256 | 188d8592f393ce45f7273102f02efee1
7321caa303fe96ded0492c747d2f353c4f7d17185656fe292ab0a59e2bd0b8d9 | telnyx 4.87.1-py3-none-any.whl |
29 | MD5
SHA256 | 5870a0bf82bbdf2687d8dce89dfa668f
cd08115806662469bbedec4b03f8427b97c8a4b3bc1442dc18b72b4e19395fe3 | telnyx 4.87.2-py3-none-any.whl |
30 | MD5
SHA256 | b1c6036b046bcf8c80601742ebcc61b0
23b1ec58649170650110ecad96e5a9490d98146e105226a16d898fbe108139e5 | (Telnyx v4.87.1) _client.py |
31 | MD5
SHA256 | 9e837f0b9e8037b06256e2ec4291f757
ab4c4aebb52027bf3d2f6b2dcef593a1a2cff415774ea4711f7d6e0aa1451d4e | (Telnyx v4.87.2) _client.py |
32 | MD5
SHA256 | 599a186de45023bddfb37ba52d69dbf5
84edce66f09c55bbb44754411bde4b092288d172734df62fac20d6f794b3a2ec | (Telnyx) FetchAudio 파이썬 스크립트 |
33 | MD5
SHA256 | d528effabbd9cd66aaa11bc8777bb110
7290353a3bc2b18e9ea574d3294b09e28edaa6b038285bb101cf09760f187dcd | (Telnyx) msbuild.exe |
34 | MD5
SHA256 | 217e86944372006f89b405b33d905884
dafc1cc5d39bc303562d8587b698b6351e843b77c01764efa8b423a36b88fa6d | (Telnyx) msbuild.exe 내 dll |
35 | MD5
SHA256 | 99ec27d6392b0fab6a7d88ac03e5c509
3e4216020945f1cd18beab71ea1a791d0d41f8c3b0ca038e27566c60bdfbd4ad | (NPM Package) react-leaflet-marker-layer(0.1.5) |
36 | MD5
SHA256 | 55a13227870b02e4c5b971d151152e45
4e1a2647daa6db51cda32247df0652fd03c903738235cdecd55ccc161ce50fdc | (NPM Package) @emilgroup/commission-sdk(1.0.2) |
37 | MD5
SHA256 | 9b0e66253bcdb92e8097148d60346a79
8b21b3f6b3e8a26cfc40cd96e91536ecfc49aba79b9ccac1f995dfc5a82c7ab6 | (NPM Package) @emilgroup/customer-sdk(1.54.5) |
38 | MD5
SHA256 | a4c3cf93cbdfc705ab2814a6360ed838
9d6c0f606af8b3ea56e2e6d795d07d32394b2d9a58f364559ee68403ab11997f | (NPM Package) @emilgroup/discount-sdk(1.5.3) |
39 | MD5
SHA256 | 9d7e17018d4a41f3aa0a2d45862bea1f
9f8249c4ff2a0453cf5a9a436030b9f47ecb458d98558b04103ef8f5554fea28 | (NPM Package) opengov-k6-core(1.0.2) |
40 | MD5
SHA256 | b9d0641e713ce18c3e6bb238b680d4a2
27e8c218d0606fcd7be1db3e9334502ebf524082e110e27ab56de3b597f67e60 | (NPM Package) cit-playwright-test(1.0.1) |
41 | MD5
SHA256 | c859e1b2ef0cba30bbd3587f4badb598
45e3e0afb0d04f4cf3680394b42e28ee0fe3c5d6da478a065dc38d964198da23 | (NPM Package) @opengov/ppf-eslint-config(0.1.11) |
42 | MD5
SHA256 | e00c35e16eefd0db81a7f137a91a6186
46b0ede453a217493b5792faa70627aeeaf0d3736af864a9907a1b89455088ed | (NPM Package) @emilgroup/task-sdk(1.0.2) |
43 | MD5
SHA256 | 564378ffd59bb29710e0fe83b068e373
53cc8a09e3f929fce84f097e69392b22e35de08d3a895bbc85caaad1e755371d | (NPM Package) babel-plugin-react-pure-component(0.1.6) |
44 | MD5
SHA256 | 85f371c9bf10c972ae899887262fea3a
53cfde5d7756a21b92d9b5d078aad50a6555547d916980c197ee0277d15b94eb | (NPM Package) @emilgroup/setting-sdk(0.2.3) |
45 | MD5
SHA256 | b18637344312319dad79c67079a29a4d
73a80f59a5ec9628d47ad94dcbe70e8e68db41fa67fd8974938baf032dede790 | (NPM Package) @teale.io/eslint-config(1.8.10) |
46 | MD5
SHA256 | a3aabfd9386758135fdc811b0d143781
379a45b1ddef1cf0bdea1ec39aa4478e2e10e2ca00dfa33306a08ca7231b649e | (NPM Package) react-leaflet-cluster-layer(0.0.4) |
47 | MD5
SHA256 | ec65d120a9a5309d451718f847afa830
707cf3d720c60cc1786f4a69c02e8f43883036d3c2a1fd89c89fa77e9a1d2283 | (NPM Package) react-autolink-text(2.0.1) |
48 | MD5
SHA256 | 370d7f3e136027746975c3e614853fb5
767fc3f1087b7946aa9694fcb7da60d0aae5237a0277f3b298cbab7643edef98 | (NPM Package) react-leaflet-heatmap-layer(2.0.1) |
49 | MD5
SHA256 | 29e170d737dcaf88435847aa4eecf553
a7d9c27d3be05c6fa0416573f3bda062b91ddc74fff7f7960b3c3f7885b61434 | (NPM Package) @leafnoise/mirage(2.0.3) |
50 | MD5
SHA256 | daa82da235b97b194a9e7efcb0d4580e
a79a16460a419fc2f1c3f8989e9267115cf9249663517f076442f370b8b80276 | (NPM Package) @opengov/ppf-backend-types(1.141.2) |
51 | MD5
SHA256 | 27d8dfa079016f1181e5eef2c66157d6
a4842b7956b217ddf955cdd860032517b79fbbe4dd9215dd783847a45da2fff1 | (NPM Package) @opengov/form-utils(0.7.2) |
52 | MD5
SHA256 | 4d6a1c841d772412a3a0bb595e602c82
b7d2a540e591492a71fa7921fdb692187d62ffe44e341114ad58105db6cf6d8b | (NPM Package) @opengov/qa-record-types-api(1.0.3) |
53 | MD5
SHA256 | b8b39afa3996040449acbc49baf9864b
d8961f91d6f51b5552b9ef8b0001d907fc3663dfc918d7d528aaca166920cecf | (NPM Package) @emilgroup/document-sdk-node(1.43.6) |
54 | MD5
SHA256 | 09ead57d2a07cf30ceab89065019f689
f23fcc3045f63d526dfa5d65f1203e5f61812e940c02654d2d22520ceb028fcb | (NPM Package) @airtm/uuid-base32(1.0.2) |
55 | MD5
SHA256 | 8e27a6be443fd1ab37c9f1c1ec3fef14
f101f3b8780afa75f8023fb10a0d5a30b9f51c1141e91a8799d12c4dfa3d5a47 | (NPM Package) @teale.io/eslint-config(1.8.13) |
56 | MD5
SHA256 | 4f26a6a3375ff139ddf6b8fc93a50a53
fcdda01cd9233d775b417605c53ebad71afada9f1b616c627c3e7a147a5b1c67 | (NPM Package) @opengov/form-renderer(0.2.20) |
57 | MD5
SHA256 | 55405de62427ac56106f0fdb1c33dedd
c37c0ae9641d2e5329fcdee847a756bf1140fdb7f0b7c78a40fdc39055e7d926 | (NPM CanisterWorm) index.js |
58 | MD5
SHA256 | 986a8af4d0b5ea68e1949de5fefdfbf2
f564bbdb350cf5a0894023795049708b01cbe76781a5e695f52e56b762955431 | (NPM CanisterWorm) index.js |
59 | MD5
SHA256 | 46e7a5c4cf645b77f24023eef873f56f
61ff00a81b19624adaad425b9129ba2f312f4ab76fb5ddc2c628a5037d31a4ba | (NPM CanisterWorm) index.js |
60 | MD5
SHA256 | 8cf49650b7a000d09e8af77c314dfdad
0c0d206d5e68c0cf64d57ffa8bc5b1dad54f2dda52f24e96e02e237498cb9c3a | (NPM CanisterWorm) index.js |
61 | MD5
SHA256 | 958c8f4f9145a7d67692db172f73c650
158091ec92a3a91d7d2d29e6b867d47479d624bcae5f067cc80af4eff91c9729 | (NPM CanisterWorm) deploy.js |
62 | MD5
SHA256 | 8bfefb76454efe404359831d4fe7137c
5e2ba7c4c53fa6e0cef58011acdd50682cf83fb7b989712d2fcf1b5173bad956 | (NPM CanisterWorm) deploy.js |
63 | MD5
SHA256 | df43394b926e609e6ad020b157b151a1
7df6cef7ab9aae2ea08f2f872f6456b5d51d896ddda907a238cd6668ccdc4bb7 | (NPM CanisterWorm) deploy.js |
64 | MD5
SHA256 | b676c0703f8e4d6a198aa370ca4f5405
b219ce2263c913655269946b884c38ee3dc577a7f1221d4524f2b2bceb1f55ad | (NPM CanisterWorm) service.py(sysmon.py) |
65 | Domain | https://socketusercontent[.]com/blob/QGKJPg-gHR5Q43Kt6GATFGgDa_B1SZpimbgZA0eXdZxo/ | |
66 | Domain | https://scan.aquasecurtiy[.]org | |
67 | Domain | https://models.litellm[.]cloud/ | |
68 | Domain | https://checkmarx[.]zone/raw | |
69 | Domain | https://tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]io/ | |
70 | Domain | http://83.142.209[.]203:8080/hangup.wav | Telnyx msbuild.exe 다운로드 |
71 | Domain | http://83.142.209[.]203:8080/ringtone.wav | Telnyx fetchaudio에서 접근 |
72 | Domain | http://83.142.209[.]203:8080/ | Telnyx 정보 탈취 C2 |
73 | IP | 46.151.182.203 | |
74 | IP | 83.142.209.11 | |
75 | IP | 45.148.10.212 | |
76 | RSA Public Key | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvahaZDo8mucujrT15ry+
08qNLwm3kxzFSMj84M16lmIEeQA8u1X8DGK0EmNg7m3J6C3KzFeIzvz0UTgSq6cV
pQWpiuQa+UjTkWmC8RDDXO8G/opLGQnuQVvgsZWuT31j/Qop6rtocYsayGzCFrMV
2/ElW1UE20tZWY+5jXonnMdWBmYwzYb5iwymbLtekGEydyLalNzGAPxZgAxgkbSE
mSHLau61fChgT9MlnPhCtdXkQRMrI3kZZ4MDPuEEJTSqLr+D3ngr3237G14SRRQB
IqIjly5OoFkqJxeNPSGJlt3Ino0qO7fy7LO0Tp9bFvXTOI5c+1lhgo0lScAu1ucA
b6Hua+xRQ6s//PzdMgWT3R1aK+TqMHJZTZa8HY0KaiFeVQ3YitWuiZ3ilwCtwhT5
TlS9cBYph8U2Ek4K20qmp1dbFmxm3kS1yQg8MmrBRxOYyjSTQtveSeIlxrbpJhaU
Z7eneYC4G/Wl3raZfFwoHtmpFXDxA7HaBUArznP55LD/rZd6gq7lTDrSy5uMXbVt
6ZnKd0IwHbLkYlX0oLeCNF6YOGhgyX9JsgrBxT0eHeGRqOzEZ7rCfCavDISbR5xK
J4VRwlUSVsQ8UXt6zIHqg4CKbrVB+WMsRo/FWu6RtcQHdmGPngy+Nvg5USAVljyk
rn3JMF0xZyXNRpQ/fZZxl40CAwEAAQ== | Trivy, CanisterWorm, LiteLLM, Telnyx 악성코드에서 공통으로 발견된 RSA공개키 |
09. 참고자료
[별첨] Trivy 공급망 공격 영향 대상 목록
Type | Name | Affected versions |
Python package | litellm | 1.82.7, 1.82.8 |
Python package | telnyx | 4.87.1, 4.87.2 |
Docker image | aquasec/trivy | 0.69.4, 0.69.5, 0.69.6 |
Docker image | ghcr.io/aquasecurity/trivy | 0.69.4, 0.69.5, 0.69.6 |
Docker image | docker.io/aquasec/trivy:0.69.4 | 0.69.4, 0.69.5, 0.69.6 |
Docker image | public.ecr.aws/aquasecurity/trivy | 0.69.4, 0.69.5, 0.69.6 |
GitHub action | aquasecurity/setup-trivy | 0.2.0 to 0.2.6 |
GitHub action | aquasecurity/trivy-action | All tags not starting with v, except 0.35.0 |
GitHub action | Checkmarx/kics-github-action | v1.1 |
GitHub action | Checkmarx/ast-github-action | v2.3.28 |
OpenVSX extension | ast-results | 2.53.0 |
OpenVSX extension | cx-dev-assist | 1.7.0 |
npm package | @pypestream/floating-ui-dom | 2.15.1 |
npm package | @leafnoise/mirage | 2.0.3 |
npm package | @opengov/ppf-backend-types | 1.141.2 |
npm package | eslint-config-ppf | 0.128.2 |
npm package | react-leaflet-marker-layer | 0.1.5 |
npm package | react-leaflet-cluster-layer | 0.0.4 |
npm package | react-autolink-text | 2.0.1 |
npm package | opengov-k6-core | 1.0.2 |
npm package | jest-preset-ppf | 0.0.2 |
npm package | cit-playwright-tests | 1.0.1 |
npm package | eslint-config-service-users | 0.0.3 |
npm package | babel-plugin-react-pure-component | 0.1.6 |
npm package | @opengov/form-renderer | 0.2.20 |
npm package | @opengov/qa-record-types-api | 1.0.3 |
npm package | @opengov/form-builder | 0.12.3 |
npm package | @opengov/ppf-eslint-config | 0.1.11 |
npm package | @opengov/form-utils | 0.7.2 |
npm package | react-leaflet-heatmap-layer | 2.0.1 |
npm package | @virtahealth/substrate-root | 1.0.1 |
npm package | @airtm/uuid-base32 | 1.0.2 |
npm package | @emilgroup/setting-sdk | 0.2.3,0.2.2,0.2.1 |
npm package | @emilgroup/partner-portal-sdk | 1.1.3,1.1.2,1.1.1 |
npm package | @emilgroup/gdv-sdk-node | 2.6.3,2.6.2,2.6.1 |
npm package | @emilgroup/docxtemplater-util | 1.1.4,1.1.3,1.1.2 |
npm package | @emilgroup/accounting-sdk | 1.27.3,1.27.2,1.27.1 |
npm package | @emilgroup/task-sdk | 1.0.4,1.0.3,1.0.2 |
npm package | @emilgroup/setting-sdk-node | 0.2.3,0.2.2,0.2.1 |
npm package | @emilgroup/task-sdk-node | 1.0.4,1.0.3,1.0.2 |
npm package | @emilgroup/partner-sdk | 1.19.3,1.19.2,1.19.1 |
npm package | @emilgroup/numbergenerator-sdk-node | 1.3.3,1.3.2,1.3.1 |
npm package | @emilgroup/customer-sdk | 1.54.5,1.54.4,1.54.3,1.54.2,1.54.1 |
npm package | @emilgroup/commission-sdk | 1.0.3,1.0.2,1.0.1 |
npm package | @emilgroup/process-manager-sdk | 1.4.2,1.4.1 |
npm package | @emilgroup/changelog-sdk-node | 1.0.3,1.0.2 |
npm package | @emilgroup/document-sdk-node | 1.43.6,1.43.5,1.43.4,1.43.3,1.43.2,1.43.1 |
npm package | @emilgroup/commission-sdk-node | 1.0.3,1.0.2,1.0.1 |
npm package | @emilgroup/document-uploader | 0.0.12,0.0.11,0.0.10 |
npm package | @emilgroup/discount-sdk | 1.5.3,1.5.2,1.5.1 |
npm package | @emilgroup/discount-sdk-node | 1.5.2,1.5.1 |
npm package | @teale.io/eslint-config | 1.8.16,1.8.15,1.8.14,1.8.13,1.8.12,1.8.11,1.8.10,1.8.9 |
npm package | @emilgroup/insurance-sdk | 1.97.6,1.97.5,1.97.4,1.97.3,1.97.2,1.97.1 |
npm package | @emilgroup/account-sdk | 1.41.2,1.41.1 |
npm package | @emilgroup/account-sdk-node | 1.40.2,1.40.1 |
npm package | @emilgroup/accounting-sdk-node | 1.26.2,1.26.1 |
npm package | @emilgroup/api-documentation | 1.19.2,1.19.1 |
npm package | @emilgroup/auth-sdk | 1.25.2,1.25.1 |
npm package | @emilgroup/auth-sdk-node | 1.21.2,1.21.1 |
npm package | @emilgroup/billing-sdk | 1.56.2,1.56.1 |
npm package | @emilgroup/billing-sdk-node | 1.57.2,1.57.1 |
npm package | @emilgroup/claim-sdk | 1.41.2,1.41.1 |
npm package | @emilgroup/claim-sdk-node | 1.39.2,1.39.1 |
npm package | @emilgroup/customer-sdk-node | 1.55.2,1.55.1 |
npm package | @emilgroup/document-sdk | 1.45.2,1.45.1 |
npm package | @emilgroup/gdv-sdk | 2.6.2,2.6.1 |
npm package | @emilgroup/insurance-sdk-node | 1.95.2,1.95.1 |
npm package | @emilgroup/notification-sdk-node | 1.4.2,1.4.1 |
npm package | @emilgroup/partner-portal-sdk-node | 1.1.2,1.1.1 |
npm package | @emilgroup/partner-sdk-node | 1.19.2,1.19.1 |
npm package | @emilgroup/payment-sdk | 1.15.2,1.15.1 |
npm package | @emilgroup/payment-sdk-node | 1.23.2,1.23.1 |
npm package | @emilgroup/process-manager-sdk-node | 1.13.2,1.13.1 |
npm package | @emilgroup/public-api-sdk | 1.33.2,1.33.1 |
npm package | @emilgroup/public-api-sdk-node | 1.35.2,1.35.1 |
npm package | @emilgroup/tenant-sdk | 1.34.2,1.34.1 |
npm package | @emilgroup/tenant-sdk-node | 1.33.2,1.33.1 |
npm package | @emilgroup/translation-sdk-node | 1.1.2,1.1.1 |























.png&blockId=345f216a-760c-805f-816c-e0372cdbf587&width=512)








