Rustsploit provides several utility modules that every module developer should know:
| Module | Import Path | Purpose |
|---|---|---|
| Core Utils | crate::utils |
Target normalization, file loading, config-aware prompts, input validation |
| Network Utils | crate::utils::network |
HTTP client builders, TCP/UDP connect helpers, honeypot check |
| Privilege Utils | crate::utils::privilege |
Root privilege check for raw-socket modules |
| Creds Utils | crate::modules::creds::utils |
Bruteforce statistics, subnet helpers, IP exclusion, scan state tracking |
| Config | crate::config |
Global target state, module config, API prompt keys, results directory |
| Global Options | crate::global_options |
Persistent setg options — checked by cfg_prompt_* after custom_prompts |
| Cred Store | crate::cred_store |
Store/query discovered credentials. Call store_credential() from modules |
| Workspace | crate::workspace |
Track hosts/services. Call track_host() / track_service() from modules |
| Loot | crate::loot |
Store collected evidence. Call store_loot() from modules |
| Module Info | crate::module_info |
ModuleInfo, ModuleRank, CheckResult types for info()/check() |
| Spool | crate::spool |
Console output logging. Call spool::sprintln() for spool-aware output |
| Jobs | crate::jobs |
Background job management via JOB_MANAGER |
| Export | crate::export |
Export engagement data to JSON/CSV/summary |
Reads a file line-by-line, trims whitespace, and drops empty lines. The standard way to load wordlists, username files, or any line-delimited input.
use crate::utils::load_lines;
let passwords = load_lines("passwords.txt")?;
for pw in &passwords {
// each entry is trimmed, non-empty
}| Parameter | Type | Description |
|---|---|---|
path |
impl AsRef<Path> |
Path to the file to read |
Returns: Vec<String> of non-empty, trimmed lines. Errors if the file cannot be opened.
Comprehensive target normalization and validation. This is the single entry point for converting any user-supplied target into a consistent format.
use crate::utils::normalize_target;
let target = normalize_target(user_input)?;
// target is now in one of:
// "192.168.1.1" (IPv4)
// "192.168.1.1:8080" (IPv4 + port)
// "[::1]" (IPv6)
// "[::1]:8080" (IPv6 + port)
// "example.com" (hostname)
// "192.168.1.0/24" (CIDR)| Input Format | Example |
|---|---|
| IPv4 | 192.168.1.1 |
| IPv4 + port | 192.168.1.1:8080 |
| IPv6 | ::1, 2001:db8::1 |
| IPv6 + port | [::1]:8080 |
| Hostname | example.com, example.com:443 |
| URL | http://example.com:8080 → extracts example.com:8080 |
| CIDR | 192.168.1.0/24, 2001:db8::/32 |
Security: Validates against DoS-length abuse (max 2048 chars), control characters, and path traversal patterns (.., //).
These are the recommended prompts for module authors. They check ModuleConfig.custom_prompts first (populated by the API), falling back to interactive stdin when running in shell mode. This makes your module work seamlessly in both shell and API modes.
Required string prompt with no default. In API mode, errors if the key is missing from custom_prompts. Priority: custom_prompts > run_context target (for "target" key) > global_options > interactive stdin.
use crate::utils::cfg_prompt_required;
let community = cfg_prompt_required("community", "SNMP community string").await?;| Parameter | Type | Description |
|---|---|---|
key |
&str |
Lookup key in ModuleConfig.custom_prompts |
msg |
&str |
Prompt message shown to user in shell mode |
Errors in API mode if key is missing (required field).
Boolean prompt. Accepts y/yes/true/1 and n/no/false/0.
use crate::utils::cfg_prompt_yes_no;
let verbose = cfg_prompt_yes_no("verbose", "Enable verbose output?", false)?;| Parameter | Type | Description |
|---|---|---|
key |
&str |
Lookup key in ModuleConfig.custom_prompts |
msg |
&str |
Prompt message shown to user in shell mode |
default_yes |
bool |
Default when input is empty or key absent in API mode |
Prompts for a file path. Validates the file exists, rejects path traversal (..), symlinks, and control characters.
use crate::utils::cfg_prompt_existing_file;
let wordlist = cfg_prompt_existing_file("password_wordlist", "Password wordlist file")?;
let lines = load_lines(&wordlist)?;| Parameter | Type | Description |
|---|---|---|
key |
&str |
Prompt key for API mode |
msg |
&str |
Interactive prompt message |
Errors in API mode if key is missing (required field).
Integer prompt with range validation.
use crate::utils::cfg_prompt_int_range;
let threads = cfg_prompt_int_range("threads", "Number of threads", 10, 1, 100)?;
let delay = cfg_prompt_int_range("delay_ms", "Delay between attempts (ms)", 50, 0, 60000)?;| Parameter | Type | Description |
|---|---|---|
key |
&str |
Prompt key |
msg |
&str |
Interactive prompt message |
default |
i64 |
Default value |
min |
i64 |
Minimum allowed value |
max |
i64 |
Maximum allowed value |
Generic string prompt with a default value.
use crate::utils::cfg_prompt_default;
let method = cfg_prompt_default("http_method", "HTTP method", "GET")?;| Parameter | Type | Description |
|---|---|---|
key |
&str |
Prompt key |
msg |
&str |
Interactive prompt message |
default |
&str |
Default value when empty |
Port number prompt. Validates range 1–65535.
use crate::utils::cfg_prompt_port;
let port = cfg_prompt_port("port", "Target port", 22)?;| Parameter | Type | Description |
|---|---|---|
key |
&str |
Prompt key |
msg |
&str |
Interactive prompt message |
default |
u16 |
Default port number |
Output filename prompt. Forces basename only — strips any directory path to prevent traversal. Rejects hidden files (starting with .) and filenames over 255 chars.
use crate::utils::cfg_prompt_output_file;
let output = cfg_prompt_output_file("output_file", "Output file", "results.txt")?;
// output is guaranteed to be a safe basename like "results.txt"Wordlist file prompt. Validates the file exists, rejects path traversal and unsafe paths (same security as cfg_prompt_existing_file). Priority: custom_prompts > global_options > interactive stdin.
use crate::utils::cfg_prompt_wordlist;
let wordlist = cfg_prompt_wordlist("wordlist", "Path to wordlist file").await?;
let lines = load_lines(&wordlist)?;| Parameter | Type | Description |
|---|---|---|
key |
&str |
Prompt key for API mode |
msg |
&str |
Interactive prompt message |
Errors in API mode if key is missing (required field). Also errors if the file does not exist.
Here's a typical module using all the core utils together:
use crate::utils::{
load_lines, normalize_target,
cfg_prompt_required, cfg_prompt_yes_no, cfg_prompt_existing_file,
cfg_prompt_int_range, cfg_prompt_default, cfg_prompt_port,
cfg_prompt_output_file, cfg_prompt_wordlist,
};
pub async fn run(target: &str) -> anyhow::Result<()> {
let target = normalize_target(target)?;
// Gather config — works in both shell and API mode
let port = cfg_prompt_port("port", "Target port", 22)?;
let user_file = cfg_prompt_existing_file("user_wordlist", "Username wordlist")?;
let pass_file = cfg_prompt_existing_file("pass_wordlist", "Password wordlist")?;
let threads = cfg_prompt_int_range("threads", "Threads", 10, 1, 100)? as usize;
let delay = cfg_prompt_int_range("delay_ms", "Delay (ms)", 50, 0, 60000)? as u64;
let verbose = cfg_prompt_yes_no("verbose", "Verbose output?", false)?;
let output = cfg_prompt_output_file("output_file", "Output file", "results.txt")?;
// Load wordlists
let users = load_lines(&user_file)?;
let passwords = load_lines(&pass_file)?;
println!("[*] Targeting {} with {} users × {} passwords", target, users.len(), passwords.len());
// ... bruteforce logic ...
Ok(())
}Import path:
use crate::modules::creds::utils::{
BruteforceStats, is_subnet_target, parse_subnet, subnet_host_count,
generate_random_public_ip, is_ip_checked, mark_ip_checked, parse_exclusions,
};Thread-safe statistics tracker for bruteforce modules. Uses atomics for counters and a Mutex<HashMap> for error categorization. Create one per module run and share via Arc.
use std::sync::Arc;
use crate::modules::creds::utils::BruteforceStats;
let stats = Arc::new(BruteforceStats::new());
// In each worker task:
let stats = Arc::clone(&stats);
tokio::spawn(async move {
match attempt_login(&host, &user, &pass).await {
Ok(true) => stats.record_success(),
Ok(false) => stats.record_failure(),
Err(e) => stats.record_error(format!("{}", e)).await,
}
// Show live progress (prints inline with \r)
stats.print_progress();
});
// After all tasks complete:
stats.print_final().await;| Method | Async | Description |
|---|---|---|
BruteforceStats::new() |
No | Create a new stats tracker (starts the timer) |
.record_success() |
No | Increment total + successful counters |
.record_failure() |
No | Increment total + failed counters |
.record_error(msg) |
Yes | Increment total + error counters, log error message |
.record_retry() |
No | Increment retry counter |
.print_progress() |
No | Print inline progress bar (\r overwrite) |
.print_final() |
Yes | Print full statistics summary with top 5 errors |
Check if a target string is CIDR notation (e.g., 192.168.8.0/21). Use this to branch between single-host and subnet-scan logic.
use crate::modules::creds::utils::is_subnet_target;
if is_subnet_target(&target) {
// Iterate subnet
} else {
// Single host
}Parse a CIDR string into an ipnetwork::IpNetwork. Does NOT allocate a Vec — callers iterate lazily with .iter(), making it safe for any prefix size (/0 through /32).
use crate::modules::creds::utils::parse_subnet;
let network = parse_subnet("192.168.1.0/24")?;
for ip in network.iter() {
println!("Scanning {}", ip);
}Returns the number of host IPs in a network. Useful for progress display and ETA calculations.
use crate::modules::creds::utils::{parse_subnet, subnet_host_count};
let net = parse_subnet("10.0.0.0/8")?;
println!("Scanning {} hosts", subnet_host_count(&net));
// → "Scanning 16777216 hosts"Generates a random IPv4 address that is not in any excluded range. Automatically skips 10.x.x.x, 127.x.x.x, and 0.x.x.x in addition to the provided exclusion list. Used by mass-scanning modules (Camxploit, etc.).
use crate::modules::creds::utils::{generate_random_public_ip, parse_exclusions};
let exclusions = parse_exclusions(&[
"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", // Private
"100.64.0.0/10", // CGNAT
"224.0.0.0/4", // Multicast
]);
let ip = generate_random_public_ip(&exclusions);
println!("Random target: {}", ip);Parses an array of CIDR strings into IpNetwork objects for use with generate_random_public_ip. Invalid CIDRs are silently skipped.
use crate::modules::creds::utils::parse_exclusions;
let excluded = parse_exclusions(&["10.0.0.0/8", "192.168.0.0/16", "not-valid"]);
// excluded.len() == 2 (invalid entry silently dropped)Persistent scan-state tracking. Prevents re-scanning the same IP across multiple runs by writing checked: <ip> lines to a state file.
use crate::modules::creds::utils::{is_ip_checked, mark_ip_checked};
let state_file = "mqtt_cidr_results.txt";
for ip in network.iter() {
if is_ip_checked(&ip, state_file).await {
continue; // Already scanned
}
// ... scan the IP ...
mark_ip_checked(&ip, state_file).await;
}| Function | Async | Description |
|---|---|---|
is_ip_checked(ip, state_file) |
Yes | Returns true if IP was previously marked. Creates the state file if missing. |
mark_ip_checked(ip, state_file) |
Yes | Appends checked: <ip> to the state file. |
Note: Both functions accept any type implementing
ToStringfor the IP parameter.
Putting both utility modules together in a real bruteforce module:
use std::sync::Arc;
use crate::utils::{
load_lines, normalize_target,
cfg_prompt_port, cfg_prompt_existing_file,
cfg_prompt_int_range, cfg_prompt_yes_no, cfg_prompt_output_file,
};
use crate::modules::creds::utils::{
BruteforceStats, is_subnet_target, parse_subnet, subnet_host_count,
generate_random_public_ip, is_ip_checked, mark_ip_checked, parse_exclusions,
};
pub async fn run(target: &str) -> anyhow::Result<()> {
let target = normalize_target(target)?;
let port = cfg_prompt_port("port", "Target port", 1883)?;
let user_file = cfg_prompt_existing_file("user_wordlist", "Username wordlist")?;
let pass_file = cfg_prompt_existing_file("pass_wordlist", "Password wordlist")?;
let threads = cfg_prompt_int_range("threads", "Threads", 10, 1, 200)? as usize;
let verbose = cfg_prompt_yes_no("verbose", "Verbose?", false)?;
let output = cfg_prompt_output_file("output_file", "Output file", "results.txt")?;
let users = load_lines(&user_file)?;
let passwords = load_lines(&pass_file)?;
let stats = Arc::new(BruteforceStats::new());
if is_subnet_target(&target) {
let network = parse_subnet(&target)?;
println!("[*] Subnet scan: {} hosts", subnet_host_count(&network));
for ip in network.iter() {
if is_ip_checked(&ip, &output).await { continue; }
// ... bruteforce ip ...
mark_ip_checked(&ip, &output).await;
}
} else {
// ... single host bruteforce ...
}
stats.print_final().await;
Ok(())
}Import path:
use crate::config::{
GLOBAL_CONFIG, GlobalConfig, TargetConfig,
ModuleConfig, get_module_config, set_module_config, clear_module_config,
results_dir,
};Thread-safe singleton that holds the current target. Set by the shell (set target) or CLI (--target). Module code reads it but rarely needs to write to it.
use crate::config::GLOBAL_CONFIG;
// Check if a target is set
if !GLOBAL_CONFIG.has_target() {
println!("No target set!");
return Ok(());
}
// Read the target as a string
let target = GLOBAL_CONFIG.get_target().unwrap();
println!("Targeting: {}", target);| Method | Returns | Description |
|---|---|---|
.set_target(target) |
Result<()> |
Set global target (IP, hostname, or CIDR). Validates input. |
.get_target() |
Option<String> |
Get the target as a display string |
.get_single_target_ip() |
Result<String> |
Get single IP; for subnets returns the network address |
.has_target() |
bool |
Check if any target is set |
.is_subnet() |
bool |
true if the target is a CIDR subnet |
.get_target_subnet() |
Option<IpNetwork> |
Returns the IpNetwork if target is a subnet |
.get_target_size() |
Option<u64> |
Number of IPs (1 for single, 2^(32-prefix) for subnets) |
.clear_target() |
() |
Unset the target |
use crate::config::TargetConfig;
pub enum TargetConfig {
Single(String), // Single IP or hostname
Subnet(IpNetwork), // CIDR subnet
}ModuleConfig bridges modules to the API. When the API server receives a /api/run request, it populates a ModuleConfig with the JSON "prompts" object. The cfg_prompt_* functions in src/utils/prompt.rs read these values instead of prompting stdin.
pub struct ModuleConfig {
pub port: Option<u16>,
pub username_wordlist: Option<String>,
pub password_wordlist: Option<String>,
pub concurrency: Option<usize>,
pub stop_on_success: Option<bool>,
pub save_results: Option<bool>,
pub output_file: Option<String>,
pub verbose: Option<bool>,
pub combo_mode: Option<bool>,
pub custom_prompts: HashMap<String, String>, // ← cfg_prompt_* reads from here
pub api_mode: bool, // ← prevents stdin fallback
}| Function | Description |
|---|---|
get_module_config() |
Get a clone of the current config (safe to call from any module) |
set_module_config(config) |
Set the config (called by API server before module execution) |
clear_module_config() |
Reset to defaults (called after module execution) |
use crate::config::get_module_config;
let config = get_module_config();
if config.api_mode {
// Running via API — don't expect stdin
}
if let Some(port) = config.port {
// Use pre-configured port
}When building API requests, use these standardized keys in the "prompts" JSON object:
Common keys (most modules):
| Key | Type | Description |
|---|---|---|
port |
u16 | Target service port |
timeout |
int | Connection timeout (seconds or ms) |
verbose |
y/n | Verbose output |
save_results |
y/n | Save results to file |
output_file |
string | Output filename |
concurrency |
int | Concurrent threads/tasks |
threads |
int | Alias for concurrency |
wordlist |
path | Path to wordlist file |
target_file |
path | File containing targets |
mode |
string | Operation mode (1, 2, 3, etc.) |
Scanner-specific keys (see full list in config.rs doc comments):
- Port Scanner:
port_range,scan_method,show_only_open - Dir Brute:
scan_mode,delay_ms,random_agent,use_https - Sequential Fuzzer:
min_length,max_length,charset,encoding - API Endpoint Scanner:
output_dir,use_spoofing,enable_delete,modules
Returns ~/.rustsploit/results/, creating it if needed. Use this when saving module output in API mode.
use crate::config::results_dir;
let out_path = results_dir().join("scan_output.txt");
std::fs::write(&out_path, results)?;| Constant | Value | Purpose |
|---|---|---|
MAX_TARGET_LENGTH |
2048 | Maximum target string length |
MAX_MODULE_PATH_LENGTH |
512 | Maximum module path length |
MAX_COMMAND_LENGTH |
8192 | Maximum command/input length |
MAX_PATH_LENGTH |
4096 | Maximum file path length |
MAX_HOSTNAME_LENGTH |
253 | Maximum hostname length (config.rs) |
Import path:
use crate::utils::network::{
build_http_client, build_http_client_with, HttpClientOpts,
tcp_connect_addr, tcp_connect_str, tcp_connect, tcp_port_open,
blocking_tcp_connect, udp_bind, quick_honeypot_check,
};Creates a standard reqwest::Client with sensible defaults (danger-accept invalid certs, no redirect limit). Use this instead of hand-rolling reqwest::Client::builder().
use crate::utils::network::build_http_client;
let client = build_http_client(Duration::from_secs(10))?;
let resp = client.get(&url).send().await?;Extended HTTP client builder with additional options.
use crate::utils::network::{build_http_client_with, HttpClientOpts};
let client = build_http_client_with(Duration::from_secs(10), HttpClientOpts {
cookie_store: true,
follow_redirects: true,
user_agent: Some("Mozilla/5.0".to_string()),
..HttpClientOpts::default()
})?;| Field | Type | Default | Description |
|---|---|---|---|
cookie_store |
bool |
false |
Enable cookie jar |
follow_redirects |
bool |
false |
Follow HTTP redirects |
user_agent |
Option<String> |
None |
Custom User-Agent header |
default_headers |
Option<HeaderMap> |
None |
Default headers for all requests |
Async TCP connection to a SocketAddr with timeout and optional source port binding. Preferred over raw TcpStream::connect — respects global source port setting.
use crate::utils::network::tcp_connect_addr;
let stream = tcp_connect_addr(addr, Duration::from_secs(5)).await?;Async TCP connection from a "host:port" string. Resolves DNS and connects.
Quick async check if a TCP port is open.
Synchronous TCP connection for use in spawn_blocking contexts.
Binds a UDP socket to the appropriate address family (IPv4 or IPv6) for the target.
Fast honeypot detection — probes common ports and returns true if 11+ respond (likely honeypot).
Call at the top of run() in modules that need raw sockets (ICMP, SYN scan, packet crafting). Returns a clean error message if the current euid is not root.
use crate::utils::privilege::require_root;
pub async fn run(target: &str) -> Result<()> {
require_root("ICMP raw socket")?;
// ... raw socket operations ...
}Used by: DoS modules (icmp_flood, syn_ack_flood, null_syn_exhaustion, dns_amplification, etc.), ping_sweep scanner.
Add new reusable helpers to src/utils/ (the appropriate submodule: prompt.rs, sanitize.rs, target.rs, network.rs, or modules.rs), creds/utils.rs, or config.rs rather than copy-pasting into individual modules. Common candidates:
- HTTP header templates
- Response fingerprinting helpers
- Common error formatters
- Credential loaders with streaming support