Pipelines for Penetration Testing
Penetration testing is inherently pipeline-shaped: reconnaissance flows into enumeration, enumeration into exploitation, exploitation into persistence. NullSec Linux uses Lateralus pipelines to chain security tools into automated, repeatable workflows.
◉ The Problem with Manual Testing
A typical recon workflow:
- Run subdomain enumeration (amass, subfinder)
- Resolve DNS for discovered hosts
- Port scan live hosts (nmap, masscan)
- Screenshot web services
- Run vulnerability scanners (nuclei)
- Compile findings into report
Each step requires manual intervention, format conversion, and context switching. Miss a step or misconfigure a tool, and you miss findings.
◉ Pipeline Approach
Express the same workflow as data flow:
import security.{amass, subfinder, nmap, gowitness, nuclei}
fn full_recon(target: str) -> Report {
target
|> discover_subdomains()
|> resolve_dns()
|> scan_ports()
|> identify_web_services()
|> screenshot_all()
|> scan_vulnerabilities()
|> generate_report()
}
Benefits:
- Repeatable — same inputs, same process
- Parallelizable — independent stages run concurrently
- Auditable — every step logged
- Extensible — add stages without rewriting
◉ Tool Wrappers
NullSec includes Lateralus wrappers for 250+ tools. Each wrapper:
- Parses tool output into structured data
- Handles common options idiomatically
- Manages temporary files
- Provides type-safe interfaces
// security.nmap wrapper
pub fn scan(target: str, opts: ScanOptions = default()) -> [Host] {
let args = build_args(target, opts)
let xml = run_nmap(args)
parse_nmap_xml(xml)
}
pub struct Host {
ip: str
hostname: Option[str]
ports: [Port]
os: Option[str]
}
pub struct Port {
number: u16
protocol: str
state: str
service: str
version: Option[str]
}
◉ Reconnaissance Pipeline
import security.{amass, subfinder, httpx, nmap}
fn recon(domain: str) -> ReconReport {
// Parallel subdomain enumeration
let subs = [
amass.enum(domain, passive: true),
subfinder.scan(domain),
] |> flatten() |> unique()
// DNS resolution and HTTP probe
let live = subs
|> httpx.probe(ports: [80, 443, 8080, 8443])
|> filter(_.is_live)
// Port scanning (only live hosts)
let hosts = live
|> map(_.ip)
|> unique()
|> nmap.scan_batch(ports: "1-10000", timing: 4)
ReconReport {
domain: domain,
subdomains: subs,
live_hosts: live,
port_data: hosts,
timestamp: now()
}
}
◉ Web Application Pipeline
import security.{burp, sqlmap, nuclei, ffuf}
fn webapp_scan(url: str) -> WebAppReport {
// Directory enumeration
let dirs = ffuf.fuzz(url, wordlist: "/usr/share/wordlists/common.txt")
// Parameter discovery
let params = burp.crawl(url, depth: 3)
|> extract_params()
// SQL injection testing
let sqli = params
|> filter(_.injectable_looking)
|> map(fn(p) { sqlmap.test(p.url, p.param, level: 2) })
|> filter(_.vulnerable)
// Template-based scanning
let vulns = nuclei.scan(url, templates: ["cves", "exposures", "misconfigs"])
WebAppReport {
url: url,
directories: dirs,
parameters: params,
sqli_findings: sqli,
vulnerabilities: vulns
}
}
◉ Credential Attacks Pipeline
import security.{hydra, hashcat, secretsdump}
import data.wordlists
fn credential_attack(target: str, users: [str]) -> CredReport {
// Password spraying (careful with lockouts)
let spray_results = users
|> hydra.spray(target,
passwords: ["Summer2024!", "Welcome1"],
service: "smb",
threads: 1, // Slow to avoid lockout
delay: 30
)
// Dump hashes from successful logins
let hashes = spray_results
|> filter(_.success)
|> map(fn(cred) {
secretsdump.dump(target, cred.user, cred.pass)
})
|> flatten()
// Crack recovered hashes
let cracked = hashes
|> hashcat.crack(
mode: 1000, // NTLM
wordlist: wordlists.rockyou,
rules: "best64"
)
CredReport {
sprayed: spray_results,
hashes: hashes,
cracked: cracked
}
}
◉ Reporting Pipeline
import report.{markdown, html, pdf}
fn generate_report(findings: [Finding]) -> Report {
let sorted = findings
|> sort_by(_.severity, descending: true)
|> group_by(_.category)
let sections = sorted
|> map(fn((cat, items)) {
Section {
title: cat,
findings: items |> map(format_finding),
remediation: get_remediation(cat)
}
})
let report = Report {
title: "Penetration Test Report",
date: now(),
scope: findings |> map(_.target) |> unique(),
executive_summary: summarize(sorted),
sections: sections,
appendices: generate_appendices(findings)
}
// Output in multiple formats
report |> markdown.render() |> write("report.md")
report |> html.render() |> write("report.html")
report |> pdf.render() |> write("report.pdf")
report
}
◉ Running Pipelines
NullSec includes CLI integration:
# Run recon pipeline
nullsec-pipeline recon example.com
# Run with custom output directory
nullsec-pipeline webapp https://target.com --output ./results
# Dry run (show what would execute)
nullsec-pipeline full-engagement target.com --dry-run
# Generate pipeline template
nullsec-pipeline new internal-pentest > engagement.ltl
◉ Scheduling and Monitoring
// Run continuous monitoring
fn continuous_monitor(targets: [str]) {
loop {
for target in targets {
let report = quick_recon(target)
// Compare to previous
let prev = db.get_latest(target)
let diff = compare_reports(prev, report)
if diff.has_changes {
alert.send("Changes detected on " + target, diff)
}
db.save(target, report)
}
sleep(hours(24))
}
}
◉ Pipeline Library
NullSec ships with ready-to-use pipelines:
recon-full— Complete external reconnaissancewebapp-basic— Web application assessmentinternal-ad— Active Directory attack pathcloud-aws— AWS security assessmentwireless— WiFi security testing
All pipelines at nullsec-pipelines. Tool wrappers at security.ltl.