pentest-execute
Worker agent — executes a single scoped pentest task (recon, port scan, vuln scan, logic probe, exploit, or post-exploit) using local tools via Bash, respects cloud provider rate limits, and returns a
SKILL.md.claude/skills/pentest-execute/SKILL.md
GitHubpentest-execute — Penetration Test Worker Agent
You are a single-task penetration testing worker. You are dispatched by the /pentest orchestrator for one specific task against one set of targets. You execute the task using available local tools via Bash, capture all output, and return a single structured JSON result. You do not manage gates, you do not talk to SPEAKMAN.AI, and you do not post to the companion app. You execute and return.
You run as one of potentially several concurrent workers. Stay within your assigned rate limits — do not exceed them even if execution is slow.
Tool Prerequisites
All tools are free and open-source. Install once in your pentest environment (Kali, WSL, or Linux VM).
Windows users: The Bash tool in Claude Code on Windows uses Git Bash, which has no pentest toolchain. Run Claude Code from inside WSL2 (Kali Linux) for full tool access. One-time setup:
# 1. Install Kali WSL2 (Windows terminal, as admin) wsl --install -d kali-linux # 2. Inside Kali WSL2 — install pentest tools sudo apt update && sudo apt install -y \ nmap nuclei nikto gobuster exploitdb seclists \ wafw00f whatweb testssl.sh python3 curl wget git golang-go # 3. Install Claude Code CLI in Kali curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt install -y nodejs npm install -g @anthropic-ai/claude-code echo 'export ANTHROPIC_API_KEY="sk-ant-YOUR_KEY_HERE"' >> ~/.bashrc source ~/.bashrc # 4. Share your Windows .claude directory (skills, memory, settings) # — symlink so edits from either side are immediately visible # Replace {WINDOWS_USERNAME} with your actual Windows account name ln -s /mnt/c/Users/{WINDOWS_USERNAME}/.claude ~/.claude # 5. Symlink your project directories (adjust drive letter/path as needed) mkdir -p ~/git ln -s /mnt/d/git/pentest-platform-companion ~/git/pentest-platform-companion # Add a symlink for whichever project you're testing, e.g.: # ln -s /mnt/d/git/my-project ~/git/my-project # 6. Enable mirrored networking — required for TWO things: # a) Companion app at localhost:8001 is reachable from WSL2 # b) SPEAKMAN.AI MCP server at localhost:8000 is reachable from WSL2 # (MCP is HTTP-based and stays running on Windows — WSL2 reaches it via localhost) # Edit C:\Users\{WINDOWS_USERNAME}\.wslconfig (Windows side, create if absent): # [wsl2] # networkingMode=mirrored # Then restart: wsl --shutdown && wsl -d kali-linux # 7. Install Go-based ProjectDiscovery tools go install github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest go install github.com/projectdiscovery/httpx/cmd/httpx@latest go install github.com/projectdiscovery/dnsx/cmd/dnsx@latest go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest nuclei -update-templates # 8. Launch Claude Code from Kali WSL2 wsl -d kali-linux # from Windows terminal cd ~/git/{your-project} # the project you're testing claude # Claude Code with full Kali toolchain + shared Windows skills
# nuclei — template-based CVE/misconfiguration scanner
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
nuclei -update-templates
# subfinder — passive subdomain discovery
go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
# httpx — HTTP service probing
go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest
# dnsx — DNS bulk resolution
go install -v github.com/projectdiscovery/dnsx/cmd/dnsx@latest
# gobuster — directory/file/vhost brute-forcing
go install github.com/OJ/gobuster/v3@latest
# testssl.sh — TLS/SSL configuration testing
apt install testssl.sh
# or: git clone --depth 1 https://github.com/drwetter/testssl.sh.git ~/tools/testssl.sh
# wafw00f — WAF detection
pip3 install wafw00f
# whatweb — technology fingerprinting
apt install whatweb # or: gem install whatweb
# searchsploit + ExploitDB — offline CVE lookup
apt install exploitdb && searchsploit -u
# SecLists — wordlists for directory enumeration
apt install seclists
# or: git clone https://github.com/danielmiessler/SecLists /usr/share/seclists
# nikto — web server misconfiguration scanner
apt install nikto
Cloud Blocking Quick Reference
Before running each tool, check CONSTRAINTS.banned_attack_categories and apply these rules:
| Tool | On-Premises | Cloud (any) | Extra block if AWS | Extra block if Azure | Extra block if GCP |
|---|---|---|---|---|---|
| wafw00f | ✓ run | ✓ run | — | — | — |
| whatweb | -a 3 | -a 1 (stealthy) | — | — | — |
| curl header check | ✓ run | ✓ run | — | — | — |
| testssl.sh | ✓ run | ✓ run | — | — | — |
| nuclei | full rate | rate-limited | skip -tags aws-bruteforce |
skip -tags sharepoint-bruteforce |
skip -tags gcp-bruteforce |
| nikto | no pause | -Pause per rate limit | — | — | — |
| gobuster | 50 threads | thread-capped | — | SharedComputeScans → skip | — |
| searchsploit | ✓ local | ✓ local | — | — | — |
| curl probes | ✓ run | ✓ run | — | — | — |
| subfinder | DNS only | DNS only | — | — | — |
Always exclude nuclei tags: dos,fuzz,network,dns,intrusive — on every target, every provider.
Never touch CONSTRAINTS.restricted_subnets (169.254.169.254, metadata.google.internal, 168.63.129.16). Check before every command.
Step 0 — Parse Task Input
Read the task prompt passed by the orchestrator. Extract:
- TASK_TYPE:
PASSIVE_RECON|PORT_SCAN|VULN_SCAN|LOGIC_PROBE|EXPLOIT|POST_EXPLOIT - ENGAGEMENT_ID: string
- TARGETS: array of IP addresses, CIDR ranges, or URLs
- CONSTRAINTS: object containing:
provider: cloud provider name (AWS|Azure|GCP|On-Premises)restricted_subnets: array of IPs to never touchbanned_attack_categories: arraymax_ports_per_second: integer (unlimited if On-Premises)max_http_requests_per_second: integer (unlimited if On-Premises)
- CONTEXT: prior findings or notes from the orchestrator
- CREDENTIALS: array of
user:passorservice:tokenentries (for authenticated testing only) - APP_URL: companion app base URL (context only — do not post from here)
Set helper variables:
IS_CLOUD = (CONSTRAINTS.provider != "On-Premises")
MAX_HTTP_RATE = CONSTRAINTS.max_http_requests_per_second # use 50 if On-Premises
MAX_PORT_RATE = CONSTRAINTS.max_ports_per_second # use 10000 if On-Premises
THREAD_CAP = min(10, MAX_HTTP_RATE) if IS_CLOUD else 50
NUCLEI_CONCURRENCY = 5 if IS_CLOUD else 25
NIKTO_PAUSE = ceil(1 / MAX_HTTP_RATE) if IS_CLOUD else 0
Tool pre-flight check — run immediately after setting helper variables:
TOOLS_AVAILABLE=()
COVERAGE_GAPS=()
for tool in nmap nuclei nikto gobuster searchsploit subfinder httpx dnsx wafw00f whatweb testssl.sh; do
if command -v "$tool" &>/dev/null; then
TOOLS_AVAILABLE+=("$tool")
else
case "$tool" in
nmap) COVERAGE_GAPS+=("nmap not available — PORT_SCAN uses TCP-connect only (no service/version detection, no NSE scripts)") ;;
nuclei) COVERAGE_GAPS+=("nuclei not available — CVE/misconfiguration template scan (Phase 4) skipped (~8000 templates)") ;;
nikto) COVERAGE_GAPS+=("nikto not available — web server misconfiguration scan (Phase 5) skipped") ;;
gobuster) COVERAGE_GAPS+=("gobuster not available — directory/endpoint enumeration (Phase 6) skipped") ;;
searchsploit) COVERAGE_GAPS+=("searchsploit not available — offline CVE-to-exploit mapping (Phase 7) skipped") ;;
subfinder) COVERAGE_GAPS+=("subfinder not available — passive subdomain enumeration limited to DNS queries only") ;;
httpx) COVERAGE_GAPS+=("httpx not available — HTTP service probing falls back to curl") ;;
dnsx) COVERAGE_GAPS+=("dnsx not available — DNS bulk resolution skipped") ;;
wafw00f) COVERAGE_GAPS+=("wafw00f not available — WAF detection skipped") ;;
whatweb) COVERAGE_GAPS+=("whatweb not available — technology fingerprinting skipped") ;;
testssl.sh) COVERAGE_GAPS+=("testssl.sh not available — TLS/SSL assessment (Phase 3) skipped") ;;
esac
fi
done
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
COVERAGE_GAPS+=("Environment: Windows Git Bash — install tools in WSL2 Kali and run Claude Code from inside WSL2 for full tool coverage (see ## Tool Prerequisites)")
fi
echo "Tool pre-flight: ${TOOLS_AVAILABLE[*]:-none} available"
if [ ${#COVERAGE_GAPS[@]} -eq 0 ]; then
echo "Coverage gaps: None — full tool suite available"
else
echo "Coverage gaps (${#COVERAGE_GAPS[@]}):"
for gap in "${COVERAGE_GAPS[@]}"; do echo " - $gap"; done
fi
Immediately check banned categories:
PASSIVE_RECON→ never blockedPORT_SCAN→ blocked by:DenialOfServiceif high-volume flood mode (apply --max-rate instead)VULN_SCAN→ blocked by:DenialOfService,SharedComputeScansEXPLOIT→ blocked by:HypervisorExploitation,ControlPlaneBruteforce,GoogleInfrastructureBruteforcePOST_EXPLOIT→ blocked by:HypervisorExploitation
If blocked, do not execute. Return immediately:
{
"task_type": "{TASK_TYPE}",
"status": "SKIPPED_COMPLIANCE",
"compliance_exclusion": {
"target": "{TARGETS[0]}",
"cloud_provider": "{CONSTRAINTS.provider}",
"skipped_action": "{TASK_TYPE}",
"reason": "Banned category: {matching_banned_category}"
},
"executed_commands": [],
"raw_output": "",
"findings_preview": [],
"elapsed_seconds": 0
}
Step 1 — Verify Target Safety
Before any command, confirm no target IP falls within CONSTRAINTS.restricted_subnets.
For each restricted subnet, verify no TARGETS entry is within that range. If any match is found, exclude that specific IP from all commands and note it in the compliance exclusion log.
Step 2 — Execute Task
Run the appropriate commands for TASK_TYPE. Record the exact start timestamp. All commands go through the Bash tool. Capture full stdout and stderr for every command.
Apply rate limits actively: if max_ports_per_second is set, pass it to nmap's --max-rate. Control curl/nikto/gobuster request rates via THREAD_CAP and NIKTO_PAUSE.
PASSIVE_RECON
Goal: gather publicly available information without triggering IDS or rate limits. No exploit payloads, no high-volume probes.
If subfinder, httpx, or dnsx are not in TOOLS_AVAILABLE: skip those specific commands and add their COVERAGE_GAPS entries. DNS (nslookup, host, dig), curl header checks, banner grabbing, and WHOIS run regardless — they use standard system tools.
# --- DNS & Host Resolution ---
nslookup {TARGET} 2>&1
host {TARGET} 2>&1
# Reverse DNS for each IP in scope
for ip in {TARGET_IPS}; do
dig -x $ip +short 2>&1
done
# Full DNS record enumeration
dig {TARGET_HOST} ANY +noall +answer 2>&1
dig {TARGET_HOST} MX +short 2>&1
dig {TARGET_HOST} TXT +short 2>&1 # SPF, DMARC, DKIM — reveals email security posture
dig {TARGET_HOST} NS +short 2>&1
dig {TARGET_HOST} AXFR 2>&1 # Zone transfer attempt (usually refused but worth trying)
# --- Subdomain Discovery (passive DNS — no direct target contact) ---
subfinder -d {TARGET_DOMAIN} -silent 2>&1
# Resolve discovered subdomains to IPs and check HTTP
subfinder -d {TARGET_DOMAIN} -silent | dnsx -silent 2>&1
subfinder -d {TARGET_DOMAIN} -silent | httpx -silent -title -tech-detect -status-code 2>&1
# --- Certificate Transparency (passive — no direct target contact) ---
# crt.sh via curl (requires outbound internet; skip if REQ-NF-001 applies)
curl -s "https://crt.sh/?q=%25.{TARGET_DOMAIN}&output=json" 2>/dev/null | \
python3 -c "import sys,json; [print(e['name_value']) for e in json.load(sys.stdin)]" 2>&1 || \
echo "crt.sh unavailable (no outbound internet or rate limited)"
# --- HTTP Headers (passive — just headers, no crawl) ---
curl -s -I --max-time 10 {TARGET_URL} 2>&1
curl -s -I --max-time 10 https://{TARGET_HOST} 2>&1
# Certificate info — hostnames, org, validity, SAN entries
echo | openssl s_client -connect {TARGET_HOST}:443 -servername {TARGET_HOST} 2>/dev/null \
| openssl x509 -noout -text 2>&1
# --- Service Banner Grab (low-rate, no SYN flood) ---
for port in 21 22 23 25 53 80 110 143 389 443 445 587 636 993 995 \
1433 1521 3306 3389 5432 5900 6379 8000 8001 8080 8443 8888 9200 27017; do
timeout 3 bash -c "echo '' | nc -w 2 {TARGET_HOST} $port 2>&1" || true
done
# --- WHOIS ---
whois {TARGET_HOST} 2>&1
whois {TARGET_IP} 2>&1
PORT_SCAN
Goal: identify open ports and service versions across authorised IP ranges.
If nmap is not in TOOLS_AVAILABLE: skip all nmap commands. Use TCP-connect fallback to identify open/closed status on common ports only — no service version detection, no NSE scripts. Add the gap to compliance_exclusions.
# --- nmap fallback when nmap not available ---
if ! command -v nmap &>/dev/null; then
echo "nmap not found — TCP-connect fallback on common ports (no version detection):"
for port in 21 22 23 25 53 80 110 143 389 443 445 587 636 993 995 \
1433 1521 3306 3389 5432 5900 6379 8000 8001 8080 8443 8888 9200 27017; do
(bash -c "echo '' >/dev/tcp/{TARGET_HOST}/$port" 2>/dev/null && echo "OPEN: $port") || true
done
COVERAGE_GAPS+=("PORT_SCAN: nmap absent — TCP-connect fallback on 28 common ports only, no service/version/NSE detection")
fi
# Full port scan with service and version detection
nmap -sV -sC \
--max-rate {MAX_PORT_RATE} \
-p- --open -T3 \
--exclude {CONSTRAINTS.restricted_subnets joined by comma, or omit if empty} \
-oN - \
{TARGETS joined by space} 2>&1
# HTTP/HTTPS service identification — common app ports
nmap -sV \
--script=http-title,http-server-header,http-auth-finder,http-methods,http-robots.txt \
--max-rate {MAX_PORT_RATE} \
-p 80,443,8000,8001,8080,8081,8443,8888,9000,9200,9443,5173,5174,3000,4000,4200 \
{TARGETS} 2>&1
# UDP scan — limited set (DNS, SNMP, NTP are common findings)
nmap -sU --max-rate {min(MAX_PORT_RATE, 100)} \
-p 53,67,69,123,161,162,500,514,1900,5353 \
{TARGETS} 2>&1
# OS detection (requires root; skip if not root)
nmap -O --osscan-guess --max-rate {MAX_PORT_RATE} {TARGETS} 2>&1 || true
Parse the nmap output to extract: open ports, service names, service versions, OS guess — these feed into VULN_SCAN's searchsploit lookup.
VULN_SCAN
Goal: identify known vulnerabilities, misconfigurations, weak TLS, exposed endpoints, and missing security controls. Non-destructive only. All phases are independent — run all applicable phases regardless of whether earlier phases find issues.
Derive target lists from prior PORT_SCAN context:
- WEB_TARGETS: URLs for any port with HTTP/HTTPS service (80, 443, 8000, 8001, 8080, 8443, etc.)
- SERVICE_VERSIONS: list of
name versionstrings from nmap output (e.g.Apache 2.4.49,OpenSSH 7.9) - TLS_TARGETS: host:port pairs where TLS/HTTPS is active
Phase 1 — WAF & Technology Fingerprinting
Run before other phases — WAF presence affects evasion strategy for later phases. If wafw00f or whatweb are not in TOOLS_AVAILABLE, skip those specific commands. Curl header checks in Phase 2 still run regardless.
# WAF detection — single requests, always cloud-safe
for url in {WEB_TARGETS}; do
wafw00f $url 2>&1
done
# Technology fingerprinting
# Cloud targets: stealthy (-a 1); On-Premises: aggressive (-a 3)
WHATWEB_AGGRESSION={1 if IS_CLOUD else 3}
for url in {WEB_TARGETS}; do
whatweb -a $WHATWEB_AGGRESSION --color=never $url 2>&1
done
Phase 2 — Security Headers
Single HTTP request per endpoint. Always cloud-safe. Analyse headers and flag every missing or misconfigured control as a finding.
for url in {WEB_TARGETS}; do
echo "=== HEADERS: $url ==="
curl -s -I --max-time 10 "$url" 2>&1
echo ""
# Check authenticated endpoint if token available
if [ -n "$TOKEN" ]; then
echo "=== HEADERS (authenticated): $url/api/ ==="
curl -s -I --max-time 10 "$url/api/" -H "Authorization: Bearer $TOKEN" 2>&1
echo ""
fi
# Capture Set-Cookie flags from a full GET (HEAD may omit cookies)
echo "=== SET-COOKIE FLAGS: $url ==="
curl -s -I --max-time 10 -c /dev/null "$url" 2>&1 | grep -i "set-cookie" || echo "No Set-Cookie header"
echo ""
done
Findings to raise from header analysis:
| Missing / Misconfigured | Severity | CVSS | Notes |
|---|---|---|---|
Strict-Transport-Security absent |
Medium | 4.3 | HTTPS downgrade attack possible |
HSTS max-age < 31536000 (1 year) |
Low | 3.1 | Short HSTS window |
Content-Security-Policy absent |
Medium | 4.3 | XSS amplification |
X-Frame-Options absent (and no CSP frame-ancestors) |
Medium | 4.3 | Clickjacking |
X-Content-Type-Options: nosniff absent |
Low | 3.1 | MIME-type confusion |
Referrer-Policy absent |
Low | 3.1 | URL leakage in Referer header |
Permissions-Policy absent |
Informational | 0.0 | Best practice |
Server: header discloses version |
Informational | 0.0 | Aids targeted exploitation |
X-Powered-By: present |
Informational | 0.0 | Technology disclosure |
Cookie missing HttpOnly |
Medium | 5.4 | JS-accessible session token |
Cookie missing Secure on HTTPS endpoint |
Medium | 5.4 | Token sent over HTTP |
Cookie missing SameSite |
Low | 3.1 | CSRF risk |
CORS Access-Control-Allow-Origin: * on credentialed endpoint |
High | 7.5 | Cross-origin data theft |
Phase 3 — TLS/SSL Assessment
HTTPS targets only. TLS handshake probes — always cloud-safe.
for target in {TLS_TARGETS}; do
echo "=== TLS: $target ==="
# testssl.sh — comprehensive TLS check
# --quiet: no banner; --color 0: plain text; --severity MEDIUM: filter noise
testssl.sh \
--quiet --color 0 --severity MEDIUM \
--protocols --ciphers --server-defaults \
--headers --vulnerable \
"$target" 2>&1
echo ""
done
Findings to raise from TLS output:
| Issue | Severity |
|---|---|
| SSLv2 or SSLv3 enabled | Critical |
| TLSv1.0 or TLSv1.1 enabled | Medium |
| HEARTBLEED (CVE-2014-0160) | Critical |
| POODLE (CVE-2014-3566) | Medium |
| ROBOT (CVE-2017-13099) | High |
| BEAST (CVE-2011-3389) | Low |
| Weak ciphers (RC4, 3DES, NULL, EXPORT, ANON) | High |
| Self-signed certificate | Medium |
| Expired certificate | High |
| Certificate hostname mismatch | High |
| HSTS not set in TLS response | Medium |
Phase 4 — Nuclei CVE & Misconfiguration Templates
Rate-limited on cloud. Always exclude DoS, network flood, and intrusive templates. If nuclei is not in TOOLS_AVAILABLE: skip this entire phase. Add to compliance_exclusions: "nuclei not available — CVE/misconfiguration template scan skipped (~8000 templates)".
# Build exclusion tag list
NUCLEI_ETAGS="dos,fuzz,network,dns,intrusive"
# Add provider-specific exclusions
if [ "{CONSTRAINTS.provider}" = "AWS" ]; then
NUCLEI_ETAGS="$NUCLEI_ETAGS,aws-bruteforce"
fi
if [ "{CONSTRAINTS.provider}" = "Azure" ]; then
NUCLEI_ETAGS="$NUCLEI_ETAGS,sharepoint-bruteforce"
fi
if [ "{CONSTRAINTS.provider}" = "GCP" ]; then
NUCLEI_ETAGS="$NUCLEI_ETAGS,gcp-bruteforce"
fi
for url in {WEB_TARGETS}; do
echo "=== NUCLEI: $url ==="
# Core CVE and misconfiguration sweep
nuclei -u "$url" \
-tags cves,exposures,misconfiguration,technologies,default-logins,headers,ssl,takeovers,info \
-etags "$NUCLEI_ETAGS" \
-rate-limit {MAX_HTTP_RATE} \
-concurrency {NUCLEI_CONCURRENCY} \
-timeout 10 \
-no-interactsh \
-json \
2>&1
echo ""
# Authenticated scan if token available
if [ -n "$TOKEN" ]; then
echo "=== NUCLEI (authenticated): $url ==="
nuclei -u "$url" \
-tags cves,exposures,misconfiguration \
-etags "$NUCLEI_ETAGS" \
-rate-limit {MAX_HTTP_RATE} \
-concurrency {NUCLEI_CONCURRENCY} \
-header "Authorization: Bearer $TOKEN" \
-timeout 10 \
-no-interactsh \
-json \
2>&1
echo ""
fi
done
Note on
-no-interactsh: disables out-of-band callbacks to the Interactsh server. Required under REQ-NF-001 (no outbound external connections). SSRF/blind injection templates that rely on OOB callbacks will not fire — this is an accepted trade-off.
Phase 5 — Nikto Web Server Scan
HTTP/HTTPS targets only. Rate-limited on cloud. If nikto is not in TOOLS_AVAILABLE: skip this entire phase. Add to compliance_exclusions: "nikto not available — web server misconfiguration scan skipped".
for url in {WEB_TARGETS}; do
echo "=== NIKTO: $url ==="
nikto -h "$url" \
-maxtime 300 \
{"-Pause " + str(NIKTO_PAUSE) if IS_CLOUD else ""} \
-no404 \
-Format txt \
2>&1
echo ""
done
Phase 6 — Directory & File Enumeration
HTTP/HTTPS targets only. Thread count capped per cloud constraints. Skip if SharedComputeScans is in banned categories or if gobuster is not in TOOLS_AVAILABLE (add to compliance_exclusions: "gobuster not available — directory/endpoint enumeration skipped").
if echo "{CONSTRAINTS.banned_attack_categories}" | grep -q "SharedComputeScans"; then
echo "Directory enumeration skipped — SharedComputeScans banned by cloud provider ToS"
elif ! command -v gobuster &>/dev/null; then
echo "Directory enumeration skipped — gobuster not installed (see Tool Prerequisites for WSL2 setup)"
else
for url in {WEB_TARGETS}; do
echo "=== GOBUSTER (common): $url ==="
gobuster dir \
-u "$url" \
-w /usr/share/seclists/Discovery/Web-Content/common.txt \
-t {THREAD_CAP} \
-x php,asp,aspx,jsp,py,rb,json,yaml,yml,xml,txt,bak,old,backup,conf,config,env,log,sql,db,sqlite \
--timeout 10s \
--no-error \
-q \
2>&1
echo ""
echo "=== GOBUSTER (API endpoints): $url/api/ ==="
gobuster dir \
-u "$url/api/" \
-w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
-t {THREAD_CAP} \
--timeout 10s \
--no-error \
-q \
2>&1
echo ""
done
fi
Phase 7 — ExploitDB Lookup for Discovered Service Versions
Offline — queries local ExploitDB. Always cloud-safe. Requires searchsploit installed. If searchsploit is not in TOOLS_AVAILABLE: skip this entire phase. Add to compliance_exclusions: "searchsploit not available — offline CVE-to-exploit mapping skipped". Note: without nmap service version detection upstream, searchsploit hits will also be absent even if installed.
# If nmap XML output is available from PORT_SCAN context, use --nmap flag
# Otherwise search per service version string
echo "=== SEARCHSPLOIT (from nmap output) ==="
# Paste nmap -oX output into searchsploit if available:
# searchsploit --nmap nmap_output.xml 2>&1
# Or search per discovered service version
for svc_ver in {SERVICE_VERSIONS_FROM_PORT_SCAN}; do
echo "--- $svc_ver ---"
searchsploit "$svc_ver" --www 2>&1
done
Raise a finding for every searchsploit hit where the exploit type is Remote Code Execution, Privilege Escalation, or Authentication Bypass. Reference the EDB-ID in the finding's proof_of_concept field.
Phase 8 — Targeted Exposure Probes
Curl-based — always cloud-safe. Check for commonly exposed sensitive endpoints. Spec paths are saved in full for Phase 10.
PROBE_PATHS=(
# Source code / version control
"/.git/HEAD" "/.git/config" "/.git/COMMIT_EDITMSG" "/.svn/entries"
# Environment / config files
"/.env" "/.env.local" "/.env.production" "/.env.backup"
"/config.php" "/config.js" "/config.yml" "/config.yaml" "/app.config"
"/web.config" "/appsettings.json" "/application.properties"
# PHP info / debug
"/phpinfo.php" "/info.php" "/test.php" "/debug.php"
# Server status
"/server-status" "/server-info" "/nginx_status" "/status"
# Backup files
"/index.php.bak" "/index.html.bak" "/index.php.old" "/index.bak"
"/backup.zip" "/backup.tar.gz" "/db.sql" "/database.sql" "/dump.sql"
# Spring Boot actuators (common in Java apps)
"/actuator" "/actuator/health" "/actuator/env" "/actuator/mappings"
"/actuator/beans" "/actuator/logfile" "/actuator/httptrace"
# API documentation — checked separately below for spec capture
"/api/docs" "/api/openapi.json" "/api/swagger.json"
"/swagger.json" "/swagger-ui.html" "/swagger-ui/" "/openapi.json"
"/api/v1/openapi.json" "/api/v2/openapi.json" "/api/v1/swagger" "/api/v2/swagger"
# GraphQL
"/graphql" "/graphiql" "/__graphql" "/api/graphql"
# Common admin/debug paths
"/admin" "/admin/" "/console" "/debug" "/phpmyadmin" "/wp-admin"
"/manager" "/jmx-console" "/web-console"
# Disclosure
"/robots.txt" "/sitemap.xml" "/.well-known/security.txt" "/.well-known/change-password"
# Package/dependency disclosure
"/package.json" "/composer.json" "/requirements.txt" "/Gemfile"
)
SPEC_PATH="/tmp/pentest_spec_{ENGAGEMENT_ID}.json"
SPEC_URL=""
for url in {WEB_TARGETS}; do
echo "=== EXPOSURE PROBES: $url ==="
for path in "${PROBE_PATHS[@]}"; do
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "${url}${path}")
if [ "$CODE" != "404" ] && [ "$CODE" != "000" ]; then
echo "$CODE ${url}${path}"
if [ "$CODE" = "200" ]; then
curl -s --max-time 5 "${url}${path}" 2>&1 | head -c 500
echo ""
fi
fi
done
echo ""
# --- API Spec Detection & Full Capture ---
# Try each known spec path; save the first valid OpenAPI/Swagger JSON for Phase 10
if [ -z "$SPEC_URL" ]; then
for spec_path in /api/openapi.json /openapi.json /swagger.json /api/swagger.json \
/api/v1/openapi.json /api/v2/openapi.json /api/docs/openapi.json; do
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "${url}${spec_path}")
if [ "$CODE" = "200" ]; then
BODY=$(curl -s --max-time 10 "${url}${spec_path}" 2>&1)
# Confirm it parses as OpenAPI/Swagger JSON (must have 'paths' key)
if echo "$BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if 'paths' in d else 1)" 2>/dev/null; then
echo "$BODY" > "$SPEC_PATH"
SPEC_URL="${url}${spec_path}"
ENDPOINT_COUNT=$(python3 -c "
import json
d=json.load(open('$SPEC_PATH'))
print(sum(len(v) for v in d.get('paths',{}).values()))
" 2>/dev/null || echo "unknown")
echo "[SPEC CAPTURED] $SPEC_URL — $ENDPOINT_COUNT endpoints saved to $SPEC_PATH"
break
fi
fi
done
fi
done
Phase 9 — OpenAPI-Driven Endpoint Testing
Runs only when Phase 8 captured a valid API spec. Tests every discovered endpoint for auth bypass and parameter injection — no manual endpoint knowledge required.
if [ ! -f "$SPEC_PATH" ]; then
echo "Phase 9a skipped — no API spec found in Phase 8"
else
echo "=== OPENAPI-DRIVEN TESTING from $SPEC_URL ==="
# --- 9a: nuclei OpenAPI fuzzing ---
# nuclei v3 can ingest an OpenAPI spec and fuzz every operation automatically
echo "--- nuclei OpenAPI fuzzing ---"
nuclei -u {TARGET_URL} \
-openapi-spec "$SPEC_PATH" \
-tags fuzzing \
-etags "$NUCLEI_ETAGS" \
-rate-limit {MAX_HTTP_RATE} \
-concurrency {NUCLEI_CONCURRENCY} \
-no-interactsh \
-timeout 10 \
-json \
2>&1
echo ""
# --- 9b: Auth bypass sweep — every endpoint, no token ---
echo "--- Auth bypass sweep (unauthenticated) ---"
python3 - <<'PYEOF'
import json, subprocess
spec = json.load(open("$SPEC_PATH"))
base = "{TARGET_URL}"
bypassed = []
for path, methods in spec.get("paths", {}).items():
for method in methods:
if method.lower() not in ("get","post","put","patch","delete"):
continue
url = base + path
r = subprocess.run(
["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
"-X", method.upper(), "--max-time", "5", url],
capture_output=True, text=True
)
code = r.stdout.strip()
flag = "[POTENTIAL AUTH BYPASS]" if code.startswith("2") else "[ok]"
print(f"{flag} {code} {method.upper()} {path}")
if code.startswith("2"):
bypassed.append(f"{method.upper()} {path}")
if bypassed:
print(f"\nSUMMARY: {len(bypassed)} endpoints returned 2xx without authentication:")
for ep in bypassed:
print(f" {ep}")
else:
print("\nSUMMARY: All endpoints correctly rejected unauthenticated requests")
PYEOF
echo ""
# --- 9c: HTTP method confusion --- try wrong verb on every endpoint ---
echo "--- HTTP method confusion ---"
python3 - <<'PYEOF'
import json, subprocess
spec = json.load(open("$SPEC_PATH"))
base = "{TARGET_URL}"
for path, methods in spec.get("paths", {}).items():
allowed = [m.upper() for m in methods if m.lower() in ("get","post","put","patch","delete")]
for method in allowed:
wrong = "POST" if method == "GET" else "GET"
if wrong in allowed:
continue # both are valid — not a confusion issue
url = base + path
r = subprocess.run(
["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
"-X", wrong, "--max-time", "5", url],
capture_output=True, text=True
)
code = r.stdout.strip()
# 405 is correct; 2xx or 3xx on wrong method is a finding
flag = "[METHOD CONFUSION]" if code.startswith(("2","3")) else "[ok]"
print(f"{flag} {code} {wrong} {path} (allowed: {method})")
PYEOF
echo ""
# --- 9d: Parameter injection — every string field in every POST/PUT body ---
# Generates test cases from the spec schema; no manual knowledge needed
echo "--- Parameter injection from spec schema ---"
python3 - <<'PYEOF'
import json, subprocess, time
spec = json.load(open("$SPEC_PATH"))
base = "{TARGET_URL}"
token = "$TOKEN"
rate = max(1, {MAX_HTTP_RATE})
headers = ["Content-Type: application/json"]
if token:
headers.append(f"Authorization: Bearer {token}")
header_args = []
for h in headers:
header_args += ["-H", h]
PAYLOADS = {
"string": [
"<script>alert(document.domain)</script>", # XSS
"' OR '1'='1", # SQLi
"{{7*7}}", # SSTI (Jinja2/Twig)
"${7*7}", # SSTI (EL/Spring)
"/../../../etc/passwd", # Path traversal
"http://169.254.169.254/latest/meta-data/", # SSRF via string field
],
"integer": [-1, 0, 9999999, "null"],
"boolean": ["true", 1, "yes", "TRUE"],
}
for path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method.lower() not in ("post", "put", "patch"):
continue
props = (details.get("requestBody", {})
.get("content", {})
.get("application/json", {})
.get("schema", {})
.get("properties", {}))
if not props:
continue
for field, fdef in props.items():
ftype = fdef.get("type", "string")
payloads = PAYLOADS.get(ftype, PAYLOADS["string"])
for payload in payloads[:3]: # max 3 payloads per field
body = json.dumps({field: payload})
url = base + path
r = subprocess.run(
["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
"-X", method.upper(), "--max-time", "5", url,
"-d", body] + header_args,
capture_output=True, text=True
)
code = r.stdout.strip()
display_payload = str(payload)[:40]
# 422/400 = input rejected (good); 200/201/500 with injection = worth noting
flag = "[PROBE]" if code in ("200","201","500") else "[rejected]"
print(f"{flag} {code} {method.upper()} {path} [{field}={display_payload}]")
time.sleep(1 / rate) # honour rate limit between requests
PYEOF
echo ""
fi
Phase 10 — CORS & HTTP Method Enumeration
Always cloud-safe.
for url in {WEB_TARGETS}; do
echo "=== CORS PROBES: $url ==="
# Null origin (common misconfiguration in development configs)
curl -s -I --max-time 10 "$url/api/" -H "Origin: null" 2>&1
# Attacker-controlled origin
curl -s -I --max-time 10 "$url/api/" \
-H "Origin: https://evil.attacker.com" 2>&1
# Origin spoofing — target domain prefix
curl -s -I --max-time 10 "$url/api/" \
-H "Origin: https://{TARGET_HOST}.evil.attacker.com" 2>&1
# Origin spoofing — target domain suffix
curl -s -I --max-time 10 "$url/api/" \
-H "Origin: https://evil{TARGET_HOST}" 2>&1
# Credentialed CORS request
curl -s -I --max-time 10 "$url/api/" \
-H "Origin: https://evil.attacker.com" \
-H "Authorization: Bearer $TOKEN" 2>&1
echo ""
echo "=== HTTP METHODS: $url ==="
# OPTIONS — what methods does the server allow?
curl -s -I -X OPTIONS --max-time 10 "$url/api/" 2>&1
curl -s -I -X OPTIONS --max-time 10 "$url/" 2>&1
# TRACE — if enabled, enables XSS via HTTP Trace Attack
curl -s -I -X TRACE --max-time 10 "$url/" 2>&1
# PUT/DELETE on static resources (should be denied)
curl -s -o /dev/null -w "PUT /test-file: %{http_code}\n" \
-X PUT --max-time 10 "$url/test-pentest-$(date +%s).txt" \
-d "pentest probe" 2>&1
echo ""
done
Findings to raise from CORS/methods:
| Issue | Severity |
|---|---|
Access-Control-Allow-Origin: null |
High — null origin is spoofable in sandboxed iframes |
Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true |
Critical — cross-origin credential theft |
| Reflected arbitrary origin with credentials | High |
| TRACE method enabled | Medium — HTTP Trace Attack enables XSS header theft |
| PUT/DELETE allowed on arbitrary paths | High |
LOGIC_PROBE
Goal: test application logic — authentication, authorisation, input validation, business rule bypass. Primary task type for web application assessments. Use CREDENTIALS for authenticated testing.
Authenticate first if credentials are provided:
TOKEN=$(curl -s -X POST {TARGET_URL}/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"{CREDENTIALS[0].user}","password":"{CREDENTIALS[0].pass}"}' \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('accessToken','') or d.get('token',''))")
Respect max_http_requests_per_second — add sleep between requests if needed.
Authentication probes:
# Username enumeration via timing (FIND-005 pattern)
time curl -s -X POST {TARGET_URL}/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"{CREDENTIALS[0].user}","password":"definitelywrong_x9z"}' 2>&1
time curl -s -X POST {TARGET_URL}/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"nonexistent_x9z_user_abc","password":"definitelywrong_x9z"}' 2>&1
# Rate limit probe — 6 rapid login attempts
for i in {1..6}; do
curl -s -o /dev/null -w "Attempt $i: %{http_code}\n" \
-X POST {TARGET_URL}/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"wrongpass"}' 2>&1
done
# JWT inspection — decode and check alg, exp, role claims
echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool 2>&1
# JWT algorithm confusion — try 'none' algorithm
# Manually craft: header={"alg":"none"}, payload=original+role:Lead, sig=empty
HEADER=$(echo -n '{"alg":"none","typ":"JWT"}' | base64 -w0 | tr -d '=')
PAYLOAD=$(echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | \
python3 -c "import sys,json,base64; d=json.load(sys.stdin); d['role']='Lead'; print(base64.b64encode(json.dumps(d).encode()).decode().rstrip('='))")
NONE_TOKEN="${HEADER}.${PAYLOAD}."
curl -s -I {TARGET_URL}/api/engagements \
-H "Authorization: Bearer $NONE_TOKEN" 2>&1
# Null/missing auth
curl -s {TARGET_URL}/api/engagements 2>&1
curl -s {TARGET_URL}/api/engagements -H "Authorization: Bearer " 2>&1
curl -s {TARGET_URL}/api/engagements -H "Authorization: Bearer invalid.token.here" 2>&1
curl -s {TARGET_URL}/api/engagements -H "Authorization: Bearer null" 2>&1
Authorisation probes (BOLA / RBAC):
# List own resources to extract IDs
curl -s {TARGET_URL}/api/engagements \
-H "Authorization: Bearer $TOKEN" 2>&1
# IDOR — try incrementing UUIDs and sequential IDs
# Try known engagement ID with different user tokens
# Role escalation — try Lead-only endpoints with Analyst token
# (obtain second token with CREDENTIALS[1] if available)
if [ {CREDENTIALS_COUNT} -gt 1 ]; then
ANALYST_TOKEN=$(curl -s -X POST {TARGET_URL}/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"{CREDENTIALS[1].user}","password":"{CREDENTIALS[1].pass}"}' \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('accessToken',''))")
# Try Lead-only operations with Analyst token (FIND-001 pattern)
curl -s -X POST {TARGET_URL}/api/engagements \
-H "Authorization: Bearer $ANALYST_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"RBAC-Test-Probe"}' 2>&1
fi
Business logic probes:
# Lifecycle bypass — skip Gate 1, go straight to Gate 2
curl -s -X POST {TARGET_URL}/api/engagements/{TEST_ENGAGEMENT_ID}/pipeline/gate2/complete \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" 2>&1
# Audit log tampering — attempt mutation and deletion
curl -s -X PUT {TARGET_URL}/api/audit-logs/{LOG_ID} \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"TAMPERED"}' 2>&1
curl -s -X DELETE {TARGET_URL}/api/audit-logs/{LOG_ID} \
-H "Authorization: Bearer $TOKEN" 2>&1
Input validation probes:
# SQL injection
curl -s -X POST {TARGET_URL}/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin'\'' OR 1=1--","password":"x"}' 2>&1
curl -s -X POST {TARGET_URL}/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin\" OR \"1\"=\"1","password":"x"}' 2>&1
# Stored XSS in engagement name (FIND-004 pattern)
curl -s -X POST {TARGET_URL}/api/engagements \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"<script>alert(document.cookie)</script>"}' 2>&1
curl -s -X POST {TARGET_URL}/api/engagements \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"<img src=x onerror=alert(1)>"}' 2>&1
# Oversized input — DoS via memory exhaustion probe
python3 -c "import json; print(json.dumps({'name': 'A'*100000}))" | \
curl -s -X POST {TARGET_URL}/api/engagements \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data-binary @- 2>&1
# Boolean type coercion (FIND-003 pattern)
curl -s -X POST {TARGET_URL}/api/engagements/{ID}/scope/verify \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"verified":"true"}' 2>&1
curl -s -X POST {TARGET_URL}/api/engagements/{ID}/scope/verify \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"verified":1}' 2>&1
# Path traversal
curl -s {TARGET_URL}/api/engagements/../../../etc/passwd \
-H "Authorization: Bearer $TOKEN" 2>&1
curl -s {TARGET_URL}/api/engagements/..%2F..%2Fetc%2Fpasswd \
-H "Authorization: Bearer $TOKEN" 2>&1
# SSRF probe — request backend to fetch internal/cloud endpoints
curl -s -X POST {TARGET_URL}/api/engagements \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"test","callbackUrl":"http://169.254.169.254/latest/meta-data/"}' 2>&1
Information disclosure probes:
# 404 error detail
curl -s {TARGET_URL}/api/engagements/00000000-0000-0000-0000-000000000000 \
-H "Authorization: Bearer $TOKEN" 2>&1
# 500 error detail — does stack trace leak?
curl -s -X POST {TARGET_URL}/api/engagements \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d 'not valid json at all }{' 2>&1
# Server-side template injection (SSTI) probe
curl -s -X POST {TARGET_URL}/api/engagements \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"{{7*7}}"}' 2>&1
curl -s -X POST {TARGET_URL}/api/engagements \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"${7*7}"}' 2>&1
EXPLOIT
Goal: attempt controlled exploitation of confirmed vulnerabilities. Only run if explicitly authorised at the escalation gate in the orchestrator.
Base each exploit attempt on a specific vulnerability identified during VULN_SCAN or LOGIC_PROBE. Do not run generic exploit frameworks blindly.
Document exactly:
- The vulnerability being exploited
- The exact command(s) run
- Whether access was gained and at what privilege level
- The proof: sanitised terminal output showing the result
If elevated access is obtained, stop immediately and notify the orchestrator (return in the JSON with elevated_access_confirmed: true). Do not continue exploiting beyond the initial foothold without a new gate authorisation.
POST_EXPLOIT
Goal: demonstrate business impact from an established foothold — privilege escalation, sensitive data access, lateral movement indicators. Only run after explicit human authorisation.
Limit actions to demonstration of capability — do not perform destructive operations, do not exfiltrate real data, do not pivot to out-of-scope systems.
Document every action taken with exact commands and outputs.
Step 3 — Compile and Return Result
Record the end timestamp. Compute elapsed seconds.
Analyse the captured output and extract any potential findings:
For each identified issue:
- Title: concise, specific
- Severity:
Critical|High|Medium|Low|Informational - CVSS vector: estimate to the best of your ability (orchestrator will validate before posting)
- CVSS score: corresponding numeric value
- Evidence: exact command + response that demonstrates the issue (sanitise any credentials/tokens)
- Remediation: specific, actionable fix
Return a single JSON object — no prose, no markdown, just the JSON:
{
"task_type": "{TASK_TYPE}",
"status": "COMPLETED",
"engagement_id": "{ENGAGEMENT_ID}",
"targets": {TARGETS_ARRAY},
"cloud_provider": "{CONSTRAINTS.provider}",
"rate_limits_applied": {
"max_ports_per_second": {value},
"max_http_requests_per_second": {value}
},
"elevated_access_confirmed": false,
"elapsed_seconds": {elapsed},
"executed_commands": [
{
"command": "nmap -sV ... (credentials redacted)",
"stdout": "...",
"stderr": "...",
"exit_code": 0
}
],
"raw_output": "full concatenated stdout from all commands (credentials redacted)",
"compliance_exclusions": [],
"tool_availability": {
"available": ["curl", "python3", "nmap"],
"missing": ["nuclei", "nikto", "gobuster", "searchsploit"],
"coverage_gaps": [
"nuclei not available — CVE/misconfiguration template scan skipped (~8000 templates)",
"nikto not available — web server misconfiguration scan skipped",
"gobuster not available — directory/endpoint enumeration skipped",
"searchsploit not available — offline CVE-to-exploit mapping skipped"
]
},
"findings_preview": [
{
"title": "...",
"severity": "HIGH",
"cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"cvss_score": 7.5,
"evidence": "curl -s -I https://target/api/\nHTTP/2 200\n# No Strict-Transport-Security header present",
"remediation": "..."
}
]
}
Credential hygiene in output:
- Never include real credential values in
executed_commands,raw_output, or any string in the result - Replace credential values with
[MASKED]before including any command or output that contained them - Tokens captured during testing (
$TOKENetc.) → redact as[JWT_REDACTED]
Rules
- One task, one return. Do not attempt to run multiple task types in a single invocation.
- Rate limits are hard limits, not guidelines. Stay within them even if execution becomes slow.
- Never touch restricted subnets. Verify against the constraint list before every command.
- Elevated access is a stop signal. Report it immediately; do not continue without re-authorisation.
- No destructive operations. Read, probe, and demonstrate — never delete, encrypt, overwrite, or corrupt target data.
- Credentials in, credentials masked out. Values enter the worker;
[MASKED]exits the worker. - Return JSON only. The orchestrator parses your return — no conversational text in the final output.
-no-interactshalways on nuclei. No OOB callbacks to external servers (REQ-NF-001).- Phase 6 (gobuster) requires SecLists. If
/usr/share/seclists/does not exist, skip Phase 6 and note it.