9.6 KiB
Exploit Modules Guide
Best practices for writing and extending exploit modules in Rustsploit.
CVE Referencing
Always mention CVE IDs, vendor names, and affected products in:
- The module file docstring / top-level comments
- Output messages (e.g.,
[*] Testing CVE-2025-14847 on {}, target) - The Module Catalog
Response Validation
Validate server responses before declaring success — false positives hurt credibility:
if response.status() == 200 && body.contains("expected_indicator") {
println!("{} Confirmed vulnerable: {}", "[+]".green(), target);
} else {
println!("{} Not vulnerable or patched", "[-]".red());
}
Error Handling — no .unwrap(), no swallowing
Modules must surface transport and decode failures explicitly. The framework
treats a panicking module as a hard build/runtime failure, and silently
swallowing a failed send() or text() causes both check() and run()
to lie about what happened on the wire.
Forbidden patterns:
// .unwrap() / .expect() — panic on error.
let resp = client.get(&url).send().await.unwrap();
// .unwrap_or_default() on Result-returning HTTP calls — turns transport
// or decode failure into an empty body the caller assumes is the real one.
let body = resp.text().await.unwrap_or_default();
// `if let Ok(r) = ...` without an `else` arm — drops the network error
// and silently falls through to "NotVulnerable" / "not detected".
if let Ok(r) = client.get(&url).send().await {
/* … */
}
Preferred patterns:
In check(...) -> CheckResult, return CheckResult::Error(…) for failures so
operators can distinguish "couldn't reach the host" from "host is patched":
match client.get(&url).send().await {
Ok(r) => match r.text().await {
Ok(body) if body.contains("foo") =>
CheckResult::Vulnerable("foo detected".into()),
Ok(_) => CheckResult::NotVulnerable("foo not present".into()),
Err(e) => CheckResult::Error(format!("body decode: {}", e)),
},
Err(e) => CheckResult::Error(format!("request failed: {}", e)),
}
In run(...) -> anyhow::Result<()>, prefer ? with anyhow::Context for
abort-the-flow failures, and use match with crate::mprintln! when the
loop should report-and-continue:
use anyhow::Context;
let resp = client.get(&url).send().await.context("probe failed")?;
let body = resp.text().await.context("read body")?;
for path in candidate_paths {
match client.get(format!("{}{}", base, path)).send().await {
Ok(r) => { /* inspect r */ }
Err(e) => crate::mprintln!("{} {}: {}", "[-]".red(), path, e),
}
}
Helpers available (all in src/utils/network.rs, re-exported as crate::utils::*):
http_get_status_body(&client, &url) -> anyhow::Result<(u16, String)>— single call that surfaces both transport and body-decode failures viaanyhow::Context. Use it inrun()withlet (_, body) = ... .await?;. Use it incheck()withmatch.http_get_status_headers_body(&client, &url) -> anyhow::Result<(u16, HeaderMap, String)>— same but also returns the response headers, for fingerprints that look atServer/Via/X-Powered-By.header_string(headers, "name") -> String— reads a header asString. Returns""if absent and the literal sentinel"<non-utf8>"if present-but-non-utf8, so the swallow that.to_str().ok().unwrap_or("")does silently is now visible in module output. Avoid the older.to_str().ok().unwrap_or("")chain — it hides the non-utf8 case.
The full list of banned patterns lives in BAD_PATTERNS.md — 91 regex patterns covering panicking error handling, silent error swallowing, lint suppression, panic vectors, lossy numeric casts, async/blocking pitfalls, logging discipline, HTTP-client policy, iterator glitches, style and embedded secrets. The doc ships with a copy-paste reproducer that returns a non-zero exit on any hit. Quick-reference banned highlights:
.unwrap(),.expect("…"),.unwrap_or_default(),.unwrap_or(…),.unwrap_or_else(…),.to_str().ok().unwrap_or(…)#[allow(dead_code)],#[allow(unused_imports)],#[allow(unused_variables)], any other#[allow(...)]panic!(),unreachable!(),todo!(),unimplemented!(),assert!,assert_eq!if let Ok(_) = ...without anelse(silent fall-through) — usematchErr(_) => …anonymous binding even when the arm has a side effect. Always bind the value and surface it viaDisplay— e.g.Err(elapsed) => anyhow::bail!("connect to {} timed out after {:?}: {}", addr, CONNECT_TIMEOUT, elapsed)let _ = <expression>;where the expression returnsResult— useif let Err(e) = ...and loge- Direct array index
arr[i]and slice range&buf[..n]— usearr.get(i)/buf.get(..n).context(...)? - Lossy numeric casts (
as u16,as u64, etc.) — usetry_into().with_context(...)? - Sync I/O in async functions (
std::fs::*,std::process::Command,std::thread::sleep,std::net::TcpStream) — usetokio::*
Native libraries (crate::native::*): prefer in-tree helpers over third-party crates / hand-rolled formatting:
crate::native::hex::encode(&bytes)for response-byte preview hex.crate::utils::url_encode(s)(delegates tocrate::native::url_encoding::encode) for query / form payload encoding.crate::native::network::*for raw-socket DoS modules.
Artifact Handling
If the exploit downloads or writes files (e.g., memory dumps, webshells):
- Store in the current working directory or a named subfolder
- Name files descriptively:
mongobleed_results_{target}.txt,nginx_pwner_results_{target}.txt - Inform the operator where output was written
Clean-Up Instructions
If the exploit adds credentials or accounts (e.g., camera modules), document:
- The impact of the change
- How to revert (e.g., default creds to restore, commands to run)
Interactive Options
Use cfg_prompt_* helpers from crate::utils if end-user input is needed. These respect the priority chain (API custom_prompts > global options > interactive stdin), ensuring modules work in shell, API, and CLI modes:
use crate::utils::{cfg_prompt_default, cfg_prompt_yes_no};
let command = cfg_prompt_default("command", "Command to execute", "id").await?;
let deploy = cfg_prompt_yes_no("deploy_webshell", "Deploy webshell?", true).await?;
Mass-Scan Support
For modules supporting internet-wide scanning (target 0.0.0.0/0):
if target == "0.0.0.0" || target == "0.0.0.0/0" || target == "random" {
loop {
let ip = generate_random_public_ip();
if !is_excluded_ip(ip) {
execute(ip.to_string().as_str()).await.ok();
}
}
}
See EXCLUDED_RANGES documentation in Security & Validation.
Disable honeypot detection in mass-scan mode to avoid interactive prompts blocking the scan loop.
Module-Specific Notes
Hikvision RCE (CVE-2021-36260)
- Safe check — writes/reads a test file to verify exploitability
- Unsafe reboot — reboots the device to confirm (destructive)
- Command exec — output retrieved, supports blind mode
- SSH shell — deploys Dropbear SSH on port 1337
MongoBleed (CVE-2025-14847)
- Sends malicious compressed packet with inflated
uncompressedSize - Parses error response to extract leaked memory chunks (field names / types)
- Prints any leaked strings (potential credentials / data) to console
- Includes deep-scan mode for extended analysis
n8n RCE (CVE-2025-68613)
- Authenticates via
/rest/login(token / cookie-based) - Creates a malicious workflow with expression injection payload
- Triggers via
/rest/workflows/{id}/run - Cleans up test workflow after execution
- 6 payload types: Info, Command, Environment, Read File, Write File, Reverse Shell
FortiWeb SQLi → RCE (CVE-2025-25257)
- SQL injection via
Authorization: Bearer ';{injection}header - Writes webshell via
SELECT INTO OUTFILE - Uses
.pthtrigger for Pythonchmodexecution - Interactive modes: deploy webshell, execute command, test SQLi only
NginxPwner
- 10 checks: version disclosure, CRLF injection, PURGE method, variable leakage, merge slashes, header bypass / IP spoofing, alias traversal,
X-Accel-Redirectbypass, PHP detection, CVE-2017-7529 integer overflow - Results saved to
nginx_pwner_results_{target}.txt - Prints reminders for manual checks (Redis, CORS, request smuggling)
DoS / Stress Testing
⚠️ Authorized testing only. These modules can cause service disruption.
| Module | Notes |
|---|---|
null_syn_exhaustion |
Raw socket, IP spoofing, XorShift128+ RNG, configurable PPS, >1M PPS capable |
connection_exhaustion_flood |
FD-bounded semaphore, supports infinite mode with graceful Ctrl+C |
tcp_connection_flood |
DNS pre-resolved, high-concurrency handshake stress, infinite mode |
http2_rapid_reset |
CVE-2023-44487 — HTTP/2 stream reset flood |
Framework-Level Multi-Target Support
All exploit modules automatically benefit from the framework's multi-target dispatcher. There is no need to implement target iteration inside individual modules. The framework handles:
- Comma-separated targets —
192.168.1.1,192.168.1.2,192.168.1.3 - CIDR ranges —
192.168.1.0/24expands to all hosts in the subnet - File-based targets — pass a file path containing one target per line
- Random targets —
randomor0.0.0.0/0generates random public IPs withEXCLUDED_RANGESenforcement
The dispatcher calls the module's run() function once per resolved target. Modules only need to handle a single target string.