mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 05:34:55 +03:00
391 lines
14 KiB
Python
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()
|