mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 13:44:56 +03:00
383 lines
14 KiB
Python
383 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
External Network Penetration Test — Automation Process
|
|
|
|
Automates reconnaissance, scanning, and reporting phases of an external
|
|
network penetration test. Requires: nmap, subfinder, nuclei, python-nmap.
|
|
|
|
Usage:
|
|
python process.py --target target.com --ip-range 203.0.113.0/24 --output ./results
|
|
"""
|
|
|
|
import subprocess
|
|
import json
|
|
import csv
|
|
import os
|
|
import sys
|
|
import argparse
|
|
import socket
|
|
import ssl
|
|
import datetime
|
|
import ipaddress
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
|
|
def run_command(cmd: list[str], timeout: int = 300) -> tuple[str, str, int]:
|
|
"""Execute a shell command and return stdout, stderr, return code."""
|
|
try:
|
|
result = subprocess.run(
|
|
cmd,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=timeout
|
|
)
|
|
return result.stdout, result.stderr, result.returncode
|
|
except subprocess.TimeoutExpired:
|
|
return "", f"Command timed out after {timeout}s", -1
|
|
except FileNotFoundError:
|
|
return "", f"Command not found: {cmd[0]}", -1
|
|
|
|
|
|
def enumerate_subdomains(domain: str, output_dir: Path) -> list[str]:
|
|
"""Enumerate subdomains using subfinder."""
|
|
print(f"[*] Enumerating subdomains for {domain}...")
|
|
subfinder_out = output_dir / "subdomains_subfinder.txt"
|
|
|
|
stdout, stderr, rc = run_command(
|
|
["subfinder", "-d", domain, "-silent", "-o", str(subfinder_out)],
|
|
timeout=600
|
|
)
|
|
|
|
subdomains = set()
|
|
if subfinder_out.exists():
|
|
with open(subfinder_out) as f:
|
|
subdomains.update(line.strip() for line in f if line.strip())
|
|
|
|
print(f"[+] Found {len(subdomains)} subdomains")
|
|
return sorted(subdomains)
|
|
|
|
|
|
def resolve_domains(subdomains: list[str], output_dir: Path) -> dict[str, list[str]]:
|
|
"""Resolve subdomains to IP addresses."""
|
|
print(f"[*] Resolving {len(subdomains)} subdomains...")
|
|
resolved = {}
|
|
for sub in subdomains:
|
|
try:
|
|
ips = [r[4][0] for r in socket.getaddrinfo(sub, None, socket.AF_INET)]
|
|
resolved[sub] = list(set(ips))
|
|
except socket.gaierror:
|
|
continue
|
|
|
|
output_file = output_dir / "resolved_domains.json"
|
|
with open(output_file, "w") as f:
|
|
json.dump(resolved, f, indent=2)
|
|
|
|
unique_ips = set()
|
|
for ips in resolved.values():
|
|
unique_ips.update(ips)
|
|
print(f"[+] Resolved to {len(unique_ips)} unique IPs")
|
|
return resolved
|
|
|
|
|
|
def nmap_scan(targets: str, output_dir: Path, scan_type: str = "quick") -> dict:
|
|
"""Run nmap scan on target range."""
|
|
print(f"[*] Running nmap {scan_type} scan on {targets}...")
|
|
output_prefix = str(output_dir / f"nmap_{scan_type}")
|
|
|
|
if scan_type == "quick":
|
|
cmd = ["nmap", "-sS", "-sV", "--top-ports", "1000", "-T4",
|
|
"-oA", output_prefix, targets]
|
|
elif scan_type == "full":
|
|
cmd = ["nmap", "-sS", "-sV", "-p-", "-T4", "--min-rate", "1000",
|
|
"-oA", output_prefix, targets]
|
|
elif scan_type == "udp":
|
|
cmd = ["nmap", "-sU", "--top-ports", "100", "-T4",
|
|
"-oA", output_prefix, targets]
|
|
elif scan_type == "scripts":
|
|
cmd = ["nmap", "-sV", "-sC", "--script=vuln,exploit",
|
|
"-oA", output_prefix, targets]
|
|
else:
|
|
cmd = ["nmap", "-sS", "-sV", "-T4",
|
|
"-oA", output_prefix, targets]
|
|
|
|
stdout, stderr, rc = run_command(cmd, timeout=3600)
|
|
|
|
results = {
|
|
"scan_type": scan_type,
|
|
"targets": targets,
|
|
"return_code": rc,
|
|
"output_files": {
|
|
"nmap": f"{output_prefix}.nmap",
|
|
"xml": f"{output_prefix}.xml",
|
|
"gnmap": f"{output_prefix}.gnmap",
|
|
}
|
|
}
|
|
|
|
if rc == 0:
|
|
print(f"[+] Nmap {scan_type} scan completed successfully")
|
|
else:
|
|
print(f"[-] Nmap scan returned code {rc}: {stderr[:200]}")
|
|
|
|
return results
|
|
|
|
|
|
def check_ssl_tls(host: str, port: int = 443) -> dict:
|
|
"""Check SSL/TLS configuration for a host."""
|
|
result = {
|
|
"host": host,
|
|
"port": port,
|
|
"ssl_version": None,
|
|
"cipher": None,
|
|
"cert_subject": None,
|
|
"cert_issuer": None,
|
|
"cert_expiry": None,
|
|
"issues": []
|
|
}
|
|
|
|
try:
|
|
context = ssl.create_default_context()
|
|
with socket.create_connection((host, port), timeout=10) as sock:
|
|
with context.wrap_socket(sock, server_hostname=host) as ssock:
|
|
result["ssl_version"] = ssock.version()
|
|
result["cipher"] = ssock.cipher()
|
|
cert = ssock.getpeercert()
|
|
if cert:
|
|
result["cert_subject"] = dict(x[0] for x in cert.get("subject", []))
|
|
result["cert_issuer"] = dict(x[0] for x in cert.get("issuer", []))
|
|
result["cert_expiry"] = cert.get("notAfter")
|
|
|
|
# Check expiry
|
|
expiry = datetime.datetime.strptime(
|
|
cert["notAfter"], "%b %d %H:%M:%S %Y %Z"
|
|
)
|
|
if expiry < datetime.datetime.now():
|
|
result["issues"].append("Certificate expired")
|
|
elif expiry < datetime.datetime.now() + datetime.timedelta(days=30):
|
|
result["issues"].append("Certificate expires within 30 days")
|
|
|
|
except ssl.SSLCertVerificationError as e:
|
|
result["issues"].append(f"Certificate verification failed: {e}")
|
|
except (socket.timeout, ConnectionRefusedError, OSError) as e:
|
|
result["issues"].append(f"Connection failed: {e}")
|
|
|
|
return result
|
|
|
|
|
|
def run_nuclei_scan(targets_file: str, output_dir: Path) -> str:
|
|
"""Run nuclei vulnerability scanner."""
|
|
print("[*] Running nuclei vulnerability scan...")
|
|
output_file = output_dir / "nuclei_results.json"
|
|
|
|
cmd = [
|
|
"nuclei", "-l", targets_file,
|
|
"-severity", "critical,high,medium",
|
|
"-json", "-o", str(output_file),
|
|
"-rate-limit", "50",
|
|
"-bulk-size", "25",
|
|
"-concurrency", "10"
|
|
]
|
|
|
|
stdout, stderr, rc = run_command(cmd, timeout=3600)
|
|
|
|
if rc == 0:
|
|
print(f"[+] Nuclei scan completed. Results: {output_file}")
|
|
else:
|
|
print(f"[-] Nuclei scan issue: {stderr[:200]}")
|
|
|
|
return str(output_file)
|
|
|
|
|
|
def parse_nmap_gnmap(gnmap_file: str) -> list[dict]:
|
|
"""Parse nmap gnmap output to extract open ports."""
|
|
hosts = []
|
|
try:
|
|
with open(gnmap_file) as f:
|
|
for line in f:
|
|
if "Ports:" not in line:
|
|
continue
|
|
parts = line.split("\t")
|
|
host_part = parts[0]
|
|
ip = host_part.split(" ")[1]
|
|
ports_part = [p for p in parts if p.startswith("Ports:")]
|
|
if not ports_part:
|
|
continue
|
|
port_entries = ports_part[0].replace("Ports: ", "").split(", ")
|
|
open_ports = []
|
|
for entry in port_entries:
|
|
fields = entry.strip().split("/")
|
|
if len(fields) >= 5 and fields[1] == "open":
|
|
open_ports.append({
|
|
"port": int(fields[0]),
|
|
"protocol": fields[2],
|
|
"service": fields[4],
|
|
"version": fields[6] if len(fields) > 6 else ""
|
|
})
|
|
if open_ports:
|
|
hosts.append({"ip": ip, "open_ports": open_ports})
|
|
except FileNotFoundError:
|
|
print(f"[-] File not found: {gnmap_file}")
|
|
|
|
return hosts
|
|
|
|
|
|
def generate_report(
|
|
scan_results: dict,
|
|
resolved: dict,
|
|
ssl_results: list[dict],
|
|
output_dir: Path
|
|
) -> str:
|
|
"""Generate a summary report in markdown format."""
|
|
print("[*] Generating report...")
|
|
report_file = output_dir / "pentest_report.md"
|
|
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
|
|
|
|
with open(report_file, "w") as f:
|
|
f.write(f"# External Network Penetration Test Report\n\n")
|
|
f.write(f"**Generated:** {timestamp}\n\n")
|
|
f.write("---\n\n")
|
|
|
|
# Subdomain summary
|
|
f.write("## Subdomain Enumeration\n\n")
|
|
f.write(f"Total subdomains discovered: **{len(resolved)}**\n\n")
|
|
unique_ips = set()
|
|
for ips in resolved.values():
|
|
unique_ips.update(ips)
|
|
f.write(f"Unique IP addresses: **{len(unique_ips)}**\n\n")
|
|
|
|
if resolved:
|
|
f.write("| Subdomain | IP Address(es) |\n")
|
|
f.write("|-----------|---------------|\n")
|
|
for sub, ips in sorted(resolved.items()):
|
|
f.write(f"| {sub} | {', '.join(ips)} |\n")
|
|
f.write("\n")
|
|
|
|
# Port scan summary
|
|
f.write("## Port Scan Results\n\n")
|
|
for scan_type, result in scan_results.items():
|
|
f.write(f"### {scan_type.title()} Scan\n\n")
|
|
gnmap = result.get("output_files", {}).get("gnmap")
|
|
if gnmap and os.path.exists(gnmap):
|
|
hosts = parse_nmap_gnmap(gnmap)
|
|
if hosts:
|
|
for host in hosts:
|
|
f.write(f"**{host['ip']}**\n\n")
|
|
f.write("| Port | Protocol | Service | Version |\n")
|
|
f.write("|------|----------|---------|----------|\n")
|
|
for port in host["open_ports"]:
|
|
f.write(
|
|
f"| {port['port']} | {port['protocol']} "
|
|
f"| {port['service']} | {port['version']} |\n"
|
|
)
|
|
f.write("\n")
|
|
else:
|
|
f.write("No open ports discovered in this scan.\n\n")
|
|
else:
|
|
f.write(f"Scan output not available (return code: {result.get('return_code')})\n\n")
|
|
|
|
# SSL/TLS results
|
|
f.write("## SSL/TLS Assessment\n\n")
|
|
if ssl_results:
|
|
f.write("| Host | SSL Version | Cipher | Expiry | Issues |\n")
|
|
f.write("|------|-------------|--------|--------|--------|\n")
|
|
for sr in ssl_results:
|
|
issues = "; ".join(sr["issues"]) if sr["issues"] else "None"
|
|
f.write(
|
|
f"| {sr['host']} | {sr.get('ssl_version', 'N/A')} "
|
|
f"| {sr.get('cipher', ('N/A',))[0] if sr.get('cipher') else 'N/A'} "
|
|
f"| {sr.get('cert_expiry', 'N/A')} | {issues} |\n"
|
|
)
|
|
f.write("\n")
|
|
|
|
# Recommendations
|
|
f.write("## Recommendations\n\n")
|
|
f.write("1. Remediate all critical and high severity findings within 48 hours\n")
|
|
f.write("2. Patch all identified CVEs on internet-facing services\n")
|
|
f.write("3. Implement network segmentation for exposed services\n")
|
|
f.write("4. Enable MFA on all externally accessible portals\n")
|
|
f.write("5. Deploy WAF for web-facing applications\n")
|
|
f.write("6. Review and harden TLS configurations\n")
|
|
f.write("7. Remove unnecessary open ports and services\n")
|
|
f.write("8. Implement rate limiting and account lockout policies\n\n")
|
|
|
|
f.write("---\n")
|
|
f.write(f"*Report generated by external pentest automation tool*\n")
|
|
|
|
print(f"[+] Report generated: {report_file}")
|
|
return str(report_file)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="External Network Penetration Test Automation"
|
|
)
|
|
parser.add_argument("--target", required=True, help="Target domain (e.g., target.com)")
|
|
parser.add_argument("--ip-range", help="Target IP range in CIDR notation")
|
|
parser.add_argument("--output", default="./results", help="Output directory")
|
|
parser.add_argument("--skip-recon", action="store_true", help="Skip reconnaissance phase")
|
|
parser.add_argument("--skip-scan", action="store_true", help="Skip scanning phase")
|
|
parser.add_argument("--scan-type", default="quick",
|
|
choices=["quick", "full", "udp", "scripts"],
|
|
help="Nmap scan type")
|
|
args = parser.parse_args()
|
|
|
|
output_dir = Path(args.output)
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
(output_dir / "evidence").mkdir(exist_ok=True)
|
|
(output_dir / "scans").mkdir(exist_ok=True)
|
|
|
|
print("=" * 60)
|
|
print(" External Network Penetration Test")
|
|
print(f" Target: {args.target}")
|
|
print(f" Output: {output_dir.absolute()}")
|
|
print(f" Started: {datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}")
|
|
print("=" * 60)
|
|
|
|
# Phase 1: Reconnaissance
|
|
resolved = {}
|
|
if not args.skip_recon:
|
|
subdomains = enumerate_subdomains(args.target, output_dir)
|
|
resolved = resolve_domains(subdomains, output_dir)
|
|
else:
|
|
print("[*] Skipping reconnaissance phase")
|
|
|
|
# Phase 2: Scanning
|
|
scan_results = {}
|
|
ssl_results = []
|
|
if not args.skip_scan:
|
|
scan_target = args.ip_range or args.target
|
|
scan_results[args.scan_type] = nmap_scan(
|
|
scan_target, output_dir / "scans", args.scan_type
|
|
)
|
|
|
|
# SSL/TLS checks on discovered web services
|
|
ssl_hosts = [args.target]
|
|
if resolved:
|
|
ssl_hosts.extend(list(resolved.keys())[:20])
|
|
for host in ssl_hosts:
|
|
ssl_result = check_ssl_tls(host)
|
|
if ssl_result["ssl_version"] or ssl_result["issues"]:
|
|
ssl_results.append(ssl_result)
|
|
else:
|
|
print("[*] Skipping scanning phase")
|
|
|
|
# Phase 3: Nuclei scan
|
|
targets_file = output_dir / "targets.txt"
|
|
with open(targets_file, "w") as f:
|
|
f.write(f"https://{args.target}\n")
|
|
for sub in resolved:
|
|
f.write(f"https://{sub}\n")
|
|
run_nuclei_scan(str(targets_file), output_dir)
|
|
|
|
# Phase 4: Report generation
|
|
report_path = generate_report(scan_results, resolved, ssl_results, output_dir)
|
|
|
|
print("\n" + "=" * 60)
|
|
print(" Penetration Test Automation Complete")
|
|
print(f" Report: {report_path}")
|
|
print("=" * 60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|