개발자를 해킹하는 LinkedIn 채용 과제 사기
요약
LinkedIn 채용 과제를 악용한 개발자 해킹 사례를 분석했습니다. 공격자는 React/Web3 프로젝트로 위장한 GitHub 저장소에 `tailwind.config.js` 같은 설정 파일에 난독화된 JavaScript 악성코드를 숨겼습니다. 이 코드는 원격 제어 백도어를 가동하여 SSH 키, 클라우드 자격 증명 등 민감 정보를 수집하는 것이 목적이었습니다.
핵심 포인트
- 채용 과제는 비신뢰 코드로 취급해야 합니다.
- 악성코드는 설정 파일이나 공백에 숨겨질 수 있습니다.
- 원격 제어 백도어를 통해 다양한 정보가 탈취됩니다.
- 감염 시 네트워크 차단 및 비밀번호 교체가 필수입니다.
- LinkedIn에서 소프트웨어 엔지니어 채용을 제안한 뒤 정상적인 React/Web3 과제로 위장한 비공개 GitHub 저장소를 전달했으며, 실행하면
tailwind.config.js
에 숨겨진 JavaScript 악성코드가 개발자 컴퓨터에서 작동함
- 30,987바이트짜리 설정 파일은 수천 개의 공백 뒤에 27KB 난독화 코드를 숨겼고, 원격 서버에서 AES-256-CBC로 암호화된 2단계 페이로드를 받아
%TEMP%\pack
에 저장한 뒤 node pack
으로 실행함
- Windows 11 ARM 격리 VM에서 약 10분간 관찰한 결과, 페이로드는
원격 제어 백도어, 브라우저·지갑 탈취기, 재귀 파일 스캐너, 클립보드 감시기라는 네 구성 요소를 가동함 - Socket.IO를 통해 터미널·SSH·화면·키보드·마우스를 제어하고 Chromium 데이터베이스, 지갑 확장 저장소, SSH 키, 소스 코드, 설정 파일 등을 수집했으며, 파일 업로드 포트에서
2,588건의 요청이 관찰됨 - 낯선 사람이 보낸 과제 저장소는 비신뢰 코드로 취급해 실제 브라우저 프로필·SSH 키·클라우드 자격 증명·지갑이 없는 일회용 VM이나 컨테이너에서 검사해야 하며, 감염됐다면 네트워크 차단과 증거 보존 후
비밀 정보까지 교체해야 함
정상적인 Web3 채용 과제로 위장한 저장소
- LinkedIn 사용자 Wayan Adrian은 shrapnel.com의 소프트웨어 엔지니어 직무를 제안하고,
yevhen-o
계정의 비공개 GitHub 저장소 tech-active-workplace-frontend-main
을 과제로 전달함
BLAIEXS라는 NFT 캠페인 플랫폼은 겉으로는 일반적인 React/Web3 프로젝트였음
- React 프런트엔드는 브랜드·관리자 대시보드, 캠페인 관리, NFT 생성 흐름 등을 제공함
- Express API는 인증, 대시보드 데이터, KYB 검사, NFT 생성·클레임, 파일 업로드와 일부 스텁 엔드포인트를 처리함
- 시드 스크립트는 superadmin·브랜드·소비자 데모 계정,
Demo NFT Drop
캠페인, 100개가 제공되는 Demo Collectible
NFT를 생성함
- 실제 과제 범위는 단순한
MetaMask 네트워크 표시 기능이었음
src/utils/ethereum.js
의 비동기 getChainId()
가 eth_chainId
를 호출하고 파싱한 16진수 값 또는 null
을 반환하도록 구현함
- 같은 파일의
getNetworkLabel(chainId)
가 기존 NETWORKS
객체에서 읽기 쉬운 네트워크 이름을 찾도록 구현함
src/components/Wallet/ConnectWalletButton.js
가 지갑 연결 후 두 함수를 호출하도록 수정함
- 구현을 마치고 개발 서버를 실행했지만 오랫동안 시작되지 않았고, 이상을 감지한 사용자는 인터넷 케이블을 분리함
tailwind.config.js
에 숨겨진 실행 코드
ls -la
에서 tailwind.config.js
는 30,987바이트였지만 편집기에서는 97줄만 보였으며, wc -c
로도 같은 크기를 확인함
-
95번째 줄 부근에는 수천 개의 공백 뒤로 사람이 읽기 어려운 대형 JavaScript 블롭이 숨겨져 있었음
-
코드는 일반적인
JavaScript 난독화 방식을 사용함 -
중요한 문자열을 큰 배열에 저장함
-
체크섬이 맞을 때까지 배열을 회전함
-
문자열 접근을 디코더 함수 뒤에 숨김
-
명확한 이름을 숫자 인덱스와 래퍼 호출로 교체함
-
두 디코더 중 하나는 Base64 형태로 문자열을 복구하고, 다른 하나는 문자열별 키와
RC4 유사 스트림 암호를 적용했으며, 디코더 인덱스가 맞기 전에 문자열 배열을 회전시킴 -
악성코드를 실행하지 않고도 다음 순서로 난독화를 해제할 수 있었음
-
문자열 배열을 추출함
-
예상 체크섬에 도달하도록 배열 회전을 재현함
-
디코더 함수를 다시 구현함
b(0x214)
, c(0x2f1, "key")
같은 호출을 실제 문자열로 치환함
- 래퍼 객체와 도우미 함수를 단순화해 실행 의도를 드러냄
1단계 드로퍼의 동작
- 해제된 코드는
child_process
, os
, fs
, path
, crypto
를 불러오고 임시 디렉터리에 axios
와 socket.io-client
를 설치함
- 프로세스 수준의
uncaughtException
과 unhandledRejection
처리기는 오류를 무시하도록 구성됨
원격 페이로드 실행 흐름은 다음과 같음
- UID는
59e605dd78fb2aafccd1b622f06a00ca
임
http://45.146.252.17/api/service/59e605dd78fb2aafccd1b622f06a00ca
에서 데이터를 가져옴
- UID와 문자열
salt
를 crypto.scryptSync
에 넣어 32바이트 키를 파생함
- 응답을
base64_iv:base64_ciphertext
형식으로 분리하고 aes-256-cbc
로 복호화함
- 평문을 임시 디렉터리의
pack
파일에 기록함
child_process.execSync("node pack", { windowsHide: true, cwd: os.tmpdir() })
로 실행함
- 자체 기능을 수행하기보다 다른 악성 소프트웨어를 내려받아 실행하는
멀웨어 드로퍼임 - 다운로드한 코드에는 서명 검증, 해시 허용 목록, 출처 고정, 경로 제한, 실행 방지 가드가 없어서 원격 엔드포인트가 Tailwind 설정을 불러온 운영체제 계정의 권한으로 코드를 실행할 수 있음
격리 VM에서 수행한 동적 분석
-
호스트 시스템을 노출하지 않도록 UTM으로 일회용
Windows 11 ARM VM을 구성함 -
메모리
4096 MiB -
디스크
64 GiB
shared/NAT
네트워킹
-
공유 폴더 비활성화
-
여러 관점에서 악성코드 동작을 수집하도록 도구를 설치함
-
악성코드의 수집 대상을 확인하기 위해
미끼 데이터를 배치함 -
SSH 키 쌍
-
비공개 GitHub 저장소
-
암호화폐 지갑
-
브라우저 데이터
-
정보 탈취기가 노릴 만한 기타 파일
-
VM에서
yarn start
를 실행하고 약 10분 동안 관찰한 뒤 run1-full.pcapng
, run1-sysmon.evtx
, stage2-pack.bin
을 확보함
네트워크에서 확인된 감염 흐름
45.146.252.17
로 향한 HTTP 요청은 포트별로 역할이 나뉘어 있었음
- 포트
80
: 1단계 페이로드 가져오기, 호스트 등록, 로그 전송
- 포트
7641
: Socket.IO 명령·제어 백도어
- 포트
7646
: 파일 업로드 2,588건
- 포트
7649
: 더 크지만 수가 적은 브라우저 데이터 업로드 3건
- 첫 요청은
GET /api/service/59e605dd78fb2aafccd1b622f06a00ca
였으며, 응답은 Content-Length: 150897
인 base64_iv:base64_ciphertext
형식이었음
tailwind.config.js
는 응답을 AES-256-CBC로 복호화하고 평문을 %TEMP%\pack
에 쓴 뒤 node pack
으로 실행함
-
확보한
2단계 페이로드의 특성은 다음과 같음 -
SHA-256:
68a64d8c015c06fd70bcb8c5878c1e430da827dd00b62f8e6ef69e76bb94de5b -
크기:
113136
바이트
-
형식: 한 줄로 구성된 ASCII JavaScript
-
복호화된
pack
도 다시 난독화돼 있었지만 첫 단계와 같은 기법을 사용해 동일한 절차로 해제할 수 있었음
네 개 프로그램으로 구성된 2단계 페이로드
pack
은 단일 스크립트가 아니라 다음 네 프로그램을 실행하는 작은 프로세스 트리였음
scdata
명령·제어 백도어를 임시 디렉터리에 기록하고 실행함
ldata
브라우저·지갑 탈취기를 임시 디렉터리에 기록하고 실행함
- 재귀 파일 스캐너를
node -e
로 메모리에서 직접 실행함
- 클립보드 감시기를
node -e
로 메모리에서 직접 실행함
- 주요 설정은 UID
59e605dd78fb2aafccd1b622f06a00ca
, 사용자 키 308
, 호스트 45.146.252.17
, Socket.IO 포트 7641
, 키 포트 7648
, 브라우저 업로드 포트 7649
, 파일 업로드 포트 7646
이었음
scdata
: 대화형 원격 제어 백도어
scdata
는 %TEMP%
에 파일로 기록된 뒤 npm i axios socket.io-client ... && node scdata
로 실행되며, 장시간 동작하는 자식 프로세스로 남음
- 실행 후 자신을 정상적인 프로세스처럼 보이도록 구성함
- 원격 제어 기능에 필요한 추가 패키지도 설치함
socket.io-client
, ssh2
, node-pty
: 터미널과 SSH 유사 기능
sharp
, screenshot-desktop
, clipboardy
, @nut-tree-fork/nut-js
: 스크린샷, 클립보드, 마우스 이동·클릭·스크롤, 키보드 입력
Socket.IO 이벤트는 원격 제어 범위를 직접 보여줌
- 터미널:
start-terminal
, terminal-input
, terminal-resize
, stop-terminal
, command
- 호스트 확인과 캡처:
whour
, capture
- 입력 제어:
mouseMove
, mouseClick
, mouseScroll
, keyTap
, keyCombo
- 클립보드:
copyText
, pasteText
- SSH와 종료:
start_ssh
, ssh_input
, kill
- PCAP에서는 포트
7641
의 폴링·WebSocket 핸드셰이크와 최초 서버 이벤트 whour
가 확인됐고, Sysmon에서는 node.exe scdata
, 시스템 정보 조회 PowerShell, 두 그룹의 npm 패키지 설치가 차례로 포착됨
- 단순 정보 탈취를 넘어 공격자에게
셸과 데스크톱 제어 기능까지 제공함
ldata
: 브라우저와 지갑 데이터 탈취기
ldata
는 %TEMP%
에 기록되고 npm i axios && node ldata
로 실행되며, 프로세스 제목은 npm-cache
임
- 브라우저 프로필과 지갑 확장 저장소를 수집해
http://45.146.252.17:7649/upload
로 보내고, LevelDB 상태는 /cldbs
에서 확인함
- 대상 브라우저는 운영체제별로 폭넓게 설정돼 있음
- Windows: Chrome, Edge, Brave, LT Browser
- macOS: Chrome, Brave, Opera, LT Browser, Edge와 로컬 로그인 키체인
- Linux: 대응하는 Chromium 계열 프로필 폴더
Default
또는 Profile*
디렉터리에서 다음 Chromium 데이터베이스를 업로드함
Login Data
Login Data For Account
Web Data
- 이후
Local Extension Settings/<extension-id>
를 탐색하며, 하드코딩된 지갑 확장 ID에는 MetaMask의 nkbihfbeogaeaoehlefnkodbefgpgknn
도 들어 있음
- 첫 8개 지갑 확장 경로는 LevelDB 디렉터리를 임시 폴더로 복사해 개별 파일을 업로드하고, 서버의
cldbs
응답에 따라 완료 여부나 재시도를 결정함
- 포트
7649
에서 확인된 세 업로드는 다음 데이터베이스였음
Login_Data.sqlite
: 51,200바이트
Login_Data_For_Account.sqlite
: 51,200바이트
Web_Data.sqlite
: 262,144바이트
-
실험 데이터에는 저장된 비밀번호나 카드 행이 없었지만
자동완성 값 6개와 브라우저 메타데이터가 포함돼 있었음 -
Chrome 계열 브라우저는 일부 필드를 로컬에서 암호화하므로 데이터베이스만으로 항상 평문 비밀을 복구할 수는 없음
-
다만 운영체제 비밀·쿠키·확장 저장소·잠금 해제된 브라우저와 결합하면 자격 증명 탈취 파이프라인의 일부가 됨
-
필요한 브라우저 데이터와 LevelDB를 업로드하거나 서버가 완료로 표시하면 종료되며, 운영자의 별도 명령을 기다리지 않음
재귀 파일 스캐너와 클립보드 감시기
재귀 파일 스캐너는 별도 파일을 만들지 않고 node -e
로 실행되며 사용자 홈 디렉터리부터 탐색함
- Windows에서는
Get-CimInstance Win32_LogicalDisk
로 드라이브 문자를 열거하고 각 드라이브 루트도 검사함
- 수집 파일 패턴에는
*.env*
, *.md
, *.pem
, *.ini
, *.secret
, *.json
, *.js
, *.ts
, *.csv
, *.txt
, *.doc
, *.docx
, *.pdf
, *.xlsx
, .zsh_history
, .bash_history
가 포함됨
~/.ssh
, ~/.aws
, ~/.azure
, ~/.config
, ~/.foundry
는 자동으로 관심 폴더로 취급함
metamask
, bitcoin
, btc
, solana
, secret phrase
, private key
같은 이름도 찾음
- 결과는
http://45.146.252.17:7646/upload
로 전송함
- 미끼 환경에서는
id_ed25519
, id_ed25519.pub
, History.txt
, seed.js
, password.js
, tokens.js
, ConnectWalletButton.js
, ExportWallet.js
, RegisterWallet.js
, tailwind.config.js
, aptos-cli.json
등이 실제로 업로드됨
ldata
가 브라우저와 지갑 저장소를 전담한다면 파일 스캐너는 SSH 키·설정·소스 코드·로컬 비밀·셸 기록·프로젝트 파일을 광범위하게 수집함
클립보드 감시기도 node -e
로 실행되며 프로세스 제목은 npm-compiler.log
임
- 형식을 해석하지 않아도 사용자가 복사하는 비밀번호, 복구 문구, 개인 키, API 토큰, 일회용 코드, GitHub URL, 지갑 주소, 배포 비밀을 그대로 포착할 수 있음
채용 과제를 실행하기 전 지켜야 할 원칙
- 낯선 사람이 보낸 과제 저장소는 안전성이 확인되기 전까지
비신뢰 실행 코드로 취급해야 함
yarn install
, npm install
, yarn build
, yarn start
전에 다음 항목을 검토해야 함
package.json
-
수명 주기 스크립트
-
설정 파일
-
명백한 의존성 훅
-
이 사례의 악성코드는 사람들이 지나치기 쉬운
tailwind.config.js
에 숨어 있었으며, 설정 파일·의존성·빌드 도구도 모두 코드로 실행될 수 있음
-
면접 프로젝트는 실제 비밀이 마운트되지 않은
일회용 VM이나 컨테이너에서 실행해야 함 -
개인 브라우저 프로필
-
비밀번호 관리자
-
SSH 키
-
암호화폐 지갑
-
GitHub 토큰
-
클라우드 자격 증명
-
은행 세션
-
기본 이메일 계정
-
계정이나 지갑이 필요한 과제에는 자금이 없고 다른 서비스에서 재사용하지 않은 새 테스트 신원을 사용해야 함
-
Web3 과제라면 테스트넷만 사용하고, 무해해 보여도 실제 지갑을 알 수 없는 프로젝트에 연결하지 않아야 함
감염 여부를 확인하는 지표
- macOS·Linux에서는 다음 항목을 우선 확인함
node
, pack
, scdata
, ldata
, npm-compiler
, vhost.ctl
관련 실행 프로세스
45.146.252.17
또는 포트 7641
, 7646
, 7649
와의 연결
- 임시 디렉터리, Downloads, Desktop, Documents, Library 아래의
pack
, scdata
, ldata
, vhost.ctl
*/.npm/vhost.ctl
표시 파일
- 내려받은 프로젝트 안의 IP, UID,
/api/service/makelog
, node scdata
, node ldata
, node pack
문자열
- Windows에서는 다음 항목을 확인함
node
, npm
, cmd
, powershell
프로세스 가운데 명령줄에 pack
, scdata
, ldata
, node -e
, IP 주소, Get-Clipboard
가 있는 항목
45.146.252.17
또는 원격 포트 7641
, 7646
, 7649
로 열린 TCP 연결
%TEMP%
아래의 pack
, scdata
, ldata
, vhost.ctl
, .npm\vhost.ctl
-
면접 저장소를 복제한 디렉터리 안의 알려진 C2 문자열
-
살아 있는 Node 프로세스, 해당 IP 연결,
pack
·scdata
·ldata
산출물 중 하나라도 발견되면 시스템이 노출된 것으로 간주해야 함
- 이 지표는 분석한 샘플에 특화된 1차 점검 사항이며 완전한 포렌식 절차는 아님
감염 시스템 정리와 비밀 교체
-
가장 먼저 컴퓨터를
네트워크에서 분리하고, 감염 환경에서 계속 탐색하거나 디버깅하거나 새 비밀을 복사하지 않아야 함 -
증거가 필요하다면 삭제 전에 다음 자료를 보존함
-
프로세스 목록
-
네트워크 연결
-
의심스러운 파일
-
브라우저 기록
-
악성 저장소
-
이후
node pack
, node scdata
, node ldata
, node -e
, npm-compiler
, vhost.ctl
관련 프로세스를 종료하고 임시 디렉터리의 pack
, scdata
, ldata
, .npm/vhost.ctl
을 제거함
- 일회용 VM이고 정상적인 Node 작업을 보존할 필요가 없다면 macOS·Linux의
pkill node
나 Windows의 전체 node
프로세스 강제 종료를 사용할 수 있음
- 악성 저장소와
node_modules
를 삭제하되, 추가 분석이 필요하면 ZIP 사본을 오프라인에 보존함
-
파일 삭제만으로는 충분하지 않으며 다음
비밀 정보를 교체·폐기해야 함 -
SSH 키
-
GitHub·npm 토큰
-
클라우드 키와 API 키
-
배포 비밀
-
브라우저에 저장된 비밀번호
-
활성 로그인 세션
-
지갑 시드나 개인 키가 감염 시스템에 닿았을 가능성이 있다면 깨끗한 장치에서 새 지갑을 만들고 자금을 이전해야 함
개발 도구의 신뢰 경계를 악용한 공격
-
전통적인 백신 제품은 이 저장소를 탐지하지 않았고, 과제 해결에 사용된 인기
AI 코딩 에이전트도 프로젝트에 숨겨진 악성코드를 식별하지 못함 -
Tailwind는 JavaScript 설정 파일을 Node 모듈로 평가하므로, 개발 도구·빌드 스크립트·편집기 통합·Tailwind CLI·번들러·CI 작업이 설정을 불러오는 순간 95번째 줄의 IIFE가 실행됨
-
정상적인 Tailwind 설정은 94번째 줄까지였고, 그 뒤에 약
27KB의 난독화 코드가 추가돼 있었음 -
다운로드된 페이로드는 설정을 불러온 사용자와 같은 운영체제 권한으로 실행됨
-
로컬 파일, 브라우저 프로필, 저장 자격 증명, 지갑 확장 데이터, SSH 키, 환경 변수, 패키지 토큰, 클라우드 자격 증명, 소스 코드, 클립보드에 접근할 수 있음
-
CI에서 실행되면 배포 비밀, 빌드 산출물, 레지스트리 자격 증명, 프로덕션 접근 토큰도 노출될 수 있음
-
필요한 수정은
tailwind.config.js
에서 난독화된 JavaScript 블롭을 완전히 제거하는 것임
댓글과 토론
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기