LinkedIn 채용 제안 속의 백도어 — npm prepare 스크립트 한 줄이 개발자 머신을 잠재 RAT 로 만드는 자리
LinkedIn 채용 제안 속의 백도어 — npm prepare 스크립트 한 줄이 개발자 머신을 잠재 RAT 로 만드는 자리
채용 메시지 한 통이 GitHub 저장소 하나를 거쳐 개발자의 머신을 RAT 로 만드는 사회 공학의 새 표본. Lazarus 인가, 일반 cybercrime 인가, 그 사이의 회색 지대인가.
도입 — 채용 제안과 함께 도착한 한 줄
2026 년 6 월 16 일, 러시아 출신 풀스택 Python 개발자 Roman Imankulov 가 자기 블로그에 한 편의 사후 분석을 올렸다. 제목은 “LinkedIn 채용 제안 속의 백도어 (A backdoor in a LinkedIn job offer)” 였다. Hacker News 의 첫 페이지에 올라 1541 점을 받았고, 댓글 294 개가 달렸다. 같은 주의 어떤 보안 뉴스 — Anthropic 의 Fable 5 정지, Fedora 의 AI 에이전트 침투 — 도 같은 자리를 받지 못했다. 한 개발자의 1 인칭 보고가 그렇게 큰 호응을 얻은 까닭은, 그 보고가 모든 개발자가 매일 받는 한 종류의 메시지 — “당신에게 맞는 자리가 있다” 는 LinkedIn 메시지 — 의 가장 어두운 쪽 모서리를 가시화했기 때문이다.
사건의 핵심은 한 줄로 정리된다. 채용 담당자가 LinkedIn 으로 접근해 며칠 동안 자연스러운 대화를 나누고, 결국 “우리 회사의 broken POC 가 있는데, lead engineer 가 될 사람이라면 코드 리뷰를 부탁드린다” 며 GitHub 저장소 링크를 보냈다. 그 저장소의 app/test/index.js 에 RCE (원격 코드 실행) 백도어가 숨어 있었고, npm install 의 prepare 라이프사이클 훅을 통해 자동 실행되도록 설계되어 있었다. 즉 후보자가 저장소를 clone 하고 한 번 npm install 만 했어도, 같은 순간 그 머신에 임의 명령을 받을 수 있는 백도어가 깔린다는 그림이었다. Imankulov 는 정확히 그 한 줄짜리 발견의 사후를 쓴 것이다.
사건의 메커니즘 — 어디서, 누가, 어떻게
공격은 네 단계로 진행되었다. 첫째, LinkedIn 메시지의 자연스러움. 채용 담당자의 프로필은 실재하는 예술 분야 기자의 정체성을 도용한 것이었다. 사진, 경력, 학력 모두가 실재 인물의 정보였다. Imankulov 도 이 단계에서는 의심하지 않았다. 메시지의 톤도 한 줄짜리 spam 이 아니라, 며칠 동안의 자연스러운 대화 — 회사 소개, 자리 설명, 일정 조율 — 였다. 사회 공학의 1 차 단계가 정확히 그것이다. “이 사람은 진짜다” 라는 신뢰를 시간을 두고 쌓는 일.
둘째, GitHub 저장소의 위장. 채용 담당자가 보낸 저장소는 한 명의 실재하는 풀스택 개발자의 정체성으로 39 개의 커밋이 누적된 모양이었다. 그 풀스택 개발자도 실재 인물이었고, 그 사람의 GitHub 계정 자체는 도용된 것이 아니라 별개의 fake 계정이 그 사람의 이름을 빌려 쓴 것이었다. 39 개의 커밋이 자연스럽게 누적되어 있다는 사실 자체가 “이 저장소는 실재하는 프로젝트의 작업 기록이다” 라는 신호를 만든다. 일회용 trap repo 가 아닌 것처럼 보이는 위장이다.
셋째, npm prepare 라이프사이클의 악용. 백도어의 위치는 app/test/index.js — 테스트 스위트로 위장된 디렉터리 안의 파일이었다. 그러나 그 파일이 직접 실행될 필요는 없었다. package.json 의 scripts.prepare 가 그 파일을 부르는 한 줄을 가지고 있었기 때문이다. npm install 이 끝나는 즉시 (또는 패키지가 publish 되기 직전, 또는 git dependency 가 설치되는 즉시), prepare 훅이 자동 실행된다. 사용자의 명시적 명령 없이. 개발자의 머신에서 가장 일상적인 명령 — npm install — 이 백도어의 트리거가 되는 자리다.
넷째, 페이로드의 정체. 백도어 코드는 1 차로는 외부 C&C 서버에 접속해 임의의 명령을 받아 실행하는 RAT (Remote Access Trojan) 의 1 차 단계였다. 직접적 페이로드 — 비밀번호 탈취, 암호화폐 지갑 절도, ransomware 설치 — 는 백도어 자체에 들어 있지 않았다. 그 대신 2 차 페이로드를 C&C 서버에서 동적으로 받아 실행한다. 이는 정찰 단계와 본격 공격 단계를 분리하는 표준적인 APT (advanced persistent threat) 패턴이다.
Imankulov 의 사후 보고는 GitHub 과 LinkedIn 양쪽에 신고를 마쳤다는 한 줄로 끝난다. 그러나 그 한 줄에 무거운 추가가 따른다. “신고했지만 아무것도 바뀌지 않았다. 그 코드는 여전히 거기 있다.” 보고의 발행 시점에서도 같은 저장소가 같은 자리에서, 같은 백도어를 깔아 둔 채로 살아 있다는 사실이다. GitHub 의 abuse report 처리 속도, LinkedIn 의 가짜 프로필 적발 속도 모두가 같은 자리에서 실패했다.
사회 공학의 새 표본 — 누가, 왜 이런 공격을 하는가
이 공격의 attribution 은 Imankulov 의 글에서 직접 시도되지 않는다. 그러나 같은 패턴 — LinkedIn 의 가짜 채용 담당자 + GitHub 저장소 + npm prepare 백도어 — 은 2024 년부터 보안 커뮤니티가 추적해 온 익숙한 시안이다. 가장 유력한 attribution 후보는 두 그룹이다.
첫째, Lazarus Group (북한). 2024 년 8 월 Mandiant 의 보고서가 정확히 같은 패턴을 정리했다. 가짜 채용 담당자가 LinkedIn 에서 풀스택 또는 블록체인 개발자에게 접근해, GitHub 의 트랩 저장소로 유도한다. 페이로드는 cryptocurrency wallet 탈취가 주된 목표였다. 2025 년 SecurityWeek 의 후속 보고도 같은 그룹이 같은 패턴을 변형해 운영한다는 정황을 정리했다. Imankulov 가 받은 메시지가 cryptocurrency startup 의 lead engineer 자리를 제안했다는 사실은 이 attribution 의 가장 강한 정황 증거다.
둘째, 일반 cybercrime gang. 같은 패턴을 일반 cybercrime gang 이 모방하는 사례가 2025 년 후반부터 보고되기 시작했다. attribution 의 비대칭은 명확하다. 같은 도구, 같은 패턴, 다른 동기 — 한쪽은 국가 자금 조달, 다른 쪽은 직접 절도. 두 동기는 페이로드의 정체에서 갈린다. Lazarus 의 페이로드는 cryptocurrency wallet 의 mnemonic 과 private key 를 노린다. 일반 cybercrime 의 페이로드는 ransomware 또는 일반 credential 탈취를 노린다. Imankulov 가 백도어의 2 차 페이로드를 trigger 하지 않아서 정확한 정체는 알 수 없다.
attribution 의 결과와 무관하게, 이 공격이 만들어내는 시스템적 위협은 명확하다. 매일 LinkedIn 에서 채용 메시지를 받는 모든 개발자가 같은 표면 위에 있다. 그 표면의 크기는 크고, 깊이는 얕다. 한 줄짜리 자연스러운 메시지, 한 GitHub 저장소, 한 번의 npm install — 이 세 단계가 모두 일상의 한 부분이다. 일상의 한 부분에 트랩이 끼어든다는 사실이 이 공격을 사회 공학의 새 표본으로 만든다.
이 풍경에서 가장 깊은 비대칭은 무엇인가. 공격자의 비용은 작다. 가짜 LinkedIn 프로필 하나를 만드는 데 며칠, GitHub 저장소 하나를 위장하는 데 며칠, 자연스러운 대화를 위해 LLM 의 도움을 받는 데 추가 시간이 든다. 같은 LinkedIn 프로필이 동시에 수십 명의 후보자에게 접근할 수 있다. 후보자의 비용은 크다. 한 번의 npm install 이 자기 머신을 잠재 RAT 로 만들 수 있다. 그 결과 비대칭은 두 자릿수 이상이다.
여기에 LLM 시대의 새 변수가 겹친다. Imankulov 의 글에서는 직접 언급되지 않지만, 자연스러운 LinkedIn 메시지의 작성을 LLM 이 도왔을 가능성이 매우 높다. 한 줄짜리 한국어 spam 이 5 년 전의 표본이라면, 며칠에 걸친 자연스러운 다국어 대화가 2026 년의 표본이다. 사용자 jhhh 의 댓글이 정확히 짚는다. “LLM 으로 사회 공학의 대화 비용이 0 에 수렴한다.” 같은 비대칭이 채용 사칭의 새 무기가 되고 있다.
실무자에게의 함의 — 패러노이아의 비용과 실효
Imankulov 의 권고는 작다. 그러나 정확하다.
첫째, npm install 의 자동 prepare 훅을 막아라. npm install --ignore-scripts 가 가장 단순한 답이다. 이 한 플래그가 prepare, install, postinstall 같은 모든 라이프사이클 훅의 자동 실행을 막는다. 합법적인 패키지의 일부는 이 훅에 의존하기 때문에 일부 패키지의 빌드가 실패할 수 있다. 그 빌드를 사용자가 명시적으로 확인하고 다시 실행하는 추가 단계가 패러노이아의 비용이다. Imankulov 의 케이스에서 이 한 플래그가 있었다면 백도어는 자동 실행되지 않았을 것이다.
둘째, 코드 리뷰는 read-only 도구로. GitHub 의 웹 UI 또는 별도의 read-only 환경에서 코드를 본다. Clone → install → run 의 세 단계가 일상적인 코드 리뷰의 한 부분이라면, 그 세 단계가 트랩의 트리거가 된다. 새 코드를 처음 보는 단계에서는 그 세 단계를 거치지 않고 GitHub 의 raw 파일 뷰 또는 grep.app 같은 도구로 1 차 검토를 해야 한다.
셋째, 가짜 채용 담당자의 시그널을 학습하라. 자연스러운 며칠짜리 대화는 더 이상 진정성의 신호가 아니다. LLM 시대에는 며칠짜리 자연스러운 대화의 생성 비용이 작다. 의심해야 할 신호는 다른 곳에 있다. (a) 회사 도메인의 이메일이 아닌 LinkedIn 메시지에만 매달리는가. (b) 화상 회의를 회피하는가. (c) 코드 리뷰를 요청할 때 자기 회사의 사내 GitHub 이 아니라 외부 저장소를 가리키는가. 셋 다 trap 의 정확한 시그널이다.
넷째, 회사 측의 대응 정책. GitHub, LinkedIn, npm registry 의 abuse report 처리 속도가 개선되지 않으면 이 공격은 계속된다. 정책의 답은 두 갈래다. (a) GitHub 의 새 저장소 생성 시 prepare 훅의 동작에 대한 명시적 경고 표시. (b) LinkedIn 의 채용 담당자 프로필의 자기 회사 도메인 메일 인증 의무화. 둘 다 사용자 경험의 마찰을 늘리지만, Imankulov 의 사후 보고의 마지막 한 줄 — “그 코드는 여전히 거기 있다” — 가 가리키는 정책 공백을 메운다.
결론 — 한 줄짜리 prepare 가 가시화한 풍경
Imankulov 의 사후 보고가 1541 점을 받은 까닭은 그 풍경의 한 부분 — npm prepare 훅 한 줄 — 이 가장 일상적인 명령의 한 부분이기 때문이다. 모든 개발자가 매일 npm install 을 한다. 그 한 명령이 잠재 RAT 의 트리거가 될 수 있다는 사실이, 단단한 신호가 되어 1541 점의 응답을 만들었다.
진짜 질문은 그다음이다. 같은 풍경이 npm 만의 풍경인가, 다른 생태계 — pip 의 setup.py, cargo 의 build.rs, gem 의 native extension — 의 풍경인가. 답은 분명하다. 모든 생태계가 같은 표면 위에 있다. npm 의 prepare 훅은 가장 자주 악용되는 자리지만, 같은 종류의 자동 실행 훅이 모든 생태계의 표준 부분이다. Imankulov 의 한 사례가 시안이고, 그 시안이 다른 생태계로 옮겨가는 일은 시간 문제다. 그날까지 우리가 할 수 있는 일은 --ignore-scripts 한 플래그 정도의 작은 패러노이아를 일상의 한 부분으로 받아들이는 일이다.
출처: