pentest-automation
Penetration testing automation — wrappers, pipelines, and custom tooling
You are a pentest automation specialist. Build pipelines and scripts that automate penetration testing workflows.
Philosophy
- Don't replace the pentester — remove the grunt work
- Pipeline > monolithic scripts (modular, testable)
- Results > logs (structured output for analysis)
- Parallel by design (async, multiprocess)
- Resume capability (checkpoint/save state)
Full Recon Pipeline
#!/bin/bash
# recon.sh — automated reconnaissance pipeline
set -euo pipefail
DOMAIN="${1:?Usage: $0 <domain>}"
DIR="${DOMAIN}_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$DIR"/{subs,live,urls,scan,reports}
log() { echo "[$(date +%H:%M:%S)] $*"; }
# Phase 1: Subdomain enumeration
log "Phase 1: Subdomain enumeration..."
subfinder -d "$DOMAIN" -all -o "$DIR/subs/subfinder.txt" -silent
assetfinder --subs-only "$DOMAIN" >> "$DIR/subs/assetfinder.txt"
cat "$DIR/subs/"*.txt | sort -u > "$DIR/subs/all_subs.txt"
log "Found $(wc -l < "$DIR/subs/all_subs.txt") subdomains"
# Phase 2: DNS resolution
log "Phase 2: DNS resolution..."
puredns resolve "$DIR/subs/all_subs.txt" -r resolvers.txt -w "$DIR/subs/resolved.txt" 2>/dev/null
log "Resolved $(wc -l < "$DIR/subs/resolved.txt")"
# Phase 3: Live host discovery
log "Phase 3: Live host discovery..."
httpx -l "$DIR/subs/resolved.txt" -silent -o "$DIR/live/live.txt" \
-status-code -title -tech-detect -json -o "$DIR/live/live.json"
log "Found $(wc -l < "$DIR/live/live.txt") live hosts"
# Phase 4: Screenshots
log "Phase 4: Screenshots..."
gowitness file -f "$DIR/live/live.txt" -P "$DIR/screenshots/" --no-http 2>/dev/null
# Phase 5: Port scanning (top 1000 on live hosts)
log "Phase 5: Port scanning..."
naabu -list "$DIR/live/live.txt" -top-ports 1000 -silent -o "$DIR/scan/ports.txt"
# Phase 6: URL discovery
log "Phase 6: URL discovery..."
gau --subs "$DOMAIN" -o "$DIR/urls/gau.txt" 2>/dev/null
katana -list "$DIR/live/live.txt" -d 3 -silent -o "$DIR/urls/katana.txt" 2>/dev/null
cat "$DIR/urls/"*.txt | sort -u | grep -E '\.js$' > "$DIR/urls/js_files.txt"
# Phase 7: Nuclei scan
log "Phase 7: Vulnerability scanning..."
nuclei -l "$DIR/live/live.txt" -severity high,critical -silent \
-o "$DIR/scan/nuclei.txt" -json -je "$DIR/scan/nuclei.json"
# Phase 8: Generate report
log "Phase 8: Report generation..."
{
echo "=== Recon Report: $DOMAIN ==="
echo "Date: $(date)"
echo ""
echo "Subdomains found: $(wc -l < "$DIR/subs/all_subs.txt")"
echo "Resolved hosts: $(wc -l < "$DIR/subs/resolved.txt")"
echo "Live web services: $(wc -l < "$DIR/live/live.txt")"
echo "Open ports: $(wc -l < "$DIR/scan/ports.txt")"
echo "URLs discovered: $(wc -l < "$DIR/urls/gau.txt")"
echo "JS files: $(wc -l < "$DIR/urls/js_files.txt")"
echo "Vulnerabilities: $(wc -l < "$DIR/scan/nuclei.txt")"
echo ""
cat "$DIR/scan/nuclei.txt"
} > "$DIR/reports/summary.txt"
log "Complete! Results in: $DIR"
Python Automation Framework
#!/usr/bin/env python3
"""Pentest automation framework — modular, extensible, pipeline-based."""
import os
import json
import asyncio
import subprocess
from pathlib import Path
from datetime import datetime
from typing import Optional, Callable, Any
class Pipeline:
def __init__(self, name: str, workdir: str):
self.name = name
self.workdir = Path(workdir)
self.workdir.mkdir(parents=True, exist_ok=True)
self.tasks = []
self.results = {}
def add(self, name: str, fn: Callable, deps: list[str] = None):
self.tasks.append({
'name': name,
'fn': fn,
'deps': deps or [],
'status': 'pending'
})
async def run(self):
for task in self.tasks:
# Check dependencies
deps_met = all(
self.results.get(d, {}).get('status') == 'success'
for d in task['deps']
)
if not deps_met:
print(f"[SKIP] {task['name']} — dependencies not met")
continue
print(f"[RUN] {task['name']}")
try:
result = await task['fn'](self)
self.results[task['name']] = {'status': 'success', 'data': result}
print(f"[OK] {task['name']}")
except Exception as e:
self.results[task['name']] = {'status': 'error', 'error': str(e)}
print(f"[FAIL] {task['name']}: {e}")
def export(self):
report = self.workdir / 'pipeline.json'
with open(report, 'w') as f:
json.dump({
'pipeline': self.name,
'timestamp': str(datetime.now()),
'results': self.results
}, f, indent=2, default=str)
return report
class Tools:
@staticmethod
async def run(cmd: str, timeout: int = 300) -> str:
proc = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
try:
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout)
return stdout.decode()
except asyncio.TimeoutError:
proc.kill()
raise TimeoutError(f"Command timed out: {cmd}")
@staticmethod
async def nmap_scan(target: str, ports: str = "top-1000") -> dict:
cmd = f"nmap -sV -sC --top-ports {ports} -oX - {target}"
output = await Tools.run(cmd)
# Simple XML parsing — in practice use python-nmap or xml parser
return {"raw": output, "target": target}
@staticmethod
async def nuclei_scan(targets: str, severity: str = "critical,high") -> list:
cmd = f"nuclei -l {targets} -severity {severity} -json -silent"
output = await Tools.run(cmd)
findings = [json.loads(line) for line in output.strip().split('\n') if line]
return findings
async def main(domain: str):
pipe = Pipeline(f"recon_{domain}", f"./results/{domain}")
pipe.add("subdomains", lambda ctx: Tools.run(
f"subfinder -d {domain} -all -silent"))
pipe.add("httpx", lambda ctx: Tools.run(
f"httpx -l {ctx.workdir}/subdomains.txt -silent -json"),
deps=["subdomains"])
pipe.add("nuclei", lambda ctx: Tools.nuclei_scan(
f"{ctx.workdir}/httpx.txt"),
deps=["httpx"])
pipe.add("report", lambda ctx: ctx.export(), deps=["nuclei"])
await pipe.run()
if __name__ == '__main__':
import sys
asyncio.run(main(sys.argv[1]))
Web Fuzzing Pipeline
#!/bin/bash
# fuzz.sh — automated parameter discovery
set -euo pipefail
URL="${1:?Usage: $0 <url>}"
DIR="fuzz_$(echo "$URL" | sed 's|https\?://||;s|/|_|g')_$(date +%Y%m%d)"
mkdir -p "$DIR"
echo "=== Fuzzing $URL ==="
# Directory/File discovery
echo "[dir] Directory brute force..."
ffuf -u "$URL/FUZZ" -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
-o "$DIR/directories.json" -of json -ac -t 50 -sf
# Parameter discovery
echo "[params] Parameter fuzzing..."
ffuf -u "$URL?FUZZ=test" -w /usr/share/wordlists/params.txt \
-o "$DIR/params.json" -of json -ac -t 50 -sf
# Value fuzzing (for known params)
echo "[values] Value fuzzing..."
ffuf -u "$URL?id=FUZZ" -w /usr/share/wordlists/ids.txt \
-o "$DIR/values.json" -of json -ac -t 50 -sf -mc all -fc 404
# POST fuzzing
echo "[post] POST data fuzzing..."
ffuf -u "$URL" -X POST -d "name=FUZZ" -w /usr/share/wordlists/xss-payloads.txt \
-H "Content-Type: application/x-www-form-urlencoded" \
-o "$DIR/post.json" -of json -ac -t 30 -sf
# Header fuzzing
echo "[headers] Header injection..."
ffuf -u "$URL" -H "X-Forwarded-For: FUZZ" -w /usr/share/wordlists/ip.txt \
-o "$DIR/headers.json" -of json -ac -t 50
# Summarize
echo "=== Results ==="
for f in "$DIR"/*.json; do
total=$(jq '.results | length' "$f" 2>/dev/null || echo "0")
echo " $(basename "$f"): $total results"
done
Report Generator
#!/bin/bash
# report.sh — generate HTML pentest report from structured findings
set -euo pipefail
INPUT="${1:?Usage: $0 <findings.json> > report.html}"
read -d '' REPORT_TEMPLATE << 'HTML' || true
<html>
<head><title>Pentest Report</title>
<style>
body { font-family: monospace; margin: 40px; }
.high { color: red; font-weight: bold; }
.medium { color: orange; }
.low { color: blue; }
table { border-collapse: collapse; width: 100%; }
td, th { border: 1px solid #ccc; padding: 8px; text-align: left; }
</style></head>
<body>
<h1>Pentest Report</h1>
<p>Generated: $(date)</p>
<table>
<tr><th>Severity</th><th>Type</th><th>URL</th><th>Description</th></tr>
HTML
echo "$REPORT_TEMPLATE"
jq -r '
.results[]
| select(.template_id != null)
| "<tr class=\"\(.info.severity)\">
<td>\(.info.severity)</td>
<td>\(.info.name)</td>
<td>\(.host // .matched)</td>
<td>\(.info.description // "No description")</td>
</tr>"' "$INPUT" 2>/dev/null || echo "<tr><td colspan=4>No vulnerabilities found</td></tr>"
echo "</table></body></html>"
Automation Principles
1. Always use structured output (JSON) — never parse terminal colors
2. Implement retry with backoff for external tools
3. Rate limit requests (respect scope/legal boundaries)
4. Use checkpoint files to resume interrupted runs
5. Log all commands for reproducibility
6. Parameterize everything — no hardcoded paths/IPs
7. Validate input: domain format, target scope, wordlist existence
8. Fail fast on configuration errors, fail gracefully on runtime
9. Never auto-exploit — only report potential vulnerabilities
10. Respect robots.txt and scope exclusions
Tools Reference
| Tool | Purpose | Best for | |------|---------|----------| | ffuf | Web fuzzing | Directory, parameter, value discovery | | httpx | HTTP probing | Live host detection, technology fingerprint | | nuclei | Template scanning | Vulnerability detection | | subfinder | Subdomain enum | Passive subdomain discovery | | gau/waybackurls | URL discovery | Historical URL collection | | katana | Web crawling | Active URL discovery | | naabu | Port scanning | Fast port discovery | | gowitness | Screenshots | Visual recon | | jq | JSON processing | Report generation, data extraction | | ripgrep (rg) | Text search | Fast pattern matching in results | | interactsh | OOB detection | Blind SSRF/XXE detection |