mcpField
P
performing-agentless-vulnerability-scanning

performing-agentless-vulnerability-scanning

github.com/mukul975セキュリティ35.2k79 回インストール8 週間前に更新
リポジトリ

performing-agentless-vulnerability-scanning について

エンドポイントへのエージェント導入なしで脆弱性を調べる方法を扱うスキルです。セキュリティ評価や監査の担当者向けに、SSH、WMI/WinRM、クラウドスナップショット、コンテナイメージの手法と Vuls の設定例を紹介します。

主な機能

  1. 01SSH 経由で Linux/Unix をスキャン
  2. 02WMI または WinRM で Windows をスキャン
  3. 03クラウドスナップショットで VM ディスクを分析
  4. 04コンテナイメージとクラウド設定をスキャン
  5. 05Vuls の設定と実行

活用シーン

  1. 01エンドポイントエージェントを導入できないシステムの評価
  2. 02スナップショットによるクラウド VM の確認
  3. 03Linux と Windows ホストの定期的なセキュリティ監査
  4. 04コンテナイメージの脆弱性検査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill performing-agentless-vulnerability-scanning

インストール後、Claude Code で/performing-agentless-vulnerability-scanningを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

LICENSE
assets/
template.md
references/
api-reference.md
standards.md
workflows.md
scripts/
agent.py
process.py

詳細

エージェントレス脆弱性スキャンの実施

概要

エージェントレス脆弱性スキャンでは、エンドポイントにエージェントをインストールせずに、システムのセキュリティ上の弱点を評価します。この方法では、既存のネットワークプロトコル(Linux では SSH、Windows では WMI)、スナップショットベースの分析を行うクラウドプロバイダー API、認証済みのリモートチェックを活用します。Microsoft Defender for Cloud、Wiz、Datadog、Tenable などの最新のクラウドプラットフォームは、ディスクスナップショットを取得し、OS の設定やインストール済みパッケージをオフラインで調査することで、帯域外分析を実施します。オープンソースツールの Vuls は、Linux/FreeBSD システムを対象に、NVD および OVAL のデータに基づくエージェントレススキャンを提供します。このスキルでは、オンプレミス、クラウド、コンテナ化された環境にわたるエージェントレススキャンの設定方法を説明します。

使用するタイミング

  • エージェントレス脆弱性スキャンを実施するセキュリティ評価を行う場合
  • 関連するセキュリティイベントに対するインシデント対応手順に従う場合
  • 定期的なセキュリティテストや監査を実施する場合
  • 実地テストを通じてセキュリティ制御を検証する場合

前提条件

  • Linux/Unix の対象で SSH キーベースの認証が設定されていること
  • Windows の対象で、適切な認証情報を使用した WMI/WinRM アクセスが可能であること
  • クラウドプロバイダーの API 認証情報(AWS IAM、Azure RBAC、GCP IAM)
  • 必要なポートを使用して、スキャナーから対象システムへのネットワークアクセスが可能であること
  • 対象システムの設定に対する読み取り専用アクセス権を持つサービスアカウント
  • カスタムのスキャン自動化用の Python 3.8+

基本概念

エージェントレススキャンとエージェントベーススキャンの比較
項目エージェントレスエージェントベース
導入ソフトウェアのインストールは不要すべてのエンドポイントにエージェントをインストール
ネットワーク依存性ネットワーク接続が必要クラウド同期によりオフラインでも動作
パフォーマンスへの影響対象システムへの影響は最小限軽微な継続的負荷
対象範囲の深さプロトコル/認証情報によるローカルに深くアクセス
クラウドスナップショット分析ネイティブで対応該当なし
最適な対象クラウド VM、IoT、レガシーシステム、OT管理対象エンドポイント、ノート PC
エージェントレススキャンの方法
方法プロトコル対象 OSポート用途
SSH リモートコマンドSSHLinux/Unix22パッケージの列挙、設定監査
WMI リモートクエリWMI/DCOMWindows135, 445ホットフィックスの列挙、レジストリの確認
WinRM PowerShellWS-ManWindows5985/5986リモートコマンドの実行
SNMP コミュニティSNMP v2c/v3ネットワークデバイス161デバイスのフィンガープリント取得、ファームウェアの確認
クラウドスナップショットプロバイダー APIクラウド VMN/Aディスクイメージ分析
コンテナレジストリHTTPSコンテナイメージ443イメージの脆弱性スキャン
API ベースREST/HTTPSSaaS/クラウド443設定評価
クラウドスナップショット分析の流れ
1. Scanner requests disk snapshot via cloud API
2. Cloud provider creates snapshot of VM root + data disks
3. Scanner mounts snapshot in isolated analysis environment
4. Scanner examines OS packages, configurations, file system
5. Snapshot is deleted after analysis (no persistent copies)
6. Results sent to central management console

ワークフロー

ステップ 1:SSH ベースのエージェントレススキャン(Linux)
# Create dedicated scan SSH key pair
ssh-keygen -t ed25519 -f /opt/scanner/.ssh/scan_key -N "" \
  -C "[email protected]"

# Deploy public key to targets via Ansible
# ansible-playbook deploy_scan_key.yml

# Test connectivity to target
ssh -i /opt/scanner/.ssh/scan_key -o ConnectTimeout=10 \
  scanner@target-host "cat /etc/os-release && dpkg -l 2>/dev/null || rpm -qa"
import paramiko
import json

class AgentlessLinuxScanner:
    """SSH-based agentless vulnerability scanner for Linux systems."""

    def __init__(self, key_path):
        self.key_path = key_path

    def connect(self, hostname, username="scanner", port=22):
        """Establish SSH connection to target."""
        client = paramiko.SSHClient()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        key = paramiko.Ed25519Key.from_private_key_file(self.key_path)
        client.connect(hostname, port=port, username=username, pkey=key,
                       timeout=30, banner_timeout=30)
        return client

    def get_os_info(self, client):
        """Detect OS type and version."""
        _, stdout, _ = client.exec_command("cat /etc/os-release", timeout=10)
        os_release = stdout.read().decode()
        info = {}
        for line in os_release.strip().split("\n"):
            if "=" in line:
                key, val = line.split("=", 1)
                info[key] = val.strip('"')
        return info

    def get_installed_packages(self, client):
        """Enumerate installed packages."""
        # Try dpkg (Debian/Ubuntu)
        _, stdout, _ = client.exec_command(
            "dpkg-query -W -f='${Package}|${Version}|${Architecture}\\n'",
            timeout=30
        )
        output = stdout.read().decode().strip()
        if output:
            packages = []
            for line in output.split("\n"):
                parts = line.split("|")
                if len(parts) >= 2:
                    packages.append({
                        "name": parts[0],
                        "version": parts[1],
                        "arch": parts[2] if len(parts) > 2 else "",
                        "manager": "dpkg"
                    })
            return packages

        # Try rpm (RHEL/CentOS/Fedora)
        _, stdout, _ = client.exec_command(
            "rpm -qa --queryformat '%{NAME}|%{VERSION}-%{RELEASE}|%{ARCH}\\n'",
            timeout=30
        )
        output = stdout.read().decode().strip()
        packages = []
        for line in output.split("\n"):
            parts = line.split("|")
            if len(parts) >= 2:
                packages.append({
                    "name": parts[0],
                    "version": parts[1],
                    "arch": parts[2] if len(parts) > 2 else "",
                    "manager": "rpm"
                })
        return packages

    def check_kernel_version(self, client):
        """Get running kernel version."""
        _, stdout, _ = client.exec_command("uname -r", timeout=10)
        return stdout.read().decode().strip()

    def check_listening_ports(self, client):
        """Enumerate listening network services."""
        _, stdout, _ = client.exec_command(
            "ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null",
            timeout=10
        )
        return stdout.read().decode().strip()

    def scan_host(self, hostname, username="scanner"):
        """Perform full agentless scan of a host."""
        print(f"[*] Scanning {hostname}...")
        client = self.connect(hostname, username)

        result = {
            "hostname": hostname,
            "os_info": self.get_os_info(client),
            "kernel": self.check_kernel_version(client),
            "packages": self.get_installed_packages(client),
            "listening_ports": self.check_listening_ports(client),
        }

        client.close()
        print(f"  [+] Found {len(result['packages'])} packages on {hostname}")
        return result
ステップ 2:WinRM ベースのエージェントレススキャン(Windows)
import winrm

class AgentlessWindowsScanner:
    """WinRM-based agentless vulnerability scanner for Windows."""

    def __init__(self, username, password, domain=None):
        self.username = username
        self.password = password
        self.domain = domain

    def connect(self, hostname, use_ssl=True):
        """Create WinRM session."""
        port = 5986 if use_ssl else 5985
        transport = "ntlm"
        user = f"{self.domain}\\{self.username}" if self.domain else self.username
        session = winrm.Session(
            f"{'https' if use_ssl else 'http'}://{hostname}:{port}/wsman",
            auth=(user, self.password),
            transport=transport,
            server_cert_validation="ignore"
        )
        return session

    def get_installed_hotfixes(self, session):
        """Get installed Windows updates/hotfixes."""
        cmd = "Get-HotFix | Select-Object HotFixID,InstalledOn,Description | ConvertTo-Json"
        result = session.run_ps(cmd)
        if result.status_code == 0:
            return json.loads(result.std_out.decode())
        return []

    def get_installed_software(self, session):
        """Enumerate installed software from registry."""
        cmd = """
        $paths = @(
            'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*',
            'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'
        )
        Get-ItemProperty $paths -ErrorAction SilentlyContinue |
            Where-Object {$_.DisplayName} |
            Select-Object DisplayName, DisplayVersion, Publisher |
            ConvertTo-Json
        """
        result = session.run_ps(cmd)
        if result.status_code == 0:
            return json.loads(result.std_out.decode())
        return []

    def get_os_info(self, session):
        """Get Windows OS details."""
        cmd = "Get-CimInstance Win32_OperatingSystem | Select-Object Caption,Version,BuildNumber,OSArchitecture | ConvertTo-Json"
        result = session.run_ps(cmd)
        if result.status_code == 0:
            return json.loads(result.std_out.decode())
        return {}

    def scan_host(self, hostname):
        """Perform full agentless scan of Windows host."""
        print(f"[*] Scanning {hostname} via WinRM...")
        session = self.connect(hostname)

        result = {
            "hostname": hostname,
            "os_info": self.get_os_info(session),
            "hotfixes": self.get_installed_hotfixes(session),
            "software": self.get_installed_software(session),
        }

        print(f"  [+] Found {len(result['hotfixes'])} hotfixes, "
              f"{len(result['software'])} software entries")
        return result
ステップ 3:クラウドスナップショットスキャン(AWS)
import boto3
import time

class AWSSnapshotScanner:
    """AWS EC2 agentless snapshot-based vulnerability scanner."""

    def __init__(self, region="us-east-1"):
        self.ec2 = boto3.client("ec2", region_name=region)

    def create_snapshot(self, volume_id, description="Security scan snapshot"):
        """Create EBS snapshot for analysis."""
        snapshot = self.ec2.create_snapshot(
            VolumeId=volume_id,
            Description=description,
            TagSpecifications=[{
                "ResourceType": "snapshot",
                "Tags": [
                    {"Key": "Purpose", "Value": "VulnScan"},
                    {"Key": "AutoDelete", "Value": "true"},
                ]
            }]
        )
        snapshot_id = snapshot["SnapshotId"]
        print(f"  [*] Creating snapshot {snapshot_id} from {volume_id}...")

        waiter = self.ec2.get_waiter("snapshot_completed")
        waiter.wait(SnapshotIds=[snapshot_id])
        print(f"  [+] Snapshot {snapshot_id} ready")
        return snapshot_id

    def delete_snapshot(self, snapshot_id):
        """Clean up snapshot after analysis."""
        self.ec2.delete_snapshot(SnapshotId=snapshot_id)
        print(f"  [+] Deleted snapshot {snapshot_id}")

    def scan_instance(self, instance_id):
        """Scan an EC2 instance via snapshot analysis."""
        print(f"[*] Agentless scan of instance {instance_id}")

        instance = self.ec2.describe_instances(
            InstanceIds=[instance_id]
        )["Reservations"][0]["Instances"][0]

        root_volume = None
        for bdm in instance.get("BlockDeviceMappings", []):
            if bdm["DeviceName"] == instance.get("RootDeviceName"):
                root_volume = bdm["Ebs"]["VolumeId"]
                break

        if not root_volume:
            print("  [!] No root volume found")
            return None

        snapshot_id = self.create_snapshot(root_volume)
        try:
            # Analysis would be performed here
            # Mount snapshot, examine packages, check configs
            result = {
                "instance_id": instance_id,
                "snapshot_id": snapshot_id,
                "root_volume": root_volume,
                "platform": instance.get("Platform", "linux"),
                "state": instance["State"]["Name"],
            }
            return result
        finally:
            self.delete_snapshot(snapshot_id)
ステップ 4:Vuls オープンソースエージェントレススキャナー
# /etc/vuls/config.toml - Vuls configuration for agentless scanning

[servers]

[servers.web-server-01]
host = "192.168.1.10"
port = "22"
user = "vuls"
keyPath = "/opt/vuls/.ssh/scan_key"
scanMode = ["fast"]

[servers.db-server-01]
host = "192.168.1.20"
port = "22"
user = "vuls"
keyPath = "/opt/vuls/.ssh/scan_key"
scanMode = ["fast-root"]
[servers.db-server-01.optional]
  [servers.db-server-01.optional.sudo]
    password = ""

[servers.container-host-01]
host = "192.168.1.30"
port = "22"
user = "vuls"
keyPath = "/opt/vuls/.ssh/scan_key"
scanMode = ["fast"]
containersIncluded = ["${running}"]
# Run Vuls agentless scan
vuls scan

# Generate report
vuls report -format-json -to-localfile

# View results
vuls tui

ベストプラクティス

  1. Linux のスキャンでは、パスワードではなく SSH キーベースの認証を使用します
  2. スキャン用に、最小限の読み取り専用権限を持つ専用サービスアカウントを作成します
  3. ストレージコストやデータ漏えいを避けるため、分析後は必ずクラウドスナップショットを削除します
  4. 包括的なカバレッジを実現するため、エージェントレススキャンとエージェントベーススキャンを組み合わせます
  5. パフォーマンスへの影響を最小限に抑えるため、利用の少ない時間帯にスキャンをスケジュールします
  6. スキャン用認証情報を定期的にローテーションし、シークレット保管庫に保存します
  7. 本番環境のスキャンをスケジュールする前に、スキャナーの接続性をテストします
  8. ネットワークデバイスのスキャンでは、認証と暗号化を使用する SNMPv3 を利用します

よくある落とし穴

  • 適切に分離せず、複数の環境で共有認証情報を使用する
  • クラウド環境で一時スナップショットを削除し忘れる
  • エージェントレススキャンはパフォーマンスにまったく影響しないと想定する(ネットワークと CPU を使用します)
  • 新しいデプロイで WinRM/SSH のファイアウォールルールが不足し、スキャンに失敗する
  • SSH ホストキーの変更によって認証に失敗することを考慮しない
  • 安全に処理できないプロトコルで OT/ICS デバイスをスキャンする

関連スキル

  • implementing-rapid7-insightvm-for-scanning
  • implementing-wazuh-for-vulnerability-detection
  • deploying-osquery-for-endpoint-monitoring
  • performing-remediation-validation-scanning

コメント