L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Productivité · SKILL.md

performing-agentless-vulnerability-scanning

performing-agentless-vulnerability-scanning

配置并执行无代理漏洞扫描(agentless vulnerability scanning),利用网络协议、云快照分析和基于 API 的发现方式评估系统安全,无需在端点安装 Agent。

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: performing-agentless-vulnerability-scanning description: 配置并执行无代理漏洞扫描(agentless vulnerability scanning),利用网络协议、云快照分析和基于 API 的发现方式评估系统安全,无需在端点安装 Agent。 domain: cybersecurity subdomain: vulnerability-management tags: [agentless-scanning, vulnerability-assessment, cloud-security, ssh, wmi, snapshot-analysis, vuls, tenable] version: "1.0" author: mahipal license: Apache-2.0

执行无代理漏洞扫描(Performing Agentless Vulnerability Scanning)

概述

无代理漏洞扫描(Agentless vulnerability scanning)无需在端点安装 Agent 即可评估系统安全弱点。该方法利用现有网络协议(Linux 使用 SSH、Windows 使用 WMI)、云服务商 API 进行基于快照的分析,以及经身份验证的远程检查。Microsoft Defender for Cloud、Wiz、Datadog 和 Tenable 等现代云平台通过获取磁盘快照并离线检查操作系统配置和已安装软件包,实现带外分析(out-of-band analysis)。开源工具 Vuls 基于 NVD 和 OVAL 数据为 Linux/FreeBSD 系统提供无代理扫描。本技能涵盖在本地、云端和容器化环境中配置无代理扫描。

前置条件

  • 在 Linux/Unix 目标上配置基于 SSH 密钥的身份验证
  • 在 Windows 目标上具有适当凭据的 WMI/WinRM 访问权限
  • 云服务商 API 凭据(AWS IAM、Azure RBAC、GCP IAM)
  • 扫描器到目标系统所需端口的网络访问权限
  • 具有目标系统配置只读访问权限的服务账户
  • Python 3.8+(用于自定义扫描自动化)

核心概念

无代理扫描 vs 基于 Agent 扫描

对比维度无代理扫描基于 Agent 扫描
部署方式无需安装软件需在每个端点安装 Agent
网络依赖需要网络连接可离线运行并通过云同步
性能影响对目标系统影响极小持续轻量级开销
覆盖深度取决于协议/凭据深度本地访问
云快照分析原生支持不适用
适用场景云虚拟机、IoT、遗留系统、OT受管端点、笔记本电脑

无代理扫描方法

方法协议目标操作系统端口适用场景
SSH 远程命令SSHLinux/Unix22软件包枚举、配置审计
WMI 远程查询WMI/DCOMWindows135, 445热补丁枚举、注册表检查
WinRM PowerShellWS-ManWindows5985/5986远程命令执行
SNMP 团体字SNMP v2c/v3网络设备161设备指纹识别、固件检查
云快照云服务商 API云虚拟机N/A磁盘镜像分析
容器镜像仓库HTTPS容器镜像443镜像漏洞扫描
基于 APIREST/HTTPSSaaS/Cloud443配置评估

云快照分析流程

1. 扫描器通过云 API 请求磁盘快照
2. 云服务商为虚拟机根磁盘和数据磁盘创建快照
3. 扫描器在隔离分析环境中挂载快照
4. 扫描器检查操作系统软件包、配置和文件系统
5. 分析完成后删除快照(不保留持久副本)
6. 结果发送到中央管理控制台

实施步骤

步骤 1:基于 SSH 的无代理扫描(Linux)

# 创建专用扫描 SSH 密钥对
ssh-keygen -t ed25519 -f /opt/scanner/.ssh/scan_key -N "" \
  -C "vuln-scanner@security.local"

# 通过 Ansible 将公钥部署到目标
# ansible-playbook deploy_scan_key.yml

# 测试与目标的连通性
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 的 Linux 系统无代理漏洞扫描器。"""

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

    def connect(self, hostname, username="scanner", port=22):
        """与目标建立 SSH 连接。"""
        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):
        """检测操作系统类型和版本。"""
        _, 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):
        """枚举已安装的软件包。"""
        # 尝试 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

        # 尝试 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):
        """获取运行中的内核版本。"""
        _, stdout, _ = client.exec_command("uname -r", timeout=10)
        return stdout.read().decode().strip()

    def check_listening_ports(self, client):
        """枚举正在监听的网络服务。"""
        _, 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"):
        """对主机执行完整的无代理扫描。"""
        print(f"[*] 正在扫描 {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"  [+] 在 {hostname} 上发现 {len(result['packages'])} 个软件包")
        return result

步骤 2:基于 WinRM 的无代理扫描(Windows)

import winrm

class AgentlessWindowsScanner:
    """基于 WinRM 的 Windows 系统无代理漏洞扫描器。"""

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

    def connect(self, hostname, use_ssl=True):
        """创建 WinRM 会话。"""
        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):
        """获取已安装的 Windows 更新/热补丁。"""
        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):
        """从注册表枚举已安装的软件。"""
        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):
        """获取 Windows 操作系统详情。"""
        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):
        """对 Windows 主机执行完整的无代理扫描。"""
        print(f"[*] 通过 WinRM 扫描 {hostname}...")
        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"  [+] 发现 {len(result['hotfixes'])} 个热补丁,"
              f"{len(result['software'])} 个软件条目")
        return result

步骤 3:云快照扫描(AWS)

import boto3
import time

class AWSSnapshotScanner:
    """基于 AWS EC2 EBS 快照的无代理漏洞扫描器。"""

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

    def create_snapshot(self, volume_id, description="安全扫描快照"):
        """创建用于分析的 EBS 快照。"""
        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"  [*] 正在从 {volume_id} 创建快照 {snapshot_id}...")

        waiter = self.ec2.get_waiter("snapshot_completed")
        waiter.wait(SnapshotIds=[snapshot_id])
        print(f"  [+] 快照 {snapshot_id} 已就绪")
        return snapshot_id

    def delete_snapshot(self, snapshot_id):
        """分析完成后清理快照。"""
        self.ec2.delete_snapshot(SnapshotId=snapshot_id)
        print(f"  [+] 已删除快照 {snapshot_id}")

    def scan_instance(self, instance_id):
        """通过快照分析扫描 EC2 实例。"""
        print(f"[*] 对实例 {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("  [!] 未找到根卷")
            return None

        snapshot_id = self.create_snapshot(root_volume)
        try:
            # 此处执行分析
            # 挂载快照,检查软件包,核查配置
            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 配置

[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}"]
# 运行 Vuls 无代理扫描
vuls scan

# 生成报告
vuls report -format-json -to-localfile

# 查看结果
vuls tui

最佳实践

  1. 对 Linux 扫描使用基于 SSH 密钥的身份验证,而非密码
  2. 为扫描创建具有最小只读权限的专用服务账户
  3. 分析完成后始终清理云快照,以避免存储成本和数据暴露
  4. 结合无代理扫描与 Agent 扫描以实现全面覆盖
  5. 在低活动期安排扫描,以最小化性能影响
  6. 定期轮换扫描凭据并存储在 Secrets Vault 中
  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
Skills enregistrées