0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

n8nに致命的RCE脆弱性〜AIワークフロー自動化が全世界10万台のサーバーを危険にさらす〜

0
Posted at

この記事の対象読者

  • n8nでワークフロー自動化を構築・運用しているエンジニアの方
  • AIエージェントやノーコード/ローコード自動化基盤のセキュリティに関心がある方
  • セルフホスト型の自動化ツールを管理しているインフラエンジニアの方

この記事で得られること

  • CVE-2025-68613(CVSS 9.9 Critical)の攻撃メカニズム: ワークフローの式評価エンジンにおけるサンドボックス脱出がなぜ完全なサーバー乗っ取りに直結するのか理解できる
  • 10万台以上が即座にリスクにさらされた現実: Censysの調査で判明した、インターネットに公開された脆弱なn8nインスタンスの実態
  • 今すぐ実行すべきパッチ適用と緩和策: バージョン確認からネットワーク隔離まで、環境別にコピペで対応できる

この記事で扱わないこと

  • n8n自体の基本的な使い方やワークフロー作成の入門
  • 他のワークフロー自動化ツール(Zapier、Make等)との比較
  • Node.jsのサンドボックス実装の一般論

1. n8nのRCEとの出会い

「ワークフローを作れるだけで、サーバーを丸ごと乗っ取れるって?」

2025年12月19日、n8nプロジェクトが緊急セキュリティアドバイザリを公開した。CVSS 9.9——ほぼ満点のCriticalスコア。認証済みユーザーであれば、ワークフローの式フィールドに悪意あるペイロードを1行入力するだけで、サーバー上で任意のOSコマンドを実行できる。

「いや、認証が必要なんでしょ? じゃあ大丈夫じゃない?」

甘い。n8nにおける「認証済みユーザー」とは、ワークフローを作成・編集できる権限を持つユーザーのことだ。これはn8nの最低権限ユーザーにデフォルトで付与される権限であり、実質的にn8nにログインできる人間全員が攻撃者になりうる。

さらにSecureLayer7がPoCを公開したことで、リスクは一気に具体化した。Censysの調査では全世界で103,476台の脆弱なn8nインスタンスがインターネットに公開されていた。

ここまでで、この脆弱性がなぜ「CVSS 9.9」なのか感じ取れたと思う。次は、この記事で使う用語を整理しておこう。


2. 前提知識の確認

本題に入る前に、この記事で登場する用語を確認する。

2.1 n8nとは

オープンソースのワークフロー自動化プラットフォーム。「ノーコード/ローコードのZapier」と呼ばれることもあるが、セルフホストが可能でカスタマイズ性が非常に高い。400以上の外部サービス(Slack、GitHub、AWS、データベース等)との統合を提供し、DevOpsチーム、SaaSプロバイダー、社内IT自動化で広く採用されている。料理で例えれば「自分専用の全自動キッチン」——あらゆる調理器具を連携させて自動で料理を作れるが、調理プログラムに毒を仕込まれたら食事全体が汚染される。

2.2 n8nのExpression(式)評価とは

n8nのワークフローでは、ノード間でデータを動的に処理するためにJavaScript式を {{ }} の中に記述できる。例えば {{ $json.name }} で前のノードの出力からname フィールドを取得する。この式はサーバーサイドで評価される。

2.3 サンドボックスとは

実行環境を制限する仕組み。n8nでは式の評価時にユーザーの入力が意図した範囲外のシステムリソースにアクセスしないよう、サンドボックスで隔離している——はずだった。CVE-2025-68613の核心は、このサンドボックスの隔離が不十分だった点にある。

2.4 Expression Injection(式インジェクション)とは

サーバーサイドで評価される式フィールドに悪意あるコードを注入する攻撃手法。SQLインジェクションの「式」版と考えるとわかりやすい。n8nの場合、{{ }} 内に記述されたJavaScriptがサーバー上で実行されるため、サンドボックスを脱出できれば事実上のRCEとなる。

これらの用語が押さえられたら、この脆弱性が生まれた背景を見ていこう。


3. n8n RCEが生まれた背景

3.1 ワークフロー自動化が「信頼された中枢」になった現実

n8nは「ただのタスク自動化ツール」ではない。現代の組織において、ワークフロー自動化プラットフォームは中央オーケストレーション層として機能している。

接続先 保持する情報
クラウドサービス(AWS、GCP、Azure) アクセスキー、サービスアカウント
データベース 接続文字列、DBパスワード
CI/CDパイプライン デプロイキー、シークレット
コミュニケーション(Slack、Email) OAuthトークン、APIキー
外部API(Stripe、Twilio等) APIシークレット

n8nが侵害されるということは、これらすべての接続先への認証情報が一度に漏洩することを意味する。

3.2 「式の柔軟性」がセキュリティ負債になったメカニズム

n8nの式評価エンジンは、設計上「強力」であることが売りだった。動的なデータ処理、条件分岐、ランタイム変換——これらすべてを {{ }} 内のJavaScript式で実現できる。

しかし、この柔軟性が裏目に出た。脆弱なバージョンでは、ユーザー提供の式が評価される際、thisオブジェクトへのアクセスが適切に制限されておらず、JavaScriptランタイムのグローバルオブジェクトにアクセスできてしまっていた。

設計意図:
  ユーザー式 → サンドボックス内で評価 → 安全な結果のみ返却

実際:
  ユーザー式 → サンドボックスの隔離が不十分
             → Node.jsコアモジュールにアクセス可能
             → require('child_process').exec() が実行可能
             → 任意のOSコマンド実行(RCE)

3.3 AI統合が攻撃面を拡大した

n8nはAIワークフロー自動化を積極的に推進しており、LLMを組み込んだワークフローの構築が容易だ。これが新たな攻撃面を生む。

例えば、Webhook経由で外部データを受け取り、そのデータをn8nの式フィールドで処理するワークフローがある場合、外部からの入力が式として評価される可能性がある。AIエージェントの出力がワークフローに流れ込む設計であれば、プロンプトインジェクション → 式インジェクション → RCE という連鎖攻撃が成立しうる。

出典: Resecurity - CVE-2025-68613: Remote Code Execution via Expression Injection in n8n

背景がわかったところで、脆弱性の具体的な仕組みを見ていこう。


4. 基本概念と仕組み:攻撃メカニズムの詳細

4.1 攻撃フロー

攻撃フロー(CVE-2025-68613):

1. アクセス: 攻撃者がn8nに認証済みユーザーとしてログイン
   (ワークフロー作成・編集権限があれば十分 = 最低権限)

2. 注入: ワークフロー内のノード(Setノード等)の式フィールドに
   悪意あるJavaScriptペイロードを入力
   例: {{ ... 悪意あるコード ... }}

3. 脱出: 式評価エンジンのサンドボックス隔離が不十分なため、
   注入されたコードがNode.jsランタイムのグローバルオブジェクトに到達

4. 実行: Node.jsのコアモジュール(child_process等)にアクセスし、
   任意のOSコマンドをn8nプロセス権限で実行

5. 結果: サーバーの完全な制御権を獲得
   → APIキー窃取、ファイル読み書き、バックドア設置、ラテラルムーブメント

4.2 攻撃の入口(Attack Surface)

脆弱性が発火しうるポイントは複数ある。

攻撃経路 説明
ワークフローエディタ(Web UI) ノードのフィールド値に悪意ある式を入力し、「Execute Workflow」をクリック
REST API(ワークフロー作成) POST /workflows で悪意ある式を含むワークフロー定義をAPI経由で送信
REST API(ワークフロー更新) PATCH /workflows/{id} で既存ワークフローに悪意ある式を注入
Webhook トリガー Webhookで受信したデータが式評価に渡される場合
「Test Step」ボタン ワークフローエディタの個別ステップテスト

4.3 CVSS 9.9の内訳

CVSS指標 理由
Attack Vector Network リモートから攻撃可能
Attack Complexity Low 式フィールドに入力するだけ
Privileges Required Low ワークフロー作成権限のみ
User Interaction None 被害者の操作不要
Scope Changed n8nの権限を超えてOSレベルに影響
Confidentiality High 全データにアクセス可能
Integrity High ファイル・ワークフロー改ざん可能
Availability High サービス停止可能

Scope: Changed が9.9のポイントだ。脆弱性がn8nアプリケーションの範囲を超えて、ホストOS全体に影響を及ぼすことを示している。

4.4 影響の全体像

項目 内容
CVE ID CVE-2025-68613
CVSS 9.9 (Critical)
影響を受けるバージョン 0.211.0 以降、1.120.4 / 1.121.1 / 1.122.0 未満
修正済みバージョン 1.120.4, 1.121.1, 1.122.0
公開日 2025年12月19日
PoC公開 SecureLayer7が2025年12月21日に公開
露出インスタンス数 103,476台(Censys調べ)
主な地域 米国、ドイツ、フランス、ブラジル、シンガポール

出典: Censys Advisory - CVE-2025-68613

基本概念が理解できたところで、実際に自分の環境をチェックして対策を行おう。


5. 実践:今すぐ確認・対策すべきこと

5.1 バージョン確認と更新

# n8nのバージョンを確認
n8n --version

# npmでグローバルインストールしている場合
npm list -g n8n

# Dockerの場合
docker exec <container_name> n8n --version

# 修正版にアップデート(npm)
npm update -g n8n

# Dockerの場合(docker-compose.ymlでバージョン指定)
# image: n8nio/n8n:1.122.0 に変更してから
docker-compose pull && docker-compose up -d

5.2 環境別の設定ファイル

個人開発環境用(docker-compose.dev.yaml)

# docker-compose.dev.yaml - 個人開発環境用
version: '3.8'
services:
  n8n:
    image: n8nio/n8n:1.122.0  # 必ず修正済みバージョンを指定
    ports:
      - "127.0.0.1:5678:5678"  # localhostのみにバインド
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=${N8N_USER}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
      # 式評価のセキュリティ設定
      - N8N_BLOCK_ENV_ACCESS_IN_NODE=true
      - NODE_FUNCTION_ALLOW_EXTERNAL=  # 外部モジュール読み込み禁止
    volumes:
      - n8n_data:/home/node/.n8n
    restart: unless-stopped

volumes:
  n8n_data:

本番環境用(docker-compose.production.yaml)

# docker-compose.production.yaml - 本番環境用
version: '3.8'
services:
  n8n:
    image: n8nio/n8n:1.122.0
    ports:
      - "127.0.0.1:5678:5678"  # リバースプロキシ経由のみ
    environment:
      # 認証強化
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=${N8N_USER}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
      # セキュリティ設定
      - N8N_BLOCK_ENV_ACCESS_IN_NODE=true
      - NODE_FUNCTION_ALLOW_EXTERNAL=
      - N8N_DIAGNOSTICS_ENABLED=false
      - N8N_TEMPLATES_ENABLED=false  # コミュニティテンプレート無効化
      # データベース(SQLiteではなくPostgreSQLを推奨)
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=${DB_HOST}
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=${DB_NAME}
      - DB_POSTGRESDB_USER=${DB_USER}
      - DB_POSTGRESDB_PASSWORD=${DB_PASSWORD}
    volumes:
      - n8n_data:/home/node/.n8n
    # non-rootで実行
    user: "1000:1000"
    # リソース制限
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G
    # ネットワーク隔離
    networks:
      - n8n-internal
    restart: unless-stopped

  # リバースプロキシ
  nginx:
    image: nginx:alpine
    ports:
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    networks:
      - n8n-internal
      - external
    depends_on:
      - n8n

networks:
  n8n-internal:
    driver: bridge
    internal: true  # 外部アクセス不可
  external:
    driver: bridge

volumes:
  n8n_data:

CI/CD環境用(テスト・検証用)

# docker-compose.ci.yaml - CI/CD環境用
version: '3.8'
services:
  n8n-test:
    image: n8nio/n8n:1.122.0
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=test
      - N8N_BASIC_AUTH_PASSWORD=test_only_ci
      - N8N_BLOCK_ENV_ACCESS_IN_NODE=true
      - NODE_FUNCTION_ALLOW_EXTERNAL=
      # テスト用: 機密情報を使わない
      - EXECUTIONS_DATA_SAVE_ON_SUCCESS=none
      - EXECUTIONS_DATA_SAVE_ON_ERROR=none
    tmpfs:
      - /home/node/.n8n  # テスト終了時にデータ消去
    # 自動停止(1時間後)
    stop_grace_period: 5s

  # 脆弱性スキャン
  vuln-check:
    image: aquasec/trivy:latest
    command: image --severity CRITICAL,HIGH n8nio/n8n:1.122.0
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

5.3 よくあるエラーと対処法

症状・状況 原因 対処法
n8n --versionがパッチ前のバージョンを返す npm/Dockerイメージが未更新 npm update -g n8n または Docker image を 1.122.0 に更新
ワークフローの式が更新後に動作しなくなった パッチが式の評価コンテキストを制限した 正当な式は引き続き動作するはず。動作しない場合は式を見直す
n8nがインターネットに直接公開されている リバースプロキシを介さず直接公開 即座にファイアウォールで制限。リバースプロキシ経由に変更
複数ユーザーがワークフロー作成権限を持っている n8nのデフォルト権限設定 最小権限の原則を適用。ワークフロー作成は信頼されたユーザーのみに制限
Webhookがインターネットに公開されている n8nのWebhookエンドポイントが外部からアクセス可能 Webhook URLに認証トークンを含める。IP制限を適用

5.4 環境診断スクリプト

#!/usr/bin/env python3
"""
n8n CVE-2025-68613 脆弱性診断スクリプト
実行方法: python check_n8n_vuln.py
"""

import subprocess
import sys
import re
import json
from typing import List, Tuple


# 修正済みバージョン
PATCHED_VERSIONS = [
    (1, 120, 4),
    (1, 121, 1),
    (1, 122, 0),
]

# 影響を受ける最小バージョン
AFFECTED_FROM = (0, 211, 0)


def parse_version(version_str: str) -> Tuple[int, ...]:
    """バージョン文字列をタプルに変換"""
    match = re.search(r'(\d+\.\d+\.\d+)', version_str)
    if match:
        return tuple(int(x) for x in match.group(1).split('.'))
    return (0, 0, 0)


def is_version_vulnerable(version: Tuple[int, ...]) -> bool:
    """バージョンが脆弱かどうかを判定"""
    if version < AFFECTED_FROM:
        return False  # 影響範囲外

    for patched in PATCHED_VERSIONS:
        # 同じメジャー.マイナー系列で修正済みバージョン以上なら安全
        if version >= patched:
            return False

    return True


def get_n8n_version() -> str:
    """n8nのバージョンを取得"""
    # ローカルインストール
    try:
        result = subprocess.run(
            ["n8n", "--version"],
            capture_output=True, text=True, timeout=10
        )
        if result.stdout.strip():
            return result.stdout.strip()
    except (FileNotFoundError, subprocess.TimeoutExpired):
        pass

    # Docker
    try:
        result = subprocess.run(
            ["docker", "ps", "--format", "{{.Image}} {{.Names}}"],
            capture_output=True, text=True, timeout=10
        )
        for line in result.stdout.strip().split('\n'):
            if 'n8n' in line.lower():
                image = line.split()[0]
                # イメージからバージョンを取得
                tag = image.split(':')[-1] if ':' in image else 'latest'
                return f"Docker: {image} (tag: {tag})"
    except (FileNotFoundError, subprocess.TimeoutExpired):
        pass

    return "n8n not found"


def check_network_exposure() -> List[str]:
    """n8nのネットワーク露出をチェック"""
    issues = []

    # ポート5678のリスニング状態を確認
    try:
        result = subprocess.run(
            ["ss", "-tlnp"],
            capture_output=True, text=True, timeout=5
        )
        for line in result.stdout.split('\n'):
            if '5678' in line:
                if '0.0.0.0:5678' in line or ':::5678' in line:
                    issues.append(
                        "CRITICAL: n8n (port 5678) が全インターフェースで"
                        "リッスンしています。127.0.0.1にバインドしてください。"
                    )
                elif '127.0.0.1:5678' in line:
                    print("  [OK] n8nはlocalhostのみでリッスンしています")
    except (FileNotFoundError, subprocess.TimeoutExpired):
        pass

    return issues


def check_docker_config() -> List[str]:
    """Docker環境のn8n設定をチェック"""
    issues = []

    try:
        result = subprocess.run(
            ["docker", "ps", "--format", "{{.Names}}"],
            capture_output=True, text=True, timeout=10
        )
        for container in result.stdout.strip().split('\n'):
            if 'n8n' in container.lower() and container:
                # コンテナの設定を確認
                inspect_result = subprocess.run(
                    ["docker", "inspect", container],
                    capture_output=True, text=True, timeout=10
                )
                try:
                    config = json.loads(inspect_result.stdout)
                    if config:
                        # ポートマッピングチェック
                        ports = (
                            config[0].get("NetworkSettings", {})
                            .get("Ports", {})
                        )
                        for port_key, bindings in ports.items():
                            if bindings:
                                for b in bindings:
                                    if b.get("HostIp") == "0.0.0.0":
                                        issues.append(
                                            f"WARNING: コンテナ {container} "
                                            f"のポート {port_key}"
                                            f"0.0.0.0 にバインドされています"
                                        )

                        # root実行チェック
                        user = (
                            config[0].get("Config", {}).get("User", "")
                        )
                        if not user or user == "root" or user == "0":
                            issues.append(
                                f"WARNING: コンテナ {container}"
                                f"rootユーザーで実行されています"
                            )
                except (json.JSONDecodeError, IndexError):
                    pass

    except (FileNotFoundError, subprocess.TimeoutExpired):
        pass

    return issues


def main():
    """メイン診断処理"""
    print("=" * 60)
    print("  n8n CVE-2025-68613 脆弱性診断ツール v1.0")
    print("  CVSS 9.9 (Critical) - Expression Injection → RCE")
    print("=" * 60)

    all_issues = []

    # 1. バージョンチェック
    print("\n[1] n8n バージョンチェック")
    version_str = get_n8n_version()
    print(f"  検出: {version_str}")

    if version_str != "n8n not found":
        version = parse_version(version_str)
        if version != (0, 0, 0) and is_version_vulnerable(version):
            issue = (
                f"CRITICAL: n8n {version_str} はCVE-2025-68613に脆弱です。"
                f"1.122.0以上に更新してください。"
            )
            all_issues.append(issue)
            print(f"  [!] {issue}")
        elif version != (0, 0, 0):
            print("  [OK] 修正済みバージョンです")

    # 2. ネットワーク露出チェック
    print("\n[2] ネットワーク露出チェック")
    all_issues.extend(check_network_exposure())

    # 3. Docker設定チェック
    print("\n[3] Docker設定チェック")
    all_issues.extend(check_docker_config())

    # サマリー
    print("\n" + "=" * 60)
    print("  診断結果")
    print("=" * 60)

    critical = [i for i in all_issues if "CRITICAL" in i]
    warnings = [i for i in all_issues if "WARNING" in i]

    if critical:
        print(f"\n  CRITICAL: {len(critical)}")
        for c in critical:
            print(f"    [!] {c}")
    if warnings:
        print(f"\n  WARNING: {len(warnings)}")
        for w in warnings:
            print(f"    [!] {w}")
    if not all_issues:
        print("\n  [OK] 重大な問題は検出されませんでした")

    print(f"\n  修正方法:")
    print(f"    npm: npm update -g n8n")
    print(f"    Docker: image を n8nio/n8n:1.122.0 に変更")

    return 1 if critical else 0


if __name__ == "__main__":
    sys.exit(main())

5.5 Nginx リバースプロキシ設定(本番用セキュリティ強化)

# nginx.conf - n8nセキュリティ強化用リバースプロキシ設定
upstream n8n_backend {
    server n8n:5678;
}

server {
    listen 443 ssl http2;
    server_name n8n.example.com;

    ssl_certificate     /etc/nginx/certs/cert.pem;
    ssl_certificate_key /etc/nginx/certs/key.pem;

    # アクセス制限(社内IPのみ許可)
    allow 10.0.0.0/8;
    allow 172.16.0.0/12;
    allow 192.168.0.0/16;
    deny all;

    # Webhook以外のAPIアクセスをIP制限
    location /api/ {
        allow 10.0.0.0/8;
        deny all;
        proxy_pass http://n8n_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Webhook(外部からアクセスが必要な場合)
    location /webhook/ {
        # レートリミット
        limit_req zone=webhook burst=10 nodelay;
        proxy_pass http://n8n_backend;
        proxy_set_header Host $host;
    }

    location / {
        proxy_pass http://n8n_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket対応
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

# レートリミットゾーン定義
limit_req_zone $binary_remote_addr zone=webhook:10m rate=5r/s;

実装方法がわかったので、次はユースケース別の対応を見ていく。


6. ユースケース別ガイド

6.1 ユースケース1: セルフホストのn8nを社内で運用している管理者

想定読者: 社内業務の自動化(Slack通知、データ同期、レポート生成等)にn8nを使っている方

リスクレベル: ★★★★★(極めて高い — 社内ユーザー全員が潜在的な攻撃者)

サンプルコード(緊急パッチ適用手順):

#!/bin/bash
# n8n_emergency_patch.sh
# n8n CVE-2025-68613 緊急パッチ適用スクリプト

set -euo pipefail

echo "=== n8n CVE-2025-68613 緊急パッチ適用 ==="
echo "CVSS 9.9 - 即座にパッチを適用してください"
echo ""

# Docker Compose環境の場合
if [ -f "docker-compose.yml" ] || [ -f "docker-compose.yaml" ]; then
    echo "[1/4] Docker Compose環境を検出"

    # 現在のバージョンを確認
    CURRENT=$(docker exec $(docker ps -q -f name=n8n) n8n --version 2>/dev/null || echo "unknown")
    echo "  現在のバージョン: $CURRENT"

    # バックアップ
    echo "[2/4] データバックアップを作成中..."
    docker exec $(docker ps -q -f name=n8n) \
        tar czf /tmp/n8n_backup.tar.gz /home/node/.n8n 2>/dev/null || true
    docker cp $(docker ps -q -f name=n8n):/tmp/n8n_backup.tar.gz \
        ./n8n_backup_$(date +%Y%m%d).tar.gz 2>/dev/null || true
    echo "  バックアップ完了"

    # docker-compose.ymlのイメージバージョンを更新
    echo "[3/4] イメージバージョンを更新中..."
    if grep -q "n8nio/n8n:" docker-compose.yml 2>/dev/null; then
        sed -i 's|n8nio/n8n:[^ ]*|n8nio/n8n:1.122.0|g' docker-compose.yml
    fi

    # 再起動
    echo "[4/4] コンテナを再起動中..."
    docker-compose pull
    docker-compose up -d

    # 確認
    sleep 5
    NEW_VER=$(docker exec $(docker ps -q -f name=n8n) n8n --version 2>/dev/null || echo "unknown")
    echo ""
    echo "  更新後のバージョン: $NEW_VER"

# npm環境の場合
elif command -v n8n &>/dev/null; then
    echo "[1/3] npm環境を検出"
    echo "  現在のバージョン: $(n8n --version)"

    echo "[2/3] 更新中..."
    npm update -g n8n

    echo "[3/3] 確認"
    echo "  更新後のバージョン: $(n8n --version)"
fi

echo ""
echo "=== 追加で確認すべきこと ==="
echo "1. n8nがインターネットに直接公開されていないか確認"
echo "2. ワークフロー作成権限を持つユーザーを最小限に制限"
echo "3. 保存されている認証情報(APIキー等)のローテーション検討"
echo ""
echo "詳細: https://github.com/n8n-io/n8n/security/advisories"

6.2 ユースケース2: n8nでAI統合ワークフローを構築している開発者

想定読者: n8nにOpenAI、Anthropic等のLLM APIを組み込み、AIワークフローを構築している方

リスクレベル: ★★★★☆(高い — AI出力がワークフロー内で式評価に渡される可能性)

サンプルコード(AI出力サニタイズ):

// n8n Function Nodeで使用する安全なAI出力処理
// ワークフロー内のFunctionノードに貼り付けて使用

function sanitizeAIOutput(output) {
  /**
   * AI(LLM)の出力から潜在的に危険なパターンを検出・除去する
   * CVE-2025-68613対策: 式インジェクションの防止
   */
  if (typeof output !== 'string') {
    return output;
  }

  // n8n式パターン {{ ... }} の検出
  const expressionPattern = /\{\{[\s\S]*?\}\}/g;
  if (expressionPattern.test(output)) {
    console.warn('[SECURITY] AI output contains n8n expression pattern');
    // エスケープして安全な文字列にする
    output = output.replace(/\{\{/g, '{ {').replace(/\}\}/g, '} }');
  }

  // Node.js危険パターンの検出
  const dangerousPatterns = [
    /require\s*\(/gi,
    /child_process/gi,
    /process\.env/gi,
    /\.exec\s*\(/gi,
    /__dirname/gi,
    /global\./gi,
  ];

  for (const pattern of dangerousPatterns) {
    if (pattern.test(output)) {
      console.warn(`[SECURITY] Dangerous pattern detected: ${pattern}`);
    }
  }

  return output;
}

// ワークフローでの使用例
const aiOutput = $input.all()[0].json.ai_response;
const safeOutput = sanitizeAIOutput(aiOutput);

return [{ json: { result: safeOutput } }];

6.3 ユースケース3: マルチテナント環境でn8nを提供しているSaaS事業者

想定読者: 複数の顧客にn8nベースの自動化機能を提供しているプラットフォーム事業者

リスクレベル: ★★★★★(極めて高い — テナント間のラテラルムーブメント)

サンプルコード(テナント隔離チェックリスト用スクリプト):

#!/bin/bash
# multi_tenant_n8n_audit.sh
# マルチテナントn8n環境のセキュリティ監査

echo "=== マルチテナントn8n セキュリティ監査 ==="

ISSUES=0

# 1. バージョンチェック
echo "[1] バージョンチェック"
for container in $(docker ps --format "{{.Names}}" | grep -i n8n); do
    VER=$(docker exec "$container" n8n --version 2>/dev/null || echo "unknown")
    echo "  $container: $VER"
done

# 2. ネットワーク隔離チェック
echo ""
echo "[2] ネットワーク隔離チェック"
for container in $(docker ps --format "{{.Names}}" | grep -i n8n); do
    NETWORKS=$(docker inspect "$container" \
        --format '{{range $net, $conf := .NetworkSettings.Networks}}{{$net}} {{end}}')
    echo "  $container networks: $NETWORKS"
    if echo "$NETWORKS" | grep -q "bridge"; then
        echo "    [WARNING] デフォルトbridgeネットワーク使用 — 隔離不十分"
        ISSUES=$((ISSUES + 1))
    fi
done

# 3. 実行ユーザーチェック
echo ""
echo "[3] 実行ユーザーチェック"
for container in $(docker ps --format "{{.Names}}" | grep -i n8n); do
    USER=$(docker exec "$container" whoami 2>/dev/null || echo "unknown")
    echo "  $container: $USER"
    if [ "$USER" = "root" ]; then
        echo "    [CRITICAL] rootで実行中"
        ISSUES=$((ISSUES + 1))
    fi
done

echo ""
echo "=== 結果: $ISSUES 件の問題を検出 ==="

ユースケースを把握できたところで、この先の学習パスを確認しよう。


7. 学習ロードマップ

この記事を読んだ後、次のステップとして以下をおすすめする。

初級者向け(まずはここから)

  1. 上記の診断スクリプトを自分のn8n環境で実行する
  2. n8nを修正済みバージョン(1.122.0以上)にアップデートする
  3. n8nがインターネットに直接公開されていないか確認する

中級者向け(実践に進む)

  1. SecureLayer7のPoC分析でExpression Injectionの仕組みを理解する
  2. リバースプロキシ(Nginx/Traefik)を導入し、IP制限とレート制限を設定する
  3. ワークフロー作成権限の最小権限化を実施する

上級者向け(さらに深く)

  1. Resecurityの調査レポートでポストエクスプロイテーションのリスクを学ぶ
  2. SonicWallの技術分析でWAF/IPSルールの設計を理解する
  3. Node.jsサンドボックスの設計と限界について vm2 のセキュリティ歴史を調査する

8. まとめ

この記事では、n8nのCritical RCE脆弱性CVE-2025-68613について解説した。

  1. CVSS 9.9 — ほぼ満点のCritical: ワークフロー作成権限(最低権限)だけで、サーバー上の任意コマンド実行が可能
  2. 10万台以上が即座にリスクにさらされた: Censysの調査で103,476台の脆弱なインスタンスがインターネットに公開
  3. AI統合が攻撃面を拡大: Webhook → AI出力 → 式評価 → RCEという連鎖攻撃のリスク

私の所感

n8nの脆弱性で最も考えさせられたのは、**「自動化の利便性とセキュリティのトレードオフ」**だ。

n8nの式評価エンジンが「強力で柔軟」であることは、ユーザーにとって大きな価値だ。動的なデータ処理、条件分岐、複雑な変換——これらを簡単な式で実現できることがn8nの強みだった。しかし、その「柔軟性」がそのまま「攻撃面の広さ」になった。

ワークフロー自動化ツールは、組織のあらゆるサービスの認証情報を集中管理する**「鍵束」**のような存在だ。その鍵束が盗まれれば、被害は一つのサービスに留まらない。

自動化プラットフォームは「ただの便利ツール」ではなく、クリティカルインフラとして扱うべきだ。 ネットワーク隔離、最小権限、監査ログ——データベースサーバーに施す保護と同等以上のセキュリティを、ワークフロー自動化基盤にも適用する時代が来ている。


参考文献


この記事が役に立ったら、いいね・ストックしていただけると励みになります。

他にもAI/セキュリティ関連の記事を書いています:


X(Twitter)でもAI/ML系の情報を発信中 → @geneLab_999

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?