mathmain 암호화 로더, 수식이 악성코드 열쇠였다
검사 환경에서 실행되지 않는 조건부 페이로드는 평판·다운로드 수·기본 버전 검사만으로 잡기 어렵다. 소스 저장소가 아니라 실제 레지스트리 아카이브를 해시·행위 기준으로 검증해야 한다.
AI 뉴스를 놓치지 마세요
매주 핵심 AI 소식을 이메일로 받아보세요.
평범한 수학 계산이 실행 조건이었다
SafeDep의 mathmain 분석은 악성 패키지가 어떻게 정적 검사와 샌드박스를 피해 가는지 보여 준다. mathmain은 널리 쓰이는 mathjs를 흉내 낸 npm 패키지였다. 추가된 코드는 단순 import나 설치 시 실행되지 않았다. 선형방정식 풀이 함수 lusolve()가 특정 3×3 파스칼 행렬을 처리할 때 생기는 LU 분해의 하삼각행렬을 비밀번호로 삼아 암호화된 파일을 풀었다.
트리거 입력은 [[1,1,1],[1,2,3],[1,3,6]], 복호화 비밀번호가 되는 L은 [[1,0,0],[1,1,0],[1,0.5,1]]이었다. 로더는 scrypt로 256비트 키를 만들고 AES-256-GCM으로 파일명과 페이로드를 복호화했다. 첫 파일은 graph.js였고, 실행 뒤 Slack 메시지를 10초마다 확인해 셸 명령을 수행하는 원격 접근 임플란트로 이어졌다. Base Sepolia 테스트넷과 Telegram도 통신 경로에 사용됐다.
한 패키지가 아니라 분리된 공격 체계
분석팀은 mathmain, mathsbase, math-universe의 5개 버전에서 같은 로더 계열을 찾았다. 암호화된 파일은 최대 약 1.18MB였으며, 공개 GitHub 저장소 두 곳에는 로더가 없었다. 악성 변경이 소스 커밋이 아니라 npm 게시 과정에서 들어갔다는 의미다. GitHub 코드만 검토하거나 저장소 배지를 신뢰하는 방식으로는 놓칠 수 있다.
더 교묘한 점은 트리거와 페이로드의 분리다. 악성 라이브러리는 특정 행렬이 들어오기 전에는 인증 태그 검사에서 멈추고 파일을 쓰지 않는다. 공격자가 통제하는 두 번째 패키지나 비공개 애플리케이션이 그 입력을 공급해야 완성된다. SafeDep과 JFrog는 공개 코드에서 트리거 호출자를 찾지 못했다. 이는 피해가 없다는 증거가 아니라 공개 검색의 가시성 한계다. JFrog의 npm 위협 연구처럼 레지스트리 악성코드는 이름·설치 훅뿐 아니라 런타임 데이터 흐름까지 봐야 한다.
| 검사 방식 | 이 공격에서 보이는 것 | 놓치는 것 | 보완책 |
|---|---|---|---|
package.json 검사 | 설치 훅이 없음 | 함수 실행 뒤의 로더 | 호출 그래프·파일 쓰기 탐지 |
| GitHub 소스 검토 | 정상 solver 코드 | 게시물에만 추가된 변경 | npm tarball과 Git 태그 비교 |
| 샌드박스 import | 즉시 행위 없음 | 특정 행렬 트리거 | 입력 변형·지연 행위 분석 |
| 다운로드 수 확인 | 비정상적으로 큰 수치 | 실제 설치·실행 여부 | 내부 lockfile·EDR 증거 대조 |
| 알려진 해시 IOC | 동일 파일 식별 | 변형된 새 페이로드 | 행위 규칙과 egress 정책 |
다운로드 수를 감염 규모로 읽으면 안 된다
npm은 9월 12~18일 기준 세 패키지에 각각 60만5157회, 192만3059회, 56만9730회 다운로드를 보고했다. 그러나 같은 기간 레지스트리 전체가 0으로 표시된 날이 있었고 공개 의존 프로젝트와 jsDelivr 트래픽도 거의 없었다. 분석팀은 이 숫자로 실제 설치나 실행 호스트 수를 판단할 수 없다고 명시했다. 숫자가 크다는 이유만으로 대규모 감염을 단정해서도, 공개 사용처가 없다는 이유로 무해하다고 결론 내려서도 안 된다.
npm의 패키지 무결성 모델은 lockfile과 무결성 해시로 같은 아카이브를 재현하는 데 도움을 준다. 하지만 최초로 잠근 아카이브 자체가 악성이면 막지 못한다. SLSA 공급망 프레임워크가 출처와 빌드 provenance를 강조하는 이유다. 소스 커밋, 빌드 과정, 배포 아카이브가 연결되어야 ‘저장소는 깨끗하다’는 말이 실제 설치물까지 확장된다.
한국 개발팀의 즉시 대응 순서
먼저 세 패키지 이름과 지목된 버전을 SBOM, lockfile, npm 캐시, CI 아티팩트에서 검색해야 한다. 일치가 있더라도 실행됐다고 단정하지 말고, SafeDep이 공개한 암호화·복호화 파일 SHA-256과 파일 생성 흔적을 대조한다. 이어 Node 프로세스가 Slack API, Telegram API, Base·Ethereum Sepolia RPC로 비정상 통신했는지 EDR·프록시 로그를 확인한다. 비밀정보가 노출됐을 가능성이 있으면 해당 실행 환경의 토큰을 회전하고 이미지를 재구축한다.
예방 측면에서는 개발자와 코딩 에이전트 모두에게 임의 패키지 추가 권한을 주지 않아야 한다. 새 의존성은 이름 유사성, 게시자 이력, tarball diff, 설치·런타임 파일 쓰기와 네트워크 행위를 검토한 뒤 허용한다. RubyGems 사건, AI 에이전트 보안의 현실과 RubyGems 사건, 에이전트 보안의 공급망 경고가 보여 주듯 자동화된 게시와 설치는 공격자의 탐색 비용도 낮춘다. Read the Docs DDoS, AI 크롤러 시대의 인프라 경고와 함께 보면 에이전트의 외부 행동을 기본 허용하는 운영 모델 자체를 재검토해야 한다.
자주 묻는 질문
Q1: 패키지를 import하기만 해도 감염되나요?
A: 분석된 경로에서는 아니다. 특정 solver 입력이 복호화 비밀번호를 만들 때 로더가 활성화됐다. 다른 경로의 부재까지 보장하는 말은 아니다.
Q2: 다운로드 수가 곧 감염 호스트 수인가요?
A: 아니다. 보고 수치에 이상이 있었고 트리거가 따로 필요해 설치·실행·감염은 서로 다른 지표다.
Q3: GitHub 저장소가 정상이라면 안전한가요?
A: 아니다. 이번 사례는 npm 배포 아카이브에만 로더가 존재했다. 실제 설치 tarball과 소스 태그를 비교해야 한다.
Q4: 왜 AES-GCM을 사용했나요?
A: 정확한 키가 없으면 내용과 파일명 모두 드러나지 않고 인증에 실패해 실행 행위도 줄어들어 분석을 어렵게 한다.
Q5: 가장 먼저 해야 할 일은 무엇인가요?
A: 의존성·캐시에서 패키지와 해시를 찾고, 일치 시 파일 생성과 C2 통신 흔적을 조사한 뒤 관련 자격증명을 회전해야 한다.
관련 토픽 더 보기
📰 원본 출처
safedep.io이 기사는 AI 기술을 활용하여 작성되었으며, 원본 뉴스 소스를 기반으로 분석 및 해설을 추가한 콘텐츠입니다. 정확한 정보 전달을 위해 노력하고 있으나, 원본 기사를 함께 확인하시기를 권장합니다.