In this article
Glasswormへの対策:サプライチェーンセキュリティを書き換える、目に見えないマルウェア
2025年10月、セキュリティ研究者のKoi Securityは、目に見えないUnicode文字を使って悪意のあるコードを見えない形で隠す、VS Code拡張機能を標的とした初の自己増殖型ワーム「Glassworm」を発見しました。35,800件を超えるインストールが侵害され、C2インフラも現在なお稼働しているGlasswormは、従来のコードレビューによるセキュリティモデルを崩すサプライチェーン攻撃の進化を示す、新たな事例です。
この記事ではGlassworm攻撃を検証し、その起源を初期のTrojan Source脆弱性(CVE-2021-42574)までさかのぼります。また、Unicode攻撃を検出・防止するオープンソースのコマンドライン検出ツールanti-trojan-sourceを使って、開発者やセキュリティチームがこうした目に見えない文字による攻撃に対処する方法を紹介します。このツールには、カテゴリベースのUnicode分析と拡張されたレポート機能が追加されています。
要点:
目に見えないUnicode文字によって、コードレビュー担当者には文字どおり見えない実行可能な悪意あるコードを隠せる
従来のコードレビュー、一部の差分表示、構文ハイライトでは、こうした攻撃をまったく検出できない
攻撃は盗んだ認証情報を使って自己増殖し、被害者を新たな感染経路に変える
検出には、明示的なリストだけでなく、Unicode文字をカテゴリ別に分析する専用ツールが必要
目に見えない文字を使った攻撃の進化
その名も「Trojan Source」という学術論文が発端です。ただし、コンピューティングにおけるセキュリティインシデントの歴史を正確に振り返ると、Unicode文字やグリフは、システムやユーザーを欺き混乱させる目的で、それ以前の数十年にもわたって使われてきました。つまり、この手法自体は新しいものではありません。
2021年11月、ケンブリッジ大学の研究者Nicholas Boucher氏とRoss Anderson氏は、画期的な論文Trojan Source: Invisible Vulnerabilitiesを発表し、事実上あらゆる現代のプログラミング言語に影響する重大な脆弱性(CVE-2021-42574)を明らかにしました。
根本的な問題は、Unicodeの双方向(bidi)テキスト制御文字にあります。この文字を使うと、ソースコードの見た目上の順序を入れ替えて人間のレビュー担当者を欺きながら、コンパイラやインタープリターが従う論理的な実行順序はそのまま維持できます。
次のCコードを見てください。
bool isAdmin = false;
/*
begin admins only
*/ if (isAdmin) {
printf("You are an admin.\n");
/*
end admins only
*/ }上のコードは、アクセスチェックで保護されているように見えます。しかし、隠されたUnicode制御文字によって、コメントがコードであるかのように表示され、ロジックの流れが完全に変わることがあります。コンパイラが見るものと、人間のレビュー担当者が見るものは異なるのです。
攻撃手法:
早期return:`return`文がコメント内にあるように見せる
コメントのコード化:コメントが実行可能なコードに見えるようにする
文字列の引き伸ばし:文字列リテラルがコードに見えるようにする
同形異字:異なる文字体系から見た目が同一の文字を使う(CVE-2021-42694)
サプライチェーンで攻撃が拡大する仕組み
Trojan Sourceの研究者は、現代のソフトウェア開発における重大な増幅効果を指摘しました。
「攻撃者が人間のレビュー担当者を欺いて、オープンソースコードに標的型の脆弱性を混入させることに成功すれば、下流のソフトウェアもその脆弱性を引き継ぐ可能性が高い。」
まさにこれが、現在の実際の攻撃で起きていることです。
Glassworm:次なる進化(2025年)
Trojan Sourceの公開から4年後、攻撃者はこうした手法を実際の攻撃に悪用しました。2025年10月、OpenVSXマーケットプレイスのVS Code拡張機能を侵害する、目に見えないUnicode文字を使った初の自己増殖型ワームGlasswormが出現しました。
Glasswormの特徴:
目に見えないコードの注入:視覚的な出力がまったくないUnicode異体字セレクターを使用。難読化や圧縮ではなく、人間の目には文字どおり見えない
自己増殖:NPM、GitHub、OpenVSXの認証情報を盗み、追加のパッケージや拡張機能を自動的に侵害して、感染を指数関数的に拡大
停止できないインフラ:
主要なC2としてSolanaブロックチェーンを使用(不変で、停止させられない)
バックアップC2としてGoogle Calendarを使用(正規サービスのため、セキュリティ制御を回避)
動的暗号化を用いた直接IP接続
完全なRAT機能:感染した開発者のワークステーションを、SOCKSプロキシ、隠しVNCサーバー、P2Pコマンドチャネルを備えた犯罪インフラに変える
Glassworm攻撃の仕組み
ステージ1:目に見えないペイロード
セキュリティ研究者が侵害されたCodeJoyのVS Code拡張機能(バージョン1.8.3)を調査したところ、2行目と7行目の間に空行のように見える箇所が見つかりました。
import * as vscode from 'vscode';
// [MASSIVE GAP HERE - APPEARS EMPTY]
export function activate(context: vscode.ExtensionContext) {しかし、その空白には実行可能なJavaScriptコードが含まれていました。具体的には、印字できないUnicode文字、つまり視覚的に表示されないUnicode異体字セレクターを使ってエンコードされていたのです。
コードレビューを行う開発者には、空行または空白に見えます。
静的解析ツールには、分析対象が何もないように見えます。
JavaScriptインタープリターには、実行可能な悪意あるコードとして認識されます。
ステージ2:ブロックチェーンを利用したC2
目に見えないコードはSolanaブロックチェーンに接続し、ハードコードされたウォレットアドレスからトランザクションのメモを読み取ります。そのメモには、次のステージをダウンロードするためのリンクをBase64エンコードしたJSONオブジェクトが含まれています。
{"link":"aHR0cDovLzIxNy42OS4zLjIxOC9xUUQlMkZKb2kzV0NXU2s4Z2dHSGlTdg=="}デコード結果:http://217.69.3.218/qQD%2FJoi3WCWSk8ggGHiSv%3D%3D
ブロックチェーンのトランザクションは不変であり、削除も変更もできません。暗号資産ウォレットは仮名で利用されるため、ホスティングプロバイダーやレジストラを介する必要がありません。攻撃者はごくわずかなコストで、新たなペイロードURLを含むトランザクションを投稿できます。
ステージ3:認証情報の窃取
ダウンロードされたペイロード(AES-256-CBCで暗号化され、キーはHTTPレスポンスヘッダーで渡される)が、次の機密性の高い認証情報を標的にします。
NPM認証トークン — 悪意あるパッケージを公開するため
GitHubトークン — リポジトリを侵害するため
OpenVSX認証情報 — さらに拡張機能を注入するため
Git認証情報 — 悪意あるコードをプッシュするため
49種類の暗号資産ウォレット拡張機能 — MetaMask、Phantom、Coinbaseなど
さらに、Google Calendarを使ったバックアップC2もあります。マルウェアは、タイトルにBase64エンコードされたURLを含むカレンダーイベントを取得します。
aHR0cDovLzIxNy42OS4zLjIxOC9nZXRfem9tYmlfcGF5bG9hZC9xUUQlMkZKb2kzV0NXU2s4Z2dHSGlUdg==パスに注目してください:/get_zombi_payload/。「zombi」はゾンビボットネットを意味します。
次にして最後となるステージはZOMBI — フル機能のRAT(リモートアクセス型トロイの木馬)です。最終ペイロードは、感染した開発者のワークステーションを犯罪インフラネットワークのノードに変えます。SOCKSプロキシで企業ネットワーク内のトラフィックを中継し、WebRTC、BitTorrent、隠しVNCなどのネットワーク機能も利用します。
攻撃者は、ログイン済みセッションがあるブラウザーを使い、メールやSlackにアクセスし、ソースコードを読み、さらに認証情報を盗み、ネットワーク内の別のシステムへ侵入できます。その間、画面上には何も起きていないように見えます。
anti-trojan-sourceで目に見えない文字を検出する
anti-trojan-sourceは、Glassworm型の目に見えない文字の注入を含む、Unicodeベースの攻撃を検出するために開発されたオープンソースのセキュリティツールです。Trojan Source攻撃の検出を目的に作られたこのツールは、明示的な文字検出とカテゴリベースのUnicode分析の両方に対応し、包括的な保護を提供するよう進化しています。
anti-trojan-sourceが検出するもの
明示的に定義された277種類の紛らわしい文字:
双方向Unicode制御文字(U+202A-U+202E、U+2066-U+2069)
ゼロ幅文字(U+200B、U+200C、U+200D)
異体字セレクター(U+FE00-U+FE0F)— 基本の16種類のセレクター
拡張異体字セレクター(U+E0100-U+E01EF)— Glasswormで使われた追加の240文字
ノーブレークスペース(U+00A0)
ワードジョイナー、ソフトハイフン、その他の目に見えない文字
カテゴリベースの検出(将来にも対応):
すべてのUnicode書式文字(Cfカテゴリ)— カテゴリに基づいて目に見えない書式文字を検出
一般的に使われる空白文字(TAB、LF、CR)を除く、すべてのUnicode制御文字(Ccカテゴリ)
カテゴリベースのアプローチにより、Unicodeに新たな目に見えない文字が追加された場合でも、文字リストを明示的に更新することなくanti-trojan-sourceで検出できます。
検出機能
1. シンプルな真偽値による検出
import { hasConfusables } from 'anti-trojan-source'
const code = readFileSync('suspicious-file.js', 'utf-8')
const isDangerous = hasConfusables({ sourceText: code })
if (isDangerous) {
console.error('⚠️ Invisible characters detected!')
}2. 詳細な検出結果
import { hasConfusables } from 'anti-trojan-source'
const findings = hasConfusables({
sourceText: code,
detailed: true
})
findings.forEach(finding => {
console.log(`Line ${finding.line}:${finding.column}`)
console.log(` Character: ${finding.codePoint} ${finding.name}`)
console.log(` Category: ${finding.category}`)
console.log(` Context: ${finding.snippet}`)
})出力例:
Line 3:45
Character: U+E0100 VARIATION SELECTOR-17
Category: Variation Selector
Context: const value = getUserInput()
Line 12:8
Character: U+200B ZERO WIDTH SPACE
Category: Cf (Format)
Context: if (isAdmin) {3. 複数の出力モードを使ったCLIの使用例
# Simple mode (exit code 1 if found)
npx anti-trojan-source --files='src/**/*.js'
# Verbose mode with detailed information
npx anti-trojan-source --files='src/**/*.js' --verbose
# JSON mode for programmatic processing
npx anti-trojan-source --files='src/**/*.js' --jsonanti-trojan-source導入ガイド
コマンドラインツールをプロジェクトにインストールします。
1npm install -D anti-trojan-sourceまたは、npxで直接実行できます(インストール不要)。
npx anti-trojan-source --files='**/*.js'続いて、単一のファイルをスキャンします。
npx anti-trojan-source src/index.jsシェルのグロブ記法を使って、複数のファイルをスキャンすることもできます。
npx anti-trojan-source --files='src/**/*.{js,ts,jsx,tsx}'anti-trojan-source CLIは、詳細な出力を表示するverboseモードに対応しています。何がどこで検出されたのかを正確に把握したい場合に便利です。
npx anti-trojan-source --files='src/**/*.js' --verbose出力例:
[x] Detected cases of trojan source in the following files:
|
- src/utils.js
Line 12:34 - U+200B ZERO WIDTH SPACE [Cf (Format)]
Snippet: const value = getUserInput()
Line 45:10 - U+202E RIGHT-TO-LEFT OVERRIDE [Cf (Format)]
Snippet: if (isAdmin) { // Check permissions
Line 78:22 - U+E0100 VARIATION SELECTOR-17 [Variation Selector]
Snippet: const token = process.env.API_KEYJSON出力モードにも対応しており、プログラムから分析できます。自動化、CI/CDへの統合、独自レポートの作成に便利です。
npx anti-trojan-source --files='src/**/*.js' --json出力例:
[
{
"file": "src/utils.js",
"findings": [
{
"line": 12,
"column": 34,
"codePoint": "U+200B",
"name": "ZERO WIDTH SPACE",
"category": "Cf (Format)",
"snippet": "const value = getUserInput()"
},
{
"line": 45,
"column": 10,
"codePoint": "U+202E",
"name": "RIGHT-TO-LEFT OVERRIDE",
"category": "Cf (Format)",
"snippet": "if (isAdmin) { // Check permissions"
}
]
}
]CI/CDとの統合
次は、CIプロセスに組み込めるGitHub Actionの作成例です。
ファイル.github/workflows/security-scan.ymlを作成します。
name: Unicode Security Scan
on:
pull_request:
push:
branches: [main, master, develop]
jobs:
scan-invisible-chars:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Scan for invisible Unicode attacks
run: npx anti-trojan-source --files='**/*.{js,ts,jsx,tsx,py,java,go,rs}' --json
- name: Fail on detection
if: failure()
run: |
echo "::error::Invisible Unicode characters detected in source code"
echo "::error::This could indicate a Trojan Source or Glassworm-style attack"
exit 1高度なカテゴリベース検出
Unicodeカテゴリを理解する
anti-trojan-sourceは特定の文字を探すだけでなく、Unicodeカテゴリに基づいて文字を分析するため、新たな攻撃にも対応できます。
書式カテゴリ(Cf):
書式に影響するものの、視覚的な出力を生成しない文字
双方向制御文字、不可視の区切り文字、ゼロ幅文字などが含まれる
例:U+200B(ZERO WIDTH SPACE)、U+202E(RIGHT-TO-LEFT OVERRIDE)
制御カテゴリ(Cc):
印字できない制御文字
範囲:U+0000-U+001FおよびU+007F-U+009F
例外:TAB(U+0009)、LF(U+000A)、CR(U+000D)は正規の文字として許可リストに登録
カテゴリベースの検出が重要な理由
従来のアプローチは、次のようなJavaScriptコードに似たものになります。
const dangerousChars = ['\u200B', '\u200C', '\u200D', /* ... explicit list */];
// Problem: New Unicode versions add new characters
// Problem: Attackers can use unlisted characters一方、カテゴリベースのアプローチには次のような利点があります。
明示的にリストされていないものも含め、すべての書式(Cf)文字を検出
ホワイトリストに登録された空白文字を除く、すべての制御(Cc)文字を検出
今後の対策
VS CodeやGitHubのオンライン差分ビューアーなど、一部のIDEがテキスト中のUnicode文字を示す機能を追加しているとはいえ、目視によるコードレビューだけに頼らないでください。「問題なさそうに見える」からといって安心せず、使用しているツールで目に見えない文字が自動的に表示されるとも考えないでください。代わりに、積極的にスキャンしましょう。
プルリクエストをマージする前に、すべてのコードをスキャンすることをおすすめします。Snykと統合(無料)すると、静的コード解析が自動実行され、GitHubソースのリポジトリやその他のインテグレーションを通じてプロジェクトを継続的に監視できます。
anti-trojan-sourceのコマンドラインユーティリティを活用して、ソースコード内の危険性がある文字を検出しましょう。
次世代のサプライチェーン脅威に備えて、開発パイプラインを将来にわたって保護しませんか?ホワイトペーパーをダウンロード:現代のソフトウェアサプライチェーンを乗りこなす — オープンソース、AI生成コード、SBOMコンプライアンスを保護する。
リソース
ツール:
anti-trojan-source CLI:https://github.com/lirantal/anti-trojan-source
ESLintプラグイン:https://github.com/lirantal/eslint-plugin-anti-trojan-source
調査:
Trojan Source論文:https://trojansource.codes/trojan-source.pdf
Glasswormの分析:https://www.koi.ai/blog/glassworm-first-self-propagating-worm-using-invisible-code-hits-openvsx-marketplace
CVE-2021-42574:Trojan Source脆弱性の公開情報
CVE-2021-42694:同形異字攻撃
標準: