24 KiB
Utilities & Helpers
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 |
crate::utils — Core Utilities
load_lines(path) → Result<Vec<String>>
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.
normalize_target(raw) → Result<String>
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 (.., //).
Config-Aware Prompt Wrappers (cfg_prompt_*)
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.
cfg_prompt_required(key, msg) → Result<String>
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).
cfg_prompt_yes_no(key, msg, default_yes) → Result<bool>
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 |
cfg_prompt_existing_file(key, msg) → Result<String>
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).
cfg_prompt_int_range(key, msg, default, min, max) → Result<i64>
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 |
cfg_prompt_default(key, msg, default) → Result<String>
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 |
cfg_prompt_port(key, msg, default) → Result<u16>
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 |
cfg_prompt_output_file(key, msg, default) → Result<String>
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"
cfg_prompt_wordlist(key, msg) → Result<String>
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.
Complete Module Integration Example
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(())
}
crate::modules::creds::utils — Credential Module Utilities
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,
};
BruteforceStats
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;
Methods
| 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 |
is_subnet_target(target) → bool
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_subnet(target) → Result<IpNetwork>
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);
}
subnet_host_count(net) → u128
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"
generate_random_public_ip(exclusions) → IpAddr
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);
parse_exclusions(cidrs) → Vec<IpNetwork>
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)
is_ip_checked(ip, state_file) → bool / mark_ip_checked(ip, state_file)
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.
Complete Credential Module Example
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(())
}
crate::config — Framework Configuration
Import path:
use crate::config::{
GLOBAL_CONFIG, GlobalConfig, TargetConfig,
ModuleConfig, get_module_config, set_module_config, clear_module_config,
results_dir,
};
GLOBAL_CONFIG (static GlobalConfig)
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);
GlobalConfig Methods
| 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 |
TargetConfig Enum
use crate::config::TargetConfig;
pub enum TargetConfig {
Single(String), // Single IP or hostname
Subnet(IpNetwork), // CIDR subnet
}
ModuleConfig & API Prompt Keys
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.
Struct Fields
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
}
Helper Functions
| 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
}
Standardized API Prompt Keys
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
results_dir() → PathBuf
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)?;
Constants
| 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) |
crate::utils::network — Network Utilities
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,
};
build_http_client(timeout) → Result<Client>
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?;
build_http_client_with(timeout, opts) → Result<Client>
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()
})?;
HttpClientOpts Fields
| 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 |
tcp_connect_addr(addr, timeout) → io::Result<TcpStream>
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?;
tcp_connect_str(addr_str, timeout) → io::Result<TcpStream>
Async TCP connection from a "host:port" string. Resolves DNS and connects.
tcp_port_open(ip, port, timeout) → bool
Quick async check if a TCP port is open.
blocking_tcp_connect(addr, timeout) → io::Result<TcpStream>
Synchronous TCP connection for use in spawn_blocking contexts.
udp_bind(target_ip) → io::Result<UdpSocket>
Binds a UDP socket to the appropriate address family (IPv4 or IPv6) for the target.
quick_honeypot_check(ip) → bool
Fast honeypot detection — probes common ports and returns true if 11+ respond (likely honeypot).
crate::utils::privilege — Privilege Checks
require_root(context) → Result<()>
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.
Extending Utils
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