# 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](Module-Catalog.md) --- ## Response Validation Validate server responses before declaring success — false positives hurt credibility: ```rust 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 > **Note:** Rustsploit is exploitation-only — the `check()` / `CheckResult` > verification phase was removed. The `check(...)` snippets below are kept only > to illustrate the error-handling discipline; apply the same patterns in your > `run(&ModuleCtx) -> Result` body and emit `Finding`s instead of > returning a `CheckResult`. 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 `run()` to lie about what happened on the wire. **Forbidden patterns:** ```rust // .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": ```rust 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: ```rust 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 via `anyhow::Context`. Use it in `run()` with `let (_, body) = ... .await?;`. Use it in `check()` with `match`. - `http_get_status_headers_body(&client, &url) -> anyhow::Result<(u16, HeaderMap, String)>` — same but also returns the response headers, for fingerprints that look at `Server` / `Via` / `X-Powered-By`. - `header_string(headers, "name") -> String` — reads a header as `String`. Returns `""` if absent and the literal sentinel `""` 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`](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 an `else` (silent fall-through) — use `match` - `Err(_) => …` anonymous binding even when the arm has a side effect. Always bind the value and surface it via `Display` — e.g. `Err(elapsed) => anyhow::bail!("connect to {} timed out after {:?}: {}", addr, CONNECT_TIMEOUT, elapsed)` - `let _ = ;` where the expression returns `Result` — use `if let Err(e) = ...` and log `e` - Direct array index `arr[i]` and slice range `&buf[..n]` — use `arr.get(i)` / `buf.get(..n).context(...)?` - Lossy numeric casts (`as u16`, `as u64`, etc.) — use `try_into().with_context(...)?` - Sync I/O in async functions (`std::fs::*`, `std::process::Command`, `std::thread::sleep`, `std::net::TcpStream`) — use `tokio::*` **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 to `crate::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: ```rust 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 Compatibility Mass-scan fan-out is handled by `scheduler::run` for every module. Modules **do not** implement their own target iteration — the scheduler fans out `Cidr` / `File` / `Random` / `Multi` targets and calls `run()` once per host with `Target::Single`. ### What modules must do 1. **Use target-specific filenames.** When writing output files, include the target in the filename to avoid clobbering under concurrent mass scan: ```rust // Bad — concurrent tasks overwrite each other let path = "results.txt"; // Good — each target gets its own file let safe = target.replace(['/', ':', '.', '[', ']'], "_"); let path = format!("results_{}.txt", safe); ``` Examples of modules already fixed: `mongobleed` (`vulnerable_mongodb_{timestamp}.txt`), `pachev_ftp` (`results_{target}.txt`), ZTE (`config_{host}.bin`), Tomcat RCE (per-invocation temp directory), JWKS (`jwks_{target}_{kid}_{i}.pem`). 2. **Guard interactive/REPL code with `is_batch_mode()`:** ```rust if crate::utils::is_batch_mode() { anyhow::bail!("Interactive REPL not supported in mass-scan mode."); } ``` Local-only exploits (e.g. Windows DWM CVE-2026-20805) should also bail: ```rust if crate::utils::is_batch_mode() { anyhow::bail!("Local exploit generator — not suitable for mass scan."); } ``` 3. **Use framework network wrappers** so `setg source_port` is honoured across all concurrent fan-out tasks. See _Source Port Awareness_ in [Credential-Modules-Guide](Credential-Modules-Guide.md). ### What modules must NOT do - **No per-module `EXCLUDED_RANGES` constants or `generate_random_public_ip()` calls.** The scheduler's `crate::exclusions::ExclusionSet` handles this. - **No `if is_mass_scan_target(target) { run_mass_scan(...) }` branches.** This pattern was removed in v0.5.1. The scheduler is the single fan-out engine. - **No raw `TcpStream::connect` or `UdpSocket::bind`.** Use the wrappers in `crate::utils::network`. --- ## 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 `.pth` trigger for Python `chmod` execution - 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-Redirect` bypass, 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/24` expands to all hosts in the subnet - **File-based targets** — pass a file path containing one target per line - **Random targets** — `random` or `0.0.0.0/0` generates random public IPs with exclusion-set enforcement The dispatcher calls the module's `run()` function once per resolved target. Modules only need to handle a single target string.