Skip to content

Latest commit

 

History

History
259 lines (190 loc) · 11.4 KB

File metadata and controls

259 lines (190 loc) · 11.4 KB

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

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<ModuleOutcome> body and emit Findings 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:

// .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 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 "<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 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 _ = <expression>; 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:

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:

    // 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():

    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:

    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.

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 targets192.168.1.1,192.168.1.2,192.168.1.3
  • CIDR ranges192.168.1.0/24 expands to all hosts in the subnet
  • File-based targets — pass a file path containing one target per line
  • Random targetsrandom 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.