mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-20 06:20:58 +03:00
Initial commit - 611 cybersecurity skills across all subdomains
This commit is contained in:
@@ -0,0 +1,390 @@
|
||||
---
|
||||
name: performing-agentless-vulnerability-scanning
|
||||
description: Configure and execute agentless vulnerability scanning using network protocols, cloud snapshot analysis, and API-based discovery to assess systems without installing endpoint agents.
|
||||
domain: cybersecurity
|
||||
subdomain: vulnerability-management
|
||||
tags: [agentless-scanning, vulnerability-assessment, cloud-security, ssh, wmi, snapshot-analysis, vuls, tenable]
|
||||
version: "1.0"
|
||||
author: mahipal
|
||||
license: MIT
|
||||
---
|
||||
# Performing Agentless Vulnerability Scanning
|
||||
|
||||
## Overview
|
||||
Agentless vulnerability scanning assesses systems for security weaknesses without requiring endpoint agent installation. This approach leverages existing network protocols (SSH for Linux, WMI for Windows), cloud provider APIs for snapshot-based analysis, and authenticated remote checks. Modern cloud platforms like Microsoft Defender for Cloud, Wiz, Datadog, and Tenable perform out-of-band analysis by taking disk snapshots and examining OS configurations and installed packages offline. The open-source tool Vuls provides agentless scanning based on NVD and OVAL data for Linux/FreeBSD systems. This skill covers configuring agentless scans across on-premises, cloud, and containerized environments.
|
||||
|
||||
## Prerequisites
|
||||
- SSH key-based authentication configured on Linux/Unix targets
|
||||
- WMI/WinRM access on Windows targets with appropriate credentials
|
||||
- Cloud provider API credentials (AWS IAM, Azure RBAC, GCP IAM)
|
||||
- Network access from scanner to target systems on required ports
|
||||
- Service account with read-only access to target system configurations
|
||||
- Python 3.8+ for custom scanning automation
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Agentless vs Agent-Based Scanning
|
||||
|
||||
| Aspect | Agentless | Agent-Based |
|
||||
|--------|-----------|-------------|
|
||||
| Deployment | No software installation needed | Agent install on every endpoint |
|
||||
| Network dependency | Requires network connectivity | Works offline with cloud sync |
|
||||
| Performance impact | Minimal on target systems | Light continuous overhead |
|
||||
| Coverage depth | Depends on protocol/credentials | Deep local access |
|
||||
| Cloud snapshot analysis | Native capability | Not applicable |
|
||||
| Ideal for | Cloud VMs, IoT, legacy systems, OT | Managed endpoints, laptops |
|
||||
|
||||
### Agentless Scanning Methods
|
||||
|
||||
| Method | Protocol | Target OS | Port | Use Case |
|
||||
|--------|----------|-----------|------|----------|
|
||||
| SSH Remote Commands | SSH | Linux/Unix | 22 | Package enumeration, config audit |
|
||||
| WMI Remote Query | WMI/DCOM | Windows | 135, 445 | Hotfix enumeration, registry checks |
|
||||
| WinRM PowerShell | WS-Man | Windows | 5985/5986 | Remote command execution |
|
||||
| SNMP Community | SNMP v2c/v3 | Network devices | 161 | Device fingerprinting, firmware check |
|
||||
| Cloud Snapshot | Provider API | Cloud VMs | N/A | Disk image analysis |
|
||||
| Container Registry | HTTPS | Container images | 443 | Image vulnerability scanning |
|
||||
| API-Based | REST/HTTPS | SaaS/Cloud | 443 | Configuration assessment |
|
||||
|
||||
### Cloud Snapshot Analysis Flow
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: SSH-Based Agentless Scanning (Linux)
|
||||
|
||||
```bash
|
||||
# Create dedicated scan SSH key pair
|
||||
ssh-keygen -t ed25519 -f /opt/scanner/.ssh/scan_key -N "" \
|
||||
-C "vuln-scanner@security.local"
|
||||
|
||||
# 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"
|
||||
```
|
||||
|
||||
```python
|
||||
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
|
||||
```
|
||||
|
||||
### Step 2: WinRM-Based Agentless Scanning (Windows)
|
||||
|
||||
```python
|
||||
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
|
||||
```
|
||||
|
||||
### Step 3: Cloud Snapshot Scanning (AWS)
|
||||
|
||||
```python
|
||||
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)
|
||||
```
|
||||
|
||||
### Step 4: Vuls Open-Source Agentless Scanner
|
||||
|
||||
```toml
|
||||
# /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}"]
|
||||
```
|
||||
|
||||
```bash
|
||||
# Run Vuls agentless scan
|
||||
vuls scan
|
||||
|
||||
# Generate report
|
||||
vuls report -format-json -to-localfile
|
||||
|
||||
# View results
|
||||
vuls tui
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
1. Use SSH key-based authentication instead of passwords for Linux scanning
|
||||
2. Create dedicated service accounts with minimal read-only privileges for scanning
|
||||
3. Always clean up cloud snapshots after analysis to avoid storage costs and data exposure
|
||||
4. Combine agentless scanning with agent-based for comprehensive coverage
|
||||
5. Schedule scans during low-activity periods to minimize any performance impact
|
||||
6. Rotate scanning credentials regularly and store in a secrets vault
|
||||
7. Test scanner connectivity before scheduling production scans
|
||||
8. Use SNMPv3 with authentication and encryption for network device scanning
|
||||
|
||||
## Common Pitfalls
|
||||
- Using shared credentials across multiple environments without proper segmentation
|
||||
- Not cleaning up temporary snapshots in cloud environments
|
||||
- Assuming agentless scanning has zero performance impact (network and CPU are used)
|
||||
- Missing WinRM/SSH firewall rules causing scan failures on new deployments
|
||||
- Not accounting for SSH host key changes causing authentication failures
|
||||
- Scanning OT/ICS devices with protocols they cannot safely handle
|
||||
|
||||
## Related Skills
|
||||
- implementing-rapid7-insightvm-for-scanning
|
||||
- implementing-wazuh-for-vulnerability-detection
|
||||
- deploying-osquery-for-endpoint-monitoring
|
||||
- performing-remediation-validation-scanning
|
||||
@@ -0,0 +1,30 @@
|
||||
# Agentless Vulnerability Scan Report Template
|
||||
|
||||
## Scan Summary
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Scan Date | [YYYY-MM-DD] |
|
||||
| Scan Method | [SSH / WinRM / Cloud Snapshot / API] |
|
||||
| Total Hosts Targeted | [N] |
|
||||
| Successfully Scanned | [N] |
|
||||
| Failed / Unreachable | [N] |
|
||||
| Total Packages Enumerated | [N] |
|
||||
| Total Vulnerabilities Found | [N] |
|
||||
|
||||
## Host Inventory
|
||||
| Hostname | IP | OS | Kernel | Packages | Vulns | Status |
|
||||
|----------|----|----|--------|----------|-------|--------|
|
||||
| [host] | [IP] | [OS] | [kernel] | [N] | [N] | [Success/Failed] |
|
||||
|
||||
## Vulnerability Summary by Severity
|
||||
| Severity | Count | % | Hosts Affected |
|
||||
|----------|-------|---|----------------|
|
||||
| Critical | [N] | [%] | [N] |
|
||||
| High | [N] | [%] | [N] |
|
||||
| Medium | [N] | [%] | [N] |
|
||||
| Low | [N] | [%] | [N] |
|
||||
|
||||
## Scan Failures
|
||||
| Hostname | Error | Recommended Action |
|
||||
|----------|-------|--------------------|
|
||||
| [host] | [Auth failure / Timeout / Port closed] | [Fix SSH key / Open firewall / Enable WinRM] |
|
||||
@@ -0,0 +1,23 @@
|
||||
# Standards and References - Agentless Vulnerability Scanning
|
||||
|
||||
## Tools and Platforms
|
||||
- Vuls (Open Source): https://vuls.io/
|
||||
- Microsoft Defender for Cloud Agentless: https://learn.microsoft.com/en-us/azure/defender-for-cloud/concept-agentless-data-collection
|
||||
- Tenable Agentless Discovery: https://www.tenable.com/cloud-security/capabilities/agentless-asset-vulnerability-discovery
|
||||
- Wiz Agentless VM: https://www.wiz.io/solutions/vulnerability-management
|
||||
- Datadog Agentless Scanning: https://www.datadoghq.com/blog/agentless-scanning/
|
||||
|
||||
## Industry Standards
|
||||
- **NIST SP 800-115**: Technical Guide to Information Security Testing and Assessment
|
||||
- **CIS Controls v8.1 Control 7.5**: Perform Automated Vulnerability Scans of Internal Assets
|
||||
- **PCI DSS v4.0 Req 11.3**: External and internal vulnerability scanning
|
||||
- **ISO 27001:2022 A.8.8**: Management of technical vulnerabilities
|
||||
|
||||
## Protocol Requirements
|
||||
| Protocol | Port | Auth Method | Use Case |
|
||||
|----------|------|-------------|----------|
|
||||
| SSH | 22 | Key-based or password | Linux/Unix scanning |
|
||||
| WinRM | 5985/5986 | NTLM/Kerberos | Windows scanning |
|
||||
| WMI | 135 + dynamic | NTLM | Windows legacy |
|
||||
| SNMP v3 | 161 | AuthPriv | Network devices |
|
||||
| Cloud APIs | 443 | IAM roles/keys | Cloud VMs |
|
||||
@@ -0,0 +1,52 @@
|
||||
# Workflows - Agentless Vulnerability Scanning
|
||||
|
||||
## Workflow 1: Multi-Protocol Scanning Pipeline
|
||||
|
||||
```
|
||||
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||||
│ Asset Discovery │────>│ Classify by │────>│ Select Scanning │
|
||||
│ (CMDB/Network) │ │ OS / Platform │ │ Protocol │
|
||||
└──────────────────┘ └──────────────────┘ └──────────────────┘
|
||||
│
|
||||
┌──────────────┬──────────────┬─────────────────┘
|
||||
v v v
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ SSH Scan │ │ WinRM Scan │ │ Cloud API │
|
||||
│ (Linux) │ │ (Windows) │ │ Snapshot Scan│
|
||||
└──────────────┘ └──────────────┘ └──────────────┘
|
||||
│ │ │
|
||||
└──────────────┴──────────────┘
|
||||
│
|
||||
v
|
||||
┌──────────────────┐
|
||||
│ Normalize & │
|
||||
│ Correlate Results│
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
## Workflow 2: Cloud Snapshot Scan Process
|
||||
|
||||
```
|
||||
For each cloud VM:
|
||||
1. Identify attached volumes (root + data)
|
||||
2. Create snapshot of root volume via cloud API
|
||||
3. Mount snapshot in isolated analysis environment
|
||||
4. Extract OS metadata (packages, configs, users)
|
||||
5. Compare against vulnerability databases (NVD, vendor)
|
||||
6. Generate findings with CVE mappings
|
||||
7. Delete temporary snapshot
|
||||
8. Report findings to central dashboard
|
||||
```
|
||||
|
||||
## Workflow 3: Credential Validation Before Scan
|
||||
|
||||
```
|
||||
Pre-Scan Credential Check:
|
||||
For each target:
|
||||
1. Test SSH/WinRM connectivity (TCP handshake)
|
||||
2. Authenticate with stored credentials
|
||||
3. Execute lightweight test command
|
||||
4. Verify sudo/admin privileges if required
|
||||
5. Log result: Success / Auth Failure / Network Error
|
||||
6. Only proceed with scan if credential test passes
|
||||
```
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Agentless Vulnerability Scanning Orchestrator
|
||||
|
||||
Performs SSH-based agentless vulnerability scanning on Linux hosts
|
||||
by enumerating packages and checking against known vulnerabilities.
|
||||
|
||||
Requirements:
|
||||
pip install paramiko requests pandas
|
||||
|
||||
Usage:
|
||||
python process.py scan --hosts hosts.txt --key /path/to/ssh_key
|
||||
python process.py check --host 192.168.1.10 --user scanner --key /path/to/ssh_key
|
||||
python process.py report --input scan_results.json --output report.csv
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
import pandas as pd
|
||||
import paramiko
|
||||
import requests
|
||||
|
||||
|
||||
NVD_API = "https://services.nvd.nist.gov/rest/json/cves/2.0"
|
||||
|
||||
|
||||
class AgentlessScanner:
|
||||
"""SSH-based agentless vulnerability scanner."""
|
||||
|
||||
def __init__(self, key_path, username="scanner"):
|
||||
self.key_path = key_path
|
||||
self.username = username
|
||||
|
||||
def _connect(self, hostname, port=22):
|
||||
"""Establish SSH connection."""
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
try:
|
||||
key = paramiko.Ed25519Key.from_private_key_file(self.key_path)
|
||||
except paramiko.ssh_exception.SSHException:
|
||||
key = paramiko.RSAKey.from_private_key_file(self.key_path)
|
||||
client.connect(hostname, port=port, username=self.username,
|
||||
pkey=key, timeout=30)
|
||||
return client
|
||||
|
||||
def _exec(self, client, command, timeout=30):
|
||||
"""Execute command and return output."""
|
||||
_, stdout, stderr = client.exec_command(command, timeout=timeout)
|
||||
return stdout.read().decode().strip(), stderr.read().decode().strip()
|
||||
|
||||
def get_os_info(self, client):
|
||||
"""Detect OS distribution and version."""
|
||||
out, _ = self._exec(client, "cat /etc/os-release")
|
||||
info = {}
|
||||
for line in out.split("\n"):
|
||||
if "=" in line:
|
||||
k, v = line.split("=", 1)
|
||||
info[k] = v.strip('"')
|
||||
return info
|
||||
|
||||
def get_packages_dpkg(self, client):
|
||||
"""Get packages via dpkg (Debian/Ubuntu)."""
|
||||
out, _ = self._exec(
|
||||
client,
|
||||
"dpkg-query -W -f='${Package}|${Version}|${Architecture}\\n'"
|
||||
)
|
||||
packages = []
|
||||
for line in out.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 "",
|
||||
})
|
||||
return packages
|
||||
|
||||
def get_packages_rpm(self, client):
|
||||
"""Get packages via rpm (RHEL/CentOS/Fedora)."""
|
||||
out, _ = self._exec(
|
||||
client,
|
||||
"rpm -qa --queryformat '%{NAME}|%{VERSION}-%{RELEASE}|%{ARCH}\\n'"
|
||||
)
|
||||
packages = []
|
||||
for line in out.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 "",
|
||||
})
|
||||
return packages
|
||||
|
||||
def get_kernel(self, client):
|
||||
"""Get running kernel version."""
|
||||
out, _ = self._exec(client, "uname -r")
|
||||
return out
|
||||
|
||||
def get_listening_services(self, client):
|
||||
"""Get listening network services."""
|
||||
out, _ = self._exec(client, "ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null")
|
||||
return out
|
||||
|
||||
def scan_host(self, hostname, port=22):
|
||||
"""Perform full agentless scan."""
|
||||
result = {
|
||||
"hostname": hostname,
|
||||
"scan_time": datetime.utcnow().isoformat(),
|
||||
"status": "success",
|
||||
"os_info": {},
|
||||
"kernel": "",
|
||||
"packages": [],
|
||||
"listening_services": "",
|
||||
}
|
||||
|
||||
try:
|
||||
client = self._connect(hostname, port)
|
||||
result["os_info"] = self.get_os_info(client)
|
||||
result["kernel"] = self.get_kernel(client)
|
||||
|
||||
os_id = result["os_info"].get("ID", "").lower()
|
||||
if os_id in ("ubuntu", "debian", "kali"):
|
||||
result["packages"] = self.get_packages_dpkg(client)
|
||||
else:
|
||||
result["packages"] = self.get_packages_rpm(client)
|
||||
|
||||
result["listening_services"] = self.get_listening_services(client)
|
||||
client.close()
|
||||
|
||||
print(f" [+] {hostname}: {result['os_info'].get('PRETTY_NAME', 'Unknown')} | "
|
||||
f"{len(result['packages'])} packages | kernel {result['kernel']}")
|
||||
|
||||
except Exception as e:
|
||||
result["status"] = "error"
|
||||
result["error"] = str(e)
|
||||
print(f" [!] {hostname}: {e}")
|
||||
|
||||
return result
|
||||
|
||||
def scan_hosts(self, hosts, port=22):
|
||||
"""Scan multiple hosts."""
|
||||
results = []
|
||||
for host in hosts:
|
||||
host = host.strip()
|
||||
if not host or host.startswith("#"):
|
||||
continue
|
||||
print(f"[*] Scanning {host}...")
|
||||
results.append(self.scan_host(host, port))
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Agentless Vulnerability Scanning Orchestrator"
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
|
||||
scan_p = subparsers.add_parser("scan", help="Scan hosts from file")
|
||||
scan_p.add_argument("--hosts", required=True, help="File with hostnames/IPs")
|
||||
scan_p.add_argument("--key", required=True, help="SSH private key path")
|
||||
scan_p.add_argument("--user", default="scanner", help="SSH username")
|
||||
scan_p.add_argument("--port", type=int, default=22, help="SSH port")
|
||||
scan_p.add_argument("--output", default="scan_results.json")
|
||||
|
||||
check_p = subparsers.add_parser("check", help="Scan a single host")
|
||||
check_p.add_argument("--host", required=True)
|
||||
check_p.add_argument("--key", required=True)
|
||||
check_p.add_argument("--user", default="scanner")
|
||||
check_p.add_argument("--port", type=int, default=22)
|
||||
|
||||
report_p = subparsers.add_parser("report", help="Generate CSV report")
|
||||
report_p.add_argument("--input", required=True, help="Scan results JSON")
|
||||
report_p.add_argument("--output", default="scan_report.csv")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "scan":
|
||||
scanner = AgentlessScanner(args.key, args.user)
|
||||
with open(args.hosts) as f:
|
||||
hosts = f.readlines()
|
||||
results = scanner.scan_hosts(hosts, args.port)
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(results, f, indent=2)
|
||||
print(f"\n[+] Results saved to {args.output}")
|
||||
successful = sum(1 for r in results if r["status"] == "success")
|
||||
print(f" Scanned: {len(results)} | Success: {successful}")
|
||||
|
||||
elif args.command == "check":
|
||||
scanner = AgentlessScanner(args.key, args.user)
|
||||
result = scanner.scan_host(args.host, args.port)
|
||||
print(json.dumps(result, indent=2, default=str))
|
||||
|
||||
elif args.command == "report":
|
||||
with open(args.input) as f:
|
||||
results = json.load(f)
|
||||
rows = []
|
||||
for r in results:
|
||||
for pkg in r.get("packages", []):
|
||||
rows.append({
|
||||
"hostname": r["hostname"],
|
||||
"os": r.get("os_info", {}).get("PRETTY_NAME", ""),
|
||||
"kernel": r.get("kernel", ""),
|
||||
"package": pkg["name"],
|
||||
"version": pkg["version"],
|
||||
})
|
||||
df = pd.DataFrame(rows)
|
||||
df.to_csv(args.output, index=False)
|
||||
print(f"[+] Report with {len(rows)} package entries saved to {args.output}")
|
||||
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user