Surface Threat Reconnaissance & Identification Platform
A lightweight, containerized security reconnaissance platform for automated external attack surface discovery and vulnerability assessment.
This project exists to demonstrate what disciplined, repeatable discovery looks like when treated as a system rather than a collection of tools. It is intentionally opinionated, intentionally incomplete by design, and intentionally boring.
STRIP is an open-source reconnaissance platform developed by Jonathan Risto as part of security research at ZenzizenSec Inc.
Related frameworks: VMMM v2 | CTEMMM
This project provides practical tooling that complements vulnerability and exposure management maturity assessment frameworks.
STRIP automates the discovery and assessment of internet-facing assets through a coordinated pipeline of industry-standard open-source security tools. It's designed for security teams, penetration testers, and bug bounty hunters who need fast, reliable visibility into external attack surfaces.
targets.txt (domains / IPs / CIDRs) → subdomain discovery → DNS resolution
→ [opt-in ASN seed expansion] → port scanning → web probing (+ favicon hash)
→ screenshots → crawling (sweep) → vulnerability + TLS scanning
→ subdomain-takeover + email-posture checks → normalized reports
Key Capabilities:
- 🔍 Automated subdomain enumeration
- 🌐 DNS resolution and validation
- 🎯 Domain, IP, and CIDR range targets
- 🛰️ Opt-in ASN → CIDR seed expansion (asnmap, scope-controlled)
- 🔓 Port and service discovery
- 🌍 HTTP/HTTPS service profiling
- 🧬 Favicon hashing (cross-host correlation anchor)
- 🕸️ Web crawling for surface expansion (weekly sweep)
- 📸 Screenshot capture / visual recon
- 🛡️ Template-based vulnerability scanning
- 🎭 Subdomain takeover detection (confirmed / potential)
- ✉️ Email/domain security posture (SPF/DMARC/DKIM/BIMI/MTA-STS/CAA)
- 🔒 TLS/SSL configuration assessment
- 📊 Structured outputs (NDJSON, CSV)
- Docker & Docker Compose (v2)
yq(YAML processor) - Installation guidejq(JSON processor) - Usually pre-installed on Linux/macOS
Platform notes:
- Linux — runs natively;
network_mode: hostgives real host networking (best performance). Installyq/jqvia your package manager. - macOS — Docker Desktop; works out of the box. amd64-only tool images (gowitness) run under emulation on Apple Silicon (screenshots still work, just slower).
- Windows — run inside WSL2 (Docker Desktop for Windows already uses it); clone and run
stripctlfrom the WSL2 shell. Native PowerShell is not supported.
# Clone the repository
git clone https://github.com/jonathanristo/strip.git
cd strip
# Create required directories
mkdir -p data/{in,out,.config,.cache}
# Add target domains (one per line)
cat > data/in/domains.txt <<EOF
example.com
test.com
EOF
# Run your first scan
./stripctl run dailyLinux, macOS, and Windows (WSL2) users: The default configuration works out of the box.
# Daily scan (High/Critical findings only)
./stripctl run daily
# Results will be in:
./data/out/[timestamp]/That's it. STRIP works out of the box.
# Full daily workflow (recommended)
./stripctl run daily
# Full weekly workflow (deeper scanning)
./stripctl run weekly
# Individual phases
./stripctl setup # First-time Nuclei template setup (auto-checked on runs)
./stripctl discover # Subdomain → DNS → port → HTTP discovery
./stripctl merge # Normalize outputs to NDJSON/CSVEdit strip.yaml to customize:
scan:
naabu:
top_ports: "1000" # Number of ports to scan
rate: 5000 # Packets per second
httpx:
rate_limit: 400 # Requests per second
nuclei:
severity_daily: "high,critical"
severity_weekly: "medium,high,critical"
katana:
enable: true # Web crawl during the weekly sweep (daily never crawls)
tls:
testssl: true # TLS/SSL assessment of live HTTPS services
sslyze: false # Raw sidecar only — writes sslyze.json but is NOT normalized into the contract
email:
enable: true # SPF/DMARC/DKIM/BIMI/MTA-STS/CAA posture (dnsx TXT/CAA; DNSSEC/DANE not covered)
takeover:
enable: true # Subdomain takeover via nuclei takeover templates over CNAME'd hosts
seed_expansion:
enable: false # OPT-IN: expand scope to the org's ASN CIDRs (asnmap). Changes WHAT is scanned
exclusions: [] # CIDRs excluded BEFORE scanning
acknowledge_full_asn: false # allow expansion with an empty exclusion list. asnmap needs PDCP_API_KEY
screenshots:
enable: true # Screenshot capture with gowitnessdata/out/20260709-143205/
│ # ── raw tool output ──
├── domains.txt # Input targets (domains / IPs / CIDRs)
├── resolved.json # DNS resolution incl. CNAME chains (dnsx -json)
├── email_txt.json # SPF/DMARC/DKIM/BIMI/MTA-STS records (dnsx -txt)
├── email_caa.json # CAA records (dnsx -caa)
├── asnmap.cidrs.txt # ASN CIDRs (asnmap; opt-in seed expansion)
├── scan_targets.txt # Combined port-scan targets
├── ports.naabu.txt # Open ports (naabu)
├── nmap.xml / nmap6.xml # Service fingerprints, IPv4 / IPv6 (nmap -sV -Pn on discovered ports; C2 import)
├── httpx.json # HTTP probe details (incl. TLS grab + favicon hash)
├── nuclei.jsonl # Raw vulnerability findings
├── takeover.jsonl # Raw subdomain-takeover matches (nuclei takeover pass)
├── testssl.json # Raw TLS assessment
├── screenshots/ # gowitness screenshots
│ # ── normalized output (stable NDJSON contract; one JSON object per line) ──
├── hosts.ndjson # One record per observed IP
├── services.ndjson # One record per (host, port, protocol) — naabu + nmap merged
├── web_assets.ndjson # One record per HTTP(S) endpoint (incl. favicon_hash)
├── tls.ndjson # One record per TLS certificate (SHA-256 fingerprint)
├── dns.ndjson # One record per DNS observation (A/AAAA/CNAME/TXT/CAA)
├── findings.ndjson # One record per finding (nuclei + testssl + email posture + takeover)
├── run_manifest.json # Run provenance: operator, authorization, timings, counts, seed-expansion
└── *.csv # Lossy human-readable summaries (hosts, open_ports, web_assets, tls, findings)
STRIP leverages best-in-class open-source security tools:
| Phase | Tool | Purpose |
|---|---|---|
| Discovery | subfinder | Subdomain enumeration (passive) |
| amass | Subdomain enumeration (comprehensive) | |
| dnsx | DNS resolution, validation & email-posture records | |
| asnmap | ASN → CIDR seed expansion (opt-in; needs PDCP_API_KEY) |
|
| Scanning | naabu | Fast port scanning |
| nmap | Service fingerprinting (discovered ports, -Pn, IPv4 + IPv6) |
|
| httpx | HTTP probing, tech detection & favicon hashing | |
| Assessment | nuclei | Vulnerability scanning + subdomain-takeover templates |
| testssl.sh | TLS/SSL security assessment | |
| Recon | katana | Web crawling / surface expansion (weekly sweep) |
| gowitness | Screenshot capture / visual recon |
STRIP works without API keys, but adding free API keys significantly improves subdomain discovery results.
Without keys: ~40-60% coverage (passive sources only)
With keys: ~90%+ coverage (10+ data sources)
- VirusTotal (500 requests/day): https://www.virustotal.com/gui/sign-up
- Censys (250 queries/month): https://censys.io/register
- SecurityTrails (50 queries/month): https://securitytrails.com/app/signup
# Create config file
mkdir -p data/.config/subfinder
cat > data/.config/subfinder/provider-config.yaml <<EOF
virustotal:
- "YOUR_VIRUSTOTAL_API_KEY"
censys:
- "YOUR_CENSYS_API_ID"
- "YOUR_CENSYS_SECRET"
securitytrails:
- "YOUR_SECURITYTRAILS_API_KEY"
EOFSee docs/API_KEYS.md for detailed setup instructions.
Opt-in ASN → CIDR seed expansion (seed_expansion.enable: true) uses asnmap, which needs a free
ProjectDiscovery Cloud key. Provide it via the environment (it is
passed through to the asnmap container):
export PDCP_API_KEY="your-pdcp-key"Seed expansion refuses to run without an exclusion list unless you also set
seed_expansion.acknowledge_full_asn: true — it changes what gets scanned, so scope control is a
prerequisite, not an option. Exclusions are enforced by CIDR containment (a broad exclusion such as
10.0.0.0/8 also drops the child ranges asnmap returns), which needs python3 on the host; if python3
is absent while exclusions are set, expansion refuses rather than under-enforce the guardrail.
# Daily cron job
0 2 * * * /opt/strip/stripctl run daily
# Weekly deep scan
0 3 * * 0 /opt/strip/stripctl run weekly# Gather intelligence before penetration test
./stripctl run daily
# Review findings in data/out/[timestamp]/findings_summary.csv# Monitor multiple programs
cat >> data/in/domains.txt <<EOF
bugcrowd-target.com
hackerone-target.com
EOF
./stripctl run daily# Comprehensive enumeration
./stripctl run weekly
# Import data/out/[timestamp]/nmap.xml into your C2- discover — subdomain enumeration → DNS resolution (+ CNAME) → naabu port scan → nmap
-sV -Pnon the discovered ports (IPv4 + IPv6) → httpx probing (incl. favicon) - web-scan — Nuclei (high/critical) against the httpx-validated live URLs (not the raw port cross-product)
- tls — testssl.sh assessment of live HTTPS services
- shots — Screenshot capture (gowitness)
- email — SPF/DMARC/DKIM/BIMI/MTA-STS/CAA posture (records →
dns.ndjson, gaps →findings.ndjson) - takeover — Subdomain-takeover detection over CNAME'd hosts (confirmed / potential)
- merge — Normalize all outputs to NDJSON/CSV
Opt-in seed expansion: with
seed_expansion.enable, asnmap expands scope to the org's ASN CIDRs before scanning — scope-controlled (refuses without an exclusion list unlessacknowledge_full_asn) and needsPDCP_API_KEY.
Duration: ~10-30 minutes (depending on target size)
Everything in the daily scan, plus a deeper sweep:
- Amass (comprehensive subdomain discovery, unioned into one scan)
- Katana (web crawling → expands the validated URL set Nuclei tests)
- Nuclei (medium/high/critical severity)
Duration: ~30-90 minutes
Machine-readable, easy to parse and query:
# Critical findings
jq 'select(.severity=="critical")' data/out/*/findings.ndjson
# Findings that carry a CVE
jq 'select(.cve_id != null) | {cve_id, severity, url}' data/out/*/findings.ndjson
# Subdomain takeovers (confirmed or potential)
jq 'select(.category=="takeover") | {host: .hostname_at_observation_time, state, severity}' data/out/*/findings.ndjson
# Email-security gaps (missing SPF/DMARC/MTA-STS/CAA, DMARC p=none)
jq 'select(.category=="email_security") | {host: .hostname_at_observation_time, .template_id}' data/out/*/findings.ndjson
# Web assets running a given technology (tech is an array of objects)
jq 'select(.tech[]?.name | ascii_downcase | test("wordpress"))' data/out/*/web_assets.ndjson
# Favicon hashes (cross-host correlation anchor)
jq -r 'select(.favicon_hash != null) | [.hostname_at_observation_time // .ip_at_observation_time, .favicon_hash] | @tsv' data/out/*/web_assets.ndjson
# TLS certificate fingerprints (cross-host correlation anchor)
jq -r '[.hostname_at_observation_time // .ip_at_observation_time, .fingerprint_sha256] | @tsv' data/out/*/tls.ndjson
# Count findings by severity
jq -r '.severity' data/out/*/findings.ndjson | sort | uniq -cEvery record is self-describing (source_module, source_tool, scan_id, observed_at) and
immutable. An empty stream is written as a single sentinel line (a record with a message key) —
skip those when parsing.
Lossy human summaries (NDJSON is the authoritative format):
hosts.csv - Live hosts
open_ports.csv - Services (host, port, service, product, version)
web_assets.csv - Web endpoints
tls.csv - Certificates
findings_summary.csv - Findings
┌─────────────────────────────────────────────────────────────┐
│ stripctl (Bash) │
│ Orchestration & Workflow Engine │
└─────────────────────────────────────────────────────────────┘
│
┌───────────┬────────┼────────┬────────────┐
│ │ │ │ │
┌────▼─────┐ ┌───▼────┐ ┌─▼──────┐ ┌▼──────────┐
│subfinder │ │ dnsx │ │ naabu │ │ asnmap │
│ amass │ │(resolve│ │ nmap │ │ ASN→CIDR │
│ │ │+email) │ │ │ │ (opt-in) │
└────┬─────┘ └───┬────┘ └───┬────┘ └───────────┘
│ │ │
└───────────┴────┬─────┘
│
┌───────▼────────┐
│ httpx │
│ (probe + favi) │
└───────┬────────┘
│
┌───────────┬───┴───┬────────────┐
│ │ │ │
┌────▼────┐ ┌────▼────┐ ┌▼───────┐ ┌──▼─────────┐
│ nuclei │ │ testssl │ │ katana │ │ gowitness │
│ (vulns) │ │ (TLS) │ │(crawl) │ │ (screens) │
└────┬────┘ └────┬────┘ └───┬────┘ └──────┬─────┘
│ │ │ │
└───────────┴─────┬────┴─────────────┘
│
┌───────▼────────┐
│ merge │
│ NDJSON + CSV │
└────────────────┘
Phase 1 (
run daily) flows discovery → httpx → nuclei → testssl → gowitness → email posture → takeover → merge. asnmap seed expansion is opt-in and feeds scan scope before naabu. The weekly sweep (run weekly) adds amass and katana (crawl → expands the validated URL set nuclei tests).
Design Principles:
- Containerized - Every tool runs in isolation
- Stateless - No persistent services (except OpenVAS if added later)
- Timestamped - Each run creates a new output directory
- Fail-safe - Individual tool failures don't crash the pipeline
- Modular - Run phases independently or as a complete workflow
| Feature | STRIP | Recon-ng | Amass Alone | Metasploit | Commercial Tools |
|---|---|---|---|---|---|
| Setup Time | < 5 min | ~15 min | < 5 min | ~30 min | Hours/Days |
| Infrastructure | Docker only | Python deps | Go binary | Complex | Cloud/VM |
| Cost | Free | Free | Free | Free | $$$$ |
| Automation | Full | Manual/scripted | Partial | Partial | Full |
| Vulnerability Scanning | ✓ (Nuclei) | ✗ | ✗ | ✓ | ✓ |
| Visual Recon | ✓ | ✗ | ✗ | ✗ | ✓ |
| Domain / IP / CIDR input | ✓ | Partial | Partial | ✓ | ✓ |
| Learning Curve | Low | Medium | Low | High | Medium |
# Fix Docker socket permissions
sudo usermod -aG docker $USER
# Log out and back in# Requires elevated privileges
# Option 1: Run stripctl with sudo
sudo ./stripctl run daily
# Option 2: Grant CAP_NET_RAW
sudo setcap cap_net_raw+ep /usr/bin/docker# Check if domains.txt is formatted correctly (one domain per line)
cat data/in/domains.txt
# Run subfinder manually to debug
docker compose run --rm subfinder -d example.com -all# Install yq
# macOS
brew install yq
# Linux
wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq
chmod +x /usr/local/bin/yqThe gowitness image is amd64-only, so on Apple Silicon (M-series) it runs under emulation —
handled automatically by the platform: linux/amd64 pin in docker-compose.yml. Screenshots
still work, just slower. On amd64 Linux/Windows it runs natively.
- OpenVAS/Greenbone integration (full vulnerability management)
- Web UI for viewing results
- Slack/email notifications
- Differential scanning (track changes over time)
- Report generation (PDF/HTML)
- API for integration with ticketing systems
- Support for authenticated scanning
- Kubernetes deployment option
STRIP ships a layered regression suite under tests/ — plain bash plus the tools STRIP
already needs (jq, docker); no extra framework to install. Run it under bash (the suite sources
stripctl, which relies on bash features BASH_REMATCH/arrays):
tests/run.sh # static + parsers + golden merge + tool contracts
tests/run.sh parsers # a single layer, by name filter
STRIP_INTEGRATION=1 tests/run.sh # also run the opt-in network integration layer| Layer | Needs | Proves |
|---|---|---|
| Static | — (docker/shellcheck optional) | bash -n, docker compose config, every tool invoked maps to a real compose service |
| Parsers | — | host:port parsing (IPv4 / IPv6 / bracketed), URL building, resolver cleanup, nmap family split, nuclei target selection, run-dir selection |
| Golden merge | docker (nmap fixture only) | real merge output vs a locked baseline for all six streams + run_manifest.json, across IPv4 / IPv6-literal / dual-stack / empty-run fixtures |
| Contracts | docker | the current tool images accept the exact flags STRIP passes them |
| Integration | docker + network, opt-in | live dnsx custom-resolver resolve + error capture |
After an intentional change to merge, refresh the golden baseline with tests/regen_golden.sh and
review the diff. See tests/README.md for fixture details.
Contributions welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Before submitting, please verify:
bash -n stripctlparses clean anddocker compose config -qvalidates.mergeoutput still conforms tosensor-output/1.0— the six NDJSON streams plusrun_manifest.json,null(not"") for absent scalars, oneservicesrow per host/port/protocol.- Parsing changes preserve behavior across IPv4, IPv6 (literal and bracketed), and hostname targets.
Then run the regression suite — tests/run.sh (see Testing above).
Areas where we'd love help:
- Additional tool integrations
- Report generation templates
- Documentation improvements
- Bug fixes and performance optimization
- Legal: Only scan assets you own or have explicit permission to test
- Ethical: Follow responsible disclosure practices
- Rate Limiting: Be respectful of target infrastructure
- Data Handling: Outputs may contain sensitive information - handle appropriately
- Store credentials securely (use
.gitignorefordata/creds/) - Rotate API keys regularly
- Review findings before sharing with third parties
- Consider using a VPN or dedicated scanning infrastructure
MIT License - see LICENSE file for details.
STRIP stands on the shoulders of giants. Huge thanks to:
- ProjectDiscovery - subfinder, nuclei, httpx, naabu, dnsx, katana
- OWASP Amass - Comprehensive subdomain discovery
- testssl.sh - TLS/SSL testing
- gowitness - Screenshot capture
And the entire open-source security community.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: Wiki
Maintained by: Jonathan Risto | ZenzizenSec Inc.
Built for the security community
