Files
T

391 lines
14 KiB
Python

#!/usr/bin/env python3
"""
Authenticated Vulnerability Scan Credential Validator and Manager
Pre-validates scanner credentials against target hosts before launching
vulnerability scans to ensure maximum authenticated coverage.
Requirements:
pip install paramiko pywinrm pysnmp pandas
Usage:
python process.py validate --targets targets.txt --creds creds.json
python process.py report --nessus-file scan_results.nessus
"""
import argparse
import json
import socket
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from pathlib import Path
import pandas as pd
try:
import paramiko
except ImportError:
paramiko = None
try:
import winrm
except ImportError:
winrm = None
class CredentialValidator:
"""Validate scanner credentials against target hosts."""
def __init__(self, timeout: int = 10):
self.timeout = timeout
self.results = []
def check_port(self, host: str, port: int) -> bool:
"""Check if a TCP port is open on the target host."""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(self.timeout)
result = sock.connect_ex((host, port))
sock.close()
return result == 0
except (socket.error, OSError):
return False
def validate_ssh(self, host: str, username: str, password: str = None,
key_file: str = None, port: int = 22) -> dict:
"""Validate SSH credentials against a target host."""
result = {
"host": host, "protocol": "SSH", "port": port,
"username": username, "status": "UNKNOWN", "details": ""
}
if not paramiko:
result["status"] = "SKIP"
result["details"] = "paramiko not installed"
return result
if not self.check_port(host, port):
result["status"] = "FAIL"
result["details"] = f"Port {port} not reachable"
return result
try:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
connect_kwargs = {
"hostname": host, "port": port, "username": username,
"timeout": self.timeout, "allow_agent": False, "look_for_keys": False
}
if key_file:
connect_kwargs["key_filename"] = key_file
elif password:
connect_kwargs["password"] = password
else:
result["status"] = "FAIL"
result["details"] = "No password or key file provided"
return result
client.connect(**connect_kwargs)
# Test command execution
_, stdout, stderr = client.exec_command("id && uname -a", timeout=10)
output = stdout.read().decode().strip()
error = stderr.read().decode().strip()
# Test sudo access
_, stdout_sudo, stderr_sudo = client.exec_command(
"sudo -n id 2>&1", timeout=10
)
sudo_output = stdout_sudo.read().decode().strip()
has_sudo = "uid=0" in sudo_output
client.close()
result["status"] = "SUCCESS"
result["details"] = f"Auth OK. Sudo: {'Yes' if has_sudo else 'No'}. {output[:100]}"
result["has_sudo"] = has_sudo
except paramiko.AuthenticationException:
result["status"] = "FAIL"
result["details"] = "Authentication failed - invalid credentials"
except paramiko.SSHException as e:
result["status"] = "FAIL"
result["details"] = f"SSH error: {str(e)[:100]}"
except Exception as e:
result["status"] = "FAIL"
result["details"] = f"Connection error: {str(e)[:100]}"
return result
def validate_winrm(self, host: str, username: str, password: str,
port: int = 5985, use_ssl: bool = False) -> dict:
"""Validate WinRM credentials against a Windows target."""
result = {
"host": host, "protocol": "WinRM", "port": port,
"username": username, "status": "UNKNOWN", "details": ""
}
if not winrm:
result["status"] = "SKIP"
result["details"] = "pywinrm not installed"
return result
check_port = 5986 if use_ssl else port
if not self.check_port(host, check_port):
result["status"] = "FAIL"
result["details"] = f"Port {check_port} not reachable"
return result
try:
scheme = "https" if use_ssl else "http"
session = winrm.Session(
f"{scheme}://{host}:{check_port}/wsman",
auth=(username, password),
transport="ntlm",
server_cert_validation="ignore" if use_ssl else "validate"
)
r = session.run_cmd("whoami")
output = r.std_out.decode().strip()
# Check admin privileges
r_admin = session.run_cmd("net", ["localgroup", "Administrators"])
admin_output = r_admin.std_out.decode()
is_admin = username.split("\\")[-1].lower() in admin_output.lower()
result["status"] = "SUCCESS"
result["details"] = f"Auth OK as {output}. Admin: {'Yes' if is_admin else 'No'}"
result["is_admin"] = is_admin
except Exception as e:
result["status"] = "FAIL"
result["details"] = f"WinRM error: {str(e)[:150]}"
return result
def validate_smb(self, host: str, username: str, password: str,
domain: str = "", port: int = 445) -> dict:
"""Validate SMB credentials against a Windows target."""
result = {
"host": host, "protocol": "SMB", "port": port,
"username": username, "status": "UNKNOWN", "details": ""
}
if not self.check_port(host, port):
result["status"] = "FAIL"
result["details"] = f"Port {port} not reachable"
return result
try:
from impacket.smbconnection import SMBConnection
conn = SMBConnection(host, host, sess_port=port)
conn.login(username, password, domain)
shares = conn.listShares()
share_names = [s["shi1_netname"].rstrip("\x00") for s in shares]
conn.logoff()
result["status"] = "SUCCESS"
result["details"] = f"Auth OK. Shares: {', '.join(share_names[:5])}"
except ImportError:
result["status"] = "SKIP"
result["details"] = "impacket not installed"
except Exception as e:
result["status"] = "FAIL"
result["details"] = f"SMB error: {str(e)[:150]}"
return result
def validate_snmpv3(self, host: str, username: str, auth_password: str,
priv_password: str, port: int = 161) -> dict:
"""Validate SNMPv3 credentials against a target."""
result = {
"host": host, "protocol": "SNMPv3", "port": port,
"username": username, "status": "UNKNOWN", "details": ""
}
try:
from pysnmp.hlapi import (
SnmpEngine, UsmUserData, UdpTransportTarget,
ContextData, ObjectType, ObjectIdentity, getCmd,
usmHMACSHAAuthProtocol, usmAesCfb128Protocol
)
iterator = getCmd(
SnmpEngine(),
UsmUserData(username, auth_password, priv_password,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmAesCfb128Protocol),
UdpTransportTarget((host, port), timeout=self.timeout),
ContextData(),
ObjectType(ObjectIdentity("SNMPv2-MIB", "sysDescr", 0))
)
errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
if errorIndication:
result["status"] = "FAIL"
result["details"] = f"SNMP error: {errorIndication}"
elif errorStatus:
result["status"] = "FAIL"
result["details"] = f"SNMP status: {errorStatus.prettyPrint()}"
else:
for varBind in varBinds:
result["status"] = "SUCCESS"
result["details"] = f"Auth OK. sysDescr: {str(varBind[1])[:100]}"
except ImportError:
result["status"] = "SKIP"
result["details"] = "pysnmp not installed"
except Exception as e:
result["status"] = "FAIL"
result["details"] = f"SNMP error: {str(e)[:150]}"
return result
def validate_all(self, targets: list, credentials: dict, max_workers: int = 20) -> list:
"""Validate credentials against all targets in parallel."""
self.results = []
tasks = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
for target in targets:
host = target.strip()
if not host:
continue
# SSH validation
if "ssh" in credentials:
cred = credentials["ssh"]
tasks.append(executor.submit(
self.validate_ssh, host, cred["username"],
cred.get("password"), cred.get("key_file"),
cred.get("port", 22)
))
# WinRM validation
if "winrm" in credentials:
cred = credentials["winrm"]
tasks.append(executor.submit(
self.validate_winrm, host, cred["username"],
cred["password"], cred.get("port", 5985),
cred.get("use_ssl", False)
))
# SMB validation
if "smb" in credentials:
cred = credentials["smb"]
tasks.append(executor.submit(
self.validate_smb, host, cred["username"],
cred["password"], cred.get("domain", ""),
cred.get("port", 445)
))
# SNMPv3 validation
if "snmpv3" in credentials:
cred = credentials["snmpv3"]
tasks.append(executor.submit(
self.validate_snmpv3, host, cred["username"],
cred["auth_password"], cred["priv_password"],
cred.get("port", 161)
))
for future in as_completed(tasks):
try:
result = future.result()
self.results.append(result)
status_icon = "[+]" if result["status"] == "SUCCESS" else "[-]"
print(f" {status_icon} {result['host']}:{result['port']} "
f"({result['protocol']}) - {result['status']}: {result['details'][:80]}")
except Exception as e:
print(f" [!] Validation error: {e}")
return self.results
def generate_report(self, output_path: str = None) -> pd.DataFrame:
"""Generate validation report."""
df = pd.DataFrame(self.results)
if df.empty:
print("[-] No validation results to report")
return df
print("\n" + "=" * 70)
print("CREDENTIAL VALIDATION SUMMARY")
print("=" * 70)
total = len(df)
success = len(df[df["status"] == "SUCCESS"])
fail = len(df[df["status"] == "FAIL"])
skip = len(df[df["status"] == "SKIP"])
print(f"Total Checks: {total}")
print(f" SUCCESS: {success} ({success/total*100:.1f}%)")
print(f" FAIL: {fail} ({fail/total*100:.1f}%)")
print(f" SKIPPED: {skip} ({skip/total*100:.1f}%)")
if success / max(total, 1) < 0.90:
print("\n[WARNING] Credential success rate below 90% - investigate failures before scanning")
# Protocol breakdown
print("\nBy Protocol:")
for proto in df["protocol"].unique():
proto_df = df[df["protocol"] == proto]
proto_success = len(proto_df[proto_df["status"] == "SUCCESS"])
print(f" {proto}: {proto_success}/{len(proto_df)} "
f"({proto_success/len(proto_df)*100:.1f}%)")
# Failed hosts
failures = df[df["status"] == "FAIL"]
if not failures.empty:
print(f"\nFailed Hosts ({len(failures)}):")
for _, row in failures.iterrows():
print(f" {row['host']}:{row['port']} ({row['protocol']}): {row['details'][:80]}")
if output_path:
df.to_csv(output_path, index=False)
print(f"\n[+] Report saved to: {output_path}")
return df
def main():
parser = argparse.ArgumentParser(description="Authenticated Scan Credential Validator")
subparsers = parser.add_subparsers(dest="command")
# Validate command
val_parser = subparsers.add_parser("validate", help="Validate credentials against targets")
val_parser.add_argument("--targets", required=True, help="File with target IPs (one per line)")
val_parser.add_argument("--creds", required=True, help="JSON file with credentials")
val_parser.add_argument("--output", default=None, help="Output CSV report path")
val_parser.add_argument("--workers", type=int, default=20, help="Max parallel workers")
val_parser.add_argument("--timeout", type=int, default=10, help="Connection timeout seconds")
args = parser.parse_args()
if args.command == "validate":
with open(args.targets) as f:
targets = [line.strip() for line in f if line.strip() and not line.startswith("#")]
with open(args.creds) as f:
credentials = json.load(f)
print(f"[*] Validating credentials against {len(targets)} targets")
print(f"[*] Protocols: {', '.join(credentials.keys())}")
validator = CredentialValidator(timeout=args.timeout)
validator.validate_all(targets, credentials, max_workers=args.workers)
output = args.output or f"cred_validation_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
validator.generate_report(output)
else:
parser.print_help()
if __name__ == "__main__":
main()