mirror of
https://github.com/s-b-repo/rustsploit
synced 2026-06-27 09:54:12 +00:00
94899781d3
Hardening (non-module framework files):
- Retry-then-continue: bounded per-host retry on transient failures across all 4
mass-scan fan-outs; '10 errors -> abort sweep' softened to warn-and-continue
- Crash fixes: shell completer char-boundary guard; unreachable! -> bail!
- WS oversize-frame desync fixed (was bricking the PQ AEAD ratchet); MCP tenant
job list/kill, out-of-range port, non-string option now correct/errored
- No silent error swallowing: swept framework files, every dropped error now
bound + surfaced (warn for logged-only/data-loss, debug for already-propagated
or aggregated per-host); removed _ => {} and Err(_)/|_| discards
Docs + release: README + docs/ updated for the release; RELEASE_NOTES.txt
section 6d added; new RELEASE_GITHUB.txt (GitHub release body).
Build: 0 errors, 0 warnings, 40/40 targeted tests green.
260 lines
11 KiB
Markdown
260 lines
11 KiB
Markdown
# 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<ModuleOutcome>` 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 `"<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`](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:
|
|
|
|
```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.
|