開発者をハッキングするLinkedIn採用課題詐欺
(aisafe.io)- LinkedInでソフトウェアエンジニア職を提案した後、通常のReact/Web3課題に偽装した非公開GitHubリポジトリを渡し、実行すると
tailwind.config.jsに隠されたJavaScriptマルウェアが開発者のPC上で動作した - 30,987バイトの設定ファイルは数千個の空白の後ろに27KBの難読化コードを隠しており、リモートサーバーからAES-256-CBCで暗号化された第2段階ペイロードを受け取って
%TEMP%\packに保存した後、node packで実行した - Windows 11 ARMの隔離VMで約10分間観察した結果、ペイロードは遠隔操作バックドア、ブラウザ・ウォレット窃取機能、再帰的ファイルスキャナー、クリップボード監視機能の4つのコンポーネントを起動した
- 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 CollectibleNFTを生成する
- 実際の課題範囲は単純なMetaMaskネットワーク表示機能だった
src/utils/ethereum.jsの非同期getChainId()を、eth_chainIdを呼び出してパースした16進数値またはnullを返すように実装する- 同じファイルの
getNetworkLabel(chainId)を、既存のNETWORKSオブジェクトから読みやすいネットワーク名を探すように実装する src/components/Wallet/ConnectWalletButton.jsを、ウォレット接続後にこの2つの関数を呼び出すよう修正する
- 実装を終えて開発サーバーを起動したが、長時間起動せず、異常を察知したユーザーはインターネットケーブルを抜いた
tailwind.config.js に隠された実行コード
ls -laではtailwind.config.jsは 30,987バイトだったが、エディタでは97行しか見えず、wc -cでも同じサイズを確認できた- 95行目付近には数千個の空白の後ろに、人間には読みづらい巨大なJavaScriptブロブが隠されていた
- コードは一般的なJavaScript難読化手法を使っていた
- 重要な文字列を大きな配列に保存する
- チェックサムが一致するまで配列を回転させる
- 文字列アクセスをデコーダ関数の背後に隠す
- わかりやすい名前を数値インデックスとラッパー呼び出しに置き換える
- 2つのデコーダのうち1つはBase64形式で文字列を復元し、もう1つは文字列ごとのキーと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() })で実行する
- UIDは
- 自身で機能を果たすのではなく、別の悪性ソフトウェアをダウンロードして実行するマルウェアドロッパーである
- ダウンロードしたコードには署名検証、ハッシュ許可リスト、出所固定、パス制限、実行防止ガードがなく、リモートエンドポイントはTailwind設定を読み込んだOSアカウント権限でコードを実行できる
隔離VMで行った動的解析
- ホストシステムを露出させないよう、UTMで使い捨てのWindows 11 ARM VMを構成した
- メモリ
4096 MiB - ディスク
64 GiB shared/NATネットワーク- 共有フォルダ無効
- メモリ
- 複数の観点からマルウェアの動作を収集できるよう、ツールを導入した
- Wireshark: パケットとC2・流出トラフィックの収集
- Sysinternals Sysmon: 永続的なWindowsイベントテレメトリ
- Procmon: 実行中プロセス・レジストリ・ファイルシステム活動の収集
- マルウェアの収集対象を確認するため、おとりデータを配置した
- 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バイト - 形式: 1行で構成されたASCII JavaScript
- SHA-256:
- 復号された
packも再び難読化されていたが、第1段階と同じ手法を使っていたため、同じ手順で解除できた
4つのプログラムで構成された第2段階ペイロード
packは単一のスクリプトではなく、次の4つのプログラムを実行する小さなプロセスツリーだった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で実行され、長時間動作する子プロセスとして残る- 実行後、自身を正常なプロセスに見せかけるよう構成する
- プロセスタイトル:
vhost.ctl - 単一インスタンス表示ファイル:
%TEMP%\.npm\vhost.ctl - ホスト登録:
http://45.146.252.17/api/service/… - ログ送信:
http://45.146.252.17/api/service/makelog - Socket.IO C2:
http://45.146.252.17:7641
- プロセスタイトル:
- リモート制御機能に必要な追加パッケージもインストールする
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、2グループのnpmパッケージインストールが順に捕捉された - 単なる情報窃取を超え、攻撃者にシェルとデスクトップ制御機能まで提供する
ldata: ブラウザとウォレットデータの窃取ツール
ldataは%TEMP%に書き出され、npm i axios && node ldataで実行され、プロセスタイトルはnpm-cacheである- ブラウザプロファイルとウォレット拡張機能の保存領域を収集して
http://45.146.252.17:7649/uploadへ送信し、LevelDBの状態は/cldbsで確認する - 対象ブラウザはOSごとに幅広く設定されている
- Windows: Chrome, Edge, Brave, LT Browser
- macOS: Chrome, Brave, Opera, LT Browser, Edge、およびローカルログインキーチェーン
- Linux: 対応するChromium系プロファイルフォルダ
DefaultまたはProfile*ディレクトリから次のChromiumデータベースをアップロードするLogin DataLogin Data For AccountWeb Data
- その後
Local Extension Settings/<extension-id>を探索し、ハードコードされたウォレット拡張IDにはMetaMaskのnkbihfbeogaeaoehlefnkodbefgpgknnも含まれている - 最初の8つのウォレット拡張パスは、LevelDBディレクトリを一時フォルダにコピーして個別ファイルをアップロードし、サーバーの
cldbs応答に応じて完了または再試行を判断する - ポート
7649で確認された3つのアップロードは次のデータベースだったLogin_Data.sqlite: 51,200バイトLogin_Data_For_Account.sqlite: 51,200バイトWeb_Data.sqlite: 262,144バイト
- 実験データには保存済みパスワードやカード行はなかったが、オートフィル値6件とブラウザメタデータが含まれていた
- Chrome系ブラウザは一部フィールドをローカルで暗号化するため、データベースだけで常に平文の秘密情報を復元できるわけではない
- ただし、OSの秘密情報・Cookie・拡張機能の保存領域・ロック解除済みブラウザと組み合わせれば、認証情報窃取パイプラインの一部になる
- 必要なブラウザデータと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へ送信する
- Windowsでは
- おとり環境では、
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である- 数秒待機した後、繰り返しクリップボードを読み取る
- macOSでは
pbpaste、Windowsではpowershell -NoProfile -NonInteractive Get-Clipboardを使用する - 変更された値は
http://45.146.252.17/api/service/makelogへ送る
- 形式を解釈しなくても、ユーザーがコピーしたパスワード、復旧フレーズ、秘密鍵、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 の難読化コード が追加されていた
- ダウンロードされたペイロードは、設定を読み込んだユーザーと同じ OS 権限で実行される
- ローカルファイル、ブラウザプロファイル、保存済み認証情報、ウォレット拡張データ、SSH キー、環境変数、パッケージトークン、クラウド認証情報、ソースコード、クリップボードにアクセスできる
- CI で実行された場合、デプロイ用シークレット、ビルド成果物、レジストリ認証情報、本番アクセス用トークンも露出する可能性がある
- 必要な修正は、
tailwind.config.jsから難読化された JavaScript blob を 完全に削除すること である
まだコメントはありません。